fix(stdlib/E4): collapse generic-struct author matrix into four choke-points
The generic-struct author-selection matrix {bare,qualified} × {site} × {layout,
body} drifted per-site across 12 attempts because method bodies were resolved by
bare template name in `fn_ast_map["Box.method"]`, independent of which author
produced the instance's layout. Collapse it into four choke-points so
layout-author ≡ body-author by construction:
CP-1 `selectGenericStructHead` — the single layout-head selector every generic
struct head site funnels through (alias-RHS .call/.parameterized, array-
literal, static head, resolveTypeCall/ParameterizedWithBindings). Emits the
visibility / missing-member diagnostics inline; returns a control-flow-only
union. No head site reads `struct_template_map` for selection directly.
CP-2 author stamp — non-optional `decl: *StructDecl` on `StructTemplate` (set at
the sole producer `buildGenericStructTemplate`) + `struct_instance_author`
written at `instantiateGenericStruct` from the SAME `tmpl` that builds the
layout; re-stamped on the dedup fast-path so an instance is never returned
without an author.
CP-3 alias metadata copy — mirror template/bindings/author from the mangled
instance onto the alias display name, so an `ABox`-typed receiver is a
first-class dispatch instance (Counter-2).
CP-4 `genericInstanceMethod` / `ensureGenericInstanceMethodLowered` — the single
body reader: inline methods select via the stamped author (`structMethodFn`,
source-pin follows for free); impl-block methods fall back to the template-
keyed `fn_ast_map` entry. Routes the four bespoke body sites (static head,
instance dispatch, param typing, protocol thunk) + the new qualified static
head (`a.Box(s64).make(7)`, finding #2).
A debug assert locks `struct_instance_author` / `struct_instance_template` keyset
coincidence so a future third writer that forgets the author trips a test.
Goldens 0777/0778/0780 (bare instance method — ptr/by-value/param-typed, finding
#1), 0779/0785 (qualified static head + missing member, finding #2), 0783 (alias
instance dispatch, Counter-2), 0782 (ambiguity containment). 0414/0415/0543 and
the FFI suites stay green.
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
// A BARE generic struct INSTANCE method (`x.tag()`) must dispatch to the method
|
||||
// authored alongside the instance's layout — the single bare-VISIBLE author —
|
||||
// NOT the global last-wins `fn_ast_map["Box.tag"]`, which a NON-visible
|
||||
// 2-flat-hop same-name template's method can win.
|
||||
//
|
||||
// `b.sx` declares a one-field `Box($T)` (size 8) whose `tag` returns `self.x+8`,
|
||||
// and itself flat-imports `c.sx`, whose two-field `Box($T)` (size 16) declares a
|
||||
// `tag` returning `self.x+16`. This file flat-imports ONLY `b.sx`, so `b.Box` is
|
||||
// one flat hop away (visible) and `c.Box` is two hops (NOT bare-visible).
|
||||
//
|
||||
// Regression (Phase E4 finding #1, instance-method site): the static head
|
||||
// `Box(s64).make(7)` already selected `b.Box` for the layout (size 8), but the
|
||||
// instance method `x.tag()` resolved by bare template name in `fn_ast_map`, so it
|
||||
// ran `c.Box.tag` (returning 7+16=23) on the b instance. Fail-before printed
|
||||
// `size=8 tag=23`; with body-author ≡ layout-author it runs b's `tag` (7+8=15).
|
||||
|
||||
#import "modules/std.sx";
|
||||
#import "0777-modules-bare-generic-instance-method-visible-author/b.sx";
|
||||
|
||||
main :: () -> s32 {
|
||||
x := Box(s64).make(7);
|
||||
print("size={} tag={}\n", size_of(type_of(x)), x.tag());
|
||||
0
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// The bare-VISIBLE author: a one-field generic `Box` (size 8). Its `tag` returns
|
||||
// `self.x + 8`. `b.sx` itself flat-imports `c.sx`, so a file importing only b.sx
|
||||
// reaches `c.Box` (and `c.Box.tag`) at two hops and must NOT dispatch to it.
|
||||
Box :: struct($T: Type) {
|
||||
x: T;
|
||||
|
||||
make :: (value: T) -> Box(T) {
|
||||
.{ x = value }
|
||||
}
|
||||
|
||||
tag :: (self: *Box) -> T {
|
||||
self.x + 8
|
||||
}
|
||||
}
|
||||
|
||||
#import "c.sx";
|
||||
@@ -0,0 +1,16 @@
|
||||
// The NON-visible 2-flat-hop author: a two-field generic `Box` (size 16) whose
|
||||
// `tag` returns `self.x + 16`. Same template NAME as b's, different body. It wins
|
||||
// the global last-wins `fn_ast_map["Box.tag"]`, so a file importing only b.sx
|
||||
// must NOT dispatch `x.tag()` to it.
|
||||
Box :: struct($T: Type) {
|
||||
x: T;
|
||||
y: T;
|
||||
|
||||
make :: (value: T) -> Box(T) {
|
||||
.{ x = value, y = value }
|
||||
}
|
||||
|
||||
tag :: (self: *Box) -> T {
|
||||
self.x + 16
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// A BARE generic struct instance method with a BY-VALUE receiver (`self: Box`)
|
||||
// must dispatch to the VISIBLE author's method, whose receiver ABI matches the
|
||||
// VISIBLE author's layout. The single visible `b.Box` is one field (size 8); the
|
||||
// 2-flat-hop `c.Box` is two fields (size 16). Selecting `c.Box.dbl` (a `{i64,i64}`
|
||||
// by-value receiver) for a `b.Box` instance (`{i64}`) mismatches the layout.
|
||||
//
|
||||
// Regression (Phase E4 finding #1, by-value receiver variant): before
|
||||
// body-author ≡ layout-author, the instance method resolved by bare template name
|
||||
// and selected `c.Box.dbl`, whose by-value receiver signature did not match the
|
||||
// `b.Box` instance layout (an LLVM verify failure / wrong body). With the fix it
|
||||
// runs `b.Box.dbl` (`self.x * 2`).
|
||||
|
||||
#import "modules/std.sx";
|
||||
#import "0778-modules-bare-generic-instance-by-value-receiver/b.sx";
|
||||
|
||||
main :: () -> s32 {
|
||||
x := Box(s64).make(5);
|
||||
print("size={} dbl={}\n", size_of(type_of(x)), x.dbl());
|
||||
0
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// The bare-VISIBLE author: a one-field generic `Box` (size 8) with a BY-VALUE
|
||||
// receiver method `dbl` (`self: Box`). `b.sx` flat-imports `c.sx`, so a file
|
||||
// importing only b.sx reaches `c.Box` at two hops and must NOT dispatch to it.
|
||||
Box :: struct($T: Type) {
|
||||
x: T;
|
||||
|
||||
make :: (value: T) -> Box(T) {
|
||||
.{ x = value }
|
||||
}
|
||||
|
||||
dbl :: (self: Box) -> T {
|
||||
self.x * 2
|
||||
}
|
||||
}
|
||||
|
||||
#import "c.sx";
|
||||
@@ -0,0 +1,17 @@
|
||||
// The NON-visible 2-flat-hop author: a two-field generic `Box` (size 16) with a
|
||||
// BY-VALUE receiver `dbl` (`self: Box`) returning `self.x * 2 + 100`. Its
|
||||
// by-value receiver ABI (`{i64,i64}`) differs from b's (`{i64}`); it wins the
|
||||
// global last-wins `fn_ast_map["Box.dbl"]`, so a file importing only b.sx must
|
||||
// NOT dispatch `x.dbl()` to it.
|
||||
Box :: struct($T: Type) {
|
||||
x: T;
|
||||
y: T;
|
||||
|
||||
make :: (value: T) -> Box(T) {
|
||||
.{ x = value, y = value }
|
||||
}
|
||||
|
||||
dbl :: (self: Box) -> T {
|
||||
self.x * 2 + 100
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// A QUALIFIED generic static-method head (`a.Box(s64).make(7)`) must instantiate
|
||||
// — and call the `make` of — the template AUTHORED by namespace `a`, for BOTH
|
||||
// the type layout and the method body. Two namespaces each author a same-name
|
||||
// generic `Box($T)` with a DIFFERENT layout and a DIFFERENT `make` (a: one field;
|
||||
// b: two fields, sets `y = v+100`). `a.Box(s64).make(7)` and `b.Box(s64).make(9)`
|
||||
// must select their OWN module's author.
|
||||
//
|
||||
// Regression (Phase E4 finding #2, qualified static-method site): the static-head
|
||||
// path only handled an IDENTIFIER inner callee (`Box(..)`), so the qualified
|
||||
// inner callee `a.Box(..)` (a `.field_access`) was not routed through
|
||||
// `qualifiedStructTemplate` — `a.Box(s64).make(7)` resolved to nothing
|
||||
// (unresolved, exit 1).
|
||||
|
||||
#import "modules/std.sx";
|
||||
a :: #import "0779-modules-qualified-generic-static-method-author/a.sx";
|
||||
b :: #import "0779-modules-qualified-generic-static-method-author/b.sx";
|
||||
|
||||
main :: () -> s32 {
|
||||
xa := a.Box(s64).make(7);
|
||||
xb := b.Box(s64).make(9);
|
||||
print("a.x={} b.x={} b.y={} sizes={} {}\n", xa.x, xb.x, xb.y, size_of(type_of(xa)), size_of(type_of(xb)));
|
||||
0
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// Author A's generic `Box` — one s64 field (size 8). Its `make` sets only `x`.
|
||||
Box :: struct($T: Type) {
|
||||
x: T;
|
||||
|
||||
make :: (value: T) -> Box(T) {
|
||||
.{ x = value }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// Author B's generic `Box` — two s64 fields (size 16). Same template NAME as
|
||||
// A's, different layout AND different `make` (sets `y = value + 100`). The
|
||||
// qualified static head must select by namespace author for both.
|
||||
Box :: struct($T: Type) {
|
||||
x: T;
|
||||
y: T;
|
||||
|
||||
make :: (value: T) -> Box(T) {
|
||||
.{ x = value, y = value + 100 }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// A BARE generic struct instance method that takes a PARAM (`x.combine(3)`) must
|
||||
// type its arguments against — and run the body of — the VISIBLE author's method.
|
||||
// The arg-typing path (`resolveCallParamTypes`) reads the instance's STAMPED
|
||||
// author for the method's param types, and dispatch runs that same author's body.
|
||||
//
|
||||
// `b.sx` (one field, size 8) declares `combine(self, v) => self.x + v`; the
|
||||
// 2-flat-hop `c.sx` (two fields) declares `combine(self, v) => self.x * v + 16`.
|
||||
// Importing only b.sx, `x.combine(3)` on the visible `b.Box` instance must run
|
||||
// b's body (`7 + 3 = 10`), not c's (`7 * 3 + 16 = 37`).
|
||||
//
|
||||
// Regression (Phase E4 finding #1, param-typed instance-method site).
|
||||
|
||||
#import "modules/std.sx";
|
||||
#import "0780-modules-bare-generic-instance-param-typed-author/b.sx";
|
||||
|
||||
main :: () -> s32 {
|
||||
x := Box(s64).make(7);
|
||||
print("combine={}\n", x.combine(3));
|
||||
0
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// The bare-VISIBLE author: a one-field generic `Box` (size 8). `combine` adds the
|
||||
// param. `b.sx` flat-imports `c.sx`, so a file importing only b.sx reaches
|
||||
// `c.Box` (and `c.Box.combine`) at two hops and must NOT dispatch to it.
|
||||
Box :: struct($T: Type) {
|
||||
x: T;
|
||||
|
||||
make :: (value: T) -> Box(T) {
|
||||
.{ x = value }
|
||||
}
|
||||
|
||||
combine :: (self: *Box, v: T) -> T {
|
||||
self.x + v
|
||||
}
|
||||
}
|
||||
|
||||
#import "c.sx";
|
||||
@@ -0,0 +1,16 @@
|
||||
// The NON-visible 2-flat-hop author: a two-field generic `Box` (size 16) whose
|
||||
// `combine` returns `self.x * v + 16`. Same template NAME / method name as b's,
|
||||
// different body. It wins the global last-wins `fn_ast_map["Box.combine"]`, so a
|
||||
// file importing only b.sx must NOT dispatch `x.combine(..)` to it.
|
||||
Box :: struct($T: Type) {
|
||||
x: T;
|
||||
y: T;
|
||||
|
||||
make :: (value: T) -> Box(T) {
|
||||
.{ x = value, y = value }
|
||||
}
|
||||
|
||||
combine :: (self: *Box, v: T) -> T {
|
||||
self.x * v + 16
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// A BARE generic static-method head `Box(s64).make(7)` whose name has ≥2 DISTINCT
|
||||
// directly-visible (1-hop flat) same-name authors is AMBIGUOUS — the head must
|
||||
// diagnose the ambiguity (consistent with the leaf / 0755 / 0767) BEFORE any
|
||||
// instantiation or method lookup, never silently pick a global last-wins author.
|
||||
//
|
||||
// `p.sx` and `q.sx` each author a generic `Box($T)` and are BOTH flat-imported
|
||||
// here, so a bare `Box` reference has two visible authors and cannot be resolved
|
||||
// without qualification. The fix is to `p.Box(s64)` / `q.Box(s64)` (0779).
|
||||
|
||||
#import "modules/std.sx";
|
||||
#import "0782-modules-bare-generic-instance-ambiguous-authors/p.sx";
|
||||
#import "0782-modules-bare-generic-instance-ambiguous-authors/q.sx";
|
||||
|
||||
main :: () -> s32 {
|
||||
x := Box(s64).make(7);
|
||||
print("{}\n", x.x);
|
||||
0
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// Author P's generic `Box` — one s64 field. Flat-imported alongside q.sx's
|
||||
// same-name `Box`, so a bare `Box` reference is ambiguous.
|
||||
Box :: struct($T: Type) {
|
||||
x: T;
|
||||
|
||||
make :: (value: T) -> Box(T) {
|
||||
.{ x = value }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// Author Q's generic `Box` — two s64 fields. Flat-imported alongside p.sx's
|
||||
// same-name `Box`, so a bare `Box` reference is ambiguous.
|
||||
Box :: struct($T: Type) {
|
||||
x: T;
|
||||
y: T;
|
||||
|
||||
make :: (value: T) -> Box(T) {
|
||||
.{ x = value, y = value }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// A generic-struct ALIAS whose RHS is a qualified head (`ABox :: a.Box(s64)`)
|
||||
// must make an ALIAS-typed receiver (`x: ABox`) a first-class dispatch instance:
|
||||
// `x.tag()` runs the author `a`'s body with `a`'s bindings — never a dead end.
|
||||
// Two namespaces author a same-name generic `Box($T)` with a DIFFERENT layout and
|
||||
// a DIFFERENT `tag`; the alias over `a.Box(s64)` must dispatch `a.Box.tag`.
|
||||
//
|
||||
// Regression (Phase E4 Counter-2): the alias registration cloned the layout into
|
||||
// a fresh type named `ABox` but did NOT mirror the instance template/bindings/
|
||||
// author onto the alias name, so `x.tag()` on an `ABox` receiver resolved to
|
||||
// nothing (unresolved). Copying the three instance-map entries to the alias name
|
||||
// makes the alias receiver dispatch its author's method.
|
||||
|
||||
#import "modules/std.sx";
|
||||
a :: #import "0783-modules-qualified-generic-alias-instance-dispatch/a.sx";
|
||||
b :: #import "0783-modules-qualified-generic-alias-instance-dispatch/b.sx";
|
||||
|
||||
ABox :: a.Box(s64);
|
||||
|
||||
main :: () -> s32 {
|
||||
x : ABox = .{ x = 5 };
|
||||
print("size={} tag={}\n", size_of(ABox), x.tag());
|
||||
0
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// Author A's generic `Box` — one s64 field (size 8). Its `tag` returns
|
||||
// `self.x + 1`. The alias `ABox :: a.Box(s64)` must dispatch to THIS `tag`.
|
||||
Box :: struct($T: Type) {
|
||||
x: T;
|
||||
|
||||
tag :: (self: *Box) -> T {
|
||||
self.x + 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// Author B's generic `Box` — two s64 fields (size 16) whose `tag` returns
|
||||
// `self.x + 2`. Same template NAME as A's, different layout/body; it wins the
|
||||
// global last-wins `fn_ast_map["Box.tag"]`, so the alias over `a.Box` must NOT
|
||||
// dispatch to it.
|
||||
Box :: struct($T: Type) {
|
||||
x: T;
|
||||
y: T;
|
||||
|
||||
tag :: (self: *Box) -> T {
|
||||
self.x + 2
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// A QUALIFIED generic static-method head `a.Box(s64).make(7)` where namespace `a`
|
||||
// exists but authors NO member named `Box` must DIAGNOSE the missing member —
|
||||
// never silently fall back to the bare last-wins `struct_template_map` and
|
||||
// instantiate an unrelated module's same-name `Box` (parallels 0775 for the
|
||||
// static-method head).
|
||||
//
|
||||
// `a.sx` authors only `Other` (no `Box`); `b.sx` authors a generic `Box($T)`.
|
||||
// The qualified static head `a.Box(s64).make(7)` must report that `a` has no
|
||||
// member `Box`, NOT resolve to `b.Box.make`.
|
||||
|
||||
#import "modules/std.sx";
|
||||
a :: #import "0785-modules-qualified-generic-static-missing-member/a.sx";
|
||||
b :: #import "0785-modules-qualified-generic-static-missing-member/b.sx";
|
||||
|
||||
main :: () -> s32 {
|
||||
x := a.Box(s64).make(7);
|
||||
print("{}\n", x.x);
|
||||
0
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// Namespace A authors only `Other` — NO `Box`. The qualified static head
|
||||
// `a.Box(s64).make(..)` must diagnose the missing member, not fall to b's `Box`.
|
||||
Other :: struct($T: Type) {
|
||||
v: T;
|
||||
|
||||
make :: (value: T) -> Other(T) {
|
||||
.{ v = value }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// Namespace B authors a generic `Box($T)` with `make`. It wins the global
|
||||
// last-wins `struct_template_map`, so the qualified head `a.Box(..)` must NOT
|
||||
// silently resolve to this when namespace `a` lacks `Box`.
|
||||
Box :: struct($T: Type) {
|
||||
x: T;
|
||||
|
||||
make :: (value: T) -> Box(T) {
|
||||
.{ x = value }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
size=8 tag=15
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
size=8 dbl=10
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
a.x=7 b.x=9 b.y=109 sizes=8 16
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
combine=10
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,5 @@
|
||||
error: type 'Box' is ambiguous: it is declared in multiple flat-imported modules; qualify the reference or remove the duplicate import
|
||||
--> examples/0782-modules-bare-generic-instance-ambiguous-authors.sx:15:10
|
||||
|
|
||||
15 | x := Box(s64).make(7);
|
||||
| ^^^
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
size=8 tag=6
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,5 @@
|
||||
error: namespace 'a' has no member 'Box'
|
||||
--> examples/0785-modules-qualified-generic-static-missing-member.sx:16:10
|
||||
|
|
||||
16 | x := a.Box(s64).make(7);
|
||||
| ^^^^^
|
||||
Reference in New Issue
Block a user