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
|
// A fixed array whose dimension is a module-global named constant
|
||||||
// (`N :: 16; [N]T`) has the same layout as a literal-dimension array
|
// (`N :: 16; [N]T`) has the same layout as a literal-dimension array
|
||||||
// (`[16]T`): correct length and element stride for scalar, slice/pointer
|
// (`[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
|
// Regression (issue 0083): a named-const dim resolved to length 0, giving a
|
||||||
// 0-byte alloca — scalar reads returned garbage and string/struct elements
|
// 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";
|
#import "modules/std.sx";
|
||||||
|
|
||||||
N :: 4;
|
N :: 4;
|
||||||
|
M :: 3;
|
||||||
|
|
||||||
P :: struct { x: s64; y: s64; }
|
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 :: () {
|
main :: () {
|
||||||
// Scalar elements: store then read back.
|
// Scalar elements (direct local): store then read back.
|
||||||
a : [N]s64 = ---;
|
a : [N]s64 = ---;
|
||||||
a[0] = 7;
|
a[0] = 7;
|
||||||
a[3] = 42;
|
a[3] = 42;
|
||||||
print("scalar a0={} a3={}\n", a[0], a[3]);
|
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 : [N]string = ---;
|
||||||
s[0] = "hi";
|
s[0] = "hi";
|
||||||
s[1] = "yo";
|
s[1] = "yo";
|
||||||
print("string s0={} s1={}\n", s[0], s[1]);
|
print("string s0={} s1={}\n", s[0], s[1]);
|
||||||
|
|
||||||
// Struct elements.
|
// Struct elements (direct local).
|
||||||
ps : [N]P = ---;
|
ps : [N]P = ---;
|
||||||
ps[0] = P.{ x = 1, y = 2 };
|
ps[0] = P.{ x = 1, y = 2 };
|
||||||
ps[2] = P.{ x = 5, y = 6 };
|
ps[2] = P.{ x = 5, y = 6 };
|
||||||
print("struct p0x={} p0y={} p2x={}\n", ps[0].x, ps[0].y, ps[2].x);
|
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
|
scalar a0=7 a3=42
|
||||||
string s0=hi s1=yo
|
string s0=hi s1=yo
|
||||||
struct p0x=1 p0y=2 p2x=5
|
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
|
> type). The stateful `Lowering.resolveArrayLen` evaluates the dimension as a
|
||||||
> compile-time integer across the comptime-constant, generic-value, and
|
> compile-time integer across the comptime-constant, generic-value, and
|
||||||
> module-global const tables, and emits a diagnostic (no fabricated length) when
|
> 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`
|
> `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
|
## Symptom
|
||||||
A fixed array whose dimension is a module-global integer constant (`N :: 16;
|
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) {
|
if (method_fd.body.data == .compiler_expr) {
|
||||||
return .{
|
return .{
|
||||||
.kind = .struct_method,
|
.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 },
|
.target = .{ .named = qualified },
|
||||||
.prepends_receiver = true,
|
.prepends_receiver = true,
|
||||||
.expands_defaults = defaultsFor(method_fd, c.args.len + 1),
|
.expands_defaults = defaultsFor(method_fd, c.args.len + 1),
|
||||||
|
|||||||
@@ -560,9 +560,9 @@ pub const Lowering = struct {
|
|||||||
} else if (cd.value.data == .struct_decl) {
|
} else if (cd.value.data == .struct_decl) {
|
||||||
self.registerStructDecl(&cd.value.data.struct_decl);
|
self.registerStructDecl(&cd.value.data.struct_decl);
|
||||||
} else if (cd.value.data == .enum_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) {
|
} 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) {
|
} else if (cd.value.data == .comptime_expr) {
|
||||||
self.lowerComptimeGlobal(cd.name, cd.value.data.comptime_expr.expr, cd.type_annotation);
|
self.lowerComptimeGlobal(cd.name, cd.value.data.comptime_expr.expr, cd.type_annotation);
|
||||||
}
|
}
|
||||||
@@ -574,10 +574,10 @@ pub const Lowering = struct {
|
|||||||
self.registerStructDecl(&sd);
|
self.registerStructDecl(&sd);
|
||||||
},
|
},
|
||||||
.enum_decl => {
|
.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 => {
|
.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 => {
|
.error_set_decl => {
|
||||||
self.registerErrorSetDecl(decl);
|
self.registerErrorSetDecl(decl);
|
||||||
@@ -675,10 +675,10 @@ pub const Lowering = struct {
|
|||||||
self.registerStructDecl(&cd.value.data.struct_decl);
|
self.registerStructDecl(&cd.value.data.struct_decl);
|
||||||
} else if (cd.value.data == .enum_decl) {
|
} else if (cd.value.data == .enum_decl) {
|
||||||
// Register enum/tagged-union types in the type table
|
// 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) {
|
} else if (cd.value.data == .union_decl) {
|
||||||
// Register plain union types in the type table
|
// 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
|
} else if (cd.value.data == .type_expr or
|
||||||
cd.value.data == .pointer_type_expr or
|
cd.value.data == .pointer_type_expr or
|
||||||
cd.value.data == .many_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)
|
cd.value.data == .function_type_expr)
|
||||||
{
|
{
|
||||||
// Type alias: MyFloat :: f64; Ptr :: *u8; Cb :: (s32) -> s32;
|
// 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 {};
|
self.program_index.type_alias_map.put(cd.name, target_ty) catch {};
|
||||||
} else if (cd.value.data == .identifier) {
|
} else if (cd.value.data == .identifier) {
|
||||||
// Identifier-RHS alias: MyAlias :: MyInt; WideAlias :: Wide;
|
// Identifier-RHS alias: MyAlias :: MyInt; WideAlias :: Wide;
|
||||||
@@ -771,7 +771,7 @@ pub const Lowering = struct {
|
|||||||
// resolve via type_bridge and register the result
|
// resolve via type_bridge and register the result
|
||||||
// under the alias name so `Vec4` in expression
|
// under the alias name so `Vec4` in expression
|
||||||
// position can `const_type(<vector tid>)`.
|
// 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) {
|
if (result_ty != .void and result_ty != .unresolved) {
|
||||||
self.program_index.type_alias_map.put(cd.name, result_ty) catch {};
|
self.program_index.type_alias_map.put(cd.name, result_ty) catch {};
|
||||||
}
|
}
|
||||||
@@ -806,11 +806,11 @@ pub const Lowering = struct {
|
|||||||
},
|
},
|
||||||
.enum_decl => {
|
.enum_decl => {
|
||||||
// Register enum/tagged-union types in the type table
|
// 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 => {
|
.union_decl => {
|
||||||
// Register plain union types in the type table
|
// 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 => {
|
.error_set_decl => {
|
||||||
self.registerErrorSetDecl(decl);
|
self.registerErrorSetDecl(decl);
|
||||||
@@ -1813,7 +1813,7 @@ pub const Lowering = struct {
|
|||||||
// Block-local type declarations
|
// Block-local type declarations
|
||||||
.struct_decl => |sd| self.registerStructDecl(&sd),
|
.struct_decl => |sd| self.registerStructDecl(&sd),
|
||||||
.enum_decl, .union_decl => {
|
.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),
|
.error_set_decl => self.registerErrorSetDecl(node),
|
||||||
.ufcs_alias => |ua| {
|
.ufcs_alias => |ua| {
|
||||||
@@ -1972,7 +1972,7 @@ pub const Lowering = struct {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (cd.value.data == .enum_decl or cd.value.data == .union_decl) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2912,7 +2912,7 @@ pub const Lowering = struct {
|
|||||||
// `t : Type = f64;` store a real TypeId; lets
|
// `t : Type = f64;` store a real TypeId; lets
|
||||||
// `t == f64` icmp at runtime against the same TypeId.
|
// `t == f64` icmp at runtime against the same TypeId.
|
||||||
if (self.isKnownTypeName(te.name)) {
|
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.builder.constType(ty);
|
||||||
}
|
}
|
||||||
break :blk self.emitError(te.name, node.span);
|
break :blk self.emitError(te.name, node.span);
|
||||||
@@ -5357,8 +5357,26 @@ pub const Lowering = struct {
|
|||||||
for (al.elements) |elem| {
|
for (al.elements) |elem| {
|
||||||
const old_tt = self.target_type;
|
const old_tt = self.target_type;
|
||||||
self.target_type = elem_ty;
|
self.target_type = elem_ty;
|
||||||
const val = self.lowerExpr(elem);
|
var val = self.lowerExpr(elem);
|
||||||
self.target_type = old_tt;
|
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;
|
elems.append(self.alloc, val) catch unreachable;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5401,7 +5419,7 @@ pub const Lowering = struct {
|
|||||||
const name_id = self.module.types.internString(id.name);
|
const name_id = self.module.types.internString(id.name);
|
||||||
return self.module.types.findByName(name_id) orelse .unresolved;
|
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| {
|
.field_access => |fa| {
|
||||||
// Module.Type — try to resolve the field as a type name
|
// Module.Type — try to resolve the field as a type name
|
||||||
const name_id = self.module.types.internString(fa.field);
|
const name_id = self.module.types.internString(fa.field);
|
||||||
@@ -6875,7 +6893,7 @@ pub const Lowering = struct {
|
|||||||
// Check for #compiler free functions
|
// Check for #compiler free functions
|
||||||
if (self.program_index.fn_ast_map.get(func_name)) |fd_check| {
|
if (self.program_index.fn_ast_map.get(func_name)) |fd_check| {
|
||||||
if (fd_check.body.data == .compiler_expr) {
|
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);
|
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 (self.program_index.fn_ast_map.get(qualified)) |method_fd| {
|
||||||
if (method_fd.body.data == .compiler_expr) {
|
if (method_fd.body.data == .compiler_expr) {
|
||||||
const ret_ty = if (method_fd.return_type) |rt|
|
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
|
else
|
||||||
.void;
|
.void;
|
||||||
return self.builder.compilerCall(qualified, method_args.items, ret_ty);
|
return self.builder.compilerCall(qualified, method_args.items, ret_ty);
|
||||||
@@ -7668,7 +7686,7 @@ pub const Lowering = struct {
|
|||||||
|
|
||||||
const ret_ty = blk: {
|
const ret_ty = blk: {
|
||||||
if (lam.return_type) |rt| {
|
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
|
// Use target closure return type if available — but only when it's
|
||||||
// a resolved type. An `.unresolved` ret comes from an unbound
|
// 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 {
|
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;
|
return .void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -9278,8 +9296,8 @@ pub const Lowering = struct {
|
|||||||
const ret_ty: TypeId = blk: {
|
const ret_ty: TypeId = blk: {
|
||||||
if (fd.return_type) |rt| {
|
if (fd.return_type) |rt| {
|
||||||
if (rt.data == .type_expr) {
|
if (rt.data == .type_expr) {
|
||||||
if (type_bridge.resolveAstType(rt, &self.module.types, &self.program_index.type_alias_map) != .unresolved) {
|
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);
|
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 .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 {
|
pub fn resolveTypeArg(self: *Lowering, node: *const Node) TypeId {
|
||||||
@@ -10612,7 +10630,7 @@ pub const Lowering = struct {
|
|||||||
},
|
},
|
||||||
.type_expr => |te| {
|
.type_expr => |te| {
|
||||||
if (self.program_index.type_alias_map.get(te.name)) |alias_ty| return alias_ty;
|
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| {
|
.call => |cl| {
|
||||||
// `type_of(x)` resolves to `inferExprType(x)` at lower
|
// `type_of(x)` resolves to `inferExprType(x)` at lower
|
||||||
@@ -10637,7 +10655,7 @@ pub const Lowering = struct {
|
|||||||
.slice_type_expr,
|
.slice_type_expr,
|
||||||
.optional_type_expr,
|
.optional_type_expr,
|
||||||
.function_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,
|
else => return .unresolved,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -11764,7 +11782,7 @@ pub const Lowering = struct {
|
|||||||
// literal (`(s32, s32)`); validate its elements are types and reject
|
// literal (`(s32, s32)`); validate its elements are types and reject
|
||||||
// non-type elements loudly (issue 0067).
|
// non-type elements loudly (issue 0067).
|
||||||
.tuple_literal => return self.resolveTupleLiteralTypeArg(node),
|
.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;
|
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 {
|
fn registerStructDecl(self: *Lowering, sd: *const ast.StructDecl) void {
|
||||||
@@ -12341,7 +12359,7 @@ pub const Lowering = struct {
|
|||||||
if (const_node.data == .const_decl) {
|
if (const_node.data == .const_decl) {
|
||||||
const cd = 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 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 {};
|
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")) {
|
if (p.data == .type_expr and std.mem.eql(u8, p.data.type_expr.name, "Self")) {
|
||||||
break :blk void_ptr_ty;
|
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;
|
ptypes.append(self.l.alloc, pty) catch unreachable;
|
||||||
}
|
}
|
||||||
@@ -306,7 +306,7 @@ pub const ProtocolResolver = struct {
|
|||||||
ret_is_self = true;
|
ret_is_self = true;
|
||||||
break :blk void_ptr_ty;
|
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;
|
} else .void;
|
||||||
method_infos.append(self.l.alloc, .{
|
method_infos.append(self.l.alloc, .{
|
||||||
.name = method.name,
|
.name = method.name,
|
||||||
@@ -393,7 +393,7 @@ pub const ProtocolResolver = struct {
|
|||||||
// Resolve the protocol's type-arg list to concrete TypeIds.
|
// Resolve the protocol's type-arg list to concrete TypeIds.
|
||||||
var arg_tys = std.ArrayList(TypeId).empty;
|
var arg_tys = std.ArrayList(TypeId).empty;
|
||||||
for (ib.protocol_type_args) |arg_node| {
|
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;
|
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
|
// parameterised impls (back-compat `target_type` string is kept for
|
||||||
// simple cases but the canonical form is the TypeExpr).
|
// simple cases but the canonical form is the TypeExpr).
|
||||||
const src_ty: TypeId = if (ib.target_type_expr) |te|
|
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)
|
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
|
else
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ const std = @import("std");
|
|||||||
const types = @import("types.zig");
|
const types = @import("types.zig");
|
||||||
const type_bridge = @import("type_bridge.zig");
|
const type_bridge = @import("type_bridge.zig");
|
||||||
const ast = @import("../ast.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 Node = ast.Node;
|
||||||
|
|
||||||
const TypeId = types.TypeId;
|
const TypeId = types.TypeId;
|
||||||
@@ -18,7 +20,7 @@ test "resolveAstType: primitive type_expr" {
|
|||||||
defer alloc.destroy(node);
|
defer alloc.destroy(node);
|
||||||
node.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .type_expr = .{ .name = "f64" } } };
|
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" {
|
test "resolveAstType: pointer type" {
|
||||||
@@ -34,7 +36,7 @@ test "resolveAstType: pointer type" {
|
|||||||
defer alloc.destroy(node);
|
defer alloc.destroy(node);
|
||||||
node.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .pointer_type_expr = .{ .pointee_type = inner } } };
|
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));
|
try std.testing.expectEqual(TypeInfo{ .pointer = .{ .pointee = .s32 } }, table.get(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,7 +57,7 @@ test "resolveAstType: optional slice" {
|
|||||||
defer alloc.destroy(opt);
|
defer alloc.destroy(opt);
|
||||||
opt.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .optional_type_expr = .{ .inner_type = slice } } };
|
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);
|
const info = table.get(id);
|
||||||
switch (info) {
|
switch (info) {
|
||||||
.optional => |o| {
|
.optional => |o| {
|
||||||
@@ -71,7 +73,7 @@ test "resolveAstType: null surfaces as .unresolved (no silent s64 default)" {
|
|||||||
var table = TypeTable.init(alloc);
|
var table = TypeTable.init(alloc);
|
||||||
defer table.deinit();
|
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" {
|
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);
|
defer alloc.destroy(sh_node);
|
||||||
sh_node.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .type_expr = .{ .name = "ShaderHandle" } } };
|
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);
|
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(std.meta.Tag(TypeInfo), .@"struct"), std.meta.activeTag(empty_info));
|
||||||
try std.testing.expectEqual(@as(usize, 0), empty_info.@"struct".fields.len);
|
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);
|
defer alloc.destroy(opaque_node);
|
||||||
opaque_node.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .type_expr = .{ .name = "Opaque" } } };
|
opaque_node.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .type_expr = .{ .name = "Opaque" } } };
|
||||||
try aliases.put("Opaque", .u64);
|
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
|
// Compound forms (`*Opaque`, `[]Opaque`, `?Opaque`) route through recursive
|
||||||
// helpers that thread the same alias_map at every step.
|
// 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);
|
const ptr_node = try alloc.create(Node);
|
||||||
defer alloc.destroy(ptr_node);
|
defer alloc.destroy(ptr_node);
|
||||||
ptr_node.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .pointer_type_expr = .{ .pointee_type = opaque_inner } } };
|
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));
|
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" {
|
test "resolveAstType: error_set_decl registers an error-set type + interns tags" {
|
||||||
const alloc = std.testing.allocator;
|
const alloc = std.testing.allocator;
|
||||||
var table = TypeTable.init(alloc);
|
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,
|
.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);
|
const info = table.get(id);
|
||||||
try std.testing.expect(info == .error_set);
|
try std.testing.expect(info == .error_set);
|
||||||
try std.testing.expectEqualStrings("ParseErr", table.getString(info.error_set.name));
|
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).
|
// Tags were interned into the global pool (round-trip a name through it).
|
||||||
try std.testing.expectEqualStrings("BadDigit", table.getTagName(table.internTag("BadDigit")));
|
try std.testing.expectEqualStrings("BadDigit", table.getTagName(table.internTag("BadDigit")));
|
||||||
// Re-resolving the same decl dedups to the same TypeId.
|
// 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 ──
|
// ── 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);
|
const node = try alloc.create(Node);
|
||||||
defer alloc.destroy(node);
|
defer alloc.destroy(node);
|
||||||
node.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .error_type_expr = .{ .name = "ParseErr" } } };
|
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" {
|
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);
|
defer alloc.destroy(b);
|
||||||
b.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .error_type_expr = .{ .name = null } } };
|
b.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .error_type_expr = .{ .name = null } } };
|
||||||
|
|
||||||
const ia = type_bridge.resolveAstType(a, &table, null);
|
const ia = type_bridge.resolveAstType(a, &table, null, null);
|
||||||
const ib = type_bridge.resolveAstType(b, &table, null);
|
const ib = type_bridge.resolveAstType(b, &table, null, null);
|
||||||
try std.testing.expect(table.get(ia) == .error_set);
|
try std.testing.expect(table.get(ia) == .error_set);
|
||||||
try std.testing.expectEqualStrings("!", table.getString(table.get(ia).error_set.name));
|
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
|
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);
|
defer alloc.destroy(tuple);
|
||||||
tuple.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .tuple_type_expr = .{ .field_types = &fields, .field_names = null } } };
|
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);
|
const info = table.get(id);
|
||||||
try std.testing.expect(info == .tuple);
|
try std.testing.expect(info == .tuple);
|
||||||
try std.testing.expectEqual(@as(usize, 2), info.tuple.fields.len);
|
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 TypeTable = ir_types.TypeTable;
|
||||||
const StringId = ir_types.StringId;
|
const StringId = ir_types.StringId;
|
||||||
const type_resolver = @import("type_resolver.zig");
|
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
|
/// The single-source type-alias table (`ProgramIndex.type_alias_map`), threaded
|
||||||
/// explicitly through every name-resolving entry point so a bare name like
|
/// 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).
|
/// `null` for contexts that never see aliases, e.g. unit tests).
|
||||||
pub const AliasMap = ?*const std.StringHashMap(TypeId);
|
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`:
|
/// Binding-free element-recursion adapter for `TypeResolver.resolveCompound`:
|
||||||
/// nested element types resolve through `type_bridge.resolveAstType` (the
|
/// nested element types resolve through `type_bridge.resolveAstType` (the
|
||||||
/// registration-time path — no generic/pack bindings). Lets type_bridge reuse
|
/// 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 {
|
const StatelessInner = struct {
|
||||||
table: *TypeTable,
|
table: *TypeTable,
|
||||||
alias_map: AliasMap,
|
alias_map: AliasMap,
|
||||||
|
consts: ConstMap,
|
||||||
pub fn resolveInner(self: StatelessInner, node: *const Node) TypeId {
|
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).
|
/// Fixed-array dimension at registration time: a literal `[16]T`, or a
|
||||||
/// Only a literal dimension is knowable here; a named-const dimension
|
/// named module-global const `N :: 16; [N]T` looked up in the const table.
|
||||||
/// (`N :: 16; [N]T`) is resolved by the stateful caller
|
/// Both yield the SAME length — registration-time paths (aliases, inline
|
||||||
/// (`Lowering.resolveArrayLen`) before it ever reaches this binding-free
|
/// union/enum fields) must lay out a named-const dim identically to a literal
|
||||||
/// path — mirroring how `pack_index_type_expr` is handled stateful-first.
|
/// (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 {
|
pub fn resolveArrayLen(self: StatelessInner, len_node: *const Node) u32 {
|
||||||
_ = self;
|
switch (len_node.data) {
|
||||||
return switch (len_node.data) {
|
.int_literal => |lit| return @intCast(lit.value),
|
||||||
.int_literal => |lit| @intCast(lit.value),
|
.identifier => |id| if (self.namedConstLen(id.name)) |n| return n,
|
||||||
else => 0,
|
.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
|
// Resolve an AST type node into an IR TypeId. Used during lowering when
|
||||||
// we only have the parsed AST (no codegen type registry).
|
// 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.
|
// 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
|
// 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
|
// "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
|
// `.s64` here would silently fabricate an 8-byte int. Surface it via the
|
||||||
// `.unresolved` sentinel (trips the sizeOf/toLLVMType panic at codegen).
|
// `.unresolved` sentinel (trips the sizeOf/toLLVMType panic at codegen).
|
||||||
const n = node orelse return .unresolved;
|
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) {
|
return switch (n.data) {
|
||||||
.type_expr => |te| resolveTypeName(te.name, table, alias_map),
|
.type_expr => |te| resolveTypeName(te.name, table, alias_map),
|
||||||
.identifier => |id| resolveTypeName(id.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
|
// `Closure(..p)` field type at registration time). These tiny fallbacks
|
||||||
// are the only stateless-specific shape code left; the stateful expand
|
// are the only stateless-specific shape code left; the stateful expand
|
||||||
// lives in PackResolver.
|
// lives in PackResolver.
|
||||||
.closure_type_expr => |ct| type_resolver.TypeResolver.resolveCompound(table, n, si) orelse resolveClosurePackShape(&ct, 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),
|
.tuple_type_expr => |tt| type_resolver.TypeResolver.resolveCompound(table, n, si) orelse resolveTupleSpreadShape(&tt, table, alias_map, consts),
|
||||||
.pack_index_type_expr => {
|
.pack_index_type_expr => {
|
||||||
// Pack-index `$args[N]` in a type position must be resolved
|
// Pack-index `$args[N]` in a type position must be resolved
|
||||||
// against an active pack binding — `type_bridge` has no access
|
// 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", .{});
|
std.debug.print("type_bridge: pack-index type expression encountered outside a pack-aware context — returning .unresolved\n", .{});
|
||||||
return .unresolved;
|
return .unresolved;
|
||||||
},
|
},
|
||||||
.tuple_literal => |tl| resolveTupleLiteralAsType(&tl, table, alias_map),
|
.tuple_literal => |tl| resolveTupleLiteralAsType(&tl, table, alias_map, consts),
|
||||||
.parameterized_type_expr => |pt| resolveParameterizedType(&pt, table, alias_map),
|
.parameterized_type_expr => |pt| resolveParameterizedType(&pt, table, alias_map, consts),
|
||||||
// An unannotated param. Its type must be resolved from context
|
// An unannotated param. Its type must be resolved from context
|
||||||
// (contextual closure typing, generic binding, or pack substitution)
|
// (contextual closure typing, generic binding, or pack substitution)
|
||||||
// *before* reaching here; if it doesn't, returning a plausible `.s64`
|
// *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.
|
// turns it into a real diagnostic.
|
||||||
.inferred_type => .unresolved,
|
.inferred_type => .unresolved,
|
||||||
// Inline type declarations (used as field types)
|
// Inline type declarations (used as field types)
|
||||||
.enum_decl => |ed| resolveInlineEnum(&ed, table, alias_map),
|
.enum_decl => |ed| resolveInlineEnum(&ed, table, alias_map, consts),
|
||||||
.struct_decl => |sd| resolveInlineStruct(&sd, table, alias_map),
|
.struct_decl => |sd| resolveInlineStruct(&sd, table, alias_map, consts),
|
||||||
.union_decl => |ud| resolveInlineUnion(&ud, table, alias_map),
|
.union_decl => |ud| resolveInlineUnion(&ud, table, alias_map, consts),
|
||||||
.error_set_decl => |esd| resolveInlineErrorSet(&esd, table),
|
.error_set_decl => |esd| resolveInlineErrorSet(&esd, table),
|
||||||
.error_type_expr => |ete| resolveErrorType(&ete, table, alias_map),
|
.error_type_expr => |ete| resolveErrorType(&ete, table, alias_map),
|
||||||
else => {
|
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
|
/// 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
|
/// pack SHAPE — a `closureTypePack` whose prefix is the fixed params. The
|
||||||
/// stateful expand lives in `PackResolver.resolveClosureTypeWithBindings`.
|
/// 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;
|
const alloc = table.alloc;
|
||||||
var param_ids = std.ArrayList(TypeId).empty;
|
var param_ids = std.ArrayList(TypeId).empty;
|
||||||
for (ct.param_types) |pt| {
|
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));
|
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
|
/// each field resolves individually (a spread field is not a type → resolves to
|
||||||
/// `.unresolved`). The stateful expand lives in
|
/// `.unresolved`). The stateful expand lives in
|
||||||
/// `PackResolver.resolveTupleTypeWithBindings`.
|
/// `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;
|
const alloc = table.alloc;
|
||||||
var field_ids = std.ArrayList(TypeId).empty;
|
var field_ids = std.ArrayList(TypeId).empty;
|
||||||
for (tt.field_types) |ft| {
|
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;
|
var name_ids: ?[]const StringId = null;
|
||||||
if (tt.field_names) |names| {
|
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
|
// 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
|
// reaches it from lowering. The sentinel is the backstop for any other
|
||||||
// (binding-free) caller.
|
// (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;
|
const alloc = table.alloc;
|
||||||
var field_ids = std.ArrayList(TypeId).empty;
|
var field_ids = std.ArrayList(TypeId).empty;
|
||||||
var name_ids_list = std.ArrayList(StringId).empty;
|
var name_ids_list = std.ArrayList(StringId).empty;
|
||||||
var any_named = false;
|
var any_named = false;
|
||||||
for (tl.elements) |el| {
|
for (tl.elements) |el| {
|
||||||
if (!isTypeShapedAstNode(el.value, table)) return .unresolved;
|
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| {
|
if (el.name) |n| {
|
||||||
any_named = true;
|
any_named = true;
|
||||||
name_ids_list.append(alloc, table.internString(n)) catch unreachable;
|
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")
|
// 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;
|
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
|
// 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))),
|
.int_literal => |lit| @intCast(@as(u64, @bitCast(lit.value))),
|
||||||
else => 0,
|
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);
|
return table.vectorOf(elem, length);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -254,7 +280,7 @@ fn resolveParameterizedType(pt: *const ast.ParameterizedTypeExpr, table: *TypeTa
|
|||||||
|
|
||||||
// ── Inline type declarations ─────────────────────────────────────────
|
// ── 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 alloc = table.alloc;
|
||||||
const name_id = table.internString(ed.name);
|
const name_id = table.internString(ed.name);
|
||||||
|
|
||||||
@@ -280,7 +306,7 @@ fn resolveInlineEnum(ed: *const ast.EnumDecl, table: *TypeTable, alias_map: Alia
|
|||||||
} else {
|
} else {
|
||||||
var sfields = std.ArrayList(TypeInfo.StructInfo.Field).empty;
|
var sfields = std.ArrayList(TypeInfo.StructInfo.Field).empty;
|
||||||
for (sd.field_names, sd.field_types) |fname, ftype_node| {
|
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, .{
|
sfields.append(alloc, .{
|
||||||
.name = table.internString(fname),
|
.name = table.internString(fname),
|
||||||
.ty = fty,
|
.ty = fty,
|
||||||
@@ -294,10 +320,10 @@ fn resolveInlineEnum(ed: *const ast.EnumDecl, table: *TypeTable, alias_map: Alia
|
|||||||
table.update(field_ty, sinfo);
|
table.update(field_ty, sinfo);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
field_ty = resolveAstType(vt, table, alias_map);
|
field_ty = resolveAstType(vt, table, alias_map, consts);
|
||||||
}
|
}
|
||||||
} else {
|
} 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 backing_type: ?TypeId = null;
|
||||||
var tag_type: ?TypeId = null;
|
var tag_type: ?TypeId = null;
|
||||||
if (ed.backing_type) |bt| {
|
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;
|
backing_type = backing_ty;
|
||||||
// Extract tag type from first field of backing struct
|
// Extract tag type from first field of backing struct
|
||||||
const backing_info = table.get(backing_ty);
|
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| {
|
if (ed.backing_type) |bt| {
|
||||||
// Only use simple backing types (u8, u16, u32, etc.), not struct backing (enum struct)
|
// Only use simple backing types (u8, u16, u32, etc.), not struct backing (enum struct)
|
||||||
if (bt.data != .struct_decl) {
|
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;
|
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 alloc = table.alloc;
|
||||||
const name_id = table.internString(sd.name);
|
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;
|
var fields = std.ArrayList(TypeInfo.StructInfo.Field).empty;
|
||||||
for (sd.field_names, sd.field_types) |fname, ftype_node| {
|
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, .{
|
fields.append(alloc, .{
|
||||||
.name = table.internString(fname),
|
.name = table.internString(fname),
|
||||||
.ty = field_ty,
|
.ty = field_ty,
|
||||||
@@ -433,7 +459,7 @@ fn resolveInlineStruct(sd: *const ast.StructDecl, table: *TypeTable, alias_map:
|
|||||||
return id;
|
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 alloc = table.alloc;
|
||||||
const name_id = table.internString(ud.name);
|
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;
|
var fields = std.ArrayList(TypeInfo.StructInfo.Field).empty;
|
||||||
for (ud.field_names, ud.field_types) |fname, ftype_node| {
|
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, .{
|
fields.append(alloc, .{
|
||||||
.name = table.internString(fname),
|
.name = table.internString(fname),
|
||||||
.ty = field_ty,
|
.ty = field_ty,
|
||||||
|
|||||||
Reference in New Issue
Block a user