Merge branch 'flow/distribution/fix-0102c' into wt-fix-0102-base
This commit is contained in:
18
examples/0722-modules-flat-same-name-own.sx
Normal file
18
examples/0722-modules-flat-same-name-own.sx
Normal file
@@ -0,0 +1,18 @@
|
||||
// fix-0102c (issue 0102): two flat FILE imports each author a same-name free
|
||||
// function `greet`. The first-wins import merge keeps exactly one `greet` in
|
||||
// the merged scope, but each module's OWN code must bind its OWN author when it
|
||||
// calls `greet` bare. `from_a` (in a.sx) returns 1; `from_b` (in b.sx) returns
|
||||
// 2 — per-source binding, resolved by identity, not first-wins.
|
||||
#import "modules/std.sx";
|
||||
#import "0722-modules-flat-same-name-own/a.sx";
|
||||
#import "0722-modules-flat-same-name-own/b.sx";
|
||||
|
||||
report :: (label: string, ok: bool) {
|
||||
if ok { print("{}: ok\n", label); } else { print("{}: FAIL\n", label); }
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
report("from_a binds a.greet", from_a() == 1);
|
||||
report("from_b binds b.greet", from_b() == 2);
|
||||
0
|
||||
}
|
||||
5
examples/0722-modules-flat-same-name-own/a.sx
Normal file
5
examples/0722-modules-flat-same-name-own/a.sx
Normal file
@@ -0,0 +1,5 @@
|
||||
// a.sx authors `greet`. Its own `from_a` calls `greet` bare — under fix-0102c
|
||||
// that binds a.sx's OWN author (own-author wins), even though b.sx also
|
||||
// authors `greet` and the first-wins merge keeps only one in the merged scope.
|
||||
greet :: () -> s64 { return 1; }
|
||||
from_a :: () -> s64 { return greet(); }
|
||||
4
examples/0722-modules-flat-same-name-own/b.sx
Normal file
4
examples/0722-modules-flat-same-name-own/b.sx
Normal file
@@ -0,0 +1,4 @@
|
||||
// b.sx authors its OWN `greet`. `from_b`'s bare `greet` must bind b.sx's
|
||||
// author (2), not the first-wins winner from a.sx.
|
||||
greet :: () -> s64 { return 2; }
|
||||
from_b :: () -> s64 { return greet(); }
|
||||
17
examples/0723-modules-flat-vs-namespaced.sx
Normal file
17
examples/0723-modules-flat-vs-namespaced.sx
Normal file
@@ -0,0 +1,17 @@
|
||||
// fix-0102c (issue 0102): one FLAT and one NAMESPACED author of `value`. The
|
||||
// bare call `value()` binds the FLAT author (10); the namespaced author is
|
||||
// reached only through `nm.value()` (20). A namespaced author must NOT make the
|
||||
// bare call ambiguous — only flat authors collide.
|
||||
#import "modules/std.sx";
|
||||
#import "0723-modules-flat-vs-namespaced/flat.sx";
|
||||
nm :: #import "0723-modules-flat-vs-namespaced/named.sx";
|
||||
|
||||
report :: (label: string, ok: bool) {
|
||||
if ok { print("{}: ok\n", label); } else { print("{}: FAIL\n", label); }
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
report("bare binds flat", value() == 10);
|
||||
report("nm.value binds named", nm.value() == 20);
|
||||
0
|
||||
}
|
||||
3
examples/0723-modules-flat-vs-namespaced/flat.sx
Normal file
3
examples/0723-modules-flat-vs-namespaced/flat.sx
Normal file
@@ -0,0 +1,3 @@
|
||||
// Flat-imported author of `value`. A bare `value()` in the consumer binds THIS
|
||||
// one — the only bare (flat) author of the name.
|
||||
value :: () -> s64 { return 10; }
|
||||
4
examples/0723-modules-flat-vs-namespaced/named.sx
Normal file
4
examples/0723-modules-flat-vs-namespaced/named.sx
Normal file
@@ -0,0 +1,4 @@
|
||||
// Namespaced-imported author of `value`. Reachable only as `nm.value`; it never
|
||||
// enters the flat merge, so it neither shadows the flat author nor makes the
|
||||
// bare call ambiguous.
|
||||
value :: () -> s64 { return 20; }
|
||||
12
examples/0724-modules-flat-same-name-ambiguous.sx
Normal file
12
examples/0724-modules-flat-same-name-ambiguous.sx
Normal file
@@ -0,0 +1,12 @@
|
||||
// fix-0102c (issue 0102): a genuinely-ambiguous bare call. `main` flat-imports
|
||||
// two modules that each author `dup` and neither is `main`'s own — a bare
|
||||
// `dup()` can't pick one, so the compiler rejects it with a loud diagnostic
|
||||
// instead of silently first-wins-binding one. Qualify the call to disambiguate.
|
||||
#import "modules/std.sx";
|
||||
#import "0724-modules-flat-same-name-ambiguous/a.sx";
|
||||
#import "0724-modules-flat-same-name-ambiguous/b.sx";
|
||||
|
||||
main :: () -> s32 {
|
||||
print("{}\n", dup());
|
||||
0
|
||||
}
|
||||
3
examples/0724-modules-flat-same-name-ambiguous/a.sx
Normal file
3
examples/0724-modules-flat-same-name-ambiguous/a.sx
Normal file
@@ -0,0 +1,3 @@
|
||||
// One of two flat authors of `dup`. A consumer that flat-imports BOTH and calls
|
||||
// `dup` bare cannot pick between them.
|
||||
dup :: () -> s64 { return 1; }
|
||||
2
examples/0724-modules-flat-same-name-ambiguous/b.sx
Normal file
2
examples/0724-modules-flat-same-name-ambiguous/b.sx
Normal file
@@ -0,0 +1,2 @@
|
||||
// The second flat author of `dup`.
|
||||
dup :: () -> s64 { return 2; }
|
||||
17
examples/0725-modules-flat-dir-same-name.sx
Normal file
17
examples/0725-modules-flat-dir-same-name.sx
Normal file
@@ -0,0 +1,17 @@
|
||||
// fix-0102c (issue 0102): two flat DIRECTORY imports each author a same-name
|
||||
// `tag`. A directory flat-import exposes the directory's authored functions, so
|
||||
// `caller1`/`caller2` are visible here, and each binds its OWN directory's `tag`
|
||||
// when it calls bare — per-source binding across directory imports (100 / 200).
|
||||
#import "modules/std.sx";
|
||||
#import "0725-modules-flat-dir-same-name/d1";
|
||||
#import "0725-modules-flat-dir-same-name/d2";
|
||||
|
||||
report :: (label: string, ok: bool) {
|
||||
if ok { print("{}: ok\n", label); } else { print("{}: FAIL\n", label); }
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
report("caller1 binds d1.tag", caller1() == 100);
|
||||
report("caller2 binds d2.tag", caller2() == 200);
|
||||
0
|
||||
}
|
||||
3
examples/0725-modules-flat-dir-same-name/d1/one.sx
Normal file
3
examples/0725-modules-flat-dir-same-name/d1/one.sx
Normal file
@@ -0,0 +1,3 @@
|
||||
// d1's author of `tag`. `caller1` (also in d1) binds d1's own `tag` (100).
|
||||
tag :: () -> s64 { return 100; }
|
||||
caller1 :: () -> s64 { return tag(); }
|
||||
4
examples/0725-modules-flat-dir-same-name/d2/two.sx
Normal file
4
examples/0725-modules-flat-dir-same-name/d2/two.sx
Normal file
@@ -0,0 +1,4 @@
|
||||
// d2's author of `tag`. `caller2` (also in d2) binds d2's own `tag` (200),
|
||||
// even though d1's `tag` is the first-wins merge winner.
|
||||
tag :: () -> s64 { return 200; }
|
||||
caller2 :: () -> s64 { return tag(); }
|
||||
23
examples/0726-modules-flat-same-name-variadic.sx
Normal file
23
examples/0726-modules-flat-same-name-variadic.sx
Normal file
@@ -0,0 +1,23 @@
|
||||
// fix-0102c F1 (issue 0102): two flat FILE imports each author same-name free
|
||||
// functions whose VARIADIC SHAPE differs. `combine` is fixed-arity in a.sx
|
||||
// (the first-wins winner) but variadic in b.sx (the shadow); `pick` is the
|
||||
// reverse. Each module's bare call must pack arguments against ITS OWN
|
||||
// author's signature — not the first-wins author's. Pre-fix the call path
|
||||
// re-fetched the first-wins AST by name to drive variadic packing, so b.sx's
|
||||
// variadic `combine` was packed as if fixed (and its fixed `pick` as if
|
||||
// variadic) → wrong lowering. Regression for the F1 review finding.
|
||||
#import "modules/std.sx";
|
||||
#import "0726-modules-flat-same-name-variadic/a.sx";
|
||||
#import "0726-modules-flat-same-name-variadic/b.sx";
|
||||
|
||||
report :: (label: string, ok: bool) {
|
||||
if ok { print("{}: ok\n", label); } else { print("{}: FAIL\n", label); }
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
report("from_a combine fixed", from_a_combine() == 30);
|
||||
report("from_b combine variadic", from_b_combine() == 10);
|
||||
report("from_a pick variadic", from_a_pick() == 6);
|
||||
report("from_b pick fixed", from_b_pick() == 5);
|
||||
0
|
||||
}
|
||||
11
examples/0726-modules-flat-same-name-variadic/a.sx
Normal file
11
examples/0726-modules-flat-same-name-variadic/a.sx
Normal file
@@ -0,0 +1,11 @@
|
||||
// a.sx is the first-wins winner for both names. `combine` is FIXED arity;
|
||||
// `pick` is VARIADIC. `from_a_*` call them bare — a authors the winner, so
|
||||
// they resolve through the existing path and pack against a's own shapes.
|
||||
combine :: (x: s64, y: s64) -> s64 { return x + y; }
|
||||
pick :: (..xs: []s64) -> s64 {
|
||||
result := 0;
|
||||
for xs: (it) { result = result + it; }
|
||||
result
|
||||
}
|
||||
from_a_combine :: () -> s64 { return combine(10, 20); }
|
||||
from_a_pick :: () -> s64 { return pick(1, 2, 3); }
|
||||
12
examples/0726-modules-flat-same-name-variadic/b.sx
Normal file
12
examples/0726-modules-flat-same-name-variadic/b.sx
Normal file
@@ -0,0 +1,12 @@
|
||||
// b.sx is the SHADOW author for both names, with the OPPOSITE shapes:
|
||||
// `combine` is VARIADIC, `pick` is FIXED. Each `from_b_*` bare call must pack
|
||||
// against b's OWN author's signature (the F1 fix) — combine sums its variadic
|
||||
// pack, pick subtracts its two fixed args.
|
||||
combine :: (..xs: []s64) -> s64 {
|
||||
result := 0;
|
||||
for xs: (it) { result = result + it; }
|
||||
result
|
||||
}
|
||||
pick :: (a: s64, b: s64) -> s64 { return b - a; }
|
||||
from_b_combine :: () -> s64 { return combine(1, 2, 3, 4); }
|
||||
from_b_pick :: () -> s64 { return pick(2, 7); }
|
||||
20
examples/0727-modules-user-ns-m0.sx
Normal file
20
examples/0727-modules-user-ns-m0.sx
Normal file
@@ -0,0 +1,20 @@
|
||||
// fix-0102c (issue 0102): a user namespace alias literally named `__m0`
|
||||
// coexists with flat same-name imports. fix-0102 resolves same-name authors by
|
||||
// FnDecl IDENTITY — there are no synthetic `__m0`-style names to collide with —
|
||||
// so a user namespace spelled `__m0` is just an ordinary namespace: `call_a`
|
||||
// binds a.ping (1), `call_b` binds b.ping (2), and `__m0.ping` reaches m.ping (99).
|
||||
#import "modules/std.sx";
|
||||
#import "0727-modules-user-ns-m0/a.sx";
|
||||
#import "0727-modules-user-ns-m0/b.sx";
|
||||
__m0 :: #import "0727-modules-user-ns-m0/m.sx";
|
||||
|
||||
report :: (label: string, ok: bool) {
|
||||
if ok { print("{}: ok\n", label); } else { print("{}: FAIL\n", label); }
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
report("call_a binds a.ping", call_a() == 1);
|
||||
report("call_b binds b.ping", call_b() == 2);
|
||||
report("__m0.ping binds m.ping", __m0.ping() == 99);
|
||||
0
|
||||
}
|
||||
3
examples/0727-modules-user-ns-m0/a.sx
Normal file
3
examples/0727-modules-user-ns-m0/a.sx
Normal file
@@ -0,0 +1,3 @@
|
||||
// Flat author of `ping`; `call_a` binds a.sx's own `ping` (1).
|
||||
ping :: () -> s64 { return 1; }
|
||||
call_a :: () -> s64 { return ping(); }
|
||||
3
examples/0727-modules-user-ns-m0/b.sx
Normal file
3
examples/0727-modules-user-ns-m0/b.sx
Normal file
@@ -0,0 +1,3 @@
|
||||
// Second flat author of `ping`; `call_b` binds b.sx's own `ping` (2).
|
||||
ping :: () -> s64 { return 2; }
|
||||
call_b :: () -> s64 { return ping(); }
|
||||
3
examples/0727-modules-user-ns-m0/m.sx
Normal file
3
examples/0727-modules-user-ns-m0/m.sx
Normal file
@@ -0,0 +1,3 @@
|
||||
// Imported under a user namespace literally named `__m0`. Reached as
|
||||
// `__m0.ping` (99); coexists with the flat `ping` collision.
|
||||
ping :: () -> s64 { return 99; }
|
||||
22
examples/0728-modules-flat-same-name-paramtype.sx
Normal file
22
examples/0728-modules-flat-same-name-paramtype.sx
Normal file
@@ -0,0 +1,22 @@
|
||||
// fix-0102c F2 (issue 0102): two flat FILE imports each author a same-name free
|
||||
// function `apply` with a DIFFERENT parameter TYPE — a.sx takes a value
|
||||
// (`x: s64`), b.sx takes a pointer (`x: *s64`). The first-wins import merge
|
||||
// keeps a.sx's value-typed `apply`, but each module's bare call must type its
|
||||
// arguments against ITS OWN author. b.sx's `from_b` passes a local `v` to its
|
||||
// pointer-param `apply` via implicit address-of; before the fix the arg was
|
||||
// typed against the first-wins (value) winner, lowered as a value, then the
|
||||
// resolved pointer-param author was called with that value bit-cast to a
|
||||
// pointer — a segfault. Regression: per-source parameter target typing.
|
||||
#import "modules/std.sx";
|
||||
#import "0728-modules-flat-same-name-paramtype/a.sx";
|
||||
#import "0728-modules-flat-same-name-paramtype/b.sx";
|
||||
|
||||
report :: (label: string, ok: bool) {
|
||||
if ok { print("{}: ok\n", label); } else { print("{}: FAIL\n", label); }
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
report("from_a binds a.apply (value param)", from_a() == 11);
|
||||
report("from_b binds b.apply (pointer param)", from_b() == 42);
|
||||
0
|
||||
}
|
||||
5
examples/0728-modules-flat-same-name-paramtype/a.sx
Normal file
5
examples/0728-modules-flat-same-name-paramtype/a.sx
Normal file
@@ -0,0 +1,5 @@
|
||||
// a.sx authors `apply` taking a VALUE. It is imported first, so it is the
|
||||
// first-wins merge winner. `from_a` calls `apply` bare on a value local — its
|
||||
// own author wins (own == winner → existing path, byte-for-byte unchanged).
|
||||
apply :: (x: s64) -> s64 { return x + 1; }
|
||||
from_a :: () -> s64 { v : s64 = 10; return apply(v); }
|
||||
7
examples/0728-modules-flat-same-name-paramtype/b.sx
Normal file
7
examples/0728-modules-flat-same-name-paramtype/b.sx
Normal file
@@ -0,0 +1,7 @@
|
||||
// b.sx authors its OWN `apply` taking a POINTER. `from_b` passes a value local
|
||||
// `v` bare; the pointer param must drive implicit address-of so the callee
|
||||
// mutates `v` in place (×2 → 42). Before the fix, `v` was typed against a.sx's
|
||||
// value-param winner, lowered as a value, then the resolved pointer-param
|
||||
// author was called with that value forced to a pointer (segfault).
|
||||
apply :: (x: *s64) { x.* = x.* * 2; }
|
||||
from_b :: () -> s64 { v : s64 = 21; apply(v); return v; }
|
||||
14
examples/0729-modules-flat-same-name-foreign.sx
Normal file
14
examples/0729-modules-flat-same-name-foreign.sx
Normal file
@@ -0,0 +1,14 @@
|
||||
// fix-0102c (issue 0102) F3 regression: two flat FILE imports each `#foreign`
|
||||
// the SAME libc symbol under the SAME sx name `absval`. The bare-call resolver
|
||||
// must NOT count `#foreign` (non-plain) authors when deciding ambiguity — it
|
||||
// filters them out, returns "no rerouting", and the existing first-wins foreign
|
||||
// dispatch binds the call. A same-name foreign collision therefore compiles and
|
||||
// runs (master behavior), it does NOT error as ambiguous.
|
||||
#import "modules/std.sx";
|
||||
#import "0729-modules-flat-same-name-foreign/a.sx";
|
||||
#import "0729-modules-flat-same-name-foreign/b.sx";
|
||||
|
||||
main :: () -> s32 {
|
||||
print("absval = {}\n", absval(-7));
|
||||
0
|
||||
}
|
||||
5
examples/0729-modules-flat-same-name-foreign/a.sx
Normal file
5
examples/0729-modules-flat-same-name-foreign/a.sx
Normal file
@@ -0,0 +1,5 @@
|
||||
// One of two flat authors of `absval`, a `#foreign` libc binding. A consumer
|
||||
// flat-importing BOTH must NOT see this as an ambiguous bare-call collision —
|
||||
// foreign authors are never rerouted by the bare-call resolver, so the call
|
||||
// falls to the existing first-wins foreign dispatch.
|
||||
absval :: (n: s32) -> s32 #foreign libc "abs";
|
||||
2
examples/0729-modules-flat-same-name-foreign/b.sx
Normal file
2
examples/0729-modules-flat-same-name-foreign/b.sx
Normal file
@@ -0,0 +1,2 @@
|
||||
// The second flat author of `absval` — the identical `#foreign` libc binding.
|
||||
absval :: (n: s32) -> s32 #foreign libc "abs";
|
||||
1
examples/expected/0722-modules-flat-same-name-own.exit
Normal file
1
examples/expected/0722-modules-flat-same-name-own.exit
Normal file
@@ -0,0 +1 @@
|
||||
0
|
||||
1
examples/expected/0722-modules-flat-same-name-own.stderr
Normal file
1
examples/expected/0722-modules-flat-same-name-own.stderr
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
2
examples/expected/0722-modules-flat-same-name-own.stdout
Normal file
2
examples/expected/0722-modules-flat-same-name-own.stdout
Normal file
@@ -0,0 +1,2 @@
|
||||
from_a binds a.greet: ok
|
||||
from_b binds b.greet: ok
|
||||
1
examples/expected/0723-modules-flat-vs-namespaced.exit
Normal file
1
examples/expected/0723-modules-flat-vs-namespaced.exit
Normal file
@@ -0,0 +1 @@
|
||||
0
|
||||
1
examples/expected/0723-modules-flat-vs-namespaced.stderr
Normal file
1
examples/expected/0723-modules-flat-vs-namespaced.stderr
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
2
examples/expected/0723-modules-flat-vs-namespaced.stdout
Normal file
2
examples/expected/0723-modules-flat-vs-namespaced.stdout
Normal file
@@ -0,0 +1,2 @@
|
||||
bare binds flat: ok
|
||||
nm.value binds named: ok
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,5 @@
|
||||
error: 'dup' is ambiguous; declared by multiple imported modules — qualify the call
|
||||
--> examples/0724-modules-flat-same-name-ambiguous.sx:10:19
|
||||
|
|
||||
10 | print("{}\n", dup());
|
||||
| ^^^
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
1
examples/expected/0725-modules-flat-dir-same-name.exit
Normal file
1
examples/expected/0725-modules-flat-dir-same-name.exit
Normal file
@@ -0,0 +1 @@
|
||||
0
|
||||
1
examples/expected/0725-modules-flat-dir-same-name.stderr
Normal file
1
examples/expected/0725-modules-flat-dir-same-name.stderr
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
2
examples/expected/0725-modules-flat-dir-same-name.stdout
Normal file
2
examples/expected/0725-modules-flat-dir-same-name.stdout
Normal file
@@ -0,0 +1,2 @@
|
||||
caller1 binds d1.tag: ok
|
||||
caller2 binds d2.tag: ok
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
from_a combine fixed: ok
|
||||
from_b combine variadic: ok
|
||||
from_a pick variadic: ok
|
||||
from_b pick fixed: ok
|
||||
1
examples/expected/0727-modules-user-ns-m0.exit
Normal file
1
examples/expected/0727-modules-user-ns-m0.exit
Normal file
@@ -0,0 +1 @@
|
||||
0
|
||||
1
examples/expected/0727-modules-user-ns-m0.stderr
Normal file
1
examples/expected/0727-modules-user-ns-m0.stderr
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
3
examples/expected/0727-modules-user-ns-m0.stdout
Normal file
3
examples/expected/0727-modules-user-ns-m0.stdout
Normal file
@@ -0,0 +1,3 @@
|
||||
call_a binds a.ping: ok
|
||||
call_b binds b.ping: ok
|
||||
__m0.ping binds m.ping: ok
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
from_a binds a.apply (value param): ok
|
||||
from_b binds b.apply (pointer param): ok
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
absval = 7
|
||||
@@ -393,6 +393,12 @@ Direct C header import:
|
||||
math :: #import "modules/math.sx"; // namespaced import
|
||||
```
|
||||
|
||||
When two flat-imported modules each define a function of the same name, every
|
||||
module's own code binds its OWN author — a bare call resolves to the same-name
|
||||
function in the caller's module (or in its single flat import that provides it).
|
||||
A bare call to a name that two or more flat imports both provide is ambiguous and
|
||||
is rejected; qualify it with a namespaced import (`m :: #import …; m.fn()`).
|
||||
|
||||
### Implicit Context
|
||||
|
||||
Every program gets an implicit `context` with a default allocator:
|
||||
|
||||
@@ -1290,14 +1290,15 @@ fn countRealBodies(module: *ir_mod.Module, name: []const u8) usize {
|
||||
}
|
||||
|
||||
// fix-0102b: two flat-imported modules each author `greet`. The first-wins merge
|
||||
// keeps a.sx's author in the merged decl list (the WINNER — lowered when `main`
|
||||
// calls `greet()`) and drops b.sx's, which `module_fns` still retains (0102a).
|
||||
// keeps a.sx's author in the merged decl list (the WINNER) and drops b.sx's,
|
||||
// which `module_fns` still retains (0102a). `main` itself can't bare-call `greet`
|
||||
// — under fix-0102c two flat authors make that ambiguous — so it calls a.sx's
|
||||
// `use_greet` wrapper, whose own-author call to `greet` binds a.sx's winner.
|
||||
// BEFORE the identity-addressable pass, only the winner has a real body — the
|
||||
// shadowed author has no slot at all (the pre-fix symptom: one `greet`).
|
||||
// `lowerRetainedSameNameAuthors` declares the shadowed author its OWN same-name
|
||||
// FuncId and lowers its body there, so BOTH authors carry distinct, non-extern
|
||||
// bodies. Call resolution is untouched: `resolveFuncByName` still returns the
|
||||
// winner, so `main`'s `greet()` binds first-wins (rerouting is fix-0102c).
|
||||
// bodies, and `resolveFuncByName` still returns the winner (the name-keyed slot).
|
||||
test "lower: shadowed same-name author gets its own FuncId + real body (fix-0102b)" {
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
@@ -1307,12 +1308,12 @@ test "lower: shadowed same-name author gets its own FuncId + real body (fix-0102
|
||||
var tmp = std.testing.tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "a.sx", .data = "greet :: () -> s64 { 1 }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "a.sx", .data = "greet :: () -> s64 { 1 }\nuse_greet :: () -> s64 { greet() }\n" });
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "b.sx", .data = "greet :: () -> s64 { 2 }\n" });
|
||||
const main_src =
|
||||
\\#import "a.sx";
|
||||
\\#import "b.sx";
|
||||
\\main :: () -> s64 { greet() }
|
||||
\\main :: () -> s64 { use_greet() }
|
||||
\\
|
||||
;
|
||||
try tmp.dir.writeFile(io, .{ .sub_path = "main.sx", .data = main_src });
|
||||
@@ -1426,4 +1427,20 @@ test "lower: shadowed same-name author gets its own FuncId + real body (fix-0102
|
||||
const shadow_fid = lowering.fn_decl_fids.get(shadow_fd.?);
|
||||
try std.testing.expect(shadow_fid != null);
|
||||
try std.testing.expect(shadow_fid.? != winner_fid.?);
|
||||
|
||||
// fix-0102c: THE bare-name resolver routes per caller file. `main` flat-
|
||||
// imports two `greet` authors and is its own author of neither → a bare
|
||||
// `greet()` from `main` is ambiguous. a.sx authors the WINNER, so its bare
|
||||
// `greet` resolves through the existing path (`.none`). b.sx authors the
|
||||
// SHADOW, so own-author-wins binds b.sx's distinct FuncId — not first-wins.
|
||||
const a_path = try std.fmt.allocPrint(alloc, "{s}/a.sx", .{absdir});
|
||||
const b_path = try std.fmt.allocPrint(alloc, "{s}/b.sx", .{absdir});
|
||||
try std.testing.expect(lowering.resolveBareCallee("greet", main_path) == .ambiguous);
|
||||
try std.testing.expect(lowering.resolveBareCallee("greet", a_path) == .none);
|
||||
switch (lowering.resolveBareCallee("greet", b_path)) {
|
||||
.func => |resolved| try std.testing.expectEqual(shadow_fid.?, resolved.fid),
|
||||
else => return error.TestUnexpectedResult,
|
||||
}
|
||||
// A name no module authors (and no flat import provides) never routes.
|
||||
try std.testing.expect(lowering.resolveBareCallee("nonexistent", b_path) == .none);
|
||||
}
|
||||
|
||||
203
src/ir/lower.zig
203
src/ir/lower.zig
@@ -1512,31 +1512,123 @@ pub const Lowering = struct {
|
||||
// Only plain free functions get an out-of-line slot; generic /
|
||||
// foreign / builtin / #compiler authors keep their existing
|
||||
// dispatch (mirrors lazyLowerFunction / declareFunction guards).
|
||||
if (fd.type_params.len > 0) continue;
|
||||
switch (fd.body.data) {
|
||||
.foreign_expr, .builtin_expr, .compiler_expr => continue,
|
||||
else => {},
|
||||
}
|
||||
if (!isPlainFreeFn(fd)) continue;
|
||||
|
||||
// Already given its own slot + body? (idempotent across reruns.)
|
||||
if (self.fn_decl_fids.get(fd)) |existing| {
|
||||
if (self.lowered_fids.contains(existing)) continue;
|
||||
}
|
||||
|
||||
// Declare a fresh same-name FuncId for this author and lower its
|
||||
// body in its OWN module's visibility context (the path key IS
|
||||
// the author's source file, matching `module_scopes`).
|
||||
const saved_src = self.current_source_file;
|
||||
self.setCurrentSourceFile(path);
|
||||
if (!self.fn_decl_fids.contains(fd)) self.declareFunction(fd, name);
|
||||
self.setCurrentSourceFile(saved_src);
|
||||
const fid = self.fn_decl_fids.get(fd) orelse continue;
|
||||
self.lowerFunctionBodyInto(fd, fid, name);
|
||||
self.lowered_fids.put(fid, {}) catch {};
|
||||
_ = self.bareAuthorFuncId(fd, name, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Result of bare-call disambiguation (fix-0102c).
|
||||
pub const BareCallee = union(enum) {
|
||||
/// Bind the call to this specific author — its identity-addressable
|
||||
/// FuncId (fix-0102b's `bareAuthorFuncId`) AND its `*FnDecl`. The decl
|
||||
/// travels with the FuncId so every callee-signature decision in the
|
||||
/// call path (variadic packing, …) reads the RESOLVED author, never a
|
||||
/// first-wins re-lookup by name (fix-0102c F1).
|
||||
func: ResolvedAuthor,
|
||||
/// ≥2 distinct flat authors are reachable from the caller and none is
|
||||
/// the caller's own — the bare call can't pick one; require a qualifier.
|
||||
ambiguous,
|
||||
/// 0 or 1 reachable author, or the resolved author IS the existing
|
||||
/// bare-name winner — defer to the existing path, byte-for-byte.
|
||||
none,
|
||||
};
|
||||
|
||||
/// A resolved bare-call author: its FuncId and the `*FnDecl` that defined
|
||||
/// it, kept together so the call path has ONE source of truth for the
|
||||
/// callee (no re-fetch by name after resolution).
|
||||
pub const ResolvedAuthor = struct { fid: FuncId, decl: *const ast.FnDecl };
|
||||
|
||||
/// THE bare-name call resolver (fix-0102c). One canonical traversal over
|
||||
/// fix-0102a's `module_fns` + `flat_import_graph` that routes a bare
|
||||
/// identifier call `name` from `caller_file` to the right same-name author
|
||||
/// when flat imports introduce a genuine collision. Every single-author /
|
||||
/// local / parameter / std / qualified name resolves through the EXISTING
|
||||
/// path unchanged: the resolver returns `.none` whenever the outcome would
|
||||
/// match first-wins, so nothing on the common path is perturbed.
|
||||
///
|
||||
/// - **own-author wins**: if `caller_file` authors `name` and the bare-name
|
||||
/// first-wins winner is a DIFFERENT author, bind the caller's own author.
|
||||
/// (When the winner already IS the caller's own — the single-author and
|
||||
/// first-importer cases — `.none` lets the existing path bind it.)
|
||||
/// - else collect the authors reachable via `caller_file`'s FLAT import
|
||||
/// edges (bare `#import` of a file or directory, never a namespaced
|
||||
/// `ns :: #import`), deduped by `FnDecl` identity (a diamond import of the
|
||||
/// same module is one author): `≥2 distinct` → `.ambiguous`; exactly one
|
||||
/// that DIFFERS from the winner → bind it; otherwise `.none`.
|
||||
///
|
||||
/// Generic / comptime / foreign / builtin authors are never rerouted — the
|
||||
/// existing dispatch owns those shapes — so the resolver returns `.none`.
|
||||
pub fn resolveBareCallee(self: *Lowering, name: []const u8, caller_file: []const u8) BareCallee {
|
||||
const module_fns = self.program_index.module_fns orelse return .none;
|
||||
const winner = self.program_index.fn_ast_map.get(name);
|
||||
|
||||
// own-author wins.
|
||||
if (module_fns.get(caller_file)) |own_fns| {
|
||||
if (own_fns.get(name)) |own| {
|
||||
if (winner != null and winner.? == own) return .none;
|
||||
if (!isPlainFreeFn(own)) return .none;
|
||||
return .{ .func = .{ .fid = self.bareAuthorFuncId(own, name, caller_file), .decl = own } };
|
||||
}
|
||||
}
|
||||
|
||||
// Caller does not author `name` → collect its flat-reachable authors.
|
||||
const flat_graph = self.program_index.flat_import_graph orelse return .none;
|
||||
const edges = flat_graph.get(caller_file) orelse return .none;
|
||||
var distinct = std.AutoHashMap(*const ast.FnDecl, []const u8).init(self.alloc);
|
||||
defer distinct.deinit();
|
||||
var edge_it = edges.iterator();
|
||||
while (edge_it.next()) |e| {
|
||||
const fns = module_fns.get(e.key_ptr.*) orelse continue;
|
||||
// Only plain free functions are eligible for rerouting; generic /
|
||||
// foreign / builtin / #compiler authors keep their existing
|
||||
// dispatch. Filtering BEFORE the count gate means a same-name
|
||||
// collision of non-plain authors (e.g. two flat-imported modules
|
||||
// each `#foreign`ing the same symbol) is NOT counted as ambiguous —
|
||||
// it falls through to `.none` and the existing first-wins path.
|
||||
if (fns.get(name)) |fd| {
|
||||
if (!isPlainFreeFn(fd)) continue;
|
||||
distinct.put(fd, e.key_ptr.*) catch {};
|
||||
}
|
||||
}
|
||||
if (distinct.count() == 0) return .none;
|
||||
if (distinct.count() >= 2) return .ambiguous;
|
||||
|
||||
var one_it = distinct.iterator();
|
||||
const entry = one_it.next().?;
|
||||
const the_one = entry.key_ptr.*;
|
||||
const the_path = entry.value_ptr.*;
|
||||
if (winner != null and winner.? == the_one) return .none;
|
||||
return .{ .func = .{ .fid = self.bareAuthorFuncId(the_one, name, the_path), .decl = the_one } };
|
||||
}
|
||||
|
||||
/// The FuncId for a resolved bare-call author, ensuring its body is lowered.
|
||||
/// Only ever called for a SHADOW (an author that is not the name-keyed
|
||||
/// winner): the winner owns the name-keyed slot and lowers through the
|
||||
/// normal lazy path, so `resolveBareCallee` returns `.none` for it. A shadow
|
||||
/// is declared a fresh same-name FuncId in its OWN module's visibility
|
||||
/// context and its body lowered into that slot via fix-0102b's identity-
|
||||
/// addressable `lowerFunctionBodyInto`. Idempotent: `lowered_fids` tracks
|
||||
/// which slots already carry a body.
|
||||
fn bareAuthorFuncId(self: *Lowering, fd: *const ast.FnDecl, name: []const u8, path: []const u8) FuncId {
|
||||
if (self.fn_decl_fids.get(fd)) |fid| {
|
||||
if (!self.lowered_fids.contains(fid)) {
|
||||
self.lowered_fids.put(fid, {}) catch {};
|
||||
self.lowerFunctionBodyInto(fd, fid, name);
|
||||
}
|
||||
return fid;
|
||||
}
|
||||
const saved_src = self.current_source_file;
|
||||
self.setCurrentSourceFile(path);
|
||||
self.declareFunction(fd, name);
|
||||
self.setCurrentSourceFile(saved_src);
|
||||
const fid = self.fn_decl_fids.get(fd).?;
|
||||
self.lowered_fids.put(fid, {}) catch {};
|
||||
self.lowerFunctionBodyInto(fd, fid, name);
|
||||
return fid;
|
||||
}
|
||||
|
||||
/// Declare a function as an extern stub (signature only, no body).
|
||||
pub fn declareFunction(self: *Lowering, fd: *const ast.FnDecl, name: []const u8) void {
|
||||
// Skip generic templates — they're monomorphized on demand, not declared as extern
|
||||
@@ -7466,6 +7558,41 @@ pub const Lowering = struct {
|
||||
}
|
||||
}
|
||||
}
|
||||
// fix-0102c: a genuine flat same-name collision — bind the
|
||||
// caller file's OWN author (or its single flat-reachable
|
||||
// author), or reject a bare call to a name ≥2 imported modules
|
||||
// author. Only a plain top-level identifier call routes here:
|
||||
// scope-mangled / UFCS-aliased / locally-shadowed names and
|
||||
// 0/1-author names fall straight to the existing path below
|
||||
// (`resolveBareCallee` returns `.none`).
|
||||
if (std.mem.eql(u8, func_name, id.name) and
|
||||
(if (self.scope) |scope| scope.lookup(id.name) == null else true))
|
||||
{
|
||||
if (self.current_source_file) |caller_file| {
|
||||
switch (self.resolveBareCallee(func_name, caller_file)) {
|
||||
.none => {},
|
||||
.ambiguous => {
|
||||
if (self.diagnostics) |d|
|
||||
d.addFmt(.err, c.callee.span, "'{s}' is ambiguous; declared by multiple imported modules — qualify the call", .{func_name});
|
||||
return Ref.none;
|
||||
},
|
||||
.func => |resolved| {
|
||||
const fid = resolved.fid;
|
||||
const func = &self.module.functions.items[@intFromEnum(fid)];
|
||||
const ret_ty = func.ret;
|
||||
const params = func.params;
|
||||
// The RESOLVED author's decl drives variadic
|
||||
// packing — not a first-wins re-lookup by name,
|
||||
// whose variadic shape may differ (fix-0102c F1).
|
||||
self.packVariadicCallArgs(resolved.decl, c, &args);
|
||||
const final_args = self.prependCtxIfNeeded(func, args.items);
|
||||
self.coerceCallArgs(final_args, params);
|
||||
if (func.is_variadic) self.promoteCVariadicArgs(final_args, params.len);
|
||||
return self.builder.call(fid, final_args, ret_ty);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check for comptime-expanded or generic functions
|
||||
if (self.program_index.fn_ast_map.get(func_name)) |fd| {
|
||||
if (hasComptimeParams(fd)) {
|
||||
@@ -11961,6 +12088,29 @@ pub const Lowering = struct {
|
||||
break :blk scoped;
|
||||
};
|
||||
|
||||
// fix-0102c F2: a genuine flat same-name collision must type this
|
||||
// call's args against the RESOLVED author's params, not the first-wins
|
||||
// winner's. Mirror the `lowerCall` routing one layer earlier so arg
|
||||
// lowering (implicit address-of, coercion) matches the author actually
|
||||
// called — otherwise a `*T`-param shadow gets a `T` value arg that is
|
||||
// later bit-cast to a pointer (segfault). Only a plain top-level
|
||||
// identifier with no scope-mangle / UFCS alias / local shadow routes
|
||||
// here; `.ambiguous` / `.none` fall to the existing first-wins path so
|
||||
// single-author / local / std resolution is byte-for-byte unchanged.
|
||||
if (std.mem.eql(u8, name, bare_name) and
|
||||
(if (self.scope) |scope| scope.lookup(bare_name) == null else true))
|
||||
{
|
||||
if (self.current_source_file) |caller_file| {
|
||||
switch (self.resolveBareCallee(bare_name, caller_file)) {
|
||||
.func => |resolved| {
|
||||
const func = &self.module.functions.items[@intFromEnum(resolved.fid)];
|
||||
return self.userParamTypes(func);
|
||||
},
|
||||
.ambiguous, .none => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check declared functions
|
||||
if (self.resolveFuncByName(name)) |fid| {
|
||||
const func = &self.module.functions.items[@intFromEnum(fid)];
|
||||
@@ -12018,6 +12168,19 @@ pub const Lowering = struct {
|
||||
return false;
|
||||
}
|
||||
|
||||
/// A plain free function: no type params (not generic) and an ordinary sx
|
||||
/// body (not `#foreign` / `#builtin` / `#compiler`). Only these get an
|
||||
/// out-of-line identity-addressable slot — the bare-call disambiguation
|
||||
/// (fix-0102c) and the shadow-author lowering pass leave every other shape
|
||||
/// to the existing name-keyed dispatch.
|
||||
fn isPlainFreeFn(fd: *const ast.FnDecl) bool {
|
||||
if (fd.type_params.len > 0) return false;
|
||||
return switch (fd.body.data) {
|
||||
.foreign_expr, .builtin_expr, .compiler_expr => false,
|
||||
else => true,
|
||||
};
|
||||
}
|
||||
|
||||
/// Pack-fn: has a trailing heterogeneous pack param (`is_variadic
|
||||
/// AND is_comptime`). Mixed shapes — non-pack comptime params
|
||||
/// before the pack — are also accepted; the mono folds those
|
||||
|
||||
Reference in New Issue
Block a user