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:
9
examples/0211-generics-struct-alias-head-facade.sx
Normal file
9
examples/0211-generics-struct-alias-head-facade.sx
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
// Companion of 0211: the re-export facade — own alias decls over another
|
||||||
|
// module's members. Flat importers of THIS file see the aliases bare.
|
||||||
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
r :: #import "0211-generics-struct-alias-head-rich.sx";
|
||||||
|
|
||||||
|
helper :: r.helper;
|
||||||
|
Thing :: r.Thing;
|
||||||
|
Box :: r.Box;
|
||||||
15
examples/0211-generics-struct-alias-head-rich.sx
Normal file
15
examples/0211-generics-struct-alias-head-rich.sx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
// Companion of 0211: the authoring module — a plain fn, a plain struct,
|
||||||
|
// and a generic struct, all re-exported by -facade.sx via alias decls.
|
||||||
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
helper :: () -> s64 { 7 }
|
||||||
|
|
||||||
|
Thing :: struct {
|
||||||
|
v: s64;
|
||||||
|
init :: () -> Thing { Thing.{ v = 42 } }
|
||||||
|
}
|
||||||
|
|
||||||
|
Box :: struct ($T: Type) {
|
||||||
|
item: T;
|
||||||
|
get :: (b: *Box(T)) -> T { b.item }
|
||||||
|
}
|
||||||
43
examples/0211-generics-struct-alias-head.sx
Normal file
43
examples/0211-generics-struct-alias-head.sx
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
// Generic-struct head aliases: `BoxAlias :: Box;` binds the alias to the
|
||||||
|
// SAME template — instantiation, methods, annotations, and alias chains all
|
||||||
|
// resolve through it. Cross-module, a facade's `Box :: r.Box;` re-export is
|
||||||
|
// the facade's OWN declaration, so it carries one flat-import level exactly
|
||||||
|
// like a plain-struct alias (companion files: -rich.sx authors the decls,
|
||||||
|
// -facade.sx re-exports them through a namespace alias).
|
||||||
|
// Regression (issue 0120): the alias head used to lower silently to an
|
||||||
|
// unresolved type and panic in the LLVM backend at instantiation.
|
||||||
|
#import "modules/std.sx";
|
||||||
|
#import "0211-generics-struct-alias-head-facade.sx";
|
||||||
|
|
||||||
|
LocalBox :: struct ($T: Type) {
|
||||||
|
item: T;
|
||||||
|
get :: (b: *LocalBox(T)) -> T { b.item }
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalAlias :: LocalBox;
|
||||||
|
ChainAlias :: LocalAlias;
|
||||||
|
|
||||||
|
main :: () {
|
||||||
|
// Same-file alias: instantiation + field + method.
|
||||||
|
b := LocalAlias(s64).{ item = 3 };
|
||||||
|
print("field: {}\n", b.item);
|
||||||
|
print("method: {}\n", b.get());
|
||||||
|
|
||||||
|
// Alias chain terminates at the template.
|
||||||
|
c := ChainAlias(s64).{ item = 11 };
|
||||||
|
print("chain: {}\n", c.item);
|
||||||
|
|
||||||
|
// Alias as a type annotation head.
|
||||||
|
a : LocalAlias(string) = .{ item = "ann" };
|
||||||
|
print("annot: {}\n", a.item);
|
||||||
|
|
||||||
|
// Cross-module re-exports carried one flat hop from the facade:
|
||||||
|
// plain fn, plain struct (static method), and the generic head.
|
||||||
|
print("helper: {}\n", helper());
|
||||||
|
t := Thing.init();
|
||||||
|
print("thing: {}\n", t.v);
|
||||||
|
f := Box(s64).{ item = 7 };
|
||||||
|
print("facade: {}\n", f.get());
|
||||||
|
x : Box(string) = .{ item = "qq" };
|
||||||
|
print("facade-annot: {}\n", x.item);
|
||||||
|
}
|
||||||
1
examples/expected/0211-generics-struct-alias-head.exit
Normal file
1
examples/expected/0211-generics-struct-alias-head.exit
Normal file
@@ -0,0 +1 @@
|
|||||||
|
0
|
||||||
1
examples/expected/0211-generics-struct-alias-head.stderr
Normal file
1
examples/expected/0211-generics-struct-alias-head.stderr
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
8
examples/expected/0211-generics-struct-alias-head.stdout
Normal file
8
examples/expected/0211-generics-struct-alias-head.stdout
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
field: 3
|
||||||
|
method: 3
|
||||||
|
chain: 11
|
||||||
|
annot: ann
|
||||||
|
helper: 7
|
||||||
|
thing: 42
|
||||||
|
facade: 7
|
||||||
|
facade-annot: qq
|
||||||
161
issues/0120-generic-struct-alias-head-unresolved-panic.md
Normal file
161
issues/0120-generic-struct-alias-head-unresolved-panic.md
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
# 0120 — aliasing a GENERIC struct head: silent `.unresolved`, backend panic
|
||||||
|
|
||||||
|
> **RESOLVED** (2026-06-11, same session — Agra-directed fix). Root
|
||||||
|
> cause: a const alias of a generic struct head was registered nowhere
|
||||||
|
> (`type_alias_map` holds TypeIds, `struct_template_map` only direct
|
||||||
|
> struct decls), and the head selector's miss fell through as
|
||||||
|
> `.not_generic`; the `.call`-node type resolver then returned
|
||||||
|
> `.unresolved` SILENTLY (its parameterized sibling diagnosed; it
|
||||||
|
> didn't). Fix, option 1 (support): `selectGenericStructHead` now
|
||||||
|
> follows const-alias decls (`aliasedStructTemplate` in
|
||||||
|
> `src/ir/lower/nominal.zig`) — own-wins / single-flat author, each hop
|
||||||
|
> resolved from the ALIAS AUTHOR's source (`namespaceAliasVerdictFrom`
|
||||||
|
> for `ns.X` RHS), depth-capped against cycles, checked BEFORE the
|
||||||
|
> template map so a facade's same-name re-export beats an invisible
|
||||||
|
> global template. Plus the missing diagnostic: an unknown `.call` type
|
||||||
|
> head now errors "unknown type 'X'" instead of silently poisoning
|
||||||
|
> (`resolveTypeCallWithBindings`). Alias-vs-alias flat collisions stay
|
||||||
|
> loud (not-visible diagnostic). Still unsupported, by scope:
|
||||||
|
> `ns.AliasName(..)` qualified heads (namespace member that is itself
|
||||||
|
> an alias). Regression test:
|
||||||
|
> `examples/0211-generics-struct-alias-head.sx` (+ `-rich.sx` /
|
||||||
|
> `-facade.sx` companions; pins same-file alias, method, chain,
|
||||||
|
> annotation, and the cross-module facade re-export). Gates: zig build
|
||||||
|
> test 426/426 (incl. fixing the PRE-EXISTING stale
|
||||||
|
> `calls.test.zig` UFCS plan test that predated 0119's opt-in model),
|
||||||
|
> suite 587/587.
|
||||||
|
|
||||||
|
## Symptom
|
||||||
|
|
||||||
|
`Alias :: Box;` where `Box` is a generic struct (`struct ($T: Type)`)
|
||||||
|
lowers without any diagnostic, and instantiating through the alias
|
||||||
|
(`Alias(s64).{ ... }`) reaches LLVM emission with an `.unresolved`
|
||||||
|
type — the backend tripwire panics:
|
||||||
|
|
||||||
|
```
|
||||||
|
panic: unresolved type reached LLVM emission — a type resolution
|
||||||
|
failure was not diagnosed/aborted
|
||||||
|
src/backend/llvm/types.zig:175 toLLVMTypeInfo
|
||||||
|
src/backend/llvm/ops.zig:1204 emitStructInit
|
||||||
|
```
|
||||||
|
|
||||||
|
Observed (one probe family, three manifestations of the same root):
|
||||||
|
|
||||||
|
- field access through the aliased instantiation → **backend panic**
|
||||||
|
(no front-end diagnostic at all);
|
||||||
|
- method call through the aliased instantiation (`b.get()`) →
|
||||||
|
misleading `unresolved 'get'` (the receiver's type never resolved);
|
||||||
|
- cross-module re-export (`facade.sx`: `Box :: r.Box;`, consumer
|
||||||
|
flat-imports facade) → consumer gets `type 'Box' is not visible;
|
||||||
|
#import the module that declares it` even though the alias is the
|
||||||
|
facade's OWN declaration.
|
||||||
|
|
||||||
|
Expected: one of the two, decided explicitly —
|
||||||
|
|
||||||
|
1. **Support it** (desirable): a const decl whose RHS names a generic
|
||||||
|
struct head (bare `Box` or qualified `r.Box`) binds the alias to the
|
||||||
|
SAME template; instantiation, methods, and one-level flat-import
|
||||||
|
carry behave exactly as the non-generic struct alias already does.
|
||||||
|
2. **Reject it loudly**: a decl-site diagnostic ("cannot alias a
|
||||||
|
generic struct head" or similar) at `Alias :: Box;`.
|
||||||
|
|
||||||
|
Silently lowering and panicking in the backend is neither — it is the
|
||||||
|
REJECTED-PATTERNS "silent unresolved" shape.
|
||||||
|
|
||||||
|
For contrast, both of these alias re-exports already WORK across one
|
||||||
|
flat-import hop (own-decl visibility): `helper :: r.helper;` (plain
|
||||||
|
fn) and `Thing :: r.Thing;` (non-generic struct, including its static
|
||||||
|
`init`). Only the generic head breaks. A fix must not regress these.
|
||||||
|
|
||||||
|
## Reproduction
|
||||||
|
|
||||||
|
Backend panic (primary):
|
||||||
|
|
||||||
|
```sx
|
||||||
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
Box :: struct ($T: Type) {
|
||||||
|
item: T;
|
||||||
|
}
|
||||||
|
|
||||||
|
BoxAlias :: Box;
|
||||||
|
|
||||||
|
main :: () {
|
||||||
|
b := BoxAlias(s64).{ item = 3 };
|
||||||
|
print("{}\n", b.item);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Method-call variant (front-end `unresolved 'get'`, same root):
|
||||||
|
|
||||||
|
```sx
|
||||||
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
Box :: struct ($T: Type) {
|
||||||
|
item: T;
|
||||||
|
get :: (b: *Box(T)) -> T { b.item }
|
||||||
|
}
|
||||||
|
|
||||||
|
BoxAlias :: Box;
|
||||||
|
|
||||||
|
main :: () {
|
||||||
|
b := BoxAlias(s64).{ item = 3 };
|
||||||
|
print("{}\n", b.get());
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Cross-module variant (`rich.sx` declares `Box`; `facade.sx` has
|
||||||
|
`r :: #import "rich.sx"; Box :: r.Box;`; a consumer flat-importing
|
||||||
|
facade.sx gets `type 'Box' is not visible` at `Box(s64).{ ... }`).
|
||||||
|
|
||||||
|
## Investigation prompt
|
||||||
|
|
||||||
|
Generic structs live as TEMPLATES in
|
||||||
|
`src/ir/program_index.zig` — `struct_template_map`
|
||||||
|
(`StringHashMap(StructTemplate)`, registered by `registerStructDecl`;
|
||||||
|
a parallel `struct_template_by_decl` exists but isn't read for
|
||||||
|
selection yet). Instantiation resolves the head name against that map
|
||||||
|
in `src/ir/lower/nominal.zig` (see the qualified-head comments around
|
||||||
|
nominal.zig:357–382) and monomorphizes via
|
||||||
|
`lower_generic.instantiateGenericStruct` (re-exported at
|
||||||
|
`src/ir/lower.zig:1820`).
|
||||||
|
|
||||||
|
`BoxAlias :: Box;` is a const decl whose RHS identifier names a
|
||||||
|
template, not a value or a concrete Type — const-decl lowering neither
|
||||||
|
registers `BoxAlias` as a template alias nor rejects the decl. The
|
||||||
|
instantiation head lookup for `BoxAlias` then misses, and the
|
||||||
|
`Name(args).{ ... }` path continues with an `.unresolved` struct type
|
||||||
|
instead of diagnosing the miss — that silent continuation is the bug
|
||||||
|
underneath all three manifestations, and fixing it is step one
|
||||||
|
regardless of the language decision: a struct_init whose head fails to
|
||||||
|
resolve must produce a hard diagnostic, never reach emission.
|
||||||
|
|
||||||
|
Then the language decision (confirm with Agra if option 2 is ever
|
||||||
|
preferred; the motivating use case wants option 1): when a const
|
||||||
|
decl's RHS resolves to a generic struct head — bare identifier or
|
||||||
|
`ns.X` through a namespace alias — register the alias name in the
|
||||||
|
template registry bound to the same `StructTemplate`, scoped to the
|
||||||
|
declaring module with ordinary own-decl visibility so one-level
|
||||||
|
flat-import carry works (mirror whatever makes `Thing :: r.Thing;`
|
||||||
|
re-export correctly today). Mind collision semantics (own-wins /
|
||||||
|
ambiguity) and that the alias must also work as a plain type head in
|
||||||
|
annotations (`x: BoxAlias(s64)`), nested generics
|
||||||
|
(`List(BoxAlias(s64))` if applicable), and method/UFCS dispatch on
|
||||||
|
instantiations through the alias.
|
||||||
|
|
||||||
|
Motivating context: the std.sx-as-pure-re-exports restructure wants
|
||||||
|
`List :: list.List;` in `modules/std.sx` (with `list :: #import
|
||||||
|
"modules/std/list.sx";`) so `List` stays bare-visible to std.sx's flat
|
||||||
|
importers. Plain fns and plain structs already re-export this way;
|
||||||
|
generic heads are the missing piece.
|
||||||
|
|
||||||
|
Verification:
|
||||||
|
|
||||||
|
1. Primary repro: prints `3`, exit 0 (option 1) — or a clean decl-site
|
||||||
|
diagnostic, no panic (option 2).
|
||||||
|
2. Matrix: method-call variant runs (`b.get()` → 3); cross-module
|
||||||
|
variant runs through the facade; `helper :: r.helper;` and
|
||||||
|
`Thing :: r.Thing;` re-exports unchanged; two facades carrying the
|
||||||
|
same alias name still diagnose ambiguity.
|
||||||
|
3. `bash tests/run_examples.sh` — full suite ok, zero failures.
|
||||||
|
4. Pin the repro as a regression example per CLAUDE.md.
|
||||||
19
readme.md
19
readme.md
@@ -483,6 +483,25 @@ Carried aliases follow declaration rules: an own declaration shadows a carried
|
|||||||
alias, two flat imports carrying the same alias make its use ambiguous, and
|
alias, two flat imports carrying the same alias make its use ambiguous, and
|
||||||
carry does not chain through a second flat hop.
|
carry does not chain through a second flat hop.
|
||||||
|
|
||||||
|
**Re-exporting through alias declarations.** Since visibility never chains,
|
||||||
|
a facade re-exports another module's members as its OWN declarations —
|
||||||
|
ordinary aliases, which its direct flat importers then see bare. This works
|
||||||
|
for functions, plain types, and generic struct heads alike (the generic
|
||||||
|
alias binds the same template, so instantiation and methods resolve
|
||||||
|
through it):
|
||||||
|
|
||||||
|
```sx
|
||||||
|
// facade.sx
|
||||||
|
r :: #import "rich.sx";
|
||||||
|
helper :: r.helper; // fn re-export
|
||||||
|
Thing :: r.Thing; // struct re-export
|
||||||
|
Box :: r.Box; // generic head re-export — same template
|
||||||
|
|
||||||
|
// consumer.sx
|
||||||
|
#import "facade.sx";
|
||||||
|
b := Box(s64).{ item = 3 }; // rich.sx's Box, via the facade
|
||||||
|
```
|
||||||
|
|
||||||
### Implicit Context
|
### Implicit Context
|
||||||
|
|
||||||
Every program gets an implicit `context` with a default allocator:
|
Every program gets an implicit `context` with a default allocator:
|
||||||
|
|||||||
19
specs.md
19
specs.md
@@ -1251,6 +1251,25 @@ A name bound to an existing type.
|
|||||||
SOME_TYPE :: f64;
|
SOME_TYPE :: f64;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
A generic struct HEAD can be aliased too — the alias binds to the same
|
||||||
|
template, so instantiation, methods, annotations, and alias chains resolve
|
||||||
|
through it:
|
||||||
|
|
||||||
|
```sx
|
||||||
|
Box :: struct ($T: Type) { item: T; }
|
||||||
|
BoxAlias :: Box; // same template
|
||||||
|
b := BoxAlias(s64).{ item = 3 };
|
||||||
|
b2 : BoxAlias(string) = .{ item = "x" }; // annotation head too
|
||||||
|
```
|
||||||
|
|
||||||
|
The RHS may be a namespace member (`Box :: r.Box;`) — the alias is an
|
||||||
|
ordinary OWN declaration of the aliasing file, so it is visible to that
|
||||||
|
file's direct flat importers like any other declaration (this is how a
|
||||||
|
facade re-exports another module's generic struct). Each hop of an alias
|
||||||
|
chain resolves with the visibility of the file that declares THAT hop,
|
||||||
|
not the use site's. Not yet supported: a qualified head whose namespace
|
||||||
|
member is itself an alias (`ns.BoxAlias(..)`).
|
||||||
|
|
||||||
### Generic Functions (Monomorphization)
|
### Generic Functions (Monomorphization)
|
||||||
Functions can be parameterized over types using `$T` syntax. The `$` prefix introduces a type parameter; subsequent uses of the name reference it.
|
Functions can be parameterized over types using `$T` syntax. The `$` prefix introduces a type parameter; subsequent uses of the name reference it.
|
||||||
```sx
|
```sx
|
||||||
|
|||||||
@@ -429,14 +429,17 @@ test "plan: free-function UFCS prepends receiver, distinct from namespace_fn" {
|
|||||||
var l = Lowering.init(&module);
|
var l = Lowering.init(&module);
|
||||||
const cr = CallResolver{ .l = &l };
|
const cr = CallResolver{ .l = &l };
|
||||||
|
|
||||||
// struct Counter, and a FREE function `bump :: (c: Counter) -> s32` — NOT
|
// struct Counter, and a FREE ufcs function `bump :: ufcs (c: Counter) ->
|
||||||
// registered as `Counter.bump`, so it can only be reached via UFCS.
|
// 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 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 c_param = ast.Param{ .name = "c", .name_span = .{ .start = 0, .end = 0 }, .type_expr = typeExpr(alloc, "Counter") };
|
||||||
const params = [_]ast.Param{c_param};
|
const params = [_]ast.Param{c_param};
|
||||||
const ret_stmt = mk(alloc, .{ .return_stmt = .{ .value = intLit(alloc, 7) } });
|
const ret_stmt = mk(alloc, .{ .return_stmt = .{ .value = intLit(alloc, 7) } });
|
||||||
const body = mk(alloc, .{ .block = .{ .stmts = &[_]*Node{ret_stmt} } });
|
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);
|
l.lowerFunction(&fd, "bump", false);
|
||||||
const fid = l.resolveFuncByName("bump").?;
|
const fid = l.resolveFuncByName("bump").?;
|
||||||
module.functions.items[@intFromEnum(fid)].has_implicit_ctx = true;
|
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
|
/// edges of flat edges do not chain). Two distinct carried targets for
|
||||||
/// the same alias are ambiguous.
|
/// the same alias are ambiguous.
|
||||||
pub fn namespaceAliasVerdict(self: *Lowering, alias: []const u8) AliasVerdict {
|
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;
|
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 (edges.getPtr(from)) |own| {
|
||||||
if (own.get(alias)) |t| return .{ .target = t };
|
if (own.get(alias)) |t| return .{ .target = t };
|
||||||
}
|
}
|
||||||
@@ -1679,6 +1687,7 @@ pub const Lowering = struct {
|
|||||||
pub const rawNamedTypePtr = lower_nominal.rawNamedTypePtr;
|
pub const rawNamedTypePtr = lower_nominal.rawNamedTypePtr;
|
||||||
pub const buildGenericStructTemplate = lower_nominal.buildGenericStructTemplate;
|
pub const buildGenericStructTemplate = lower_nominal.buildGenericStructTemplate;
|
||||||
pub const qualifiedStructTemplate = lower_nominal.qualifiedStructTemplate;
|
pub const qualifiedStructTemplate = lower_nominal.qualifiedStructTemplate;
|
||||||
|
pub const aliasedStructTemplate = lower_nominal.aliasedStructTemplate;
|
||||||
pub const qualifiedMemberMissing = lower_nominal.qualifiedMemberMissing;
|
pub const qualifiedMemberMissing = lower_nominal.qualifiedMemberMissing;
|
||||||
pub const bareVisibleStructDecl = lower_nominal.bareVisibleStructDecl;
|
pub const bareVisibleStructDecl = lower_nominal.bareVisibleStructDecl;
|
||||||
pub const bareVisibleStructTemplate = lower_nominal.bareVisibleStructTemplate;
|
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.* };
|
if (self.program_index.struct_template_map.getPtr(name)) |tmpl| return .{ .template = tmpl.* };
|
||||||
return .not_generic;
|
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.program_index.struct_template_map.getPtr(name)) |tmpl| {
|
||||||
if (self.headTypeLeak(name, span)) return .poisoned;
|
if (self.headTypeLeak(name, span)) return .poisoned;
|
||||||
if (self.bareVisibleStructTemplate(name)) |vt| return .{ .template = vt };
|
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
|
// Try as a named type
|
||||||
const name_id = self.module.types.internString(callee_name);
|
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.
|
/// 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;
|
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` +
|
/// The bare-VISIBLE single generic-struct author of `name` (its `StructDecl` +
|
||||||
/// defining source) when that author is NOT the one the global last-wins
|
/// 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
|
/// `struct_template_map` already holds — the E4 non-transitive selection for a
|
||||||
|
|||||||
Reference in New Issue
Block a user