fix(stdlib/E4): qualified generic alias head a.Box(..) selects the namespace author
The const-decl alias-registration path treated a qualified generic head (`ABox :: a.Box(s64)`) only as a gate exemption, then read the bare last-wins `struct_template_map` — so `ABox` and `BBox` both instantiated whichever same-name template won globally (both size 16). attempt-9 routed the annotation head sites through `qualifiedStructTemplate`; this applies the same selection to the two alias-registration branches (.call and .parameterized_type_expr) before the bare fallback, and extracts the shared instantiate-and-register logic into `registerGenericStructAlias`. ABox :: a.Box(s64) now resolves to a's template (size 8); BBox :: b.Box(s64) to b's (size 16). Regression 0773 pins it (fail-before alias a=16 b=16, after a=8 b=16).
This commit is contained in:
28
examples/0773-modules-qualified-generic-alias-author.sx
Normal file
28
examples/0773-modules-qualified-generic-alias-author.sx
Normal file
@@ -0,0 +1,28 @@
|
||||
// A type alias whose RHS is a qualified generic head `ns.Box(args)` must
|
||||
// instantiate the template AUTHORED by `ns`'s module — not the global same-name
|
||||
// template that won the last-wins `struct_template_map`. Two namespaces each
|
||||
// author a same-name generic `Box($T)` with a DIFFERENT layout (a: one field,
|
||||
// b: two fields). `ABox :: a.Box(s64)` and `BBox :: b.Box(s64)` must register
|
||||
// aliases over their OWN module's template (sizes 8 and 16) and stay DISTINCT,
|
||||
// so the field unique to b's layout (`y`) is reachable only through `BBox`.
|
||||
//
|
||||
// Regression (Phase E4): before the alias-registration path selected the
|
||||
// qualified author, the const-decl alias `.call` branch stripped the head to the
|
||||
// bare name and read the global `struct_template_map`, so `ABox` and `BBox` both
|
||||
// instantiated the last-wins template (both size 16). attempt-9 fixed the
|
||||
// annotation head sites; this pins the alias-registration sibling.
|
||||
|
||||
#import "modules/std.sx";
|
||||
a :: #import "0773-modules-qualified-generic-alias-author/a.sx";
|
||||
b :: #import "0773-modules-qualified-generic-alias-author/b.sx";
|
||||
|
||||
ABox :: a.Box(s64);
|
||||
BBox :: b.Box(s64);
|
||||
|
||||
main :: () -> s32 {
|
||||
ab : ABox = .{ x = 1 };
|
||||
bb : BBox = .{ x = 10, y = 20 };
|
||||
print("alias a={} b={}\n", size_of(ABox), size_of(BBox));
|
||||
print("ab.x={} bb.x={} bb.y={}\n", ab.x, bb.x, bb.y);
|
||||
0
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
// Author A's generic `Box` — one s64 field (size 8).
|
||||
Box :: struct($T: Type) { x: T; }
|
||||
@@ -0,0 +1,3 @@
|
||||
// Author B's generic `Box` — two s64 fields (size 16). Same template NAME as
|
||||
// A's, different layout: the qualified alias head must select by namespace author.
|
||||
Box :: struct($T: Type) { x: T; y: T; }
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
alias a=8 b=16
|
||||
ab.x=1 bb.x=10 bb.y=20
|
||||
@@ -1022,8 +1022,18 @@ pub const Lowering = struct {
|
||||
// A namespaced callee (`ns.Box(..)`) is an explicit qualified
|
||||
// reach, exempt from the bare-head visibility gate (E4).
|
||||
const head_qualified = call_data.callee.data == .field_access;
|
||||
// A qualified head `ABox :: a.Box(s64)` selects a's OWN
|
||||
// template via the namespace edge (mirrors the annotation
|
||||
// head site `resolveTypeCallWithBindings`), not the bare
|
||||
// last-wins `struct_template_map`.
|
||||
const qual_call_tmpl: ?StructTemplate = if (head_qualified and call_data.callee.data.field_access.object.data == .identifier)
|
||||
self.qualifiedStructTemplate(call_data.callee.data.field_access.object.data.identifier.name, callee_name)
|
||||
else
|
||||
null;
|
||||
if (callee_name.len > 0) {
|
||||
if (self.program_index.struct_template_map.getPtr(callee_name)) |tmpl| reg: {
|
||||
if (qual_call_tmpl) |qt| {
|
||||
self.registerGenericStructAlias(cd.name, &qt, call_data.args);
|
||||
} else if (self.program_index.struct_template_map.getPtr(callee_name)) |tmpl| reg: {
|
||||
// 2-hop generic-struct head leak: poison the alias with
|
||||
// `.unresolved` (suppressed downstream) so the use site
|
||||
// sees no fabricated-stub cascade, only the loud
|
||||
@@ -1032,25 +1042,7 @@ pub const Lowering = struct {
|
||||
self.putTypeAlias(self.current_source_file, cd.name, .unresolved);
|
||||
break :reg;
|
||||
}
|
||||
const inst_id = self.instantiateGenericStruct(tmpl, call_data.args);
|
||||
// Register under the alias name
|
||||
const alias_name_id = self.module.types.internString(cd.name);
|
||||
const inst_info = self.module.types.get(inst_id);
|
||||
if (inst_info == .@"struct") {
|
||||
const alias_info: types.TypeInfo = .{ .@"struct" = .{
|
||||
.name = alias_name_id,
|
||||
.fields = inst_info.@"struct".fields,
|
||||
} };
|
||||
const alias_id = if (self.module.types.findByName(alias_name_id)) |existing| existing else self.module.types.intern(alias_info);
|
||||
self.module.types.updatePreservingKey(alias_id, alias_info);
|
||||
// A generic-struct instantiation alias IS a type
|
||||
// author: route it through the unified writer so it
|
||||
// lands in `type_aliases_by_source` and the bare-TYPE
|
||||
// gate treats it like any other alias (a ns-only
|
||||
// `Secret :: Box(s32)` is rejected, a flat one
|
||||
// resolves to the same TypeId `findByName` would).
|
||||
self.putTypeAlias(self.current_source_file, cd.name, alias_id);
|
||||
}
|
||||
self.registerGenericStructAlias(cd.name, tmpl, call_data.args);
|
||||
} else if (std.mem.eql(u8, callee_name, "Vector")) {
|
||||
// Builtin type constructor — checked BEFORE
|
||||
// the generic `fn_ast_map` branch because
|
||||
@@ -1079,26 +1071,22 @@ pub const Lowering = struct {
|
||||
const pt = &cd.value.data.parameterized_type_expr;
|
||||
const base_name = if (std.mem.lastIndexOfScalar(u8, pt.name, '.')) |dot| pt.name[dot + 1 ..] else pt.name;
|
||||
const pt_qualified = std.mem.indexOfScalar(u8, pt.name, '.') != null;
|
||||
if (self.program_index.struct_template_map.getPtr(base_name)) |tmpl| reg: {
|
||||
// A qualified base `ABox :: a.Box(s64)` selects a's OWN
|
||||
// template via the namespace edge (mirrors the annotation
|
||||
// head site `resolveParameterizedWithBindings`), not the
|
||||
// bare last-wins `struct_template_map`.
|
||||
const qual_pt_tmpl: ?StructTemplate = if (pt_qualified) blk: {
|
||||
const dot = std.mem.indexOfScalar(u8, pt.name, '.').?;
|
||||
break :blk self.qualifiedStructTemplate(pt.name[0..dot], base_name);
|
||||
} else null;
|
||||
if (qual_pt_tmpl) |qt| {
|
||||
self.registerGenericStructAlias(cd.name, &qt, pt.args);
|
||||
} else if (self.program_index.struct_template_map.getPtr(base_name)) |tmpl| reg: {
|
||||
if (!pt_qualified and self.headTypeLeak(base_name, cd.value.span)) {
|
||||
self.putTypeAlias(self.current_source_file, cd.name, .unresolved);
|
||||
break :reg;
|
||||
}
|
||||
const inst_id = self.instantiateGenericStruct(tmpl, pt.args);
|
||||
const alias_name_id = self.module.types.internString(cd.name);
|
||||
const inst_info = self.module.types.get(inst_id);
|
||||
if (inst_info == .@"struct") {
|
||||
const alias_info: types.TypeInfo = .{ .@"struct" = .{
|
||||
.name = alias_name_id,
|
||||
.fields = inst_info.@"struct".fields,
|
||||
} };
|
||||
const alias_id = if (self.module.types.findByName(alias_name_id)) |existing| existing else self.module.types.intern(alias_info);
|
||||
self.module.types.updatePreservingKey(alias_id, alias_info);
|
||||
// Same as the `.call` generic-struct branch: a
|
||||
// parameterized-struct alias is a type author and
|
||||
// must reach `type_aliases_by_source` so it gates.
|
||||
self.putTypeAlias(self.current_source_file, cd.name, alias_id);
|
||||
}
|
||||
self.registerGenericStructAlias(cd.name, tmpl, pt.args);
|
||||
} else {
|
||||
// Builtin parameterised type (Vector(N, T) etc) —
|
||||
// resolve via type_bridge and register the result
|
||||
@@ -14898,6 +14886,28 @@ pub const Lowering = struct {
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Instantiate a generic struct template and register the result under an
|
||||
/// alias name (`Vec3 :: Vec(3, f32)` / `ABox :: a.Box(s64)`). Shared by the
|
||||
/// `.call` and `.parameterized_type_expr` const-decl alias branches and the
|
||||
/// qualified-head selection that precedes the bare `struct_template_map`
|
||||
/// fallback in each.
|
||||
fn registerGenericStructAlias(self: *Lowering, alias_name: []const u8, tmpl: *const StructTemplate, args: []const *const Node) void {
|
||||
const inst_id = self.instantiateGenericStruct(tmpl, args);
|
||||
const alias_name_id = self.module.types.internString(alias_name);
|
||||
const inst_info = self.module.types.get(inst_id);
|
||||
if (inst_info != .@"struct") return;
|
||||
const alias_info: types.TypeInfo = .{ .@"struct" = .{
|
||||
.name = alias_name_id,
|
||||
.fields = inst_info.@"struct".fields,
|
||||
} };
|
||||
const alias_id = if (self.module.types.findByName(alias_name_id)) |existing| existing else self.module.types.intern(alias_info);
|
||||
self.module.types.updatePreservingKey(alias_id, alias_info);
|
||||
// A generic-struct instantiation alias IS a type author: route it through
|
||||
// the unified writer so it lands in `type_aliases_by_source` and the
|
||||
// bare-TYPE gate treats it like any other alias.
|
||||
self.putTypeAlias(self.current_source_file, alias_name, alias_id);
|
||||
}
|
||||
|
||||
fn registerStructDecl(self: *Lowering, sd: *const ast.StructDecl, source_file: ?[]const u8) void {
|
||||
const table = &self.module.types;
|
||||
const name_id = table.internString(sd.name);
|
||||
|
||||
Reference in New Issue
Block a user