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.
This commit is contained in:
95
issues/0102-flat-import-same-signature-collision.md
Normal file
95
issues/0102-flat-import-same-signature-collision.md
Normal file
@@ -0,0 +1,95 @@
|
||||
# 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).
|
||||
Reference in New Issue
Block a user