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:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user