fix(ir): exhaustive named-const array dims (0083) + nested slice-literal coercion (0085)
Makes the F0.4 fixes exhaustive across every resolution / nesting path.
0083 — named-const array dimension, stateless paths. Attempt 1 fixed the
stateful resolver (direct local decls, struct fields, params, returns) but the
binding-free registration-time resolver (`type_bridge`, used for type aliases
`Arr :: [N]T` and inline union/enum field types) still resolved a named dim with
a silent `else 0`, so `Arr :: [N]s64; a : Arr` and `union { a: [N]s64 }` were
still miscompiled (garbage / bus error). Thread the module-global const table
(`ProgramIndex.module_const_map`) into `type_bridge` alongside the alias map, so
`StatelessInner.resolveArrayLen` resolves a named module-const dim to the same
length everywhere. The remaining unresolvable case (a computed/comptime dim on
the binding-free path, which the stateful path hard-errors) now bails LOUDLY
instead of fabricating a 0 length.
0085 — nested slice-literal elements. `lowerArrayLiteral` lowered each element
with the element type as target but appended the raw value. A nested `.[...]`
element at a slice element type (`[][]s64`) still lowers to an aggregate array
`[N]T`, so the outer aggregate held raw arrays where slice {ptr,len} headers
were expected — indexing the inner slice read a garbage pointer and segfaulted.
After lowering each element, coerce a same-element array to the slice element
type via the existing `array_to_slice` op. The coercion recurses with the
nesting, so `[][]T` and deeper materialize at every level — local-bound AND
direct-call-argument forms.
Regressions (fail-before/pass-after demonstrated on the pre-fix compiler):
examples/0140-types-named-const-array-dim.sx — extended with type-alias,
nested [N][M]T, and union-field named dims (s64 / string / struct elems)
examples/0142-types-nested-slice-literal-elements.sx — [][]s64 + [][]string,
local-bound vs direct-arg
src/ir/type_bridge.test.zig — named-const dim resolves to literal length
Gate: zig build, zig build test, bash tests/run_examples.sh (388 passed).
Issues 0083 and 0085 marked RESOLVED.
This commit is contained in:
@@ -1,32 +1,69 @@
|
||||
// A fixed array whose dimension is a module-global named constant
|
||||
// (`N :: 16; [N]T`) has the same layout as a literal-dimension array
|
||||
// (`[16]T`): correct length and element stride for scalar, slice/pointer
|
||||
// (string), and struct element types.
|
||||
// (string), and struct element types — on EVERY type-resolution path:
|
||||
// direct local decls, type aliases (`Arr :: [N]T`), nested fixed arrays
|
||||
// (`[N][M]T`), and inline union fields. The named dim must resolve to the
|
||||
// same length whether it flows through the stateful body-lowering resolver
|
||||
// or the stateless registration-time resolver (type_bridge).
|
||||
// Regression (issue 0083): a named-const dim resolved to length 0, giving a
|
||||
// 0-byte alloca — scalar reads returned garbage and string/struct elements
|
||||
// bus-errored.
|
||||
// bus-errored. The alias and union-field paths went through the stateless
|
||||
// resolver, which had no const table and silently fabricated a 0 length.
|
||||
#import "modules/std.sx";
|
||||
|
||||
N :: 4;
|
||||
M :: 3;
|
||||
|
||||
P :: struct { x: s64; y: s64; }
|
||||
|
||||
// Type aliases whose dimension is the named const N (stateless registration).
|
||||
Arr :: [N]s64;
|
||||
SArr :: [N]string;
|
||||
|
||||
// Inline union field with a named-const dimension (stateless registration).
|
||||
U :: union { a: [N]s64; tag: s64; }
|
||||
|
||||
main :: () {
|
||||
// Scalar elements: store then read back.
|
||||
// Scalar elements (direct local): store then read back.
|
||||
a : [N]s64 = ---;
|
||||
a[0] = 7;
|
||||
a[3] = 42;
|
||||
print("scalar a0={} a3={}\n", a[0], a[3]);
|
||||
|
||||
// Slice/pointer elements (string): used to bus-error.
|
||||
// Slice/pointer elements (string, direct local): used to bus-error.
|
||||
s : [N]string = ---;
|
||||
s[0] = "hi";
|
||||
s[1] = "yo";
|
||||
print("string s0={} s1={}\n", s[0], s[1]);
|
||||
|
||||
// Struct elements.
|
||||
// Struct elements (direct local).
|
||||
ps : [N]P = ---;
|
||||
ps[0] = P.{ x = 1, y = 2 };
|
||||
ps[2] = P.{ x = 5, y = 6 };
|
||||
print("struct p0x={} p0y={} p2x={}\n", ps[0].x, ps[0].y, ps[2].x);
|
||||
|
||||
// Type-alias dimension (scalar): same layout as the direct `[N]s64`.
|
||||
aa : Arr = ---;
|
||||
aa[0] = 11;
|
||||
aa[3] = 99;
|
||||
print("alias a0={} a3={}\n", aa[0], aa[3]);
|
||||
|
||||
// Type-alias dimension (string): no bus error, correct reads.
|
||||
sa : SArr = ---;
|
||||
sa[0] = "al";
|
||||
sa[2] = "ok";
|
||||
print("alias s0={} s2={}\n", sa[0], sa[2]);
|
||||
|
||||
// Nested fixed array `[N][M]s64`: both dimensions are named consts.
|
||||
grid : [N][M]s64 = ---;
|
||||
grid[0][0] = 1;
|
||||
grid[3][2] = 8;
|
||||
print("nested g00={} g32={}\n", grid[0][0], grid[3][2]);
|
||||
|
||||
// Inline union field with a named-const dimension.
|
||||
u : U = ---;
|
||||
u.a[0] = 70;
|
||||
u.a[3] = 7;
|
||||
print("union u0={} u3={}\n", u.a[0], u.a[3]);
|
||||
}
|
||||
|
||||
44
examples/0142-types-nested-slice-literal-elements.sx
Normal file
44
examples/0142-types-nested-slice-literal-elements.sx
Normal file
@@ -0,0 +1,44 @@
|
||||
// A nested array/slice literal (`.[.[1, 2], .[3, 4]]`) at an expected slice-of-
|
||||
// slices type (`[][]s64`) materializes each inner `[N]T` literal as a real `[]T`
|
||||
// slice, so indexing the inner slice in the callee reads element contents
|
||||
// correctly — for both the local-bound form and the direct-call-argument form.
|
||||
// Regression (issue 0085): inner literals were appended as raw `[N]T` arrays
|
||||
// under an element type of `[]T`, so the outer aggregate's elements were arrays
|
||||
// where slice {ptr,len} headers were expected; indexing the inner slice read a
|
||||
// garbage pointer and segfaulted. The per-element array->slice materialization
|
||||
// recurses with the nesting, so every level coerces.
|
||||
#import "modules/std.sx";
|
||||
|
||||
sum_nested :: (xss: [][]s64) -> s64 {
|
||||
total := 0;
|
||||
i := 0;
|
||||
while i < xss.len {
|
||||
j := 0;
|
||||
while j < xss[i].len { total += xss[i][j]; j += 1; }
|
||||
i += 1;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
count_x :: (xss: [][]string) -> s64 {
|
||||
n := 0;
|
||||
i := 0;
|
||||
while i < xss.len {
|
||||
j := 0;
|
||||
while j < xss[i].len { if xss[i][j] == "x" { n += 1; } j += 1; }
|
||||
i += 1;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
main :: () {
|
||||
// numeric [][]s64 — local-bound vs direct-arg both sum to 10.
|
||||
local : [][]s64 = .[.[1, 2], .[3, 4]];
|
||||
print("num local={}\n", sum_nested(local));
|
||||
print("num direct={}\n", sum_nested(.[.[1, 2], .[3, 4]]));
|
||||
|
||||
// string [][]string — local-bound vs direct-arg both count 4 "x"s.
|
||||
slocal : [][]string = .[.["x", "a"], .["b", "x"], .["x", "x"]];
|
||||
print("str local={}\n", count_x(slocal));
|
||||
print("str direct={}\n", count_x(.[.["x", "a"], .["b", "x"], .["x", "x"]]));
|
||||
}
|
||||
@@ -1,3 +1,7 @@
|
||||
scalar a0=7 a3=42
|
||||
string s0=hi s1=yo
|
||||
struct p0x=1 p0y=2 p2x=5
|
||||
alias a0=11 a3=99
|
||||
alias s0=al s2=ok
|
||||
nested g00=1 g32=8
|
||||
union u0=70 u3=7
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
num local=10
|
||||
num direct=10
|
||||
str local=4
|
||||
str direct=4
|
||||
@@ -9,9 +9,22 @@
|
||||
> type). The stateful `Lowering.resolveArrayLen` evaluates the dimension as a
|
||||
> compile-time integer across the comptime-constant, generic-value, and
|
||||
> module-global const tables, and emits a diagnostic (no fabricated length) when
|
||||
> it isn't one. Files: `src/ir/type_resolver.zig`, `src/ir/lower.zig`,
|
||||
> it isn't one.
|
||||
>
|
||||
> **Exhaustive follow-up (attempt 2).** The first fix covered every *stateful*
|
||||
> resolution path (direct local decls, struct fields, function params/returns),
|
||||
> but the *stateless* registration-time resolver (`type_bridge`, used for type
|
||||
> aliases `Arr :: [N]T` and inline union/enum field types) still resolved the
|
||||
> named dim with a silent `else 0` — so `Arr :: [N]s64; a : Arr` and
|
||||
> `union { a: [N]s64 }` were still miscompiled. Fix: the module-global const
|
||||
> table (`ProgramIndex.module_const_map`) is now threaded into `type_bridge`
|
||||
> alongside the alias map, so `StatelessInner.resolveArrayLen` resolves a named
|
||||
> module-const dim to the same length everywhere. The remaining unresolvable case
|
||||
> (a computed/comptime dimension on the binding-free path) bails LOUDLY instead of
|
||||
> fabricating a 0 length. Files: `src/ir/type_resolver.zig`, `src/ir/lower.zig`,
|
||||
> `src/ir/type_bridge.zig`. Regression: `examples/0140-types-named-const-array-dim.sx`
|
||||
> (s64 + string + struct element types).
|
||||
> (direct + type-alias + nested `[N][M]T` + union-field dims, s64 / string /
|
||||
> struct element types).
|
||||
|
||||
## Symptom
|
||||
A fixed array whose dimension is a module-global integer constant (`N :: 16;
|
||||
|
||||
55
issues/0085-nested-slice-literal-elements-not-coerced.md
Normal file
55
issues/0085-nested-slice-literal-elements-not-coerced.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# 0085 — nested slice literal elements are stored as raw arrays
|
||||
|
||||
> **RESOLVED.** Root cause: `Lowering.lowerArrayLiteral` lowered each element with
|
||||
> the element type as `target_type` but appended the returned value directly. For
|
||||
> a nested `.[...]` element whose expected element type is a slice (`[]T`), the
|
||||
> inner literal still lowers to an aggregate ARRAY `[N]T` — so the outer aggregate
|
||||
> (typed array-of-`[]T`) held raw arrays where slice {ptr,len} headers were
|
||||
> expected; indexing the inner slice read a garbage pointer and segfaulted. Fix:
|
||||
> after lowering each element, when the element type is a slice and the lowered
|
||||
> value is a same-element array, coerce it via the existing `array_to_slice` op
|
||||
> (materialize backing storage + build the header) — identical to the whole-
|
||||
> literal coercion the var-decl / call-arg paths already run. The coercion
|
||||
> recurses with the nesting, so `[][]T` and deeper materialize at every level.
|
||||
> Files: `src/ir/lower.zig` (`lowerArrayLiteral`). Regression:
|
||||
> `examples/0142-types-nested-slice-literal-elements.sx` (`[][]s64` + `[][]string`,
|
||||
> local-bound AND direct-call-argument forms).
|
||||
|
||||
## Symptom
|
||||
Nested array/slice literals such as `.[.[1, 2], .[3, 4]]` miscompile when the
|
||||
expected element type is a slice (`[][]s64`). Observed: both the local-bound and
|
||||
direct-call forms segfault while indexing the inner slice. Expected: both forms
|
||||
materialize each inner `[N]T` literal as a `[]T` slice and print the same value.
|
||||
|
||||
## Reproduction
|
||||
```sx
|
||||
#import "modules/std.sx";
|
||||
|
||||
sum_nested :: (xss: [][]s64) -> s64 {
|
||||
return xss[0][1] + xss[1][0];
|
||||
}
|
||||
|
||||
main :: () {
|
||||
local : [][]s64 = .[.[1, 2], .[3, 4]];
|
||||
print("local={}\n", sum_nested(local));
|
||||
print("direct={}\n", sum_nested(.[.[1, 2], .[3, 4]]));
|
||||
}
|
||||
```
|
||||
Observed on `flow/sx-foundation/F0.4`: segfault at address `0x9` before either
|
||||
line prints. Expected output:
|
||||
```text
|
||||
local=5
|
||||
direct=5
|
||||
```
|
||||
|
||||
## Investigation prompt
|
||||
Fix nested slice literal materialization. The likely area is
|
||||
`src/ir/lower.zig` in `Lowering.lowerArrayLiteral`: the outer literal can know
|
||||
its expected element type is `[]T`, and the loop sets `self.target_type =
|
||||
elem_ty` while lowering each inner literal, but it appends the returned value
|
||||
directly. For an inner `.[...]`, that returned value is still an array aggregate
|
||||
`[N]T`, not the target `[]T` slice. Add per-element coercion/materialization
|
||||
after lowering each element, using the element source type and expected
|
||||
`elem_ty` (the existing `array_to_slice` coercion should be reused). Verify the
|
||||
repro prints `local=5` and `direct=5`, then run `zig build && zig build test &&
|
||||
bash tests/run_examples.sh`.
|
||||
@@ -264,7 +264,7 @@ pub const CallResolver = struct {
|
||||
if (method_fd.body.data == .compiler_expr) {
|
||||
return .{
|
||||
.kind = .struct_method,
|
||||
.return_type = if (method_fd.return_type) |rt| type_bridge.resolveAstType(rt, &self.l.module.types, &self.l.program_index.type_alias_map) else .void,
|
||||
.return_type = if (method_fd.return_type) |rt| type_bridge.resolveAstType(rt, &self.l.module.types, &self.l.program_index.type_alias_map, &self.l.program_index.module_const_map) else .void,
|
||||
.target = .{ .named = qualified },
|
||||
.prepends_receiver = true,
|
||||
.expands_defaults = defaultsFor(method_fd, c.args.len + 1),
|
||||
|
||||
@@ -560,9 +560,9 @@ pub const Lowering = struct {
|
||||
} else if (cd.value.data == .struct_decl) {
|
||||
self.registerStructDecl(&cd.value.data.struct_decl);
|
||||
} else if (cd.value.data == .enum_decl) {
|
||||
_ = type_bridge.resolveAstType(cd.value, &self.module.types, &self.program_index.type_alias_map);
|
||||
_ = type_bridge.resolveAstType(cd.value, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map);
|
||||
} else if (cd.value.data == .union_decl) {
|
||||
_ = type_bridge.resolveAstType(cd.value, &self.module.types, &self.program_index.type_alias_map);
|
||||
_ = type_bridge.resolveAstType(cd.value, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map);
|
||||
} else if (cd.value.data == .comptime_expr) {
|
||||
self.lowerComptimeGlobal(cd.name, cd.value.data.comptime_expr.expr, cd.type_annotation);
|
||||
}
|
||||
@@ -574,10 +574,10 @@ pub const Lowering = struct {
|
||||
self.registerStructDecl(&sd);
|
||||
},
|
||||
.enum_decl => {
|
||||
_ = type_bridge.resolveAstType(decl, &self.module.types, &self.program_index.type_alias_map);
|
||||
_ = type_bridge.resolveAstType(decl, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map);
|
||||
},
|
||||
.union_decl => {
|
||||
_ = type_bridge.resolveAstType(decl, &self.module.types, &self.program_index.type_alias_map);
|
||||
_ = type_bridge.resolveAstType(decl, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map);
|
||||
},
|
||||
.error_set_decl => {
|
||||
self.registerErrorSetDecl(decl);
|
||||
@@ -675,10 +675,10 @@ pub const Lowering = struct {
|
||||
self.registerStructDecl(&cd.value.data.struct_decl);
|
||||
} else if (cd.value.data == .enum_decl) {
|
||||
// Register enum/tagged-union types in the type table
|
||||
_ = type_bridge.resolveAstType(cd.value, &self.module.types, &self.program_index.type_alias_map);
|
||||
_ = type_bridge.resolveAstType(cd.value, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map);
|
||||
} else if (cd.value.data == .union_decl) {
|
||||
// Register plain union types in the type table
|
||||
_ = type_bridge.resolveAstType(cd.value, &self.module.types, &self.program_index.type_alias_map);
|
||||
_ = type_bridge.resolveAstType(cd.value, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map);
|
||||
} else if (cd.value.data == .type_expr or
|
||||
cd.value.data == .pointer_type_expr or
|
||||
cd.value.data == .many_pointer_type_expr or
|
||||
@@ -688,7 +688,7 @@ pub const Lowering = struct {
|
||||
cd.value.data == .function_type_expr)
|
||||
{
|
||||
// Type alias: MyFloat :: f64; Ptr :: *u8; Cb :: (s32) -> s32;
|
||||
const target_ty = type_bridge.resolveAstType(cd.value, &self.module.types, &self.program_index.type_alias_map);
|
||||
const target_ty = type_bridge.resolveAstType(cd.value, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map);
|
||||
self.program_index.type_alias_map.put(cd.name, target_ty) catch {};
|
||||
} else if (cd.value.data == .identifier) {
|
||||
// Identifier-RHS alias: MyAlias :: MyInt; WideAlias :: Wide;
|
||||
@@ -771,7 +771,7 @@ pub const Lowering = struct {
|
||||
// resolve via type_bridge and register the result
|
||||
// under the alias name so `Vec4` in expression
|
||||
// position can `const_type(<vector tid>)`.
|
||||
const result_ty = type_bridge.resolveAstType(cd.value, &self.module.types, &self.program_index.type_alias_map);
|
||||
const result_ty = type_bridge.resolveAstType(cd.value, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map);
|
||||
if (result_ty != .void and result_ty != .unresolved) {
|
||||
self.program_index.type_alias_map.put(cd.name, result_ty) catch {};
|
||||
}
|
||||
@@ -806,11 +806,11 @@ pub const Lowering = struct {
|
||||
},
|
||||
.enum_decl => {
|
||||
// Register enum/tagged-union types in the type table
|
||||
_ = type_bridge.resolveAstType(decl, &self.module.types, &self.program_index.type_alias_map);
|
||||
_ = type_bridge.resolveAstType(decl, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map);
|
||||
},
|
||||
.union_decl => {
|
||||
// Register plain union types in the type table
|
||||
_ = type_bridge.resolveAstType(decl, &self.module.types, &self.program_index.type_alias_map);
|
||||
_ = type_bridge.resolveAstType(decl, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map);
|
||||
},
|
||||
.error_set_decl => {
|
||||
self.registerErrorSetDecl(decl);
|
||||
@@ -1813,7 +1813,7 @@ pub const Lowering = struct {
|
||||
// Block-local type declarations
|
||||
.struct_decl => |sd| self.registerStructDecl(&sd),
|
||||
.enum_decl, .union_decl => {
|
||||
_ = type_bridge.resolveAstType(node, &self.module.types, &self.program_index.type_alias_map);
|
||||
_ = type_bridge.resolveAstType(node, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map);
|
||||
},
|
||||
.error_set_decl => self.registerErrorSetDecl(node),
|
||||
.ufcs_alias => |ua| {
|
||||
@@ -1972,7 +1972,7 @@ pub const Lowering = struct {
|
||||
return;
|
||||
}
|
||||
if (cd.value.data == .enum_decl or cd.value.data == .union_decl) {
|
||||
_ = type_bridge.resolveAstType(cd.value, &self.module.types, &self.program_index.type_alias_map);
|
||||
_ = type_bridge.resolveAstType(cd.value, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2912,7 +2912,7 @@ pub const Lowering = struct {
|
||||
// `t : Type = f64;` store a real TypeId; lets
|
||||
// `t == f64` icmp at runtime against the same TypeId.
|
||||
if (self.isKnownTypeName(te.name)) {
|
||||
const ty = type_bridge.resolveAstType(node, &self.module.types, &self.program_index.type_alias_map);
|
||||
const ty = type_bridge.resolveAstType(node, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map);
|
||||
break :blk self.builder.constType(ty);
|
||||
}
|
||||
break :blk self.emitError(te.name, node.span);
|
||||
@@ -5357,8 +5357,26 @@ pub const Lowering = struct {
|
||||
for (al.elements) |elem| {
|
||||
const old_tt = self.target_type;
|
||||
self.target_type = elem_ty;
|
||||
const val = self.lowerExpr(elem);
|
||||
var val = self.lowerExpr(elem);
|
||||
self.target_type = old_tt;
|
||||
// A nested `.[...]` element at a slice element type lowers to an
|
||||
// aggregate array `[N]U` (lowerArrayLiteral always yields an array
|
||||
// value); materialize it into a `[]U` slice so the element is a real
|
||||
// {ptr,len} header rather than a raw array the callee would read its
|
||||
// header off of (issue 0085). This per-element coercion recurses with
|
||||
// the literal nesting, so `[][]T` and deeper coerce at every level.
|
||||
if (!elem_ty.isBuiltin()) {
|
||||
const ei = self.module.types.get(elem_ty);
|
||||
if (ei == .slice) {
|
||||
const val_ty = self.builder.getRefType(val);
|
||||
if (!val_ty.isBuiltin()) {
|
||||
const vi = self.module.types.get(val_ty);
|
||||
if (vi == .array and vi.array.element == ei.slice.element) {
|
||||
val = self.coerceToType(val, val_ty, elem_ty);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
elems.append(self.alloc, val) catch unreachable;
|
||||
}
|
||||
|
||||
@@ -5401,7 +5419,7 @@ pub const Lowering = struct {
|
||||
const name_id = self.module.types.internString(id.name);
|
||||
return self.module.types.findByName(name_id) orelse .unresolved;
|
||||
},
|
||||
.type_expr => return type_bridge.resolveAstType(te, &self.module.types, &self.program_index.type_alias_map),
|
||||
.type_expr => return type_bridge.resolveAstType(te, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map),
|
||||
.field_access => |fa| {
|
||||
// Module.Type — try to resolve the field as a type name
|
||||
const name_id = self.module.types.internString(fa.field);
|
||||
@@ -6875,7 +6893,7 @@ pub const Lowering = struct {
|
||||
// Check for #compiler free functions
|
||||
if (self.program_index.fn_ast_map.get(func_name)) |fd_check| {
|
||||
if (fd_check.body.data == .compiler_expr) {
|
||||
const ret_ty = if (fd_check.return_type) |rt| type_bridge.resolveAstType(rt, &self.module.types, &self.program_index.type_alias_map) else TypeId.void;
|
||||
const ret_ty = if (fd_check.return_type) |rt| type_bridge.resolveAstType(rt, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map) else TypeId.void;
|
||||
return self.builder.compilerCall(func_name, args.items, ret_ty);
|
||||
}
|
||||
}
|
||||
@@ -7230,7 +7248,7 @@ pub const Lowering = struct {
|
||||
if (self.program_index.fn_ast_map.get(qualified)) |method_fd| {
|
||||
if (method_fd.body.data == .compiler_expr) {
|
||||
const ret_ty = if (method_fd.return_type) |rt|
|
||||
type_bridge.resolveAstType(rt, &self.module.types, &self.program_index.type_alias_map)
|
||||
type_bridge.resolveAstType(rt, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map)
|
||||
else
|
||||
.void;
|
||||
return self.builder.compilerCall(qualified, method_args.items, ret_ty);
|
||||
@@ -7668,7 +7686,7 @@ pub const Lowering = struct {
|
||||
|
||||
const ret_ty = blk: {
|
||||
if (lam.return_type) |rt| {
|
||||
break :blk type_bridge.resolveAstType(rt, &self.module.types, &self.program_index.type_alias_map);
|
||||
break :blk type_bridge.resolveAstType(rt, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map);
|
||||
}
|
||||
// Use target closure return type if available — but only when it's
|
||||
// a resolved type. An `.unresolved` ret comes from an unbound
|
||||
@@ -8238,7 +8256,7 @@ pub const Lowering = struct {
|
||||
}
|
||||
|
||||
fn resolveReturnType2(self: *Lowering, rt: ?*const Node) TypeId {
|
||||
if (rt) |r| return type_bridge.resolveAstType(r, &self.module.types, &self.program_index.type_alias_map);
|
||||
if (rt) |r| return type_bridge.resolveAstType(r, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map);
|
||||
return .void;
|
||||
}
|
||||
|
||||
@@ -9278,8 +9296,8 @@ pub const Lowering = struct {
|
||||
const ret_ty: TypeId = blk: {
|
||||
if (fd.return_type) |rt| {
|
||||
if (rt.data == .type_expr) {
|
||||
if (type_bridge.resolveAstType(rt, &self.module.types, &self.program_index.type_alias_map) != .unresolved) {
|
||||
break :blk type_bridge.resolveAstType(rt, &self.module.types, &self.program_index.type_alias_map);
|
||||
if (type_bridge.resolveAstType(rt, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map) != .unresolved) {
|
||||
break :blk type_bridge.resolveAstType(rt, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10551,7 +10569,7 @@ pub const Lowering = struct {
|
||||
return .unresolved;
|
||||
}
|
||||
}
|
||||
return type_bridge.resolveAstType(node, &self.module.types, &self.program_index.type_alias_map);
|
||||
return type_bridge.resolveAstType(node, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map);
|
||||
}
|
||||
|
||||
pub fn resolveTypeArg(self: *Lowering, node: *const Node) TypeId {
|
||||
@@ -10612,7 +10630,7 @@ pub const Lowering = struct {
|
||||
},
|
||||
.type_expr => |te| {
|
||||
if (self.program_index.type_alias_map.get(te.name)) |alias_ty| return alias_ty;
|
||||
return type_bridge.resolveAstType(node, &self.module.types, &self.program_index.type_alias_map);
|
||||
return type_bridge.resolveAstType(node, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map);
|
||||
},
|
||||
.call => |cl| {
|
||||
// `type_of(x)` resolves to `inferExprType(x)` at lower
|
||||
@@ -10637,7 +10655,7 @@ pub const Lowering = struct {
|
||||
.slice_type_expr,
|
||||
.optional_type_expr,
|
||||
.function_type_expr,
|
||||
=> return type_bridge.resolveAstType(node, &self.module.types, &self.program_index.type_alias_map),
|
||||
=> return type_bridge.resolveAstType(node, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map),
|
||||
else => return .unresolved,
|
||||
}
|
||||
}
|
||||
@@ -11764,7 +11782,7 @@ pub const Lowering = struct {
|
||||
// literal (`(s32, s32)`); validate its elements are types and reject
|
||||
// non-type elements loudly (issue 0067).
|
||||
.tuple_literal => return self.resolveTupleLiteralTypeArg(node),
|
||||
else => return type_bridge.resolveAstType(node, &self.module.types, &self.program_index.type_alias_map),
|
||||
else => return type_bridge.resolveAstType(node, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12195,7 +12213,7 @@ pub const Lowering = struct {
|
||||
}
|
||||
return;
|
||||
}
|
||||
_ = type_bridge.resolveAstType(node, &self.module.types, &self.program_index.type_alias_map);
|
||||
_ = type_bridge.resolveAstType(node, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map);
|
||||
}
|
||||
|
||||
fn registerStructDecl(self: *Lowering, sd: *const ast.StructDecl) void {
|
||||
@@ -12341,7 +12359,7 @@ pub const Lowering = struct {
|
||||
if (const_node.data == .const_decl) {
|
||||
const cd = const_node.data.const_decl;
|
||||
const qualified = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ sd.name, cd.name }) catch continue;
|
||||
const ty: ?TypeId = if (cd.type_annotation) |ta| type_bridge.resolveAstType(ta, table, &self.program_index.type_alias_map) else null;
|
||||
const ty: ?TypeId = if (cd.type_annotation) |ta| type_bridge.resolveAstType(ta, table, &self.program_index.type_alias_map, &self.program_index.module_const_map) else null;
|
||||
self.struct_const_map.put(qualified, .{ .value = cd.value, .ty = ty }) catch {};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,7 +296,7 @@ pub const ProtocolResolver = struct {
|
||||
if (p.data == .type_expr and std.mem.eql(u8, p.data.type_expr.name, "Self")) {
|
||||
break :blk void_ptr_ty;
|
||||
}
|
||||
break :blk type_bridge.resolveAstType(p, table, &self.l.program_index.type_alias_map);
|
||||
break :blk type_bridge.resolveAstType(p, table, &self.l.program_index.type_alias_map, &self.l.program_index.module_const_map);
|
||||
};
|
||||
ptypes.append(self.l.alloc, pty) catch unreachable;
|
||||
}
|
||||
@@ -306,7 +306,7 @@ pub const ProtocolResolver = struct {
|
||||
ret_is_self = true;
|
||||
break :blk void_ptr_ty;
|
||||
}
|
||||
break :blk type_bridge.resolveAstType(rt, table, &self.l.program_index.type_alias_map);
|
||||
break :blk type_bridge.resolveAstType(rt, table, &self.l.program_index.type_alias_map, &self.l.program_index.module_const_map);
|
||||
} else .void;
|
||||
method_infos.append(self.l.alloc, .{
|
||||
.name = method.name,
|
||||
@@ -393,7 +393,7 @@ pub const ProtocolResolver = struct {
|
||||
// Resolve the protocol's type-arg list to concrete TypeIds.
|
||||
var arg_tys = std.ArrayList(TypeId).empty;
|
||||
for (ib.protocol_type_args) |arg_node| {
|
||||
const t = type_bridge.resolveAstType(arg_node, table, &self.l.program_index.type_alias_map);
|
||||
const t = type_bridge.resolveAstType(arg_node, table, &self.l.program_index.type_alias_map, &self.l.program_index.module_const_map);
|
||||
arg_tys.append(self.l.alloc, t) catch return;
|
||||
}
|
||||
|
||||
@@ -401,9 +401,9 @@ pub const ProtocolResolver = struct {
|
||||
// parameterised impls (back-compat `target_type` string is kept for
|
||||
// simple cases but the canonical form is the TypeExpr).
|
||||
const src_ty: TypeId = if (ib.target_type_expr) |te|
|
||||
type_bridge.resolveAstType(te, table, &self.l.program_index.type_alias_map)
|
||||
type_bridge.resolveAstType(te, table, &self.l.program_index.type_alias_map, &self.l.program_index.module_const_map)
|
||||
else if (ib.target_type.len > 0)
|
||||
type_bridge.resolveAstType(&.{ .span = decl.span, .data = .{ .type_expr = .{ .name = ib.target_type } } }, table, &self.l.program_index.type_alias_map)
|
||||
type_bridge.resolveAstType(&.{ .span = decl.span, .data = .{ .type_expr = .{ .name = ib.target_type } } }, table, &self.l.program_index.type_alias_map, &self.l.program_index.module_const_map)
|
||||
else
|
||||
return;
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ const std = @import("std");
|
||||
const types = @import("types.zig");
|
||||
const type_bridge = @import("type_bridge.zig");
|
||||
const ast = @import("../ast.zig");
|
||||
const program_index_mod = @import("program_index.zig");
|
||||
const ModuleConstInfo = program_index_mod.ModuleConstInfo;
|
||||
const Node = ast.Node;
|
||||
|
||||
const TypeId = types.TypeId;
|
||||
@@ -18,7 +20,7 @@ test "resolveAstType: primitive type_expr" {
|
||||
defer alloc.destroy(node);
|
||||
node.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .type_expr = .{ .name = "f64" } } };
|
||||
|
||||
try std.testing.expectEqual(TypeId.f64, type_bridge.resolveAstType(node, &table, null));
|
||||
try std.testing.expectEqual(TypeId.f64, type_bridge.resolveAstType(node, &table, null, null));
|
||||
}
|
||||
|
||||
test "resolveAstType: pointer type" {
|
||||
@@ -34,7 +36,7 @@ test "resolveAstType: pointer type" {
|
||||
defer alloc.destroy(node);
|
||||
node.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .pointer_type_expr = .{ .pointee_type = inner } } };
|
||||
|
||||
const id = type_bridge.resolveAstType(node, &table, null);
|
||||
const id = type_bridge.resolveAstType(node, &table, null, null);
|
||||
try std.testing.expectEqual(TypeInfo{ .pointer = .{ .pointee = .s32 } }, table.get(id));
|
||||
}
|
||||
|
||||
@@ -55,7 +57,7 @@ test "resolveAstType: optional slice" {
|
||||
defer alloc.destroy(opt);
|
||||
opt.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .optional_type_expr = .{ .inner_type = slice } } };
|
||||
|
||||
const id = type_bridge.resolveAstType(opt, &table, null);
|
||||
const id = type_bridge.resolveAstType(opt, &table, null, null);
|
||||
const info = table.get(id);
|
||||
switch (info) {
|
||||
.optional => |o| {
|
||||
@@ -71,7 +73,7 @@ test "resolveAstType: null surfaces as .unresolved (no silent s64 default)" {
|
||||
var table = TypeTable.init(alloc);
|
||||
defer table.deinit();
|
||||
|
||||
try std.testing.expectEqual(TypeId.unresolved, type_bridge.resolveAstType(null, &table, null));
|
||||
try std.testing.expectEqual(TypeId.unresolved, type_bridge.resolveAstType(null, &table, null, null));
|
||||
}
|
||||
|
||||
test "resolveAstType: threaded alias_map resolves named alias" {
|
||||
@@ -85,7 +87,7 @@ test "resolveAstType: threaded alias_map resolves named alias" {
|
||||
defer alloc.destroy(sh_node);
|
||||
sh_node.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .type_expr = .{ .name = "ShaderHandle" } } };
|
||||
|
||||
const empty_stub = type_bridge.resolveAstType(sh_node, &table, null);
|
||||
const empty_stub = type_bridge.resolveAstType(sh_node, &table, null, null);
|
||||
const empty_info = table.get(empty_stub);
|
||||
try std.testing.expectEqual(@as(std.meta.Tag(TypeInfo), .@"struct"), std.meta.activeTag(empty_info));
|
||||
try std.testing.expectEqual(@as(usize, 0), empty_info.@"struct".fields.len);
|
||||
@@ -102,7 +104,7 @@ test "resolveAstType: threaded alias_map resolves named alias" {
|
||||
defer alloc.destroy(opaque_node);
|
||||
opaque_node.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .type_expr = .{ .name = "Opaque" } } };
|
||||
try aliases.put("Opaque", .u64);
|
||||
try std.testing.expectEqual(TypeId.u64, type_bridge.resolveAstType(opaque_node, &table, &aliases));
|
||||
try std.testing.expectEqual(TypeId.u64, type_bridge.resolveAstType(opaque_node, &table, &aliases, null));
|
||||
|
||||
// Compound forms (`*Opaque`, `[]Opaque`, `?Opaque`) route through recursive
|
||||
// helpers that thread the same alias_map at every step.
|
||||
@@ -112,10 +114,42 @@ test "resolveAstType: threaded alias_map resolves named alias" {
|
||||
const ptr_node = try alloc.create(Node);
|
||||
defer alloc.destroy(ptr_node);
|
||||
ptr_node.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .pointer_type_expr = .{ .pointee_type = opaque_inner } } };
|
||||
const ptr_id = type_bridge.resolveAstType(ptr_node, &table, &aliases);
|
||||
const ptr_id = type_bridge.resolveAstType(ptr_node, &table, &aliases, null);
|
||||
try std.testing.expectEqual(TypeInfo{ .pointer = .{ .pointee = .u64 } }, table.get(ptr_id));
|
||||
}
|
||||
|
||||
test "resolveAstType: named-const array dimension resolves to the same length as a literal (issue 0083)" {
|
||||
const alloc = std.testing.allocator;
|
||||
var table = TypeTable.init(alloc);
|
||||
defer table.deinit();
|
||||
|
||||
// `N :: 4` in the module-const table, value backed by an int-literal node.
|
||||
const n_val = try alloc.create(Node);
|
||||
defer alloc.destroy(n_val);
|
||||
n_val.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .int_literal = .{ .value = 4 } } };
|
||||
var consts = std.StringHashMap(ModuleConstInfo).init(alloc);
|
||||
defer consts.deinit();
|
||||
try consts.put("N", .{ .value = n_val, .ty = .s64 });
|
||||
|
||||
// `[N]s64` — dimension is the named const `N`, not a literal.
|
||||
const elem = try alloc.create(Node);
|
||||
defer alloc.destroy(elem);
|
||||
elem.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .type_expr = .{ .name = "s64" } } };
|
||||
const len_node = try alloc.create(Node);
|
||||
defer alloc.destroy(len_node);
|
||||
len_node.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .identifier = .{ .name = "N" } } };
|
||||
const arr = try alloc.create(Node);
|
||||
defer alloc.destroy(arr);
|
||||
arr.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .array_type_expr = .{ .length = len_node, .element_type = elem } } };
|
||||
|
||||
// With the const table threaded, `[N]s64` lays out identically to `[4]s64`.
|
||||
const id = type_bridge.resolveAstType(arr, &table, null, &consts);
|
||||
const info = table.get(id);
|
||||
try std.testing.expect(info == .array);
|
||||
try std.testing.expectEqual(TypeId.s64, info.array.element);
|
||||
try std.testing.expectEqual(@as(u32, 4), info.array.length);
|
||||
}
|
||||
|
||||
test "resolveAstType: error_set_decl registers an error-set type + interns tags" {
|
||||
const alloc = std.testing.allocator;
|
||||
var table = TypeTable.init(alloc);
|
||||
@@ -129,7 +163,7 @@ test "resolveAstType: error_set_decl registers an error-set type + interns tags"
|
||||
.tag_names = &tag_names,
|
||||
} } };
|
||||
|
||||
const id = type_bridge.resolveAstType(node, &table, null);
|
||||
const id = type_bridge.resolveAstType(node, &table, null, null);
|
||||
const info = table.get(id);
|
||||
try std.testing.expect(info == .error_set);
|
||||
try std.testing.expectEqualStrings("ParseErr", table.getString(info.error_set.name));
|
||||
@@ -137,7 +171,7 @@ test "resolveAstType: error_set_decl registers an error-set type + interns tags"
|
||||
// Tags were interned into the global pool (round-trip a name through it).
|
||||
try std.testing.expectEqualStrings("BadDigit", table.getTagName(table.internTag("BadDigit")));
|
||||
// Re-resolving the same decl dedups to the same TypeId.
|
||||
try std.testing.expectEqual(id, type_bridge.resolveAstType(node, &table, null));
|
||||
try std.testing.expectEqual(id, type_bridge.resolveAstType(node, &table, null, null));
|
||||
}
|
||||
|
||||
// ── ERR E1.2 — failable-signature error channel resolution ──
|
||||
@@ -154,7 +188,7 @@ test "resolveAstType: `!Named` resolves to the declared error set" {
|
||||
const node = try alloc.create(Node);
|
||||
defer alloc.destroy(node);
|
||||
node.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .error_type_expr = .{ .name = "ParseErr" } } };
|
||||
try std.testing.expectEqual(set, type_bridge.resolveAstType(node, &table, null));
|
||||
try std.testing.expectEqual(set, type_bridge.resolveAstType(node, &table, null, null));
|
||||
}
|
||||
|
||||
test "resolveAstType: bare `!` resolves to a shared inferred placeholder set" {
|
||||
@@ -169,8 +203,8 @@ test "resolveAstType: bare `!` resolves to a shared inferred placeholder set" {
|
||||
defer alloc.destroy(b);
|
||||
b.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .error_type_expr = .{ .name = null } } };
|
||||
|
||||
const ia = type_bridge.resolveAstType(a, &table, null);
|
||||
const ib = type_bridge.resolveAstType(b, &table, null);
|
||||
const ia = type_bridge.resolveAstType(a, &table, null, null);
|
||||
const ib = type_bridge.resolveAstType(b, &table, null, null);
|
||||
try std.testing.expect(table.get(ia) == .error_set);
|
||||
try std.testing.expectEqualStrings("!", table.getString(table.get(ia).error_set.name));
|
||||
try std.testing.expectEqual(@as(usize, 0), table.get(ia).error_set.tags.len); // empty until E1.4 SCC
|
||||
@@ -198,7 +232,7 @@ test "resolveAstType: `(s32, !Named)` result list is a tuple ending in the error
|
||||
defer alloc.destroy(tuple);
|
||||
tuple.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .tuple_type_expr = .{ .field_types = &fields, .field_names = null } } };
|
||||
|
||||
const id = type_bridge.resolveAstType(tuple, &table, null);
|
||||
const id = type_bridge.resolveAstType(tuple, &table, null, null);
|
||||
const info = table.get(id);
|
||||
try std.testing.expect(info == .tuple);
|
||||
try std.testing.expectEqual(@as(usize, 2), info.tuple.fields.len);
|
||||
|
||||
@@ -8,6 +8,8 @@ const TypeInfo = ir_types.TypeInfo;
|
||||
const TypeTable = ir_types.TypeTable;
|
||||
const StringId = ir_types.StringId;
|
||||
const type_resolver = @import("type_resolver.zig");
|
||||
const program_index_mod = @import("program_index.zig");
|
||||
const ModuleConstInfo = program_index_mod.ModuleConstInfo;
|
||||
|
||||
/// The single-source type-alias table (`ProgramIndex.type_alias_map`), threaded
|
||||
/// explicitly through every name-resolving entry point so a bare name like
|
||||
@@ -17,6 +19,15 @@ const type_resolver = @import("type_resolver.zig");
|
||||
/// `null` for contexts that never see aliases, e.g. unit tests).
|
||||
pub const AliasMap = ?*const std.StringHashMap(TypeId);
|
||||
|
||||
/// The module-global constant table (`ProgramIndex.module_const_map`), threaded
|
||||
/// alongside the alias map so a named-const array dimension (`N :: 16; [N]T`)
|
||||
/// resolves to the same length as a literal dimension on EVERY registration-time
|
||||
/// path — type aliases (`Arr :: [N]T`), inline union/enum field types — not just
|
||||
/// the stateful body-lowering path. Without it the stateless dim resolver had no
|
||||
/// way to evaluate a named const and silently fabricated a 0 length (issue 0083).
|
||||
/// `null` for contexts with no const table (e.g. unit tests).
|
||||
pub const ConstMap = ?*const std.StringHashMap(ModuleConstInfo);
|
||||
|
||||
/// Binding-free element-recursion adapter for `TypeResolver.resolveCompound`:
|
||||
/// nested element types resolve through `type_bridge.resolveAstType` (the
|
||||
/// registration-time path — no generic/pack bindings). Lets type_bridge reuse
|
||||
@@ -25,20 +36,35 @@ pub const AliasMap = ?*const std.StringHashMap(TypeId);
|
||||
const StatelessInner = struct {
|
||||
table: *TypeTable,
|
||||
alias_map: AliasMap,
|
||||
consts: ConstMap,
|
||||
pub fn resolveInner(self: StatelessInner, node: *const Node) TypeId {
|
||||
return resolveAstType(node, self.table, self.alias_map);
|
||||
return resolveAstType(node, self.table, self.alias_map, self.consts);
|
||||
}
|
||||
/// Fixed-array dimension at registration time (no bindings / const tables).
|
||||
/// Only a literal dimension is knowable here; a named-const dimension
|
||||
/// (`N :: 16; [N]T`) is resolved by the stateful caller
|
||||
/// (`Lowering.resolveArrayLen`) before it ever reaches this binding-free
|
||||
/// path — mirroring how `pack_index_type_expr` is handled stateful-first.
|
||||
/// Fixed-array dimension at registration time: a literal `[16]T`, or a
|
||||
/// named module-global const `N :: 16; [N]T` looked up in the const table.
|
||||
/// Both yield the SAME length — registration-time paths (aliases, inline
|
||||
/// union/enum fields) must lay out a named-const dim identically to a literal
|
||||
/// (issue 0083). A dimension that is neither is not resolvable on this
|
||||
/// binding-free path (it would be a computed/comptime expression, which the
|
||||
/// stateful body-lowering path diagnoses as a hard error at the storage
|
||||
/// site); bail LOUDLY rather than fabricating a 0 length that silently gives a
|
||||
/// 0-byte array and out-of-bounds element access.
|
||||
pub fn resolveArrayLen(self: StatelessInner, len_node: *const Node) u32 {
|
||||
_ = self;
|
||||
return switch (len_node.data) {
|
||||
.int_literal => |lit| @intCast(lit.value),
|
||||
else => 0,
|
||||
};
|
||||
switch (len_node.data) {
|
||||
.int_literal => |lit| return @intCast(lit.value),
|
||||
.identifier => |id| if (self.namedConstLen(id.name)) |n| return n,
|
||||
.type_expr => |te| if (self.namedConstLen(te.name)) |n| return n,
|
||||
else => {},
|
||||
}
|
||||
std.debug.print("type_bridge: array dimension is not a literal or named integer constant — cannot resolve length at registration time (computed/comptime dimensions are unsupported here)\n", .{});
|
||||
return 0;
|
||||
}
|
||||
/// A name that resolves to a module-global integer constant → its value.
|
||||
fn namedConstLen(self: StatelessInner, name: []const u8) ?u32 {
|
||||
const consts = self.consts orelse return null;
|
||||
const ci = consts.get(name) orelse return null;
|
||||
if (ci.value.data == .int_literal) return @intCast(ci.value.data.int_literal.value);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -46,14 +72,14 @@ const StatelessInner = struct {
|
||||
// Resolve an AST type node into an IR TypeId. Used during lowering when
|
||||
// we only have the parsed AST (no codegen type registry).
|
||||
|
||||
pub fn resolveAstType(node: ?*const Node, table: *TypeTable, alias_map: AliasMap) TypeId {
|
||||
pub fn resolveAstType(node: ?*const Node, table: *TypeTable, alias_map: AliasMap, consts: ConstMap) TypeId {
|
||||
// A null node means a caller reached type resolution without a type node.
|
||||
// Every current caller either passes a non-optional node or handles the
|
||||
// "no type" case itself (returning `.void`), so this is a caller bug — and
|
||||
// `.s64` here would silently fabricate an 8-byte int. Surface it via the
|
||||
// `.unresolved` sentinel (trips the sizeOf/toLLVMType panic at codegen).
|
||||
const n = node orelse return .unresolved;
|
||||
const si = StatelessInner{ .table = table, .alias_map = alias_map };
|
||||
const si = StatelessInner{ .table = table, .alias_map = alias_map, .consts = consts };
|
||||
return switch (n.data) {
|
||||
.type_expr => |te| resolveTypeName(te.name, table, alias_map),
|
||||
.identifier => |id| resolveTypeName(id.name, table, alias_map),
|
||||
@@ -76,8 +102,8 @@ pub fn resolveAstType(node: ?*const Node, table: *TypeTable, alias_map: AliasMap
|
||||
// `Closure(..p)` field type at registration time). These tiny fallbacks
|
||||
// are the only stateless-specific shape code left; the stateful expand
|
||||
// lives in PackResolver.
|
||||
.closure_type_expr => |ct| type_resolver.TypeResolver.resolveCompound(table, n, si) orelse resolveClosurePackShape(&ct, table, alias_map),
|
||||
.tuple_type_expr => |tt| type_resolver.TypeResolver.resolveCompound(table, n, si) orelse resolveTupleSpreadShape(&tt, table, alias_map),
|
||||
.closure_type_expr => |ct| type_resolver.TypeResolver.resolveCompound(table, n, si) orelse resolveClosurePackShape(&ct, table, alias_map, consts),
|
||||
.tuple_type_expr => |tt| type_resolver.TypeResolver.resolveCompound(table, n, si) orelse resolveTupleSpreadShape(&tt, table, alias_map, consts),
|
||||
.pack_index_type_expr => {
|
||||
// Pack-index `$args[N]` in a type position must be resolved
|
||||
// against an active pack binding — `type_bridge` has no access
|
||||
@@ -90,8 +116,8 @@ pub fn resolveAstType(node: ?*const Node, table: *TypeTable, alias_map: AliasMap
|
||||
std.debug.print("type_bridge: pack-index type expression encountered outside a pack-aware context — returning .unresolved\n", .{});
|
||||
return .unresolved;
|
||||
},
|
||||
.tuple_literal => |tl| resolveTupleLiteralAsType(&tl, table, alias_map),
|
||||
.parameterized_type_expr => |pt| resolveParameterizedType(&pt, table, alias_map),
|
||||
.tuple_literal => |tl| resolveTupleLiteralAsType(&tl, table, alias_map, consts),
|
||||
.parameterized_type_expr => |pt| resolveParameterizedType(&pt, table, alias_map, consts),
|
||||
// An unannotated param. Its type must be resolved from context
|
||||
// (contextual closure typing, generic binding, or pack substitution)
|
||||
// *before* reaching here; if it doesn't, returning a plausible `.s64`
|
||||
@@ -101,9 +127,9 @@ pub fn resolveAstType(node: ?*const Node, table: *TypeTable, alias_map: AliasMap
|
||||
// turns it into a real diagnostic.
|
||||
.inferred_type => .unresolved,
|
||||
// Inline type declarations (used as field types)
|
||||
.enum_decl => |ed| resolveInlineEnum(&ed, table, alias_map),
|
||||
.struct_decl => |sd| resolveInlineStruct(&sd, table, alias_map),
|
||||
.union_decl => |ud| resolveInlineUnion(&ud, table, alias_map),
|
||||
.enum_decl => |ed| resolveInlineEnum(&ed, table, alias_map, consts),
|
||||
.struct_decl => |sd| resolveInlineStruct(&sd, table, alias_map, consts),
|
||||
.union_decl => |ud| resolveInlineUnion(&ud, table, alias_map, consts),
|
||||
.error_set_decl => |esd| resolveInlineErrorSet(&esd, table),
|
||||
.error_type_expr => |ete| resolveErrorType(&ete, table, alias_map),
|
||||
else => {
|
||||
@@ -137,13 +163,13 @@ pub const resolveTypePrimitive = type_resolver.TypeResolver.resolvePrimitive;
|
||||
/// null). type_bridge can't expand the pack (no state), so it preserves the
|
||||
/// pack SHAPE — a `closureTypePack` whose prefix is the fixed params. The
|
||||
/// stateful expand lives in `PackResolver.resolveClosureTypeWithBindings`.
|
||||
fn resolveClosurePackShape(ct: *const ast.ClosureTypeExpr, table: *TypeTable, alias_map: AliasMap) TypeId {
|
||||
fn resolveClosurePackShape(ct: *const ast.ClosureTypeExpr, table: *TypeTable, alias_map: AliasMap, consts: ConstMap) TypeId {
|
||||
const alloc = table.alloc;
|
||||
var param_ids = std.ArrayList(TypeId).empty;
|
||||
for (ct.param_types) |pt| {
|
||||
param_ids.append(alloc, resolveAstType(pt, table, alias_map)) catch unreachable;
|
||||
param_ids.append(alloc, resolveAstType(pt, table, alias_map, consts)) catch unreachable;
|
||||
}
|
||||
const ret_id = if (ct.return_type) |rt| resolveAstType(rt, table, alias_map) else TypeId.void;
|
||||
const ret_id = if (ct.return_type) |rt| resolveAstType(rt, table, alias_map, consts) else TypeId.void;
|
||||
return table.closureTypePack(param_ids.items, ret_id, @intCast(param_ids.items.len));
|
||||
}
|
||||
|
||||
@@ -152,11 +178,11 @@ fn resolveClosurePackShape(ct: *const ast.ClosureTypeExpr, table: *TypeTable, al
|
||||
/// each field resolves individually (a spread field is not a type → resolves to
|
||||
/// `.unresolved`). The stateful expand lives in
|
||||
/// `PackResolver.resolveTupleTypeWithBindings`.
|
||||
fn resolveTupleSpreadShape(tt: *const ast.TupleTypeExpr, table: *TypeTable, alias_map: AliasMap) TypeId {
|
||||
fn resolveTupleSpreadShape(tt: *const ast.TupleTypeExpr, table: *TypeTable, alias_map: AliasMap, consts: ConstMap) TypeId {
|
||||
const alloc = table.alloc;
|
||||
var field_ids = std.ArrayList(TypeId).empty;
|
||||
for (tt.field_types) |ft| {
|
||||
field_ids.append(alloc, resolveAstType(ft, table, alias_map)) catch unreachable;
|
||||
field_ids.append(alloc, resolveAstType(ft, table, alias_map, consts)) catch unreachable;
|
||||
}
|
||||
var name_ids: ?[]const StringId = null;
|
||||
if (tt.field_names) |names| {
|
||||
@@ -182,14 +208,14 @@ fn resolveTupleSpreadShape(tt: *const ast.TupleTypeExpr, table: *TypeTable, alia
|
||||
// here, so the valid path below builds the tuple and the invalid path never
|
||||
// reaches it from lowering. The sentinel is the backstop for any other
|
||||
// (binding-free) caller.
|
||||
fn resolveTupleLiteralAsType(tl: *const ast.TupleLiteral, table: *TypeTable, alias_map: AliasMap) TypeId {
|
||||
fn resolveTupleLiteralAsType(tl: *const ast.TupleLiteral, table: *TypeTable, alias_map: AliasMap, consts: ConstMap) TypeId {
|
||||
const alloc = table.alloc;
|
||||
var field_ids = std.ArrayList(TypeId).empty;
|
||||
var name_ids_list = std.ArrayList(StringId).empty;
|
||||
var any_named = false;
|
||||
for (tl.elements) |el| {
|
||||
if (!isTypeShapedAstNode(el.value, table)) return .unresolved;
|
||||
field_ids.append(alloc, resolveAstType(el.value, table, alias_map)) catch unreachable;
|
||||
field_ids.append(alloc, resolveAstType(el.value, table, alias_map, consts)) catch unreachable;
|
||||
if (el.name) |n| {
|
||||
any_named = true;
|
||||
name_ids_list.append(alloc, table.internString(n)) catch unreachable;
|
||||
@@ -233,7 +259,7 @@ pub fn isTypeShapedAstNode(node: *const Node, table: *TypeTable) bool {
|
||||
};
|
||||
}
|
||||
|
||||
fn resolveParameterizedType(pt: *const ast.ParameterizedTypeExpr, table: *TypeTable, alias_map: AliasMap) TypeId {
|
||||
fn resolveParameterizedType(pt: *const ast.ParameterizedTypeExpr, table: *TypeTable, alias_map: AliasMap, consts: ConstMap) TypeId {
|
||||
// Strip module prefix (e.g. "std.Vector" → "Vector")
|
||||
const base_name = if (std.mem.lastIndexOfScalar(u8, pt.name, '.')) |dot| pt.name[dot + 1 ..] else pt.name;
|
||||
// Vector(N, T) is a built-in parameterized type
|
||||
@@ -243,7 +269,7 @@ fn resolveParameterizedType(pt: *const ast.ParameterizedTypeExpr, table: *TypeTa
|
||||
.int_literal => |lit| @intCast(@as(u64, @bitCast(lit.value))),
|
||||
else => 0,
|
||||
};
|
||||
const elem = resolveAstType(pt.args[1], table, alias_map);
|
||||
const elem = resolveAstType(pt.args[1], table, alias_map, consts);
|
||||
return table.vectorOf(elem, length);
|
||||
}
|
||||
}
|
||||
@@ -254,7 +280,7 @@ fn resolveParameterizedType(pt: *const ast.ParameterizedTypeExpr, table: *TypeTa
|
||||
|
||||
// ── Inline type declarations ─────────────────────────────────────────
|
||||
|
||||
fn resolveInlineEnum(ed: *const ast.EnumDecl, table: *TypeTable, alias_map: AliasMap) TypeId {
|
||||
fn resolveInlineEnum(ed: *const ast.EnumDecl, table: *TypeTable, alias_map: AliasMap, consts: ConstMap) TypeId {
|
||||
const alloc = table.alloc;
|
||||
const name_id = table.internString(ed.name);
|
||||
|
||||
@@ -280,7 +306,7 @@ fn resolveInlineEnum(ed: *const ast.EnumDecl, table: *TypeTable, alias_map: Alia
|
||||
} else {
|
||||
var sfields = std.ArrayList(TypeInfo.StructInfo.Field).empty;
|
||||
for (sd.field_names, sd.field_types) |fname, ftype_node| {
|
||||
const fty = resolveAstType(ftype_node, table, alias_map);
|
||||
const fty = resolveAstType(ftype_node, table, alias_map, consts);
|
||||
sfields.append(alloc, .{
|
||||
.name = table.internString(fname),
|
||||
.ty = fty,
|
||||
@@ -294,10 +320,10 @@ fn resolveInlineEnum(ed: *const ast.EnumDecl, table: *TypeTable, alias_map: Alia
|
||||
table.update(field_ty, sinfo);
|
||||
}
|
||||
} else {
|
||||
field_ty = resolveAstType(vt, table, alias_map);
|
||||
field_ty = resolveAstType(vt, table, alias_map, consts);
|
||||
}
|
||||
} else {
|
||||
field_ty = resolveAstType(vt, table, alias_map);
|
||||
field_ty = resolveAstType(vt, table, alias_map, consts);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -311,7 +337,7 @@ fn resolveInlineEnum(ed: *const ast.EnumDecl, table: *TypeTable, alias_map: Alia
|
||||
var backing_type: ?TypeId = null;
|
||||
var tag_type: ?TypeId = null;
|
||||
if (ed.backing_type) |bt| {
|
||||
const backing_ty = resolveAstType(bt, table, alias_map);
|
||||
const backing_ty = resolveAstType(bt, table, alias_map, consts);
|
||||
backing_type = backing_ty;
|
||||
// Extract tag type from first field of backing struct
|
||||
const backing_info = table.get(backing_ty);
|
||||
@@ -394,7 +420,7 @@ fn resolveInlineEnum(ed: *const ast.EnumDecl, table: *TypeTable, alias_map: Alia
|
||||
if (ed.backing_type) |bt| {
|
||||
// Only use simple backing types (u8, u16, u32, etc.), not struct backing (enum struct)
|
||||
if (bt.data != .struct_decl) {
|
||||
enum_backing = resolveAstType(bt, table, alias_map);
|
||||
enum_backing = resolveAstType(bt, table, alias_map, consts);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -410,7 +436,7 @@ fn resolveInlineEnum(ed: *const ast.EnumDecl, table: *TypeTable, alias_map: Alia
|
||||
return id;
|
||||
}
|
||||
|
||||
fn resolveInlineStruct(sd: *const ast.StructDecl, table: *TypeTable, alias_map: AliasMap) TypeId {
|
||||
fn resolveInlineStruct(sd: *const ast.StructDecl, table: *TypeTable, alias_map: AliasMap, consts: ConstMap) TypeId {
|
||||
const alloc = table.alloc;
|
||||
const name_id = table.internString(sd.name);
|
||||
|
||||
@@ -418,7 +444,7 @@ fn resolveInlineStruct(sd: *const ast.StructDecl, table: *TypeTable, alias_map:
|
||||
|
||||
var fields = std.ArrayList(TypeInfo.StructInfo.Field).empty;
|
||||
for (sd.field_names, sd.field_types) |fname, ftype_node| {
|
||||
const field_ty = resolveAstType(ftype_node, table, alias_map);
|
||||
const field_ty = resolveAstType(ftype_node, table, alias_map, consts);
|
||||
fields.append(alloc, .{
|
||||
.name = table.internString(fname),
|
||||
.ty = field_ty,
|
||||
@@ -433,7 +459,7 @@ fn resolveInlineStruct(sd: *const ast.StructDecl, table: *TypeTable, alias_map:
|
||||
return id;
|
||||
}
|
||||
|
||||
fn resolveInlineUnion(ud: *const ast.UnionDecl, table: *TypeTable, alias_map: AliasMap) TypeId {
|
||||
fn resolveInlineUnion(ud: *const ast.UnionDecl, table: *TypeTable, alias_map: AliasMap, consts: ConstMap) TypeId {
|
||||
const alloc = table.alloc;
|
||||
const name_id = table.internString(ud.name);
|
||||
|
||||
@@ -441,7 +467,7 @@ fn resolveInlineUnion(ud: *const ast.UnionDecl, table: *TypeTable, alias_map: Al
|
||||
|
||||
var fields = std.ArrayList(TypeInfo.StructInfo.Field).empty;
|
||||
for (ud.field_names, ud.field_types) |fname, ftype_node| {
|
||||
const field_ty = resolveAstType(ftype_node, table, alias_map);
|
||||
const field_ty = resolveAstType(ftype_node, table, alias_map, consts);
|
||||
fields.append(alloc, .{
|
||||
.name = table.internString(fname),
|
||||
.ty = field_ty,
|
||||
|
||||
Reference in New Issue
Block a user