fix(resolver): share plan SelectedFunc across consumers + route UFCS through selector [stdlib C attempt-2]
Address Phase C review (C-1, C-2): make CallResolver.plan's SelectedFunc the single shared call author consumed by the lower-call sites instead of each re-resolving; route free-fn value-receiver UFCS through the selector in plan so plan typing and lowering pick the same author under a flat same-name collision. Adds regression 0740-modules-flat-same-name-ufcs-typing. Salvaged from a worker killed at the wall during its final gate step; manager verified the gate at ground truth (zig build test exit 0; run_examples 476/0 with 0722-0735 + 0740 ok; m3te ios-sim exit 0).
This commit is contained in:
17
examples/0740-modules-flat-same-name-ufcs-typing.sx
Normal file
17
examples/0740-modules-flat-same-name-ufcs-typing.sx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
// Regression (issue 0102, Phase C): value-receiver free-function UFCS under a
|
||||||
|
// flat same-name collision must be TYPED as the author lowering dispatches.
|
||||||
|
// a.sx (imported first → first-wins winner) authors `tag -> string`; b.sx
|
||||||
|
// authors its OWN `tag -> s64`. In b.sx, `v.tag()` dispatches b.tag (s64), but
|
||||||
|
// before the fix the call PLAN typed it as a.tag (string, first-wins) — so the
|
||||||
|
// pack-fn `print` boxed the raw s64 110 as a string pointer and dereferenced
|
||||||
|
// 0x6e → segfault. `CallResolver.plan` now selects the SAME author the lowering
|
||||||
|
// call-path binds, so plan-typing and dispatch can't disagree (fix-0102 F2).
|
||||||
|
#import "modules/std.sx";
|
||||||
|
#import "0740-modules-flat-same-name-ufcs-typing/a.sx";
|
||||||
|
#import "0740-modules-flat-same-name-ufcs-typing/b.sx";
|
||||||
|
|
||||||
|
main :: () -> s32 {
|
||||||
|
show_a(); // a-side: own == winner → string, byte-for-byte unchanged
|
||||||
|
show_b(); // b-side: shadow author → s64, typed + dispatched as b.tag
|
||||||
|
0
|
||||||
|
}
|
||||||
6
examples/0740-modules-flat-same-name-ufcs-typing/a.sx
Normal file
6
examples/0740-modules-flat-same-name-ufcs-typing/a.sx
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
#import "modules/std.sx";
|
||||||
|
// a.sx authors `tag` returning a string; imported first → first-wins winner.
|
||||||
|
// `show_a`'s `v.tag()` is the caller's OWN author (own == winner → existing UFCS
|
||||||
|
// path, byte-for-byte unchanged): typed AND dispatched as a.tag (string).
|
||||||
|
tag :: (x: s64) -> string { return "a-string"; }
|
||||||
|
show_a :: () { v : s64 = 10; print("a: v.tag() = {}\n", v.tag()); }
|
||||||
7
examples/0740-modules-flat-same-name-ufcs-typing/b.sx
Normal file
7
examples/0740-modules-flat-same-name-ufcs-typing/b.sx
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#import "modules/std.sx";
|
||||||
|
// b.sx authors its OWN `tag` returning s64. `show_b`'s `v.tag()` must be both
|
||||||
|
// dispatched AND typed as b.tag (s64 = 110), not the first-wins winner from a.sx
|
||||||
|
// (string). `print` types each arg from the call plan, so a mistype here boxes
|
||||||
|
// the s64 as a string pointer → segfault before the fix.
|
||||||
|
tag :: (x: s64) -> s64 { return x + 100; }
|
||||||
|
show_b :: () { v : s64 = 10; print("b: v.tag() = {}\n", v.tag()); }
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
0
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
a: v.tag() = a-string
|
||||||
|
b: v.tag() = 110
|
||||||
@@ -157,26 +157,19 @@ pub const CallResolver = struct {
|
|||||||
if (std.mem.eql(u8, bare_name, "type_of")) return refl(bare_name, .any);
|
if (std.mem.eql(u8, bare_name, "type_of")) return refl(bare_name, .any);
|
||||||
if (std.mem.eql(u8, bare_name, "field_value")) return refl(bare_name, .any);
|
if (std.mem.eql(u8, bare_name, "field_value")) return refl(bare_name, .any);
|
||||||
// Plain bare same-name flat collision (R5 §C): route through the ONE
|
// Plain bare same-name flat collision (R5 §C): route through the ONE
|
||||||
// selector so `plan` reads the SAME author the lowering call-path
|
// author producer `selectedFreeAuthor` so `plan` types the call as the
|
||||||
// binds — they can no longer disagree (fix-0102 F2). The gate mirrors
|
// SAME author the lowering call-path binds — they can no longer
|
||||||
// `lowerCall`'s: a plain top-level identifier with no scope-mangle /
|
// disagree (fix-0102 F2). A generic / foreign / builtin author is not
|
||||||
// local shadow. A generic / foreign / builtin author is not plain-free
|
// plain-free so the producer returns `.none`; `.ambiguous` / `.none`
|
||||||
// so the selector returns `.none`; `.ambiguous` / `.none` fall through
|
// fall through to the first-wins path below, byte-for-byte.
|
||||||
// to the first-wins path below, byte-for-byte.
|
switch (self.selectedFreeAuthor(c)) {
|
||||||
if (std.mem.eql(u8, name, bare_name) and
|
.func => |sf| return .{
|
||||||
(if (self.l.scope) |scope| scope.lookup(bare_name) == null else true))
|
.kind = .direct_fn,
|
||||||
{
|
.return_type = if (sf.decl.return_type) |rt| self.l.resolveType(rt) else .void,
|
||||||
if (self.l.current_source_file) |caller_file| {
|
.target = .{ .selected = sf },
|
||||||
switch (self.l.selectPlainCallableAuthor(bare_name, caller_file)) {
|
.expands_defaults = defaultsFor(sf.decl, c.args.len),
|
||||||
.func => |sf| return .{
|
},
|
||||||
.kind = .direct_fn,
|
.ambiguous, .none => {},
|
||||||
.return_type = if (sf.decl.return_type) |rt| self.l.resolveType(rt) else .void,
|
|
||||||
.target = .{ .selected = sf },
|
|
||||||
.expands_defaults = defaultsFor(sf.decl, c.args.len),
|
|
||||||
},
|
|
||||||
.ambiguous, .none => {},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Generic function — infer return type via type bindings.
|
// Generic function — infer return type via type bindings.
|
||||||
if (self.l.program_index.fn_ast_map.get(name)) |fd| {
|
if (self.l.program_index.fn_ast_map.get(name)) |fd| {
|
||||||
@@ -322,6 +315,26 @@ pub const CallResolver = struct {
|
|||||||
// the plan carries `prepends_receiver`, distinct from a true
|
// the plan carries `prepends_receiver`, distinct from a true
|
||||||
// namespace call (`pkg.fn()`), which must NOT prepend.
|
// namespace call (`pkg.fn()`), which must NOT prepend.
|
||||||
if (self.objectIsValue(cfa.object)) {
|
if (self.objectIsValue(cfa.object)) {
|
||||||
|
// Value-receiver free-fn UFCS (`recv.fn(args)` → `fn(recv, args)`)
|
||||||
|
// routes through the SAME author producer `selectedFreeAuthor` as a
|
||||||
|
// bare call, so the planned target / return type IS the author
|
||||||
|
// lowering dispatches — they can't disagree under a flat same-name
|
||||||
|
// collision (fix-0102 F2 / R5 §C). Without this, plan typed the
|
||||||
|
// first-wins winner while lowering bound the selected shadow,
|
||||||
|
// mis-tagging the call's result (a string-typed winner over an s64
|
||||||
|
// shadow boxes a raw int as a string pointer → segfault).
|
||||||
|
// `.ambiguous` / `.none` fall through to the first-wins path below,
|
||||||
|
// unchanged.
|
||||||
|
switch (self.selectedFreeAuthor(c)) {
|
||||||
|
.func => |sf| return .{
|
||||||
|
.kind = .free_fn_ufcs,
|
||||||
|
.return_type = if (sf.decl.return_type) |rt| self.l.resolveType(rt) else .void,
|
||||||
|
.target = .{ .selected = sf },
|
||||||
|
.prepends_receiver = true,
|
||||||
|
.expands_defaults = defaultsFor(sf.decl, c.args.len + 1),
|
||||||
|
},
|
||||||
|
.ambiguous, .none => {},
|
||||||
|
}
|
||||||
if (self.l.resolveFuncByName(cfa.field)) |fid| {
|
if (self.l.resolveFuncByName(cfa.field)) |fid| {
|
||||||
const func = &self.l.module.functions.items[@intFromEnum(fid)];
|
const func = &self.l.module.functions.items[@intFromEnum(fid)];
|
||||||
return .{
|
return .{
|
||||||
@@ -434,6 +447,47 @@ pub const CallResolver = struct {
|
|||||||
return .{ .kind = .unresolved, .return_type = .unresolved };
|
return .{ .kind = .unresolved, .return_type = .unresolved };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// THE single producer of the bare / value-UFCS same-name call author
|
||||||
|
/// verdict (R5 §#3). Both `plan` (typing, via its `.selected` arm) and
|
||||||
|
/// `lowerCall` (default expansion / param typing / dispatch) consume THIS one
|
||||||
|
/// result, so they can never pick different same-name authors for the same
|
||||||
|
/// call (fix-0102 F2). Side-effect-free: it consults ONLY the author selector
|
||||||
|
/// (`selectPlainCallableAuthor`) — never return-type inference or type-arg
|
||||||
|
/// resolution — so `lowerCall` can compute it eagerly without emitting a
|
||||||
|
/// premature diagnostic the full `plan` would (e.g. `cast(type)`'s type-arg).
|
||||||
|
///
|
||||||
|
/// - identifier callee: a plain bare call. The gate mirrors `plan`/`lowerCall`
|
||||||
|
/// — a builtin, a scope-mangled / UFCS-aliased name, or a locally-shadowed
|
||||||
|
/// name is never a same-name free-fn collision → `.none`.
|
||||||
|
/// - field-access callee with a VALUE receiver: a free-function UFCS
|
||||||
|
/// (`recv.fn(args)`). A namespace / type prefix receiver → `.none`. The
|
||||||
|
/// verdict over-selects a struct-method / protocol / foreign call whose
|
||||||
|
/// field happens to name a free fn, but those dispatch BEFORE the free-fn
|
||||||
|
/// UFCS path in both `plan` and `lowerCall`, so the verdict is consumed only
|
||||||
|
/// when the call truly is a free-fn UFCS.
|
||||||
|
pub fn selectedFreeAuthor(self: CallResolver, c: *const ast.Call) Lowering.BareCallee {
|
||||||
|
const caller_file = self.l.current_source_file orelse return .none;
|
||||||
|
switch (c.callee.data) {
|
||||||
|
.identifier => |id| {
|
||||||
|
const bare_name = id.name;
|
||||||
|
if (Lowering.resolveBuiltin(bare_name) != null) return .none;
|
||||||
|
const scoped = if (self.l.scope) |scope| scope.lookupFn(bare_name) orelse bare_name else bare_name;
|
||||||
|
const name = if (self.l.program_index.ufcs_alias_map.get(bare_name)) |target|
|
||||||
|
(if (self.l.scope) |scope| scope.lookupFn(target) orelse target else target)
|
||||||
|
else
|
||||||
|
scoped;
|
||||||
|
if (!std.mem.eql(u8, name, bare_name)) return .none;
|
||||||
|
if (self.l.scope) |scope| if (scope.lookup(bare_name) != null) return .none;
|
||||||
|
return self.l.selectPlainCallableAuthor(bare_name, caller_file);
|
||||||
|
},
|
||||||
|
.field_access => |cfa| {
|
||||||
|
if (!self.objectIsValue(cfa.object)) return .none;
|
||||||
|
return self.l.selectPlainCallableAuthor(cfa.field, caller_file);
|
||||||
|
},
|
||||||
|
else => return .none,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn refl(name: []const u8, rt: TypeId) CallPlan {
|
fn refl(name: []const u8, rt: TypeId) CallPlan {
|
||||||
return .{ .kind = .reflection, .return_type = rt, .target = .{ .named = name } };
|
return .{ .kind = .reflection, .return_type = rt, .target = .{ .named = name } };
|
||||||
}
|
}
|
||||||
|
|||||||
197
src/ir/lower.zig
197
src/ir/lower.zig
@@ -7361,10 +7361,27 @@ pub const Lowering = struct {
|
|||||||
c = rewritten;
|
c = rewritten;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
// fix-0102 F2 / R5 §C: select the bare / value-UFCS same-name call author
|
||||||
|
// ONCE, via `CallResolver.selectedFreeAuthor` — the SINGLE producer of
|
||||||
|
// this verdict, the exact same one `CallResolver.plan` consumes for typing.
|
||||||
|
// The call-path consumers (default expansion, param typing, dispatch) all
|
||||||
|
// read THIS one author object, so plan-typing and lowering-dispatch can no
|
||||||
|
// longer disagree about which same-name function the call names, and the
|
||||||
|
// shadow's FuncId is materialized at most once (into `author_verdict`).
|
||||||
|
// `selectedFreeAuthor` is side-effect-free (it only runs the author
|
||||||
|
// selector — no return-type inference / type-arg resolution), so computing
|
||||||
|
// it eagerly here can't emit a premature diagnostic the way the full plan
|
||||||
|
// would.
|
||||||
|
var author_verdict = self.callResolver().selectedFreeAuthor(c);
|
||||||
|
const sel_author: ?*SelectedFunc = switch (author_verdict) {
|
||||||
|
.func => |*sf| sf,
|
||||||
|
else => null,
|
||||||
|
};
|
||||||
|
const author_ambiguous = author_verdict == .ambiguous;
|
||||||
// Expand default parameter values for bare identifier callees:
|
// Expand default parameter values for bare identifier callees:
|
||||||
// when the caller omits trailing positional args, fill them in
|
// when the caller omits trailing positional args, fill them in
|
||||||
// from the callee's `param: T = expr` declarations.
|
// from the callee's `param: T = expr` declarations.
|
||||||
if (self.expandCallDefaults(c)) |expanded| c = expanded;
|
if (self.expandCallDefaults(c, sel_author, author_ambiguous)) |expanded| c = expanded;
|
||||||
// Check reflection builtins first (before lowering args — some args are type names, not values)
|
// Check reflection builtins first (before lowering args — some args are type names, not values)
|
||||||
if (c.callee.data == .identifier) {
|
if (c.callee.data == .identifier) {
|
||||||
if (self.tryLowerReflectionCall(c.callee.data.identifier.name, c)) |ref| return ref;
|
if (self.tryLowerReflectionCall(c.callee.data.identifier.name, c)) |ref| return ref;
|
||||||
@@ -7515,7 +7532,7 @@ pub const Lowering = struct {
|
|||||||
var args = std.ArrayList(Ref).empty;
|
var args = std.ArrayList(Ref).empty;
|
||||||
defer args.deinit(self.alloc);
|
defer args.deinit(self.alloc);
|
||||||
// Try to resolve param types for target_type context
|
// Try to resolve param types for target_type context
|
||||||
const param_types = self.resolveCallParamTypes(c);
|
const param_types = self.resolveCallParamTypes(c, sel_author);
|
||||||
// For enum_literal callees (.Variant(payload)), resolve the payload target type
|
// For enum_literal callees (.Variant(payload)), resolve the payload target type
|
||||||
// from the union field type so struct literal fields get proper coercion
|
// from the union field type so struct literal fields get proper coercion
|
||||||
var enum_payload_ty: ?TypeId = null;
|
var enum_payload_ty: ?TypeId = null;
|
||||||
@@ -7728,41 +7745,33 @@ pub const Lowering = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// fix-0102c: a genuine flat same-name collision — bind the
|
// fix-0102c / R5 §C: a genuine flat same-name collision — bind the
|
||||||
// caller file's OWN author (or its single flat-reachable
|
// author the call resolver selected (own-author-wins, or the single
|
||||||
// author), or reject a bare call to a name ≥2 imported modules
|
// flat-reachable author), or reject a bare call to a name ≥2
|
||||||
// author. Only a plain top-level identifier call routes here:
|
// imported modules author. `selectedFreeAuthor` (computed once
|
||||||
// scope-mangled / UFCS-aliased / locally-shadowed names and
|
// above, and the exact verdict `plan` consumes for typing) is the
|
||||||
// 0/1-author names fall straight to the existing path below
|
// single producer; lowering CONSUMES it rather than re-resolving
|
||||||
// (`selectPlainCallableAuthor` returns `.none`).
|
// the name, so typing and dispatch read the SAME author and can't
|
||||||
if (std.mem.eql(u8, func_name, id.name) and
|
// disagree (fix-0102 F2). Reached only for an identifier callee, so
|
||||||
(if (self.scope) |scope| scope.lookup(id.name) == null else true))
|
// `sel_author` / `author_ambiguous` here are the bare verdict.
|
||||||
{
|
if (author_ambiguous) {
|
||||||
if (self.current_source_file) |caller_file| {
|
if (self.diagnostics) |d|
|
||||||
switch (self.selectPlainCallableAuthor(func_name, caller_file)) {
|
d.addFmt(.err, c.callee.span, "'{s}' is ambiguous; declared by multiple imported modules — qualify the call", .{func_name});
|
||||||
.none => {},
|
return Ref.none;
|
||||||
.ambiguous => {
|
}
|
||||||
if (self.diagnostics) |d|
|
if (sel_author) |sf| {
|
||||||
d.addFmt(.err, c.callee.span, "'{s}' is ambiguous; declared by multiple imported modules — qualify the call", .{func_name});
|
const fid = self.selectedFuncId(sf, func_name);
|
||||||
return Ref.none;
|
const func = &self.module.functions.items[@intFromEnum(fid)];
|
||||||
},
|
const ret_ty = func.ret;
|
||||||
.func => |sf| {
|
const params = func.params;
|
||||||
var selected = sf;
|
// The RESOLVED author's decl drives variadic packing — not a
|
||||||
const fid = self.selectedFuncId(&selected, func_name);
|
// first-wins re-lookup by name, whose variadic shape may
|
||||||
const func = &self.module.functions.items[@intFromEnum(fid)];
|
// differ (fix-0102c F1).
|
||||||
const ret_ty = func.ret;
|
self.packVariadicCallArgs(sf.decl, c, &args);
|
||||||
const params = func.params;
|
const final_args = self.prependCtxIfNeeded(func, args.items);
|
||||||
// The RESOLVED author's decl drives variadic
|
self.coerceCallArgs(final_args, params);
|
||||||
// packing — not a first-wins re-lookup by name,
|
if (func.is_variadic) self.promoteCVariadicArgs(final_args, params.len);
|
||||||
// whose variadic shape may differ (fix-0102c F1).
|
return self.builder.call(fid, final_args, ret_ty);
|
||||||
self.packVariadicCallArgs(selected.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
|
// Check for comptime-expanded or generic functions
|
||||||
if (self.program_index.fn_ast_map.get(func_name)) |fd| {
|
if (self.program_index.fn_ast_map.get(func_name)) |fd| {
|
||||||
@@ -8247,26 +8256,24 @@ pub const Lowering = struct {
|
|||||||
// a function reached ONLY via UFCS would otherwise be declared
|
// a function reached ONLY via UFCS would otherwise be declared
|
||||||
// but never emitted (issue 0063: undefined symbol at link).
|
// but never emitted (issue 0063: undefined symbol at link).
|
||||||
//
|
//
|
||||||
// fix-0102d site 3: a free-function UFCS target with a genuine
|
// fix-0102d site 3 / R5 §C: a free-function UFCS target with a
|
||||||
// flat same-name collision must dispatch to the RESOLVED author
|
// genuine flat same-name collision dispatches to the author the
|
||||||
// for the receiver's source, not the first-wins winner. The
|
// call PLAN selected for the receiver's source — the SAME author
|
||||||
// field name is never scope-mangled, so the only gate is a
|
// plan typed the call's result as, so dispatch and typing can't
|
||||||
// known source file; `.ambiguous` → loud diagnostic; `.none`
|
// disagree (fix-0102 F2; without this, a string-typed winner over
|
||||||
// → existing first-wins path.
|
// an s64 shadow boxes a raw int as a string pointer → segfault).
|
||||||
|
// The plan is the single producer; lowering consumes its verdict
|
||||||
|
// (`sel_author` / `cplan.ambiguous_collision`, computed once above)
|
||||||
|
// rather than re-resolving the field name. `.ambiguous` → loud
|
||||||
|
// diagnostic; otherwise the existing first-wins lazy path.
|
||||||
const ufcs_fid: ?FuncId = blk_uf: {
|
const ufcs_fid: ?FuncId = blk_uf: {
|
||||||
if (self.current_source_file) |caller_file| {
|
if (author_ambiguous) {
|
||||||
switch (self.selectPlainCallableAuthor(fa.field, caller_file)) {
|
if (self.diagnostics) |d|
|
||||||
.func => |sf| {
|
d.addFmt(.err, c.callee.span, "'{s}' is ambiguous; declared by multiple imported modules — qualify the call", .{fa.field});
|
||||||
var selected = sf;
|
return Ref.none;
|
||||||
break :blk_uf self.selectedFuncId(&selected, fa.field);
|
}
|
||||||
},
|
if (sel_author) |sf| {
|
||||||
.ambiguous => {
|
break :blk_uf self.selectedFuncId(sf, fa.field);
|
||||||
if (self.diagnostics) |d|
|
|
||||||
d.addFmt(.err, c.callee.span, "'{s}' is ambiguous; declared by multiple imported modules — qualify the call", .{fa.field});
|
|
||||||
return Ref.none;
|
|
||||||
},
|
|
||||||
.none => {},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (self.program_index.fn_ast_map.get(fa.field)) |_| {
|
if (self.program_index.fn_ast_map.get(fa.field)) |_| {
|
||||||
if (!self.lowered_functions.contains(fa.field)) {
|
if (!self.lowered_functions.contains(fa.field)) {
|
||||||
@@ -12088,7 +12095,7 @@ pub const Lowering = struct {
|
|||||||
/// callee's signature provides defaults for them, return a fresh Call
|
/// callee's signature provides defaults for them, return a fresh Call
|
||||||
/// node with the defaults filled in. Returns null when no expansion is
|
/// node with the defaults filled in. Returns null when no expansion is
|
||||||
/// needed (callee unknown, all args provided, or no defaults available).
|
/// needed (callee unknown, all args provided, or no defaults available).
|
||||||
fn expandCallDefaults(self: *Lowering, c: *const ast.Call) ?*ast.Call {
|
fn expandCallDefaults(self: *Lowering, c: *const ast.Call, sel_author: ?*const SelectedFunc, author_ambiguous: bool) ?*ast.Call {
|
||||||
const fd = blk: {
|
const fd = blk: {
|
||||||
switch (c.callee.data) {
|
switch (c.callee.data) {
|
||||||
.identifier => |id| {
|
.identifier => |id| {
|
||||||
@@ -12099,30 +12106,19 @@ pub const Lowering = struct {
|
|||||||
}
|
}
|
||||||
break :blk2 scoped;
|
break :blk2 scoped;
|
||||||
};
|
};
|
||||||
// fix-0102d site 1: for a genuine flat same-name collision the
|
// fix-0102d site 1 / R5 §C: for a genuine flat same-name
|
||||||
// omitted trailing args must be filled from the RESOLVED
|
// collision the omitted trailing args are filled from the
|
||||||
// author's defaults, not the first-wins winner's. Only a plain
|
// author the call resolver selected — its `*FnDecl` defaults —
|
||||||
// top-level identifier with no scope-mangle / UFCS alias /
|
// not the first-wins winner's. lowering consumes the ONE author
|
||||||
// local shadow routes here; `.ambiguous` declines to expand
|
// verdict (`selectedFreeAuthor`, computed once in `lowerCall`)
|
||||||
// (the call path emits the single diagnostic); `.none` keeps
|
// rather than re-resolving the name, so default expansion and
|
||||||
// the existing first-wins winner, byte-for-byte.
|
// dispatch agree on the author. `.ambiguous` declines to expand
|
||||||
if (std.mem.eql(u8, eff_name, id.name) and
|
// (the call path emits the single diagnostic); a non-collision
|
||||||
(if (self.scope) |scope| scope.lookup(id.name) == null else true))
|
// call keeps the existing first-wins winner, byte-for-byte.
|
||||||
{
|
// Reading `.decl` only keeps `materialized` null — inspecting
|
||||||
if (self.current_source_file) |caller_file| {
|
// defaults must not lower the author (0102d).
|
||||||
switch (self.selectPlainCallableAuthor(id.name, caller_file)) {
|
if (author_ambiguous) return null;
|
||||||
// Default expansion needs only the author's decl
|
if (sel_author) |sf| break :blk sf.decl;
|
||||||
// (its param defaults) — never the FuncId. Reading
|
|
||||||
// `sf.decl` here keeps `materialized` null, so a
|
|
||||||
// bare call whose body is never emitted (e.g. only
|
|
||||||
// its defaults are inspected) does not lower the
|
|
||||||
// author (0102d).
|
|
||||||
.func => |sf| break :blk sf.decl,
|
|
||||||
.ambiguous => return null,
|
|
||||||
.none => {},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break :blk self.program_index.fn_ast_map.get(eff_name) orelse return null;
|
break :blk self.program_index.fn_ast_map.get(eff_name) orelse return null;
|
||||||
},
|
},
|
||||||
// Namespace call `mod.fn(args)` — args map directly to params
|
// Namespace call `mod.fn(args)` — args map directly to params
|
||||||
@@ -12192,7 +12188,7 @@ pub const Lowering = struct {
|
|||||||
return types_list.items;
|
return types_list.items;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolveCallParamTypes(self: *Lowering, c: *const ast.Call) []const TypeId {
|
fn resolveCallParamTypes(self: *Lowering, c: *const ast.Call, sel_author: ?*SelectedFunc) []const TypeId {
|
||||||
// Method calls: obj.method(args) — resolve param types from the method signature,
|
// Method calls: obj.method(args) — resolve param types from the method signature,
|
||||||
// skipping the first param (self) since it's prepended later.
|
// skipping the first param (self) since it's prepended later.
|
||||||
if (c.callee.data == .field_access) {
|
if (c.callee.data == .field_access) {
|
||||||
@@ -12345,29 +12341,20 @@ pub const Lowering = struct {
|
|||||||
break :blk scoped;
|
break :blk scoped;
|
||||||
};
|
};
|
||||||
|
|
||||||
// fix-0102c F2: a genuine flat same-name collision must type this
|
// fix-0102c F2 / R5 §C: a genuine flat same-name collision must type this
|
||||||
// call's args against the RESOLVED author's params, not the first-wins
|
// call's args against the author the call resolver selected, not the
|
||||||
// winner's. Mirror the `lowerCall` routing one layer earlier so arg
|
// first-wins winner's params. lowering consumes the ONE author verdict
|
||||||
// lowering (implicit address-of, coercion) matches the author actually
|
// (`selectedFreeAuthor`, computed once in `lowerCall`) rather than
|
||||||
// called — otherwise a `*T`-param shadow gets a `T` value arg that is
|
// re-resolving the name, so arg lowering (implicit address-of, coercion)
|
||||||
// later bit-cast to a pointer (segfault). Only a plain top-level
|
// matches the author actually dispatched — otherwise a `*T`-param shadow
|
||||||
// identifier with no scope-mangle / UFCS alias / local shadow routes
|
// gets a `T` value arg that is later bit-cast to a pointer (segfault). The
|
||||||
// here; `.ambiguous` / `.none` fall to the existing first-wins path so
|
// FuncId materializes into the SHARED verdict (once), so dispatch reuses
|
||||||
// single-author / local / std resolution is byte-for-byte unchanged.
|
// it. A non-collision call falls to the existing first-wins path below,
|
||||||
if (std.mem.eql(u8, name, bare_name) and
|
// byte-for-byte.
|
||||||
(if (self.scope) |scope| scope.lookup(bare_name) == null else true))
|
if (sel_author) |sf| {
|
||||||
{
|
const fid = self.selectedFuncId(sf, bare_name);
|
||||||
if (self.current_source_file) |caller_file| {
|
const func = &self.module.functions.items[@intFromEnum(fid)];
|
||||||
switch (self.selectPlainCallableAuthor(bare_name, caller_file)) {
|
return self.userParamTypes(func);
|
||||||
.func => |sf| {
|
|
||||||
var selected = sf;
|
|
||||||
const fid = self.selectedFuncId(&selected, bare_name);
|
|
||||||
const func = &self.module.functions.items[@intFromEnum(fid)];
|
|
||||||
return self.userParamTypes(func);
|
|
||||||
},
|
|
||||||
.ambiguous, .none => {},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check declared functions
|
// Check declared functions
|
||||||
|
|||||||
Reference in New Issue
Block a user