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
|
// A namespaced callee (`ns.Box(..)`) is an explicit qualified
|
||||||
// reach, exempt from the bare-head visibility gate (E4).
|
// reach, exempt from the bare-head visibility gate (E4).
|
||||||
const head_qualified = call_data.callee.data == .field_access;
|
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 (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
|
// 2-hop generic-struct head leak: poison the alias with
|
||||||
// `.unresolved` (suppressed downstream) so the use site
|
// `.unresolved` (suppressed downstream) so the use site
|
||||||
// sees no fabricated-stub cascade, only the loud
|
// 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);
|
self.putTypeAlias(self.current_source_file, cd.name, .unresolved);
|
||||||
break :reg;
|
break :reg;
|
||||||
}
|
}
|
||||||
const inst_id = self.instantiateGenericStruct(tmpl, call_data.args);
|
self.registerGenericStructAlias(cd.name, 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);
|
|
||||||
}
|
|
||||||
} else if (std.mem.eql(u8, callee_name, "Vector")) {
|
} else if (std.mem.eql(u8, callee_name, "Vector")) {
|
||||||
// Builtin type constructor — checked BEFORE
|
// Builtin type constructor — checked BEFORE
|
||||||
// the generic `fn_ast_map` branch because
|
// the generic `fn_ast_map` branch because
|
||||||
@@ -1079,26 +1071,22 @@ pub const Lowering = struct {
|
|||||||
const pt = &cd.value.data.parameterized_type_expr;
|
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 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;
|
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)) {
|
if (!pt_qualified and self.headTypeLeak(base_name, cd.value.span)) {
|
||||||
self.putTypeAlias(self.current_source_file, cd.name, .unresolved);
|
self.putTypeAlias(self.current_source_file, cd.name, .unresolved);
|
||||||
break :reg;
|
break :reg;
|
||||||
}
|
}
|
||||||
const inst_id = self.instantiateGenericStruct(tmpl, pt.args);
|
self.registerGenericStructAlias(cd.name, 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);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Builtin parameterised type (Vector(N, T) etc) —
|
// Builtin parameterised type (Vector(N, T) etc) —
|
||||||
// resolve via type_bridge and register the result
|
// resolve via type_bridge and register the result
|
||||||
@@ -14898,6 +14886,28 @@ pub const Lowering = struct {
|
|||||||
return null;
|
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 {
|
fn registerStructDecl(self: *Lowering, sd: *const ast.StructDecl, source_file: ?[]const u8) void {
|
||||||
const table = &self.module.types;
|
const table = &self.module.types;
|
||||||
const name_id = table.internString(sd.name);
|
const name_id = table.internString(sd.name);
|
||||||
|
|||||||
Reference in New Issue
Block a user