Files
sx/issues/0077-imported-reserved-type-name-binding.md
agra df6e830bec fix(diagnostics): reject reserved type-name bindings in every module (issue 0077)
The issue-0076 reserved-type-name binding diagnostic only ran over main-file
decls, so an imported module (or the stdlib) could still declare `s2 := ...`
and reach lowering, where the address-of family loads the whole aggregate and
passes it by value to a `ptr` param — LLVM verifier abort.

Extend coverage to every compiled module: a dedicated `checkBindingNames` walk
(in semantic_diagnostics.zig) visits every var/`:=`/typed-local binding name and
function/lambda/struct-method parameter at any depth, with NO main-file filter,
descending the `namespace_decl` that a `mod :: #import` wraps so imported-module
decls are reached. It tracks each module's source_file (save/restore per node)
so the diagnostic renders against the imported module's text. Rejection still
defers to the parser's `Type.fromName` classifier; the unknown-type check (0064)
stays main-file-only. No lowering special-case; `.identifier`-only address-of
paths are unchanged.

Stdlib audit: the only reserved-name bindings under library/ were two `u1`
locals in ui/renderer.sx (UV coords) — renamed to u_min/u_max/v_min/v_max.

Regression test: examples/1120-diagnostics-imported-reserved-type-name.sx (+
companion mod.sx) — an imported `s2 := ...` now emits the clean diagnostic at
the import's declaration site (exit 1), not an LLVM abort.

Resolves issues 0076 (coverage extension) and 0077.
2026-06-03 19:32:49 +03:00

4.1 KiB

0077 — reserved type-name binding diagnostic skips imported modules

Status: RESOLVED.

Root cause: the reserved-name binding diagnostic (issue 0076) only ran over main-file decls (UnknownTypeChecker.run's main_file filter). An imported module's s2 := … was never checked and reached lowering, where the address-of family loaded the whole aggregate and passed it by value to a *Box param — LLVM verifier abort.

Fix: the binding check (checkBindingNames in src/ir/semantic_diagnostics.zig) now walks EVERY compiled module — no main-file filter — visiting every var/:=/typed-local binding name and function/lambda/struct-method parameter at any depth, and descending the namespace_decl that a mod :: #import wraps so imported-module decls are reached. The walk tracks each module's source_file (via the diagnostic list's current_source_file, saved/restored per node) so the diagnostic renders against the imported module's text. Rejection still defers to the parser's name_class.Type.fromName classifier (no drift). The unknown-type check (issue 0064) stays main-file-only. No lowering special-case; the .identifier-only address-of paths are unchanged.

Stdlib audit: the only reserved-name bindings under library/ were two u1 locals in library/modules/ui/renderer.sx (lines 203, 382); the UV-coord locals were renamed u_min/u_max/v_min/v_max. No reserved parameter names or other reserved bindings exist in library/ or examples/.

Regression test: examples/1120-diagnostics-imported-reserved-type-name.sx (+ companion 1120-diagnostics-imported-reserved-type-name/mod.sx) — an imported module declaring s2 := … now emits the clean diagnostic at the import's declaration site (exit 1), not an LLVM abort.

Symptom

An imported module can still declare a parameter or var binding whose name is a reserved/builtin type name. Observed: the imported-module repro below reaches lowering and fails LLVM verification by passing a loaded struct value to a *Box parameter. Expected: the same declaration-site diagnostic used for main-file issue 0076 should reject the imported module's s2 binding before lowering.

Reproduction

Create these two files under the repo root, then run ./zig-out/bin/sx run .sx-tmp/issue0077_main.sx.

.sx-tmp/issue0077_mod.sx:

#import "modules/std.sx";

Box :: struct { total: s64 = 0; count: s64 = 0; }

update :: (self: *Box, n: s64) {
    self.total += n;
    self.count += 1;
}

run_imported_reserved_name :: () -> s32 {
    s2 := Box.{ total = 0, count = 0 };
    update(@s2, 5);
    s2.update(7);
    print("imported s2 total={} count={}\n", s2.total, s2.count);
    return 0;
}

.sx-tmp/issue0077_main.sx:

#import "modules/std.sx";
mod :: #import ".sx-tmp/issue0077_mod.sx";

main :: () -> s32 {
    return mod.run_imported_reserved_name();
}

Current output on flow/sx-foundation/F0.1:

LLVM verification failed: Call parameter type does not match function signature!
  %load = load { i64, i64 }, ptr %alloca, align 8, !dbg !461
 ptr  call void @update(ptr %0, { i64, i64 } %load, i64 5), !dbg !462

Investigation prompt

Investigate and fix issue 0077 in the sx compiler. The suspected area is src/ir/semantic_diagnostics.zig, especially UnknownTypeChecker.run and its main_file filter. Attempt 2 for issue 0076 added checkBindingName, but it only runs over main-file declarations; imported modules are still trusted and can hit the original LLVM verifier failure. The fix likely needs to apply reserved-type-name binding diagnostics to all user source modules that are lowered, while preserving the existing trusted-stdlib or library convention only where intentionally required. Also audit existing reserved-name bindings in library/ (for example u1 := ... in library/modules/ui/renderer.sx) and rename any source that will become newly illegal under the corrected rule.

Verification: run the two-file repro above and expect a clean diagnostic at the imported module's s2 := ... declaration, not LLVM verification failure. Then run zig build, zig build test, and bash tests/run_examples.sh.