lang: generic $R type-arg resolution + receiver-driven ufcs overload (issues 0156, 0157)

0156 Part 1: a single-type generic $R (parsed as comptime_pack_ref)
used as a type-arg in a pack-fn body (Box($R), size_of(Box($R))) hit a
missing arm in resolveTypeWithBindings -> .unresolved -> LLVM panic.
Fix: mirror resolveTypeArg's comptime_pack_ref arm (look up
type_bindings, else a loud diagnostic). Regression: examples/generics/0216.
(Part 2 -- deferred .. spread crashes -- reframed OPEN/non-blocking.)

0157: a user generic ufcs method whose name collides with a stdlib
re-export resolved via last-wins fn_ast_map with no receiver filtering,
so the wrong overload won, $R never bound, and .unresolved reached LLVM.
Fix: selectUfcsGenericByReceiver enumerates all module authors, keeps
the receiver-binding ones, picks the most receiver-specific (concrete >
bare $T), dedups re-exports, and flags a genuine tie as a deterministic
'ambiguous -- qualify' diagnostic. Regression: examples/generics/0217.
This commit is contained in:
agra
2026-06-21 18:43:49 +03:00
parent b1e06f21e3
commit d3944570b9
13 changed files with 443 additions and 2 deletions

View File

@@ -0,0 +1,28 @@
// A single-type generic binding (`$R` from `Closure(..$args) -> $R`) used as a
// generic-struct TYPE ARGUMENT inside a variadic-pack function's body —
// `Box($R)` / `size_of(Box($R))` — must resolve `$R` to its bound TypeId.
//
// Regression (issue 0156, part 1): the parser tags every `$name` expression as
// `comptime_pack_ref`, so a single-type `$R` arrived at `resolveTypeWithBindings`
// (the resolver `instantiateGenericStruct` uses for each type-arg) as a
// `comptime_pack_ref` it had no arm for → fell to the catch-all → `.unresolved`
// → an LLVM-emission panic. `resolveTypeArg` already handled this; the fix
// mirrors its arm in `resolveTypeWithBindings` (look up `type_bindings`, else a
// loud "pack used where a single type is required" diagnostic — never a silent
// default type).
#import "modules/std.sx";
Box :: struct ($R: Type) { v: R; }
// A pack fn whose body references `$R` (the closure's return type) in a
// type-arg slot: both `*Box($R)` (annotation) and `size_of(Box($R))`.
boxed :: ufcs (io: Io, worker: Closure(..$args) -> $R, ..$args) -> Box($R) {
b : *Box($R) = xx context.allocator.alloc_bytes(size_of(Box($R)));
b.v = worker(..args);
return b.*;
}
main :: () {
r := context.io.boxed((a: i64, b: i64) -> i64 => a + b, 40, 2);
print("r: {}\n", r.v); // r: 42
}

View File

@@ -0,0 +1,24 @@
// A user generic ufcs method whose name collides with a stdlib re-export must
// resolve by RECEIVER TYPE, not last-wins. `cancel` here is also re-exported by
// std.sx (io.sx's `cancel :: ufcs (f: *Future($R))`); calling `(@x).cancel()` on
// a `*Box(i64)` must pick the user's `cancel(*Box($R))` and bind `$R := i64`.
//
// Regression (issue 0157): UFCS dispatch resolved the name via a single
// last-wins `fn_ast_map` entry with no receiver filtering, so the stdlib
// `*Future($R)` overload won, `$R` never bound, and `.unresolved` reached LLVM
// → panic. Fixed by selecting the most receiver-specific binding author across
// all module authors (src/ir/lower/call.zig `selectUfcsGenericByReceiver`).
#import "modules/std.sx";
Box :: struct ($R: Type) { value: R; flag: i64; }
// Same name as std.sx's re-exported `cancel` (generic ufcs over `*Future($R)`),
// but a different receiver — the receiver type disambiguates.
cancel :: ufcs (b: *Box($R)) { b.flag = 1; }
main :: () -> i64 {
x : Box(i64) = ---; x.value = 7; x.flag = 0;
(@x).cancel(); // resolves to the user `cancel` by receiver type
print("{}\n", x.flag); // 1
return 0;
}