Files
sx/issues/0102-flat-import-same-signature-collision.md
agra b9cfe2554f refactor(ffi-linkage): Phase 9.3/9.4 — purge 'foreign' from issues/*.md; GATE PASS
Rewrote 20 issue writeups to the extern/runtime-class vocabulary (#foreign→extern,
foreign_class_map→runtime_class_map, parseForeignClassDecl→parseRuntimeClassDecl,
findForeignMethodInChain→findRuntimeMethodInChain, dedupeForeignSymbol→
dedupeExternSymbol, is_foreign_c_api→is_extern_c_api, stale filename refs to the
renamed examples, foreign-class→runtime-class, bare foreign→extern). Renamed
issues/0043-…-foreign-class-…→…-runtime-class-….

PHASE 9 COMPLETE — 9.4 GATE PASSES: zero 'foreign' across src/library/examples/
issues/docs/editors/specs/readme/CLAUDE, excluding only the SQLite API constant
SQLITE_CONSTRAINT_FOREIGNKEY + vendored sqlite3.c/.h (upstream third-party).
Suite green (644 corpus / 443 unit, 0 failed).
2026-06-15 11:18:35 +03:00

6.0 KiB
Raw Permalink Blame History

0102 — flat-import same-name function collision (per-source binding)

RESOLVED. Two flat imports (bare #import "a.sx" / a flat directory import, NOT a namespaced ns :: #import) that each author a top-level free function with the same short name collided in IR lowering. The flat/ directory merge keeps exactly one author per name in the merged decl list (first-wins), and every bare-name consumer site — call dispatch, default-arg expansion, function-value capture, free-function UFCS, comptime #run — read that one name-keyed winner. So when module b.sx authored its own greet but a.sx was imported first, b.sx's own bare greet() silently bound a.sx's author. Unlike issue 0100 (which crashed on a param-count assert when the AST/FuncId split across modules), this miscompiled silently: the wrong same-name author ran, with no diagnostic.

The defect had two faces, both rooted in name-keyed identity across a flat collision:

  1. Lowering keyed function bodies by short name (fn_ast_map / resolveFuncByName are first-wins), so a shadowed author never got its own FuncId or body — there was nothing to bind even if a consumer wanted the per-source author.
  2. Resolution at every bare-name consumer site re-looked-up the winner by name, so even once shadow authors had distinct FuncIds, the consumer sites kept binding the first-wins winner.

Fix — four sub-steps (src/imports.zig, src/ir/lower.zig)

  • 0102a — retain dup authors + identity indexes. The flat/directory merge keeps first-wins in the merged scope (unchanged), but now also retains every dropped same-name author in program_index.module_fns (path → name → *FnDecl) plus a flat_import_graph (file → flat-import edges). Resolution is untouched at this step — the indexes just make the shadowed authors addressable.

  • 0102b — identity-addressable function lowering. fn_decl_fids (*const ast.FnDecl → FuncId) lets a body be declared + lowered against a specific *FnDecl (lowerFunctionBodyInto / bareAuthorFuncId) instead of a name. A shadow author gets a fresh same-name FuncId in its own module's visibility context; the winner keeps the name-keyed slot. scanDecls keys fn_decl_fids by the stable module_fns *FnDecl.

  • 0102c — THE resolver + call path + param typing. resolveBareCallee(name, caller_file) -> .func(ResolvedAuthor) | .ambiguous | .none (src/ir/lower.zig). It returns .none whenever the outcome would equal first-wins (single author, or own-author == winner), so every single-author / local / parameter / std / qualified / extern / generic / builtin name resolves byte-for-byte as before. Only a genuine flat collision reroutes: own-author wins; else the caller's flat-reachable authors — ≥2 distinct → .ambiguous (loud "qualify the call" diagnostic), exactly one differing from the winner → bind it. Routed the primary call path and the call's parameter target typing (so a *T-param shadow gets implicit address-of, not a value bit-cast to a pointer → segfault).

  • 0102d — the four remaining bare-name sites. Routed the SAME resolver through every other site that resolved a bare callee/function-name by first-wins, each gated exactly as the call path (plain top-level identifier, no scope-mangle / UFCS alias / local shadow; act on .func / .ambiguous, fall through on .none):

    1. Default-argument expansion (expandCallDefaults): omitted trailing args fill from the RESOLVED author's defaults, not the winner's.
    2. Function-value conversion (closure(fn) and the bare-fn-as-value func_ref / fn-ptr / closure-coercion path): captures the resolved author's FuncId. The winner's body is lazily lowered ONLY on the .none fallback — a rerouted value never uses the winner, so taking a shadow as a value must not pre-lower (and possibly mis-diagnose) the winner's body.
    3. Free-function UFCS (recv.fn()fn(recv, …)): dispatches the resolved author for the receiver's source.
    4. Comptime #run of a bare call: lowerMainAndComptime now sets current_source_file per decl, so a NAME :: #run f() in an imported module resolves f from THAT module's flat imports (own-author wins) rather than the main file's perspective (where two flat authors made it spuriously .ambiguous and failed the build).

Regression tests

examples/07220735 (each a focused multi-file flat-collision scene that fails on pre-fix code and passes after):

  • 0722-modules-flat-same-name-own — own-author wins on the call path.
  • 0723-modules-flat-vs-namespaced — a flat author + a namespaced same-name author don't collide.
  • 0724-modules-flat-same-name-ambiguous≥2 flat authors, bare call → loud diagnostic.
  • 0725-modules-flat-dir-same-name — flat directory import collision.
  • 0726-modules-flat-same-name-variadic — per-source variadic packing.
  • 0728-modules-flat-same-name-paramtype — per-source parameter target typing (value vs pointer param).
  • 0729-modules-flat-same-name-extern — same-name extern authors are NOT rerouted (non-plain authors keep first-wins).
  • 0730-modules-flat-same-name-default-arg — per-source default-arg expansion.
  • 0731-modules-flat-same-name-closure — per-source closure(fn) + bare fn-value capture.
  • 0732-modules-flat-same-name-ufcs — per-source free-function UFCS dispatch.
  • 0733-modules-flat-same-name-comptime-run — per-source comptime #run callee.
  • 0734-modules-flat-same-name-ufcs-ambiguous≥2 flat authors, UFCS call → loud diagnostic (pre-fix: silently bound the winner).
  • 0735-modules-flat-same-name-fn-value-winner — the first-wins winner's body is independently broken and never used; a shadow taken as a function value binds the shadow and runs while the winner is NOT lowered (pre-fix: the fn-value site eagerly lowered the winner before the resolver rerouted, surfacing the winner's error for a function the value never touches).