lang: fn aliases dispatch like their target (fix 0121) — scan-time registration through the shared alias-chain walk
Renamed fn aliases failed for EVERY kind (the filed pack-only scope was a same-name confound: same-name re-exports already resolved through the name-keyed fn_ast_map). scanDecls now follows ident-/ns.X-RHS const alias chains (aliasedFnDecl; 0120's hop walk extracted as followAliasChain) and registers the alias name in fn_ast_map (absent-only), so every dispatch path — early pack/comptime/generic, plain lazy-lower, plan-side typing — sees the target decl unchanged. my_print :: s.print; / my_format :: s.format; now work (the std.sx re-export shape). Regression: examples/0546 (+rich). Gates: zig build test 0, suite 588/588.
This commit is contained in:
6
examples/0546-packs-fn-alias-rich.sx
Normal file
6
examples/0546-packs-fn-alias-rich.sx
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
// Companion of 0546: the authoring module for the fn-alias re-exports.
|
||||||
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
helper :: () -> s64 { 7 }
|
||||||
|
first_of :: (xs: []$T) -> T { xs[0] }
|
||||||
|
my_pack :: (..$args) -> s64 { args[0] + args[1] }
|
||||||
35
examples/0546-packs-fn-alias.sx
Normal file
35
examples/0546-packs-fn-alias.sx
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
// Function aliases dispatch exactly like their target, across every fn
|
||||||
|
// kind: plain, runtime-generic ([]$T), and comptime-pack (..$args) — with
|
||||||
|
// bare, renamed, and namespace-member RHS. The alias is an ordinary own
|
||||||
|
// declaration, so it re-exports one flat-import level (companion file
|
||||||
|
// -rich.sx authors the fns aliased here and via the namespace).
|
||||||
|
// Regression (issue 0121): comptime-pack fn aliases (and ALL renamed fn
|
||||||
|
// aliases) used to fail "unresolved '<alias>'" — only same-name re-exports
|
||||||
|
// worked, through the name-keyed global registry.
|
||||||
|
#import "modules/std.sx";
|
||||||
|
s :: #import "modules/std.sx";
|
||||||
|
r :: #import "0546-packs-fn-alias-rich.sx";
|
||||||
|
|
||||||
|
pack_sum :: (..$args) -> s64 {
|
||||||
|
args[0] + args[1]
|
||||||
|
}
|
||||||
|
sum_alias :: pack_sum; // same-file pack alias (the 0121 repro)
|
||||||
|
|
||||||
|
helper2 :: r.helper; // renamed plain, namespace RHS
|
||||||
|
head_of :: r.first_of; // renamed runtime-generic, namespace RHS
|
||||||
|
sum2 :: r.my_pack; // renamed pack, namespace RHS
|
||||||
|
|
||||||
|
my_print :: s.print; // std's print — comptime pack + $fmt
|
||||||
|
my_format :: s.format; // value-returning sibling
|
||||||
|
|
||||||
|
main :: () {
|
||||||
|
print("pack: {}\n", sum_alias(3, 4));
|
||||||
|
print("plain: {}\n", helper2());
|
||||||
|
arr := .[10, 20, 30];
|
||||||
|
xs : []s64 = arr;
|
||||||
|
print("generic: {}\n", head_of(xs));
|
||||||
|
print("ns-pack: {}\n", sum2(20, 22));
|
||||||
|
my_print("std-print: {} {}\n", 1, "two");
|
||||||
|
t := my_format("std-format {}", 42);
|
||||||
|
my_print("{}\n", t);
|
||||||
|
}
|
||||||
1
examples/expected/0546-packs-fn-alias.exit
Normal file
1
examples/expected/0546-packs-fn-alias.exit
Normal file
@@ -0,0 +1 @@
|
|||||||
|
0
|
||||||
1
examples/expected/0546-packs-fn-alias.stderr
Normal file
1
examples/expected/0546-packs-fn-alias.stderr
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
6
examples/expected/0546-packs-fn-alias.stdout
Normal file
6
examples/expected/0546-packs-fn-alias.stdout
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
pack: 7
|
||||||
|
plain: 7
|
||||||
|
generic: 10
|
||||||
|
ns-pack: 42
|
||||||
|
std-print: 1 two
|
||||||
|
std-format 42
|
||||||
112
issues/0121-pack-fn-alias-unresolved.md
Normal file
112
issues/0121-pack-fn-alias-unresolved.md
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
# 0121 — aliasing a comptime-pack fn (`..$args`): "unresolved '<alias>'"
|
||||||
|
|
||||||
|
> **RESOLVED** (2026-06-11, same session — Agra-directed). The symptom
|
||||||
|
> was broader than filed: RENAMED fn aliases failed for EVERY fn kind
|
||||||
|
> (plain `helper2 :: r.helper;` too) — the "plain fns verified working"
|
||||||
|
> claim below was a same-name confound (same-name re-exports resolve
|
||||||
|
> through the name-keyed global `fn_ast_map`, no alias mechanism
|
||||||
|
> involved; `my_pack :: r.my_pack;` already worked for packs too).
|
||||||
|
> Fix: fn aliases register at SCAN time — `scanDecls`' const-decl arm
|
||||||
|
> follows ident-/`ns.X`-RHS alias chains via `aliasedFnDecl`
|
||||||
|
> (nominal.zig; shares 0120's hop walk, now extracted as
|
||||||
|
> `followAliasChain`) and, when the chain terminates at a fn decl,
|
||||||
|
> registers the ALIAS name in `fn_ast_map` (absent-only — a real
|
||||||
|
> same-name fn keeps its slot). Every dispatch path reads that map
|
||||||
|
> (early pack/comptime/generic, plain lazy-lower, plan-side typing),
|
||||||
|
> so the alias dispatches exactly like the target with no per-path
|
||||||
|
> changes. Verified matrix: same-file pack alias (the repro), renamed
|
||||||
|
> plain / generic / pack through a facade, and `my_print :: s.print;`
|
||||||
|
> / `my_format :: s.format;` over std's real pack fns. Regression:
|
||||||
|
> `examples/0546-packs-fn-alias.sx` (+ `-rich.sx` companion). Gates:
|
||||||
|
> zig build test 0, suite 588/588.
|
||||||
|
|
||||||
|
## Symptom
|
||||||
|
|
||||||
|
A const alias of a function whose signature carries a comptime pack
|
||||||
|
(`..$args`) is not callable — every call through the alias fails with
|
||||||
|
`unresolved '<alias>'`. All three alias shapes fail identically:
|
||||||
|
|
||||||
|
- same-file bare: `sum_alias :: pack_sum;` → `unresolved 'sum_alias'`
|
||||||
|
- bare RHS over a flat import: `my_print :: print;` (std's `print`) →
|
||||||
|
`unresolved 'my_print'`
|
||||||
|
- namespace RHS: `my_print :: s.print;` with `s :: #import
|
||||||
|
"modules/std.sx";` → `unresolved 'my_print'`
|
||||||
|
|
||||||
|
Contrast (all verified working): plain concrete fns (`helper ::
|
||||||
|
r.helper;`), runtime-generic fns (`first_of :: r.first_of;` with
|
||||||
|
`(xs: []$T) -> T`), and — since 0120 — generic struct heads
|
||||||
|
(`List :: list.List;`). Only the comptime-pack shape misses.
|
||||||
|
|
||||||
|
Expected: the alias dispatches exactly like the target —
|
||||||
|
`my_print("x {}\n", 1)` behaves as `print("x {}\n", 1)`. (If fn
|
||||||
|
aliasing of pack fns is NOT meant to be promised, the hypothesis is
|
||||||
|
wrong and the decl or the call should get a clean tailored
|
||||||
|
diagnostic instead — but the std.sx-as-pure-re-exports restructure
|
||||||
|
wants `print :: fmt.print;` to work, so support is the desirable
|
||||||
|
outcome. Confirm with Agra only if support turns out prohibitively
|
||||||
|
deep.)
|
||||||
|
|
||||||
|
## Reproduction
|
||||||
|
|
||||||
|
```sx
|
||||||
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
pack_sum :: (..$args) -> s64 {
|
||||||
|
args[0] + args[1]
|
||||||
|
}
|
||||||
|
sum_alias :: pack_sum;
|
||||||
|
|
||||||
|
main :: () {
|
||||||
|
print("{}\n", sum_alias(3, 4)); // error: unresolved 'sum_alias'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Direct `pack_sum(3, 4)` works; only the aliased spelling fails.
|
||||||
|
|
||||||
|
## Investigation prompt
|
||||||
|
|
||||||
|
Comptime-pack calls (`..$args` — NOT slice-variadics `..xs: []T`)
|
||||||
|
dispatch EARLY in call lowering, keyed on the callee NAME against the
|
||||||
|
pack-fn template registry (the `fn_ast_map` entry whose FnDecl has a
|
||||||
|
comptime pack param; the early-dispatch gate lives in the call path —
|
||||||
|
`src/ir/lower/call.zig` / `src/ir/packs.zig`, grep for the pack-param
|
||||||
|
detection on the callee). An alias name has no `fn_ast_map` entry, so
|
||||||
|
the early pack dispatch misses; no later stage handles pack fns
|
||||||
|
(they cannot lower as ordinary declared functions — each call site
|
||||||
|
expands with its own bound pack), so the call falls through to the
|
||||||
|
generic `unresolved '<name>'`.
|
||||||
|
|
||||||
|
The fix likely: where the early pack dispatch resolves the callee
|
||||||
|
name, on a miss follow const-ALIAS decls to their target FnDecl and
|
||||||
|
dispatch with the TARGET's fd under the alias's call node. Issue
|
||||||
|
0120's fix added exactly this follow for generic STRUCT heads —
|
||||||
|
`aliasedStructTemplate` in `src/ir/lower/nominal.zig`
|
||||||
|
(`singleVisibleAuthor` + hop-by-hop `followToTemplate`, each hop
|
||||||
|
resolved from the ALIAS AUTHOR's source, `namespaceAliasVerdictFrom`
|
||||||
|
for `ns.X` RHS, depth-capped). Mirror that shape for fn targets — a
|
||||||
|
`followToFnDecl` sibling reusing `singleVisibleAuthor` (consider
|
||||||
|
extracting the shared walk) — and route the early pack dispatch
|
||||||
|
through it. Mind: own-wins / single-flat collision semantics must
|
||||||
|
match 0120's (≥2 flat alias authors → loud, no silent pick), and the
|
||||||
|
ufcs-alias map (`name :: ufcs target;`) is a DIFFERENT mechanism
|
||||||
|
(`ufcs_alias_map`) — don't conflate.
|
||||||
|
|
||||||
|
Verification:
|
||||||
|
|
||||||
|
1. The repro prints `7`, exit 0.
|
||||||
|
2. Matrix: same-file bare alias, bare RHS over a flat import
|
||||||
|
(`my_print :: print;`), namespace RHS (`my_print :: s.print;` /
|
||||||
|
`my_format :: s.format;` — formats AND returns a value), and a
|
||||||
|
consumer one flat hop from the aliasing facade.
|
||||||
|
3. Plain-fn and generic-fn aliases unchanged (examples 0211 family).
|
||||||
|
4. `bash tests/run_examples.sh` — 587/587 baseline must hold; pin the
|
||||||
|
repro as a regression example per CLAUDE.md.
|
||||||
|
|
||||||
|
Context: BLOCKS the std.sx-as-pure-re-exports restructure — `print` /
|
||||||
|
`format` are the prelude's most-used names and are exactly this shape
|
||||||
|
(`($fmt: string, ..$args)`). Generic struct heads (`List`) were
|
||||||
|
unblocked by 0120; this is the remaining known gap. Still unprobed
|
||||||
|
for the restructure (next session, after this fix): protocol aliases
|
||||||
|
(`Allocator`, parameterized `Into`), `#builtin` decl aliases
|
||||||
|
(`size_of`, `out`, `string :: []u8`), `#foreign` decl aliases
|
||||||
|
(`memcpy`).
|
||||||
@@ -486,9 +486,10 @@ carry does not chain through a second flat hop.
|
|||||||
**Re-exporting through alias declarations.** Since visibility never chains,
|
**Re-exporting through alias declarations.** Since visibility never chains,
|
||||||
a facade re-exports another module's members as its OWN declarations —
|
a facade re-exports another module's members as its OWN declarations —
|
||||||
ordinary aliases, which its direct flat importers then see bare. This works
|
ordinary aliases, which its direct flat importers then see bare. This works
|
||||||
for functions, plain types, and generic struct heads alike (the generic
|
for functions of every kind (plain, generic, comptime-pack like `print`),
|
||||||
alias binds the same template, so instantiation and methods resolve
|
plain types, and generic struct heads alike (the generic alias binds the
|
||||||
through it):
|
same template, so instantiation and methods resolve through it), renamed
|
||||||
|
or same-name:
|
||||||
|
|
||||||
```sx
|
```sx
|
||||||
// facade.sx
|
// facade.sx
|
||||||
|
|||||||
17
specs.md
17
specs.md
@@ -1270,6 +1270,23 @@ 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
|
not the use site's. Not yet supported: a qualified head whose namespace
|
||||||
member is itself an alias (`ns.BoxAlias(..)`).
|
member is itself an alias (`ns.BoxAlias(..)`).
|
||||||
|
|
||||||
|
### Function Aliases
|
||||||
|
|
||||||
|
Functions alias the same way — bare or namespace-member RHS, renamed or
|
||||||
|
same-name — and the alias dispatches exactly like the target. This covers
|
||||||
|
every fn kind: plain, runtime-generic (`[]$T` / `$T: Type`), and
|
||||||
|
comptime-pack (`..$args`, e.g. `print` / `format`):
|
||||||
|
|
||||||
|
```sx
|
||||||
|
s :: #import "modules/std.sx";
|
||||||
|
my_print :: s.print; // comptime-pack fn through a namespace
|
||||||
|
helper2 :: r.helper; // renamed plain fn
|
||||||
|
my_print("x = {}\n", helper2());
|
||||||
|
```
|
||||||
|
|
||||||
|
(For making an alias *dot-callable*, see `name :: ufcs target;` in the
|
||||||
|
UFCS section — that is a separate, explicit opt-in.)
|
||||||
|
|
||||||
### 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
|
||||||
|
|||||||
@@ -1688,6 +1688,7 @@ pub const Lowering = struct {
|
|||||||
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 aliasedStructTemplate = lower_nominal.aliasedStructTemplate;
|
||||||
|
pub const aliasedFnDecl = lower_nominal.aliasedFnDecl;
|
||||||
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;
|
||||||
|
|||||||
@@ -211,8 +211,7 @@ pub fn checkRequiredEntryPoints(self: *Lowering) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (self.diagnostics) |diags| {
|
if (self.diagnostics) |diags| {
|
||||||
diags.addFmt(.err, null,
|
diags.addFmt(.err, null, "target is Android but no `#jni_main` Activity declared. " ++
|
||||||
"target is Android but no `#jni_main` Activity declared. " ++
|
|
||||||
"The OS launches a Java-side Activity that delegates lifecycle " ++
|
"The OS launches a Java-side Activity that delegates lifecycle " ++
|
||||||
"callbacks into sx — declare one like:\n\n" ++
|
"callbacks into sx — declare one like:\n\n" ++
|
||||||
" Bundle :: #foreign #jni_class(\"android/os/Bundle\") {{ }}\n\n" ++
|
" Bundle :: #foreign #jni_class(\"android/os/Bundle\") {{ }}\n\n" ++
|
||||||
@@ -366,8 +365,7 @@ pub fn detectContextDecl(decls: []const *const Node) bool {
|
|||||||
for (decls) |decl| {
|
for (decls) |decl| {
|
||||||
const found = switch (decl.data) {
|
const found = switch (decl.data) {
|
||||||
.struct_decl => |sd| std.mem.eql(u8, sd.name, "Context"),
|
.struct_decl => |sd| std.mem.eql(u8, sd.name, "Context"),
|
||||||
.const_decl => |cd|
|
.const_decl => |cd| std.mem.eql(u8, cd.name, "Context") and cd.value.data == .struct_decl,
|
||||||
std.mem.eql(u8, cd.name, "Context") and cd.value.data == .struct_decl,
|
|
||||||
.namespace_decl => |ns| detectContextDecl(ns.decls),
|
.namespace_decl => |ns| detectContextDecl(ns.decls),
|
||||||
else => false,
|
else => false,
|
||||||
};
|
};
|
||||||
@@ -596,30 +594,48 @@ pub fn scanDecls(self: *Lowering, decls: []const *const Node) void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.putTypeAlias(self.current_source_file, cd.name, target_ty);
|
self.putTypeAlias(self.current_source_file, cd.name, target_ty);
|
||||||
} else if (cd.value.data == .identifier) {
|
} else if (cd.value.data == .identifier or cd.value.data == .field_access) {
|
||||||
// Identifier-RHS alias: MyAlias :: MyInt; WideAlias :: Wide.
|
// FN alias (issue 0121): `print2 :: print;` /
|
||||||
// SOURCE-AWARE (E1.5). Resolve the RHS `B` AS SEEN FROM this
|
// `my_print :: s.print;`. When the alias chain terminates
|
||||||
// alias's OWN source via `selectNominalLeaf` (E1's source-
|
// at a fn decl, register the ALIAS name in `fn_ast_map`
|
||||||
// keyed nominal leaf), NEVER the global `type_alias_map` /
|
// pointing at the target's decl — every dispatch path
|
||||||
// global `findByName` (last-wins across modules). Only the
|
// (early pack/comptime/generic, plain lazy-lower,
|
||||||
// `.resolved` outcome is written; `.pending` (B is itself a
|
// plan-side return typing) reads that map, so the alias
|
||||||
// forward alias not resolved yet), `.undeclared`, and
|
// dispatches exactly like the target. Absent-only: a real
|
||||||
// `.not_visible` (a same-name B authored only by a namespaced
|
// same-name fn keeps its slot (same-name re-exports are
|
||||||
// import) leave A UNWRITTEN so the source-aware
|
// a no-op — the target already owns the name).
|
||||||
// `resolveForwardIdentifierAliases` fixpoint re-tries A once
|
|
||||||
// the local B registers. A GLOBAL selection here would bind A
|
|
||||||
// to a namespaced same-name B, and the per-source fixpoint
|
|
||||||
// guard (`aliasResolvedInSource`) would then SKIP A — leaving
|
|
||||||
// the wrong global TypeId and re-opening 0105 one layer down
|
|
||||||
// (R1, E1.5). Same unified `putTypeAlias` writer (no-drift).
|
|
||||||
const rhs = cd.value.data.identifier;
|
|
||||||
if (self.current_source_file orelse self.main_file) |from| {
|
if (self.current_source_file orelse self.main_file) |from| {
|
||||||
switch (self.selectNominalLeaf(rhs.name, from, rhs.is_raw)) {
|
if (self.aliasedFnDecl(&decl.data.const_decl, from)) |target_fd| {
|
||||||
.resolved => |tid| self.putTypeAlias(self.current_source_file, cd.name, tid),
|
if (!self.program_index.fn_ast_map.contains(cd.name)) {
|
||||||
// `.ambiguous` (same-name RHS authored by ≥2 flat
|
self.program_index.fn_ast_map.put(cd.name, target_fd) catch {};
|
||||||
// imports) leaves A unwritten like `.not_visible`;
|
}
|
||||||
// the loud diagnostic fires where A is USED.
|
}
|
||||||
.pending, .forward, .undeclared, .not_visible, .ambiguous => {},
|
}
|
||||||
|
if (cd.value.data == .identifier) {
|
||||||
|
// Identifier-RHS alias: MyAlias :: MyInt; WideAlias :: Wide.
|
||||||
|
// SOURCE-AWARE (E1.5). Resolve the RHS `B` AS SEEN FROM this
|
||||||
|
// alias's OWN source via `selectNominalLeaf` (E1's source-
|
||||||
|
// keyed nominal leaf), NEVER the global `type_alias_map` /
|
||||||
|
// global `findByName` (last-wins across modules). Only the
|
||||||
|
// `.resolved` outcome is written; `.pending` (B is itself a
|
||||||
|
// forward alias not resolved yet), `.undeclared`, and
|
||||||
|
// `.not_visible` (a same-name B authored only by a namespaced
|
||||||
|
// import) leave A UNWRITTEN so the source-aware
|
||||||
|
// `resolveForwardIdentifierAliases` fixpoint re-tries A once
|
||||||
|
// the local B registers. A GLOBAL selection here would bind A
|
||||||
|
// to a namespaced same-name B, and the per-source fixpoint
|
||||||
|
// guard (`aliasResolvedInSource`) would then SKIP A — leaving
|
||||||
|
// the wrong global TypeId and re-opening 0105 one layer down
|
||||||
|
// (R1, E1.5). Same unified `putTypeAlias` writer (no-drift).
|
||||||
|
const rhs = cd.value.data.identifier;
|
||||||
|
if (self.current_source_file orelse self.main_file) |from| {
|
||||||
|
switch (self.selectNominalLeaf(rhs.name, from, rhs.is_raw)) {
|
||||||
|
.resolved => |tid| self.putTypeAlias(self.current_source_file, cd.name, tid),
|
||||||
|
// `.ambiguous` (same-name RHS authored by ≥2 flat
|
||||||
|
// imports) leaves A unwritten like `.not_visible`;
|
||||||
|
// the loud diagnostic fires where A is USED.
|
||||||
|
.pending, .forward, .undeclared, .not_visible, .ambiguous => {},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -686,7 +702,7 @@ pub fn scanDecls(self: *Lowering, decls: []const *const Node) void {
|
|||||||
// template via the namespace edge (mirrors the annotation
|
// template via the namespace edge (mirrors the annotation
|
||||||
// head site `resolveParameterizedWithBindings`), not the
|
// head site `resolveParameterizedWithBindings`), not the
|
||||||
// bare last-wins `struct_template_map`.
|
// bare last-wins `struct_template_map`.
|
||||||
const pt_alias: ?[]const u8 = if (pt_qualified) pt.name[0 .. std.mem.indexOfScalar(u8, pt.name, '.').?] else null;
|
const pt_alias: ?[]const u8 = if (pt_qualified) pt.name[0..std.mem.indexOfScalar(u8, pt.name, '.').?] else null;
|
||||||
// Generic-struct alias base: route layout selection through the
|
// Generic-struct alias base: route layout selection through the
|
||||||
// single choke-point (CP-1); the builtin parameterised-type
|
// single choke-point (CP-1); the builtin parameterised-type
|
||||||
// path (Vector etc.) stays as the non-generic fall-through.
|
// path (Vector etc.) stays as the non-generic fall-through.
|
||||||
@@ -1669,7 +1685,9 @@ pub fn selectNominalLeaf(self: *Lowering, name: []const u8, from: []const u8, ra
|
|||||||
else => self.namedRefTid(fa.raw, name),
|
else => self.namedRefTid(fa.raw, name),
|
||||||
};
|
};
|
||||||
if (fa_tid) |t| {
|
if (fa_tid) |t| {
|
||||||
if (found_tid) |f| { if (t != f) return .ambiguous; } else found_tid = t;
|
if (found_tid) |f| {
|
||||||
|
if (t != f) return .ambiguous;
|
||||||
|
} else found_tid = t;
|
||||||
} else {
|
} else {
|
||||||
flat_has_unregistered = true;
|
flat_has_unregistered = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -434,35 +434,48 @@ pub fn aliasedStructTemplate(self: *Lowering, name: []const u8, from: []const u8
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// One alias hop: a generic-struct author terminates the chain with its
|
/// One alias hop: a generic-struct author terminates the chain with its
|
||||||
/// rebuilt source-pinned template; an alias author recurses on its RHS —
|
/// rebuilt source-pinned template; an alias author recurses via
|
||||||
/// bare identifier from the author's own source, `ns.X` through the
|
/// `followAliasChain`.
|
||||||
/// 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 {
|
fn followToTemplate(self: *Lowering, author: resolver_mod.RawAuthor, depth: u8) ?StructTemplate {
|
||||||
|
const terminal = followAliasChain(self, author, depth) orelse return null;
|
||||||
|
const sd = structDeclOfRaw(terminal.raw) orelse return null;
|
||||||
|
if (sd.type_params.len == 0) return null;
|
||||||
|
return self.buildGenericStructTemplate(sd, terminal.source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Walk a chain of const ALIAS decls to its terminal author. Each hop
|
||||||
|
/// resolves the RHS from the hop AUTHOR's own source — a bare identifier
|
||||||
|
/// via the visible-author walk, `ns.X` through the author's namespace edge
|
||||||
|
/// into the target module's own member. A non-alias author terminates the
|
||||||
|
/// chain (callers unwrap it by domain: `structDeclOfRaw` / `fnDeclOfRaw`).
|
||||||
|
/// The depth cap breaks alias cycles (`A :: B; B :: A;`).
|
||||||
|
pub fn followAliasChain(self: *Lowering, author: resolver_mod.RawAuthor, depth: u8) ?resolver_mod.RawAuthor {
|
||||||
if (depth == 0) return null;
|
if (depth == 0) return null;
|
||||||
if (structDeclOfRaw(author.raw)) |sd| {
|
const cd = constAliasOfRaw(author.raw) orelse return author;
|
||||||
if (sd.type_params.len == 0) return null;
|
const next: ?resolver_mod.RawAuthor = switch (cd.value.data) {
|
||||||
return self.buildGenericStructTemplate(sd, author.source);
|
.identifier => |id| singleVisibleAuthor(self, id.name, author.source),
|
||||||
}
|
.field_access => |fa| blk: {
|
||||||
const cd = constAliasOfRaw(author.raw) orelse return null;
|
if (fa.object.data != .identifier) break :blk 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)) {
|
const target = switch (self.namespaceAliasVerdictFrom(fa.object.data.identifier.name, author.source)) {
|
||||||
.target => |t| t,
|
.target => |t| t,
|
||||||
.none, .ambiguous => return null,
|
.none, .ambiguous => break :blk null,
|
||||||
};
|
};
|
||||||
var res = self.resolver();
|
var res = self.resolver();
|
||||||
const member_set = res.collectNamespaceAuthors(target, fa.field);
|
const member_set = res.collectNamespaceAuthors(target, fa.field);
|
||||||
const member = member_set.own orelse return null;
|
break :blk member_set.own;
|
||||||
return followToTemplate(self, member, depth - 1);
|
|
||||||
},
|
},
|
||||||
else => return null,
|
else => null,
|
||||||
}
|
};
|
||||||
|
return followAliasChain(self, next orelse return null, depth - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The fn decl a const ALIAS chain terminates at, or null when `cd` is not
|
||||||
|
/// an alias of a function. Entry for fn-alias registration (issue 0121):
|
||||||
|
/// `cd` itself seeds the chain (it IS the first alias hop), `from` is its
|
||||||
|
/// declaring source.
|
||||||
|
pub fn aliasedFnDecl(self: *Lowering, cd: *const ast.ConstDecl, from: []const u8) ?*const ast.FnDecl {
|
||||||
|
const terminal = followAliasChain(self, .{ .raw = .{ .const_decl = cd }, .source = from }, 9) orelse return null;
|
||||||
|
return Lowering.fnDeclOfRaw(terminal.raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The bare-VISIBLE single generic-struct author of `name` (its `StructDecl` +
|
/// The bare-VISIBLE single generic-struct author of `name` (its `StructDecl` +
|
||||||
|
|||||||
Reference in New Issue
Block a user