lang: generic struct head aliases bind the template (fix 0120) — alias-follow from each author's source in head selection; loud unknown-type on the .call type tail
BoxAlias :: Box; / Box :: r.Box; now resolve instantiation, methods,
annotations, and chains through the aliased template, and re-export one
flat-import level as ordinary own decls (the facade shape the std.sx
restructure needs). selectGenericStructHead consults aliasedStructTemplate
(nominal.zig) before the global template map — own-wins/single-flat alias
author, each hop pinned to the alias author's source, ns.X RHS through
namespaceAliasVerdictFrom, depth-capped. resolveTypeCallWithBindings'
silent .unresolved tail (panicked in LLVM emission) now diagnoses
"unknown type". Also aligns the stale pre-existing calls.test.zig UFCS
plan test with the opt-in model (a47ea14). Regression: examples/0211
(+rich/+facade). Gates: zig build test 426/426, suite 587/587.
This commit is contained in:
@@ -429,14 +429,17 @@ test "plan: free-function UFCS prepends receiver, distinct from namespace_fn" {
|
||||
var l = Lowering.init(&module);
|
||||
const cr = CallResolver{ .l = &l };
|
||||
|
||||
// struct Counter, and a FREE function `bump :: (c: Counter) -> s32` — NOT
|
||||
// registered as `Counter.bump`, so it can only be reached via UFCS.
|
||||
// struct Counter, and a FREE ufcs function `bump :: ufcs (c: Counter) ->
|
||||
// s32` — NOT registered as `Counter.bump`, so it can only be reached via
|
||||
// UFCS. Dot-dispatch is OPT-IN: the fn carries `is_ufcs` and is
|
||||
// registered in `fn_ast_map`, where the plan's opt-in gate reads it.
|
||||
const counter = module.types.intern(.{ .@"struct" = .{ .name = module.types.internString("Counter"), .fields = &.{} } });
|
||||
const c_param = ast.Param{ .name = "c", .name_span = .{ .start = 0, .end = 0 }, .type_expr = typeExpr(alloc, "Counter") };
|
||||
const params = [_]ast.Param{c_param};
|
||||
const ret_stmt = mk(alloc, .{ .return_stmt = .{ .value = intLit(alloc, 7) } });
|
||||
const body = mk(alloc, .{ .block = .{ .stmts = &[_]*Node{ret_stmt} } });
|
||||
const fd = ast.FnDecl{ .name = "bump", .params = ¶ms, .return_type = typeExpr(alloc, "s32"), .body = body };
|
||||
const fd = ast.FnDecl{ .name = "bump", .params = ¶ms, .return_type = typeExpr(alloc, "s32"), .body = body, .is_ufcs = true };
|
||||
l.program_index.fn_ast_map.put("bump", &fd) catch unreachable;
|
||||
l.lowerFunction(&fd, "bump", false);
|
||||
const fid = l.resolveFuncByName("bump").?;
|
||||
module.functions.items[@intFromEnum(fid)].has_implicit_ctx = true;
|
||||
|
||||
@@ -1186,8 +1186,16 @@ pub const Lowering = struct {
|
||||
/// edges of flat edges do not chain). Two distinct carried targets for
|
||||
/// the same alias are ambiguous.
|
||||
pub fn namespaceAliasVerdict(self: *Lowering, alias: []const u8) AliasVerdict {
|
||||
const edges = self.program_index.namespace_edges orelse return .none;
|
||||
const from = self.current_source_file orelse return .none;
|
||||
return self.namespaceAliasVerdictFrom(alias, from);
|
||||
}
|
||||
|
||||
/// `namespaceAliasVerdict` with an explicit querying source — for callers
|
||||
/// resolving an alias on behalf of ANOTHER module (e.g. following a const
|
||||
/// alias decl whose RHS is `ns.X`: `ns` binds in the alias author's file,
|
||||
/// not the use site's).
|
||||
pub fn namespaceAliasVerdictFrom(self: *Lowering, alias: []const u8, from: []const u8) AliasVerdict {
|
||||
const edges = self.program_index.namespace_edges orelse return .none;
|
||||
if (edges.getPtr(from)) |own| {
|
||||
if (own.get(alias)) |t| return .{ .target = t };
|
||||
}
|
||||
@@ -1679,6 +1687,7 @@ pub const Lowering = struct {
|
||||
pub const rawNamedTypePtr = lower_nominal.rawNamedTypePtr;
|
||||
pub const buildGenericStructTemplate = lower_nominal.buildGenericStructTemplate;
|
||||
pub const qualifiedStructTemplate = lower_nominal.qualifiedStructTemplate;
|
||||
pub const aliasedStructTemplate = lower_nominal.aliasedStructTemplate;
|
||||
pub const qualifiedMemberMissing = lower_nominal.qualifiedMemberMissing;
|
||||
pub const bareVisibleStructDecl = lower_nominal.bareVisibleStructDecl;
|
||||
pub const bareVisibleStructTemplate = lower_nominal.bareVisibleStructTemplate;
|
||||
|
||||
@@ -974,6 +974,17 @@ pub fn selectGenericStructHead(self: *Lowering, name: []const u8, alias: ?[]cons
|
||||
if (self.program_index.struct_template_map.getPtr(name)) |tmpl| return .{ .template = tmpl.* };
|
||||
return .not_generic;
|
||||
}
|
||||
// Const-alias head (`BoxAlias :: Box;` / `Box :: r.Box;`, issue 0120):
|
||||
// follow the alias decl hop-by-hop to its authoring template, each hop
|
||||
// resolved from that alias author's own source. Checked BEFORE the map:
|
||||
// the alias may share its name with a same-name template that is NOT
|
||||
// visible from here (a facade's `Box :: r.Box;` re-export of rich's
|
||||
// `Box`), and the map branch would poison on that invisible author.
|
||||
// Only fires when the single visible author (own-wins / single-flat)
|
||||
// IS an alias-shaped const decl, so real template heads are untouched.
|
||||
if (self.current_source_file) |from| {
|
||||
if (self.aliasedStructTemplate(name, from)) |t| return .{ .template = t };
|
||||
}
|
||||
if (self.program_index.struct_template_map.getPtr(name)) |tmpl| {
|
||||
if (self.headTypeLeak(name, span)) return .poisoned;
|
||||
if (self.bareVisibleStructTemplate(name)) |vt| return .{ .template = vt };
|
||||
@@ -1230,7 +1241,14 @@ pub fn resolveTypeCallWithBindings(self: *Lowering, cl: *const ast.Call) TypeId
|
||||
}
|
||||
// Try as a named type
|
||||
const name_id = self.module.types.internString(callee_name);
|
||||
return self.module.types.findByName(name_id) orelse .unresolved;
|
||||
if (self.module.types.findByName(name_id)) |t| return t;
|
||||
// The callee names no known type constructor — not Vector, not a generic
|
||||
// struct template (or alias), not a type-returning function, not a named
|
||||
// type. A silent `.unresolved` here reaches LLVM emission as a panic;
|
||||
// diagnose and poison (the parameterized sibling below already does).
|
||||
if (self.diagnostics) |d|
|
||||
d.addFmt(.err, cl.callee.span, "unknown type '{s}'", .{callee_name});
|
||||
return .unresolved;
|
||||
}
|
||||
|
||||
/// Resolve a parameterized type expr, substituting bindings for type/value params.
|
||||
|
||||
@@ -394,6 +394,77 @@ pub fn qualifiedMemberMissing(self: *Lowering, alias: []const u8, member: []cons
|
||||
return true;
|
||||
}
|
||||
|
||||
/// The `*ConstDecl` a raw author wraps when it is a const ALIAS of another
|
||||
/// name — `BoxAlias :: Box;` (identifier RHS) or `Box :: r.Box;` (namespace-
|
||||
/// member RHS). Null for every other shape, including const-wrapped struct /
|
||||
/// fn DEFINITIONS, which are authors in their own right.
|
||||
fn constAliasOfRaw(ref: resolver_mod.RawDeclRef) ?*const ast.ConstDecl {
|
||||
return switch (ref) {
|
||||
.const_decl => |cd| switch (cd.value.data) {
|
||||
.identifier, .field_access => cd,
|
||||
else => null,
|
||||
},
|
||||
else => null,
|
||||
};
|
||||
}
|
||||
|
||||
/// The single author of `name` as seen from `from` — own wins, else exactly
|
||||
/// one flat-import author. Null when absent or when ≥2 flat authors compete
|
||||
/// (the use site then diagnoses the unresolved head; no silent pick).
|
||||
fn singleVisibleAuthor(self: *Lowering, name: []const u8, from: []const u8) ?resolver_mod.RawAuthor {
|
||||
var res = self.resolver();
|
||||
const set = res.collectVisibleAuthors(name, from, .user_bare_flat);
|
||||
defer if (set.flat.len > 0) self.alloc.free(set.flat);
|
||||
if (set.own) |o| return o;
|
||||
if (set.flat.len == 1) return set.flat[0];
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Resolve `name`, as seen from `from`, to a generic-struct template by
|
||||
/// following const ALIAS declarations (issue 0120). Entry for the head
|
||||
/// selector's bare tail: the FIRST hop must be alias-shaped — a direct
|
||||
/// struct author is the template map's business, never this path's. Each
|
||||
/// hop resolves from the ALIAS AUTHOR's source, so visibility is the
|
||||
/// author's, not the use site's (a consumer one flat hop from a facade
|
||||
/// reaches the facade's `Box :: r.Box;` without seeing `r` itself).
|
||||
pub fn aliasedStructTemplate(self: *Lowering, name: []const u8, from: []const u8) ?StructTemplate {
|
||||
const author = singleVisibleAuthor(self, name, from) orelse return null;
|
||||
if (constAliasOfRaw(author.raw) == null) return null;
|
||||
return followToTemplate(self, author, 8);
|
||||
}
|
||||
|
||||
/// One alias hop: a generic-struct author terminates the chain with its
|
||||
/// rebuilt source-pinned template; an alias author recurses on its RHS —
|
||||
/// bare identifier from the author's own source, `ns.X` through the
|
||||
/// author's namespace edge into the target module's own member. The depth
|
||||
/// cap breaks alias cycles (`A :: B; B :: A;`).
|
||||
fn followToTemplate(self: *Lowering, author: resolver_mod.RawAuthor, depth: u8) ?StructTemplate {
|
||||
if (depth == 0) return null;
|
||||
if (structDeclOfRaw(author.raw)) |sd| {
|
||||
if (sd.type_params.len == 0) return null;
|
||||
return self.buildGenericStructTemplate(sd, author.source);
|
||||
}
|
||||
const cd = constAliasOfRaw(author.raw) orelse return null;
|
||||
switch (cd.value.data) {
|
||||
.identifier => |id| {
|
||||
const next = singleVisibleAuthor(self, id.name, author.source) orelse return null;
|
||||
return followToTemplate(self, next, depth - 1);
|
||||
},
|
||||
.field_access => |fa| {
|
||||
if (fa.object.data != .identifier) return null;
|
||||
const target = switch (self.namespaceAliasVerdictFrom(fa.object.data.identifier.name, author.source)) {
|
||||
.target => |t| t,
|
||||
.none, .ambiguous => return null,
|
||||
};
|
||||
var res = self.resolver();
|
||||
const member_set = res.collectNamespaceAuthors(target, fa.field);
|
||||
const member = member_set.own orelse return null;
|
||||
return followToTemplate(self, member, depth - 1);
|
||||
},
|
||||
else => return null,
|
||||
}
|
||||
}
|
||||
|
||||
/// The bare-VISIBLE single generic-struct author of `name` (its `StructDecl` +
|
||||
/// defining source) when that author is NOT the one the global last-wins
|
||||
/// `struct_template_map` already holds — the E4 non-transitive selection for a
|
||||
|
||||
Reference in New Issue
Block a user