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

@@ -723,7 +723,25 @@ pub const Parser = struct {
if (args.items.len > 0) {
try self.expect(.comma);
}
// Args can be int literals (for lengths) or type expressions
// Pack-spread type arg: `Combined($R, ..sources.T)`.
if (self.current.tag == .dot_dot) {
const sp_start = self.current.loc.start;
self.advance(); // skip '..'
const operand = try self.parseTypeExpr();
try args.append(self.allocator, try self.createNode(sp_start, .{ .spread_expr = .{ .operand = operand } }));
continue;
}
// An arg is either a TYPE (`f32`, `*T`, `[]u8`, `List(T)`) or a
// compile-time integer expression in a value position — a
// `Vector` lane count or a generic `$N: u32` arg: `Vector(N, f32)`,
// `Vector(M + 1, f32)`. Parse the primary as a literal / type,
// then continue as a const-int expression iff an arithmetic
// operator follows. A complete type arg is always followed by
// `,` / `)`, so `parseBinaryRhs` is a no-op for plain types and
// the continuation is unambiguous; `Prec.additive` bounds it to
// `+ - * / %`. The shared evaluator folds the expression; a
// non-const value position is diagnosed during lowering.
var arg: *Node = undefined;
if (self.current.tag == .int_literal) {
const arg_start = self.current.loc.start;
const text = self.tokenSlice(self.current);
@@ -738,16 +756,12 @@ pub const Parser = struct {
return self.fail("invalid integer literal in type argument");
};
self.advance();
try args.append(self.allocator, try self.createNode(arg_start, .{ .int_literal = .{ .value = value } }));
} else if (self.current.tag == .dot_dot) {
// Pack-spread type arg: `Combined($R, ..sources.T)`.
const sp_start = self.current.loc.start;
self.advance(); // skip '..'
const operand = try self.parseTypeExpr();
try args.append(self.allocator, try self.createNode(sp_start, .{ .spread_expr = .{ .operand = operand } }));
arg = try self.createNode(arg_start, .{ .int_literal = .{ .value = value } });
} else {
try args.append(self.allocator, try self.parseTypeExpr());
arg = try self.parseTypeExpr();
}
arg = try self.parseBinaryRhs(arg, Prec.additive);
try args.append(self.allocator, arg);
}
try self.expect(.r_paren);
return try self.createNode(start, .{ .parameterized_type_expr = .{
@@ -3501,7 +3515,15 @@ pub const Parser = struct {
self.current.tag == .l_bracket or self.current.tag == .r_bracket or
self.current.tag == .l_paren or self.current.tag == .r_paren or
self.current.tag == .comma or self.current.tag == .int_literal or
self.current.tag == .star or self.current.tag == .question or
// Arithmetic operators appear in a const-expression dimension /
// lane / value-param in a return type: `-> [N + 1]f32`,
// `-> Vector(N + 1, f32)`. They must be skipped while scanning
// for the body brace, else the decl is misread as a bodyless
// function-type alias and the `{` body errors as "expected ';'".
// (`.star` doubles as the pointer sigil and is already listed.)
self.current.tag == .star or self.current.tag == .slash or
self.current.tag == .percent or self.current.tag == .plus or
self.current.tag == .minus or self.current.tag == .question or
self.current.tag == .bang or
self.current.tag == .colon or self.current.tag == .arrow)
{