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:
@@ -119,6 +119,11 @@ pub const SourceConstCtx = struct {
|
||||
pub fn lookupConstStructField(self: SourceConstCtx, name: []const u8, field: []const u8) ?i64 {
|
||||
return self.lowering.foldConstStructField(name, field, self.frame);
|
||||
}
|
||||
// Type-query builtin folds (`field_count`/`size_of`/`align_of`) — delegate to
|
||||
// the wrapped Lowering, which can resolve the type-expr arg.
|
||||
pub fn evalConstCallInt(self: SourceConstCtx, node: *const Node) ?i64 {
|
||||
return self.lowering.evalConstCallInt(node);
|
||||
}
|
||||
pub fn lookupQualifiedConst(self: SourceConstCtx, ns: []const u8, field: []const u8) ?i64 {
|
||||
return self.lowering.foldQualifiedConstInt(ns, field, self.frame);
|
||||
}
|
||||
@@ -850,6 +855,41 @@ pub const Lowering = struct {
|
||||
return self.comptimeIntNamed(name);
|
||||
}
|
||||
|
||||
/// Fold a pure int-returning type-query builtin CALL to its compile-time
|
||||
/// constant: `field_count(T)` / `size_of(T)` / `align_of(T)`. This is what
|
||||
/// lets a reflection-derived count drive an `inline for` bound / array dim
|
||||
/// exactly like a plain `K :: 3` const — e.g. a `($T) -> Type` builder that
|
||||
/// loops `inline for 0..field_count(T)` to assemble a variant list (the
|
||||
/// `race` result synthesis). (Reaches the self-ctx fold paths — array dim,
|
||||
/// inline-for bound; a Vector LANE in a plain type annotation resolves through
|
||||
/// the stateless type-bridge ctx, which stubs this to null.) Resolves the type
|
||||
/// arg through the same `resolveTypeArg` + accessors the value path uses, so
|
||||
/// the folded constant always matches the call's runtime value. Returns null
|
||||
/// for any non-type-query call, a wrong arg count, or an unresolved type arg
|
||||
/// → the shared folder then treats it as not-a-compile-time-integer.
|
||||
pub fn evalConstCallInt(self: *Lowering, node: *const Node) ?i64 {
|
||||
const c = switch (node.data) {
|
||||
.call => |call| call,
|
||||
else => return null,
|
||||
};
|
||||
const name = switch (c.callee.data) {
|
||||
.identifier => |id| id.name,
|
||||
else => return null,
|
||||
};
|
||||
if (c.args.len != 1) return null;
|
||||
const is_fc = std.mem.eql(u8, name, "field_count");
|
||||
const is_sz = std.mem.eql(u8, name, "size_of");
|
||||
const is_al = std.mem.eql(u8, name, "align_of");
|
||||
if (!is_fc and !is_sz and !is_al) return null;
|
||||
const ty = self.resolveTypeArg(c.args[0]);
|
||||
if (ty == .unresolved) return null;
|
||||
// `field_count` of a resolved non-aggregate is 0 (matches the value path
|
||||
// in lower/call.zig), NOT a fold failure.
|
||||
if (is_fc) return self.module.types.memberCount(ty) orelse 0;
|
||||
if (is_sz) return @intCast(self.typeSizeBytes(ty));
|
||||
return @intCast(self.typeAlignBytes(ty));
|
||||
}
|
||||
|
||||
/// Pack-length leaf for the shared integer-expression evaluator: a pack
|
||||
/// name's monomorphised arity (e.g. an `inline for 0..xs.len` bound).
|
||||
/// Resolves through `pack_param_count`, which is populated when a comptime
|
||||
|
||||
Reference in New Issue
Block a user