fix(R1): own const_decl pending alias wins over flat imports (issue 0107)
When a module declares `A :: B; B :: u64;` and both a flat import and a namespaced import export `B :: u8`, the flat import's B was discovered by flatTypeAuthorCount before the own B :: u64 was processed — binding A to u8 and silently truncating values. Fix: ownConstDeclIsPendingAlias guard added to selectNominalLeaf between the own-alias check and the flat-import walk. If the querying module has an own const_decl for the name that is not yet in type_aliases_by_source, return .pending so the forward-alias fixpoint resolves it correctly. Regression: examples/0830-modules-flat-ns-same-name-forward-alias.sx (x : A = 300 prints 300, not 44). 541/541 tests pass.
This commit is contained in:
16
examples/0830-modules-flat-ns-same-name-forward-alias.sx
Normal file
16
examples/0830-modules-flat-ns-same-name-forward-alias.sx
Normal file
@@ -0,0 +1,16 @@
|
||||
// A flat import and a namespaced import both exporting `B :: u8` must not
|
||||
// preempt the own module's `A :: B; B :: u64;` forward alias chain.
|
||||
// Regression: issue 0107 — flat B :: u8 was winning before own B :: u64
|
||||
// was processed.
|
||||
|
||||
std :: #import "modules/std.sx";
|
||||
#import "0830-modules-flat-ns-same-name-forward-alias/flat.sx";
|
||||
ns :: #import "0830-modules-flat-ns-same-name-forward-alias/ns.sx";
|
||||
A :: B;
|
||||
B :: u64;
|
||||
|
||||
main :: () -> s32 {
|
||||
x : A = 300;
|
||||
std.print("{}\n", x);
|
||||
0
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
B :: u8;
|
||||
@@ -0,0 +1 @@
|
||||
B :: u8;
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
300
|
||||
20
issues/0107-forward-alias-initial-scan-global-leak.md
Normal file
20
issues/0107-forward-alias-initial-scan-global-leak.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# RESOLVED — 0107: forward alias binds to flat-import same-name type during initial scanDecls
|
||||
|
||||
**Root cause:** `selectNominalLeaf` checked flat-import authors (`flatTypeAuthorCount`) before
|
||||
checking whether the querying module's own `const_decl` for the same name was still pending.
|
||||
When both a flat import AND a namespaced import exported `B :: u8`, and the own module had
|
||||
`A :: B; B :: u64;` (B declared after A), the flat import's `u8` was returned as `.resolved`
|
||||
before the own `B :: u64` was processed — binding A to `u8` and silently truncating values.
|
||||
|
||||
**Symptom:** `x : A = 300` printed `44` (u8 truncation of 300) when both imports had `B :: u8`.
|
||||
The namespaced import alone was not sufficient; both flat AND namespaced were required.
|
||||
|
||||
**Fix:** Added `ownConstDeclIsPendingAlias(from, name)` check between the own-alias check and
|
||||
the flat-import walk in `selectNominalLeaf`. If the querying module has an own `const_decl` for
|
||||
`name` that is not yet in `type_aliases_by_source`, return `.pending` — deferring to the
|
||||
forward-alias fixpoint instead of accepting a flat import's version.
|
||||
|
||||
**Regression test:** `examples/0830-modules-flat-ns-same-name-forward-alias.sx` (exit 0,
|
||||
prints 300 — the u64 value, not the u8 truncation).
|
||||
|
||||
**Fix in:** `src/ir/lower.zig` — `selectNominalLeaf` + new `ownConstDeclIsPendingAlias`.
|
||||
@@ -2044,6 +2044,12 @@ pub const Lowering = struct {
|
||||
return .forward;
|
||||
},
|
||||
};
|
||||
// Own-wins before flat: if this module has an own `const_decl` for
|
||||
// `name` that is not yet in `type_aliases_by_source`, it is a forward
|
||||
// type alias pending the fixpoint. Deferring here prevents a flat-import's
|
||||
// same-name type alias from winning before the own declaration is processed
|
||||
// (issue 0107: flat `B :: u8` would preempt the own `B :: u64`).
|
||||
if (self.ownConstDeclIsPendingAlias(from, name)) return .pending;
|
||||
switch (self.flatTypeAuthorCount(name, from)) {
|
||||
.none => {},
|
||||
.one => |tid| return .{ .resolved = tid },
|
||||
@@ -2151,6 +2157,20 @@ pub const Lowering = struct {
|
||||
/// `module_consts_by_source`, never `type_aliases_by_source`, so it returns
|
||||
/// null too. THE per-module "is `name` a type author here?" predicate — the
|
||||
/// single source of truth for the visibility walk (R4).
|
||||
/// True when `from` declares `name` as a `const_decl` that is NOT yet
|
||||
/// registered in `type_aliases_by_source` — an unprocessed forward type alias
|
||||
/// whose own-module resolution should take precedence over flat imports.
|
||||
fn ownConstDeclIsPendingAlias(self: *Lowering, from: []const u8, name: []const u8) bool {
|
||||
const decls = self.program_index.module_decls orelse return false;
|
||||
const m = decls.get(from) orelse return false;
|
||||
const ref = m.names.get(name) orelse return false;
|
||||
if (std.meta.activeTag(ref) != .const_decl) return false;
|
||||
if (self.program_index.type_aliases_by_source.get(from)) |inner| {
|
||||
if (inner.contains(name)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
fn moduleTypeAuthor(self: *Lowering, path: []const u8, name: []const u8) ?FlatTypeAuthor {
|
||||
if (self.program_index.type_aliases_by_source.get(path)) |inner| {
|
||||
if (inner.get(name)) |tid| return .{ .alias = tid };
|
||||
|
||||
Reference in New Issue
Block a user