fix(ir): resolve forward identifier type aliases in scanDecls (issue 0069)
scanDecls' `.identifier` alias branch registered `A :: B` into ProgramIndex.type_alias_map only when `B` was already known (in type_alias_map or the TypeTable). A forward target declared later (`MyChain :: MyInt; MyInt :: s32;`) was never present during the single forward scan, so the alias name went unregistered and the A2.4 unknown-type pass — which treats type_alias_map keys as declared types — flagged its uses as `unknown type 'MyChain'`. Add a fixpoint post-pass `resolveForwardIdentifierAliases` at the end of scanDecls that re-resolves identifier-RHS aliases until no progress, after every top-level name has been seen. A value const is never an `.identifier` node, and an alias whose target is a value const still misses both lookups, so issue 0068's value-const rejection is preserved. Regression: examples/0132-types-forward-type-alias.sx (forward alias + forward chain). Gate: zig build, zig build test, run_examples.sh -> 353/0.
This commit is contained in:
24
examples/0132-types-forward-type-alias.sx
Normal file
24
examples/0132-types-forward-type-alias.sx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
// Forward identifier type alias — an alias whose target is declared LATER
|
||||||
|
// in the file resolves the same as an ordered one. `MyChain :: MyInt;`
|
||||||
|
// appears before `MyInt :: s32;`, yet `MyChain` resolves to `s32` and a
|
||||||
|
// forward chain (`A :: B; B :: C; C :: u8;`) converges too.
|
||||||
|
// Regression (issue 0069): the scan only registered identifier aliases whose
|
||||||
|
// target was already known, so a forward alias was falsely flagged
|
||||||
|
// `unknown type`. Now a fixpoint pass over the scanned decls resolves them.
|
||||||
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
MyChain :: MyInt;
|
||||||
|
MyInt :: s32;
|
||||||
|
|
||||||
|
A :: B;
|
||||||
|
B :: C;
|
||||||
|
C :: u8;
|
||||||
|
|
||||||
|
main :: () -> s32 {
|
||||||
|
v: MyChain = 7;
|
||||||
|
n: A = 3;
|
||||||
|
print("chain s32: {}\n", size_of(MyChain));
|
||||||
|
print("forward u8: {}\n", size_of(A));
|
||||||
|
print("v + n: {}\n", v + cast(s32) n);
|
||||||
|
return v;
|
||||||
|
}
|
||||||
1
examples/expected/0132-types-forward-type-alias.exit
Normal file
1
examples/expected/0132-types-forward-type-alias.exit
Normal file
@@ -0,0 +1 @@
|
|||||||
|
7
|
||||||
1
examples/expected/0132-types-forward-type-alias.stderr
Normal file
1
examples/expected/0132-types-forward-type-alias.stderr
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
3
examples/expected/0132-types-forward-type-alias.stdout
Normal file
3
examples/expected/0132-types-forward-type-alias.stdout
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
chain s32: 4
|
||||||
|
forward u8: 1
|
||||||
|
v + n: 10
|
||||||
114
issues/0069-forward-identifier-type-alias-falsely-unknown.md
Normal file
114
issues/0069-forward-identifier-type-alias-falsely-unknown.md
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
# 0069 — forward identifier type alias is falsely rejected by unknown-type pass
|
||||||
|
|
||||||
|
> **RESOLVED.** Root cause: `Lowering.scanDecls`' `.identifier` alias branch only
|
||||||
|
> registered `A :: B` into `ProgramIndex.type_alias_map` when `B` was already
|
||||||
|
> known (in `type_alias_map` or the `TypeTable`). A forward target declared later
|
||||||
|
> (`MyChain :: MyInt; MyInt :: s32;`) was never present during the single forward
|
||||||
|
> scan, so the alias name went unregistered and the A2.4 unknown-type pass — which
|
||||||
|
> treats `type_alias_map` keys as declared types — flagged its uses.
|
||||||
|
> Fix: added a fixpoint post-pass `resolveForwardIdentifierAliases` at the end of
|
||||||
|
> `scanDecls` that re-resolves identifier-RHS aliases until no progress, after every
|
||||||
|
> top-level name has been seen. A value const is never an `.identifier` node and an
|
||||||
|
> alias whose target is a value const still misses both lookups, so issue 0068's
|
||||||
|
> value-const rejection is preserved. Regression:
|
||||||
|
> `examples/0132-types-forward-type-alias.sx`.
|
||||||
|
|
||||||
|
## Symptom
|
||||||
|
|
||||||
|
A forward-referenced identifier type alias is rejected as an unknown type, even
|
||||||
|
though the same alias chain works when ordered after its target.
|
||||||
|
|
||||||
|
Observed: `MyChain` is diagnosed as an unknown type.
|
||||||
|
|
||||||
|
Expected: `MyChain :: MyInt; MyInt :: s32;` should resolve `MyChain` to `s32`
|
||||||
|
when used in a type annotation, matching the existing ordered-chain behavior
|
||||||
|
(`MyInt :: s32; MyChain :: MyInt;`).
|
||||||
|
|
||||||
|
## Reproduction
|
||||||
|
|
||||||
|
```sx
|
||||||
|
MyChain :: MyInt;
|
||||||
|
MyInt :: s32;
|
||||||
|
|
||||||
|
main :: () -> s32 {
|
||||||
|
v: MyChain = 7;
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./zig-out/bin/sx run .sx-tmp/probe-0068-forward-alias.sx
|
||||||
|
```
|
||||||
|
|
||||||
|
Observed output:
|
||||||
|
|
||||||
|
```text
|
||||||
|
error: unknown type 'MyChain'
|
||||||
|
--> .sx-tmp/probe-0068-forward-alias.sx:7:8
|
||||||
|
|
|
||||||
|
7 | v: MyChain = 7;
|
||||||
|
| ^^^^^^^
|
||||||
|
```
|
||||||
|
|
||||||
|
The repro is standalone; the inline source above is sufficient to recreate the
|
||||||
|
scratch file under `.sx-tmp/`.
|
||||||
|
|
||||||
|
## Investigation prompt
|
||||||
|
|
||||||
|
Fix issue 0069: a forward-referenced identifier type alias must not be falsely
|
||||||
|
rejected by the A2.4 unknown-type diagnostic pass.
|
||||||
|
|
||||||
|
Context:
|
||||||
|
- This surfaced while re-reviewing `8770145`, the issue-0068 fix. That fix
|
||||||
|
correctly stopped arbitrary value consts (`NotAType :: 123`) from satisfying
|
||||||
|
the unknown-type check.
|
||||||
|
- Ordered identifier aliases still work: `MyInt :: s32; MyChain :: MyInt;`.
|
||||||
|
- `.call` type aliases still work: `Vec3 :: Vec(3, f32);` and
|
||||||
|
`Foo :: Complex(u32);`.
|
||||||
|
- The failing shape is specifically a forward identifier alias:
|
||||||
|
`MyChain :: MyInt; MyInt :: s32;`.
|
||||||
|
|
||||||
|
Suspected area:
|
||||||
|
- `src/ir/lower.zig`, `Lowering.scanDecls`, especially the `.identifier` alias
|
||||||
|
branch for `const_decl` values. It only inserts `cd.name` into
|
||||||
|
`ProgramIndex.type_alias_map` if the RHS is already in `type_alias_map` or
|
||||||
|
already registered in the `TypeTable`. A forward target is not present yet, so
|
||||||
|
the alias name is never recorded.
|
||||||
|
- `src/ir/semantic_diagnostics.zig`,
|
||||||
|
`UnknownTypeChecker.collectDeclaredTypeNames` / `reportIfUnknownType`. After
|
||||||
|
issue 0068, `.identifier` aliases are intentionally excluded from
|
||||||
|
`constValueIntroducesType` and are supposed to be covered by canonical facts
|
||||||
|
(`ProgramIndex.type_alias_map` / `TypeTable`). Because the forward alias never
|
||||||
|
reaches those facts, the checker flags the alias as unknown.
|
||||||
|
|
||||||
|
Likely fix:
|
||||||
|
- Do not reintroduce the issue-0068 bug by adding all `.identifier` const names
|
||||||
|
to the declared-type set.
|
||||||
|
- Instead, make identifier aliases converge through canonical alias facts even
|
||||||
|
when the RHS is declared later. A small two-pass alias registration/resolution
|
||||||
|
in `scanDecls`, or an explicit pending-alias graph that resolves after all
|
||||||
|
top-level declarations are scanned, would keep `ProgramIndex.type_alias_map`
|
||||||
|
authoritative without accepting value constants.
|
||||||
|
- Preserve value const rejection: `NotAType :: 123; v: NotAType` must continue
|
||||||
|
to emit `unknown type 'NotAType'`.
|
||||||
|
- Preserve ordered/chained aliases and type-returning call aliases:
|
||||||
|
`examples/0116-types-type-alias-size-align.sx`,
|
||||||
|
`examples/0201-generics-generic-struct.sx`, and
|
||||||
|
`examples/1117-diagnostics-value-const-as-type-rejected.sx`.
|
||||||
|
|
||||||
|
Verification:
|
||||||
|
- Add a focused regression for the repro above, likely in the `01xx` types block
|
||||||
|
because the desired behavior is successful alias resolution.
|
||||||
|
- Run the new regression and the existing alias/diagnostic guards:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
zig build
|
||||||
|
zig build test
|
||||||
|
bash tests/run_examples.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected result: the forward alias program compiles/runs (returning `7`), the
|
||||||
|
issue-0068 value-const case still fails with a diagnostic, and the full suite
|
||||||
|
passes.
|
||||||
@@ -1253,6 +1253,43 @@ pub const Lowering = struct {
|
|||||||
else => {},
|
else => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.resolveForwardIdentifierAliases(decls);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolve identifier-RHS type aliases whose target is declared LATER in the
|
||||||
|
/// file. The forward scan above only registers an alias (`A :: B`) when `B`
|
||||||
|
/// is already in `type_alias_map` / the `TypeTable`; a forward target isn't
|
||||||
|
/// yet present, so `A` is left unregistered and its uses get falsely flagged
|
||||||
|
/// as an unknown type (issue 0069). Re-resolve to a fixpoint now that every
|
||||||
|
/// top-level name has been seen, so `A :: B; B :: s32;` converges the same as
|
||||||
|
/// the ordered `B :: s32; A :: B;`. A value const is never an `.identifier`
|
||||||
|
/// node (`NotAType :: 123` is an int literal), and an alias whose target is a
|
||||||
|
/// value const still misses both lookups, so neither this pass nor issue 0068
|
||||||
|
/// can register a non-type name.
|
||||||
|
fn resolveForwardIdentifierAliases(self: *Lowering, decls: []const *const Node) void {
|
||||||
|
var progressed = true;
|
||||||
|
while (progressed) {
|
||||||
|
progressed = false;
|
||||||
|
for (decls) |decl| {
|
||||||
|
const cd = switch (decl.data) {
|
||||||
|
.const_decl => |c| c,
|
||||||
|
else => continue,
|
||||||
|
};
|
||||||
|
if (cd.value.data != .identifier) continue;
|
||||||
|
if (self.program_index.type_alias_map.contains(cd.name)) continue;
|
||||||
|
const rhs_name = cd.value.data.identifier.name;
|
||||||
|
if (self.program_index.type_alias_map.get(rhs_name)) |chained| {
|
||||||
|
self.program_index.type_alias_map.put(cd.name, chained) catch {};
|
||||||
|
progressed = true;
|
||||||
|
} else {
|
||||||
|
const name_id = self.module.types.internString(rhs_name);
|
||||||
|
if (self.module.types.findByName(name_id)) |tid| {
|
||||||
|
self.program_index.type_alias_map.put(cd.name, tid) catch {};
|
||||||
|
progressed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Try to convert an array literal's elements into a compile-time ConstantValue.aggregate.
|
/// Try to convert an array literal's elements into a compile-time ConstantValue.aggregate.
|
||||||
|
|||||||
Reference in New Issue
Block a user