fix(ir): route every comptime-int through the shared evaluator (0083)

Attempts 1–4 fixed the array-dimension paths but the same length-0
fabrication class survived on every other site that resolves a
compile-time integer. Unify them all on the single shared
`program_index.evalConstIntExpr` so they cannot diverge:

- All three Vector lane resolvers (resolveTypeCallWithBindings,
  resolveParameterizedWithBindings, resolveArrayLiteralType) and both
  generic value-param binders (instantiateGenericStruct,
  instantiateTypeFunction) hand-rolled an `else => 0` switch. A
  module-const lane `Vector(N, f32)` fabricated a 0-lane `<0 x float>`
  (LLVM "huge alignment" abort); a value-param `Vec(N, f32)` fabricated
  a 0 binding / wrong mangled name. They now fold through the shared
  evaluator and emit a clean diagnostic + `.unresolved` on a non-const
  operand (resolveVectorLane / resolveValueParamArg) — never 0.
- evalComptimeInt (inline-for bounds) delegated to the shared evaluator,
  so `inline for 0..M` / `0..(M+1)` fold like array dims. The `<pack>.len`
  leaf moved into the shared folder via a new `ctx.lookupPackLen`.
- The unknown-type semantic checker no longer walks a value-param
  position (`Vector(N, …)` / `Vec(N, …)`) as a type name (was reporting
  "unknown type 'N'").
- The parameterized-type-arg parser and the function-body lookahead
  (hasFnBodyAfterArrow) accept a const-EXPRESSION in a value position, so
  `Vector(M + 1, f32)` and `[M + 1]T` parse as a return type too (the
  latter a pre-existing array-dim sibling that the same heuristic broke).

Regressions: examples/1501 (named-const + const-expr lane, direct +
alias, 3/4-lane reads), 1502 (runtime lane clean-halts, exit 1, no LLVM
crash), 0207 (Vec(N)/Vec(M+1) == Vec(3) instantiation), 0610 (inline-for
const bounds). Shared-evaluator unit test extended with the pack-len arm.

zig build && zig build test && bash tests/run_examples.sh: 395 passed,
0 failed.
This commit is contained in:
agra
2026-06-04 11:32:25 +03:00
parent cd39316f5e
commit a491a1bf73
23 changed files with 340 additions and 93 deletions

View File

@@ -55,25 +55,44 @@ pub fn moduleConstInt(consts: *const std.StringHashMap(ModuleConstInfo), name: [
return null;
}
/// Evaluate a constant-expression array dimension to its integer value. Folds
/// integer `+ - * / %` and unary negate over int literals and named module /
/// comptime consts — recursively, so nested and parenthesised forms
/// (`[M + N - 1]`, `[(M + 1) * 2]`) fold (a grouping `(…)` carries no AST node;
/// the parser returns the inner expression). Leaf names resolve through
/// `ctx.lookupDimName`, so the stateful body-lowering path (which also sees
/// comptime constants and generic `$N` value bindings) and the stateless
/// registration path (module consts only) share THIS expression-folding logic
/// and cannot disagree on a dimension's value — the same unify-or-die rule that
/// keeps an array laid out via a type alias identical to the direct form
/// (issue 0083). Returns null when any operand is not a compile-time integer (a
/// runtime value, a non-comptime call, an unbound name) or the arithmetic
/// overflows / divides by zero: the caller then emits the clean compile-halting
/// diagnostic, never a fabricated length.
/// Evaluate a constant integer expression to its value. THE single
/// integer-expression folder for the compiler — array dimensions (`[N]T`,
/// `[M + 1]T`), Vector lane counts (`Vector(N, f32)`), generic value-param
/// args (`Vec(N, f32)`), and `inline for 0..M` bounds all route here so they
/// cannot disagree on what a given expression evaluates to (the issue-0083
/// two-resolver class of bug). Folds integer `+ - * / %` and unary negate over
/// int literals and named module / comptime consts — recursively, so nested and
/// parenthesised forms (`[M + N - 1]`, `[(M + 1) * 2]`) fold (a grouping `(…)`
/// carries no AST node; the parser returns the inner expression).
///
/// Leaves resolve through the ctx, so each call site shares the SAME folding
/// logic while contributing its own bindings:
/// - `ctx.lookupDimName(name)` — a name bound to a compile-time integer. The
/// stateful body-lowering ctx sees comptime constants, generic `$N` value
/// bindings, and module consts; the stateless registration ctx sees module
/// consts only.
/// - `ctx.lookupPackLen(name)` — a `<pack>.len` leaf → the pack's
/// monomorphised arity. Only the body-lowering ctx knows pack arities; the
/// stateless ctx returns null.
///
/// Returns null when any operand is not a compile-time integer (a runtime value,
/// a non-comptime call, an unbound name) or the arithmetic overflows / divides
/// by zero: the caller then emits the clean compile-halting diagnostic, never a
/// fabricated length / lane count / value-param.
pub fn evalConstIntExpr(node: *const Node, ctx: anytype) ?i64 {
return switch (node.data) {
.int_literal => |lit| lit.value,
.identifier => |id| ctx.lookupDimName(id.name),
.type_expr => |te| ctx.lookupDimName(te.name),
.field_access => |fa| blk: {
// `<pack>.len` resolves to the monomorphised arity (e.g. an
// `inline for 0..xs.len` bound). Any other field access is not a
// compile-time integer leaf.
if (fa.object.data == .identifier and std.mem.eql(u8, fa.field, "len")) {
break :blk ctx.lookupPackLen(fa.object.data.identifier.name);
}
break :blk null;
},
.unary_op => |u| switch (u.op) {
.negate => {
const v = evalConstIntExpr(u.operand, ctx) orelse return null;