feat: fold field_count/size_of/align_of as comptime constants

The int-returning type-query builtins now fold to a compile-time constant in
const-required positions (`inline for` bound, array dimension), like a plain
`K :: 3` const — previously they evaluated only as runtime values, so
`[field_count(S)]T` and `inline for 0..field_count(S)` were rejected as "not a
compile-time integer". This is what lets a `($T) -> Type` builder loop
`inline for 0..field_count(T)` to assemble a member list from a type's fields
(the `race` result synthesis).

The shared comptime-int folder `evalConstIntExpr` (program_index.zig) gained a
`.call => ctx.evalConstCallInt(node)` arm. The body-lowering ctx (`Lowering`)
implements it — resolve the type arg via `resolveTypeArg`, return
`memberCount orelse 0` / `typeSizeBytes` / `typeAlignBytes`, matching the runtime
value path in lower/call.zig exactly. `SourceConstCtx` delegates to its wrapped
Lowering; the stateless ctxs (`ModuleConstCtx`, `StatelessInner`, test `DimCtx`)
stub null (they cannot resolve a type-expr arg). A non-type-query call / wrong
arg count / unresolved type arg folds to null (not a comptime integer).

Adversarially reviewed (SHIP): the fold matches the value path across every type
kind, ctx coverage is complete, recursion is AST-depth bounded, no speculative
spurious diagnostics, `orelse 0` is field_count's definitional value for
non-aggregates (not a silent default). Locked by
examples/comptime/0648-comptime-typequery-const-fold.sx. Suite green (820/0).
This commit is contained in:
agra
2026-06-26 13:13:16 +03:00
parent f1d298764f
commit 2a6ef39829
8 changed files with 112 additions and 0 deletions

View File

@@ -0,0 +1,39 @@
// Int-returning type-query builtins (`field_count` / `size_of` / `align_of`)
// fold as comptime CONSTANTS — usable as an `inline for` bound and an array
// dimension, exactly like a plain `K :: 3` const. Previously
// they evaluated only as runtime values, so `[field_count(S)]T` and
// `inline for 0..field_count(S)` were rejected as "not a compile-time integer".
// (This is what lets a `($T) -> Type` builder loop `inline for 0..field_count(T)`
// to assemble a variant list from a type's members — the `race` result synthesis.)
#import "modules/std.sx";
S :: struct { a: i64; b: bool; c: f64; }
E :: enum { X; Y; Z; W; }
main :: () -> i32 {
// field_count as an inline-for bound
s := 0;
inline for 0..field_count(S) (i) { s = s + i; } // 0+1+2 = 3
print("field_count(S) loop sum = {}\n", s);
// field_count as an array dimension; fill it in a folded loop
xs : [field_count(S)]i64 = ---;
inline for 0..field_count(S) (i) { xs[i] = i * 10; }
print("array[field_count(S)] len = {} xs[2] = {}\n", xs.len, xs[2]);
// field_count of an enum (4 variants) driving a loop
e := 0;
inline for 0..field_count(E) (i) { e = e + 1; }
print("field_count(E) = {}\n", e);
// size_of / align_of fold too
bytes : [size_of(i64)]u8 = ---;
print("size_of(i64) array len = {}\n", bytes.len);
print("align_of(f64) = {}\n", align_of(f64));
// composed const expression as a dim
ys : [field_count(S) + 1]i64 = ---;
print("[field_count(S) + 1] len = {}\n", ys.len);
return 0;
}

View File

@@ -0,0 +1,6 @@
field_count(S) loop sum = 3
array[field_count(S)] len = 3 xs[2] = 20
field_count(E) = 4
size_of(i64) array len = 8
align_of(f64) = 8
[field_count(S) + 1] len = 4