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
|
||||
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
|
||||
|
||||
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;
|
||||
```
|
||||
|
||||
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)
|
||||
Functions can be parameterized over types using `$T` syntax. The `$` prefix introduces a type parameter; subsequent uses of the name reference it.
|
||||
```sx
|
||||
|
||||
@@ -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