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:
agra
2026-06-04 09:06:08 +03:00
parent 12552e125d
commit 1f9f944ca1
13 changed files with 329 additions and 92 deletions

View File

@@ -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]);
}

View 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"]]));
}

View File

@@ -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

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1,4 @@
num local=10
num direct=10
str local=4
str direct=4

View File

@@ -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;

View 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`.

View File

@@ -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),

View File

@@ -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 {};
}
}

View File

@@ -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;

View File

@@ -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);

View File

@@ -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,