fix(diagnostics): make reserved-type-name binding check exhaustive (issue 0076)
The reserved/builtin-type-name binding diagnostic was a hand-walked subset
of binding-bearing AST nodes with a silent `else => {}`, so each review
found another syntactic binding form that bypassed it and hit the original
LLVM verifier abort: destructure names (`s2, x := …`), `impl` method
params/locals, and `if` / `while` / `for` / match-arm / `catch` / `onfail`
captures.
Rewrite `checkBindingNames` (src/ir/semantic_diagnostics.zig) as an
EXHAUSTIVE `switch` over every `Node.Data` tag with NO `else` arm — a future
binding-bearing node type now fails to compile until it is handled here, so
coverage is enforced by the compiler instead of a hand-maintained list. The
check stays in the pre-lowering semantic pass rather than moving to the
`Scope.put` scope-registration choke point: lowering is lazy, so an
uncalled function's bindings never reach `Scope.put`, yet they must still be
rejected at their declaration (e.g. the never-called `takes_u8` in 1119).
No lowering special-case; `lower.zig` unchanged.
Regression tests (fail-before: LLVM abort or silent accept → pass-after:
clean diagnostic, exit 1):
- 1121 control-flow: destructure, if/while bindings, for capture+index,
match-arm capture
- 1122 impl-block method: reserved param AND reserved local
- 1123 catch + onfail tag bindings
- 1124 destructure name reserved in an imported module
Existing 0125 / 1119 / 0135 / 1120 tests kept; full suite 368 passed.
This commit is contained in:
@@ -11,10 +11,9 @@
|
||||
> `*self`-mutation-losing copy).
|
||||
>
|
||||
> **Fix:** a declaration-site diagnostic in the existing semantic pass
|
||||
> `src/ir/semantic_diagnostics.zig` (`UnknownTypeChecker`). New
|
||||
> `checkBindingName` rejects any parameter name or `var` binding name (local or
|
||||
> global, `:=` / typed-local forms) whose spelling collides with a reserved type
|
||||
> name; `isReservedTypeName` defers to the parser's own classifier
|
||||
> `src/ir/semantic_diagnostics.zig` (`UnknownTypeChecker`). `checkBindingName`
|
||||
> rejects any binding name whose spelling collides with a reserved type name;
|
||||
> `isReservedTypeName` defers to the parser's own classifier
|
||||
> (`types.Type.fromName`) so the rejected set never drifts from the set that
|
||||
> would parse as a type — the named builtins (`bool`, `string`, `void`, `f32`,
|
||||
> `f64`, `usize`, `isize`, `Any`) and `[su]N` over sx's 1–64 range. Bare value
|
||||
@@ -22,10 +21,32 @@
|
||||
> the `.identifier`-only address-of paths are correct once type-shaped names can
|
||||
> never be bound. The rejected `bareVarName` approach was never landed.
|
||||
>
|
||||
> **Coverage is structural (attempt 4).** Earlier landings hand-walked a subset
|
||||
> of binding-bearing nodes with a silent `else => {}`, so each review found a new
|
||||
> leaking syntactic form (destructure names, `impl` method params/locals, `if` /
|
||||
> `while` / `for` / match-arm / `catch` / `onfail` captures) that bypassed the
|
||||
> check and hit the original LLVM verifier abort. `checkBindingNames` is now an
|
||||
> **exhaustive `switch` over every `Node.Data` tag with NO `else` arm**: a future
|
||||
> binding-bearing node type fails to compile until it is handled here, so
|
||||
> coverage is enforced by the compiler rather than by a hand-maintained list. The
|
||||
> check stays in the pre-lowering semantic pass (NOT moved to the `Scope.put`
|
||||
> scope-registration choke point) because lowering is lazy — an UNCALLED
|
||||
> function's bindings never reach `Scope.put`, yet they must still be rejected at
|
||||
> their declaration (e.g. `examples/1119`'s never-called `takes_u8`).
|
||||
>
|
||||
> **Regression tests:**
|
||||
> - `examples/0125-types-type-named-var-rejected.sx` — `:=` form (`s2`) rejected.
|
||||
> - `examples/1119-diagnostics-reserved-type-name-as-identifier.sx` — parameter
|
||||
> (`u8`), typed-local (`s64`, `bool`), and `:=` (`string`) forms rejected.
|
||||
> - `examples/1121-diagnostics-reserved-name-control-flow.sx` — destructure name,
|
||||
> `if` / `while` optional bindings, `for` capture + index names, match-arm
|
||||
> capture.
|
||||
> - `examples/1122-diagnostics-reserved-name-impl-method.sx` — `impl`-block method
|
||||
> reserved param AND reserved local.
|
||||
> - `examples/1123-diagnostics-reserved-name-catch-onfail.sx` — `catch` and
|
||||
> `onfail` error-tag bindings.
|
||||
> - `examples/1124-diagnostics-imported-reserved-destructure.sx` — destructure
|
||||
> name reserved in an IMPORTED module (renders against that module's source).
|
||||
> - `examples/0135-types-self-streaming-nonreserved.sx` — positive: `*self`
|
||||
> streaming with non-reserved names (`hasher`, `ctx`) accumulates correctly via
|
||||
> both `update(@h, …)` and `h.update(…)`.
|
||||
|
||||
Reference in New Issue
Block a user