Files
sx/issues/0102-flat-import-same-signature-collision.md
agra bd24996d8b fix(lower): route remaining bare-name sites through resolver + close 0102 [0102d]
Final 0102 sub-step. fix-0102c landed resolveBareCallee and routed the
primary call path + parameter target typing through it, leaving four other
bare-name consumer sites on the old first-wins path. Route the SAME resolver
through all four, 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 so single-author / local / std / qualified / foreign-single
resolution is byte-for-byte unchanged):

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.
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) instead
   of the main file's perspective (which made it spuriously ambiguous).

Regression tests: examples/0730-0734 (default-arg, closure+fn-value, UFCS,
comptime #run, UFCS-ambiguity), each fails on pre-fix code and passes after.
issues/0102-flat-import-same-signature-collision.md written RESOLVED with the
4-sub-step root cause and regression-test paths.
2026-06-06 16:16:57 +03:00

96 lines
5.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 / foreign / 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.
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/0722``0734` (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-foreign` — same-name `#foreign` 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).