Skip to content

feat(lint): W018 — e.kind compared against an out-of-set error kind (#469)#525

Merged
InauguralPhysicist merged 1 commit into
mainfrom
feat/lint-w018-error-kind-typo-469
Jul 9, 2026
Merged

feat(lint): W018 — e.kind compared against an out-of-set error kind (#469)#525
InauguralPhysicist merged 1 commit into
mainfrom
feat/lint-w018-error-kind-typo-469

Conversation

@InauguralPhysicist

Copy link
Copy Markdown
Collaborator

What

A catch handler comparing a caught error's .kind against a string that is a near-miss of a real kind — a case variant ("IO"), a single-character typo ("index_rage"), or a kind renamed out from under the handler — is dead code that silently never fires. W018 warns and suggests the intended kind — the silent-tolerance class the lint train exists to fence.

$ eigenscript --lint app.eigs
app.eigs:4: warning[W018]: 'e.kind == "index_rage"' compares against an unknown
error kind — did you mean "index_range"? error kinds are a closed set (docs/DIAGNOSTICS.md)

Zero-false-positive by construction

Three gates, all required to fire:

  1. the .kind is read off a catch-bound variable (tracked through the walk via trycatch.err_name) — .kind on an unrelated user dict never fires;
  2. the compared string is not an exact valid kind;
  3. it is a near-miss — a case variant, or a bounded-Levenshtein edit distance of 1 — so a genuinely custom throw {kind: "..."} value, many edits from every builtin, stays silent.

The closed kind set is derived from err_kind_name(EK_INTERNAL..EK_USER) at run time — no hand list to drift when a kind is added, and it already covers the post-#509 deadlock (verified: a valid deadlock comparison stays silent). The check lives in the shared lint_run_checks, so it also flows to the LSP.

Calibration: zero hits over lib/, the test corpus, examples/, and the consumer repos (ouroboros/liferaft/tidelog/DMG/Tidepool).

Verification

  • tests/test_lint.sh (6 new cases): fires on index_rage (+ suggests index_range) and the IO case variant; silent on valid kinds incl. deadlock, a far-off custom kind, and .kind off a non-catch var.
  • Full suite green release AND ASan (detect_leaks=1): 2700/2700, 0 failed, no leak-tally increase.

Docs

  • docs/DIAGNOSTICS.md: W018 row in the W-code table.
  • CHANGELOG.md: entry under [Unreleased] → Added.

Closes #469

🤖 Generated with Claude Code

Comment thread src/lint.c
obj->data.ident.name, (op[0] == '=' ? "==" : op), lit, sugg);
}

static void w018_scan(ASTNode *n, LintContext *ctx, W018Scope *sc) {
…469)

A catch handler comparing a caught error's `.kind` against a string that
is a near-miss of a real kind — a case variant ("IO"), a single-character
typo ("index_rage"), or a kind renamed out from under the handler — is
dead code that silently never fires. W018 warns and suggests the intended
kind (the silent-tolerance class the lint train fences).

Zero-false-positive by construction, three gates:
  1. the `.kind` must be read off a CATCH-BOUND variable (tracked through
     the walk via trycatch.err_name), so `.kind` on an unrelated user dict
     never fires;
  2. an exactly-valid kind is silent;
  3. only a near-miss fires — a case variant, or a bounded-Levenshtein
     edit distance of 1 — so a genuinely custom `throw {kind: "..."}`
     value, many edits from every builtin, stays silent.

The closed kind set is derived from err_kind_name(EK_INTERNAL..EK_USER)
at run time — no hand list to drift, and it already covers the post-#509
`deadlock` kind (verified: a valid `deadlock` comparison stays silent).
Runs inside the shared lint_run_checks, so it also flows to the LSP.

Calibrated to ZERO hits over lib/, the test corpus, examples/, and the
consumer repos (ouroboros/liferaft/tidelog/DMG/Tidepool).

Regression: tests/test_lint.sh — fires on index_rage (+ suggests
index_range) and the IO case variant; silent on valid kinds incl.
deadlock, a far-off custom kind, and .kind off a non-catch var.

Full suite green release + ASan (leaks on), 2700/2700, no leak-tally
increase.

Closes #469

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@InauguralPhysicist InauguralPhysicist force-pushed the feat/lint-w018-error-kind-typo-469 branch from 40e67ac to 35b34a3 Compare July 9, 2026 22:24
@InauguralPhysicist InauguralPhysicist merged commit b5aac9e into main Jul 9, 2026
17 checks passed
@InauguralPhysicist InauguralPhysicist deleted the feat/lint-w018-error-kind-typo-469 branch July 9, 2026 22:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Lint: warn when e.kind is compared against a string outside the closed error-kind set

2 participants