fix(ir): evalConstFloatExpr reaches parity with evalConstIntExpr — numeric-limit float leaves + float % fold under the unified rule [F0.11]
The compile-time float evaluator lagged the integer one: it had no numeric-limit field-access arm, so `y : s64 = f64.true_min + 0.5` (=0.5) silently truncated to 0 even though the direct `f64.true_min` already errored; the arm-by-arm audit also found a missing `%` arm, so `y : s64 = 5.5 % 2.0` (=1.5) silently truncated to 1. Bring evalConstFloatExpr to PARITY with evalConstIntExpr: - Add a `.field_access` arm resolving a builtin FLOAT numeric-limit accessor (`f64.max`, `f32.epsilon`, `f64.true_min`, …) via the SAME `type_resolver.floatLimitFor` that `lowerNumericLimit` uses — the float twin of the int evaluator's `integerLimitFor` arm. - Add a `.mod` arm via `@rem` (matching evalConstIntExpr and codegen's `frem`): `6.0 % 4.0` folds to 2 (via int delegation), `5.5 % 2.0` = 1.5 is rejected. The two evaluators now share every leaf/operator shape, so no compile-time-const float form escapes the unified float→int rule at one site while folding at another. All five sites (local/field/param/const/ array-dim) stay consistent. Regression: 0168 (positive) adds `f64.max - f64.max` → 0, `6.0 % 4.0` → 2, integer-limit `s8.max`/`[u8.max]` unregressed, `xx` escapes for both new forms; 1146 (negative) adds `f64.true_min + 0.5` and `5.5 % 2.0` erroring at a binding site; program_index.test.zig covers the floatLimitFor arm and the `%` arm. specs.md + readme.md state the parity. issues/0095 RESOLVED banner gains the attempt-5 entry.
This commit is contained in:
@@ -368,6 +368,38 @@ test "evalConstFloatExpr folds comptime float expressions, halts on runtime leav
|
||||
try std.testing.expectEqual(@as(?f64, 2.75), eval(&fq, ctx));
|
||||
try std.testing.expectEqual(@as(?f64, 4.0), eval(&fh, ctx));
|
||||
|
||||
// A builtin FLOAT numeric-limit accessor is a compile-time float leaf — the
|
||||
// twin of `evalConstIntExpr`'s `<IntType>.min`/`.max` arm, via the shared
|
||||
// `type_resolver.floatLimitFor`. It folds as a direct leaf AND inside an
|
||||
// expression: `f64.max - f64.max` = 0.0 (integral → folds), `f64.true_min +
|
||||
// 0.5` = 0.5 (non-integral → the narrowing rule rejects it). A non-limit
|
||||
// field on a float type is not a leaf → null (issue 0095, attempt 5 parity).
|
||||
var f64ty = nIdent("f64");
|
||||
var f32ty = nIdent("f32");
|
||||
var fmax = nField(&f64ty, "max");
|
||||
var ftmin = nField(&f64ty, "true_min");
|
||||
var feps = nField(&f32ty, "epsilon");
|
||||
var fbogus = nField(&f64ty, "bogus");
|
||||
try std.testing.expectEqual(@as(?f64, std.math.floatMax(f64)), eval(&fmax, ctx));
|
||||
try std.testing.expectEqual(@as(?f64, std.math.floatTrueMin(f64)), eval(&ftmin, ctx));
|
||||
try std.testing.expectEqual(@as(?f64, @as(f64, std.math.floatEps(f32))), eval(&feps, ctx));
|
||||
try std.testing.expect(eval(&fbogus, ctx) == null);
|
||||
var lim_diff = nBin(.sub, &fmax, &fmax);
|
||||
var lim_nonint = nBin(.add, &ftmin, &half);
|
||||
try std.testing.expectEqual(@as(?f64, 0.0), eval(&lim_diff, ctx));
|
||||
try std.testing.expectEqual(@as(?f64, 0.5), eval(&lim_nonint, ctx));
|
||||
|
||||
// `%` mirrors the int folder's `.mod` (and codegen's `frem`): `@rem`. A
|
||||
// non-integral-operand remainder (`5.5 % 2.0` = 1.5) reaches this arm (the
|
||||
// integral-operand case `6.0 % 4.0` folds via the int delegation); a zero
|
||||
// divisor → null.
|
||||
var fivehalf = nFloat(5.5);
|
||||
var zero_f0 = nFloat(0.0);
|
||||
var fmod = nBin(.mod, &fivehalf, &two_f);
|
||||
var fmodz = nBin(.mod, &fivehalf, &zero_f0);
|
||||
try std.testing.expectEqual(@as(?f64, 1.5), eval(&fmod, ctx));
|
||||
try std.testing.expect(eval(&fmodz, ctx) == null);
|
||||
|
||||
// A runtime operand poisons the whole fold; a non-arithmetic operator and a
|
||||
// float division by zero are not compile-time float leaves → null.
|
||||
var zp = nBin(.add, &z, &half);
|
||||
|
||||
Reference in New Issue
Block a user