lang: const-aggregate comptime folds (PLAN-CONST-AGG step 3)

An array const's '.len' and 'K[<const idx>]' element reads, and a
struct const's field ('LIT.r'), fold as compile-time integer leaves —
usable in array dimensions and other constants' initializers. All
source-aware (the SELECTED author's elements, folded in the author's
context with the cyclic-definition frame); a const out-of-range index
diagnoses at fold time, never wraps.

- evalConstIntExpr gains the three ctx hooks (lookupConstAggLen /
  lookupConstArrayElem / lookupConstStructField) + an index_expr arm;
  all five ctx implementations extended (stateless tiers fold null).
- Array consts dual-register in module_const_map (value = the literal
  node) so the folders see elements; bare reads still hit the GLOBAL
  arm first, so no double emission.
- Untyped consts whose RHS is a const-aggregate leaf ('L :: K.len',
  'E :: K[1]', 'R :: LIT.r') register in a pass 2b AFTER aggregates,
  gated on the receiver naming a const aggregate — a namespaced member
  ('F :: m.PI_ISH') is never mis-typed by the count placeholder.

Examples: 0179 (folds in dims + const exprs), 1163 (OOB diagnostic).
This commit is contained in:
agra
2026-06-11 12:44:43 +03:00
parent 7f3bd69bd9
commit c23b76c7d6
14 changed files with 224 additions and 2 deletions

View File

@@ -113,6 +113,15 @@ const ModuleConstCtx = struct {
pub fn lookupDimName(self: ModuleConstCtx, name: []const u8) ?i64 {
return moduleConstIntFramed(self.consts, self.table, name, self.frame);
}
pub fn lookupConstAggLen(_: ModuleConstCtx, _: []const u8) ?i64 {
return null;
}
pub fn lookupConstArrayElem(_: ModuleConstCtx, _: []const u8, _: i64, _: ?ast.Span) ?i64 {
return null;
}
pub fn lookupConstStructField(_: ModuleConstCtx, _: []const u8, _: []const u8) ?i64 {
return null;
}
pub fn lookupPackLen(_: ModuleConstCtx, _: []const u8) ?i64 {
return null;
}
@@ -329,17 +338,35 @@ pub fn evalConstIntExpr(node: *const Node, ctx: anytype) ?i64 {
};
if (obj_name) |on| {
// `<pack>.len` resolves to the monomorphised arity (e.g. an
// `inline for 0..xs.len` bound).
if (std.mem.eql(u8, fa.field, "len")) break :blk ctx.lookupPackLen(on);
// `inline for 0..xs.len` bound); `<array const>.len` to the
// const's element count.
if (std.mem.eql(u8, fa.field, "len")) {
if (ctx.lookupPackLen(on)) |n| break :blk n;
break :blk ctx.lookupConstAggLen(on);
}
// `<IntType>.min` / `.max` — the same fold the value path uses
// (type_resolver), so `[u8.max]T` agrees with `u8.max` in
// expression position. A `u64.max` (= -1 as i64) folds here too;
// `foldDimU32` then rejects it as a negative array dimension.
if (type_resolver.TypeResolver.integerLimitFor(on, fa.field)) |v| break :blk v;
// A struct const's integer field (`LIT.r`) folds to the
// SELECTED author's field value.
if (ctx.lookupConstStructField(on, fa.field)) |v| break :blk v;
}
// Any other field access is not a compile-time integer leaf.
break :blk null;
},
// `K[<const idx>]` over an ARRAY const folds to the element's value
// (bounds-checked at fold time — out of range diagnoses, never wraps).
.index_expr => |ie| blk: {
const on: ?[]const u8 = switch (ie.object.data) {
.identifier => |id| if (id.is_raw) null else id.name,
else => null,
};
const name = on orelse break :blk null;
const idx = evalConstIntExpr(ie.index, ctx) orelse break :blk null;
break :blk ctx.lookupConstArrayElem(name, idx, node.span);
},
.unary_op => |u| switch (u.op) {
.negate => {
const v = evalConstIntExpr(u.operand, ctx) orelse return null;