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:
@@ -258,10 +258,17 @@ pub fn evalConstIntExpr(node: *const Node, ctx: anytype) ?i64 {
|
||||
/// / comptime consts, `<IntType>.min`/`.max`, and integer arithmetic resolve
|
||||
/// through the SINGLE int folder — no parallel integer logic here); only the
|
||||
/// genuinely float-producing shapes — a float literal, a NON-INTEGRAL float-const
|
||||
/// leaf, a unary negate, and `+ - * /` arithmetic involving a float — are
|
||||
/// evaluated here in `f64`. A `%`, comparison, or any other shape is not a
|
||||
/// leaf, a builtin FLOAT numeric-limit accessor (`f64.max`, `f32.epsilon`,
|
||||
/// `f64.true_min`, …), a unary negate, and `+ - * / %` arithmetic involving a
|
||||
/// float — are evaluated here in `f64`. A comparison or any other shape is not a
|
||||
/// compile-time float leaf → null.
|
||||
///
|
||||
/// This evaluator is at PARITY with `evalConstIntExpr` — every leaf / node kind
|
||||
/// the int folder recognises (literal, named const leaf, numeric-limit
|
||||
/// field-access, unary negate, `+ - * / %`) is mirrored here in `f64` (delegating
|
||||
/// integer subtrees), so no compile-time-const float shape escapes the unified
|
||||
/// float→int narrowing rule at one site while folding at another.
|
||||
///
|
||||
/// A NAMED-const leaf resolves through `ctx.lookupFloatName`, the float twin of
|
||||
/// the `lookupDimName` the int folder uses: a numeric module const whose value is
|
||||
/// a non-integral float (`F : f64 : 2.5`) surfaces here so `F + 0.25` (= 2.75) is
|
||||
@@ -280,6 +287,24 @@ pub fn evalConstFloatExpr(node: *const Node, ctx: anytype) ?f64 {
|
||||
// float (the integral / integer cases were caught by the int delegation).
|
||||
.identifier => |id| ctx.lookupFloatName(id.name),
|
||||
.type_expr => |te| ctx.lookupFloatName(te.name),
|
||||
.field_access => |fa| blk: {
|
||||
// A numeric-limit accessor on a builtin FLOAT type (`f64.true_min`,
|
||||
// `f32.epsilon`, `f64.max`, …) is a compile-time float leaf — the
|
||||
// float twin of `evalConstIntExpr`'s `<IntType>.min`/`.max` arm, via
|
||||
// the SAME `type_resolver` fold (the facility `lowerNumericLimit`
|
||||
// uses) so the two evaluators can't disagree on what `f64.max`
|
||||
// evaluates to. Integer limits and `<pack>.len` are already resolved
|
||||
// by the int delegation above, so only the float-limit case remains.
|
||||
const obj_name: ?[]const u8 = switch (fa.object.data) {
|
||||
.identifier => |id| id.name,
|
||||
.type_expr => |te| te.name,
|
||||
else => null,
|
||||
};
|
||||
if (obj_name) |on| {
|
||||
if (type_resolver.TypeResolver.floatLimitFor(on, fa.field)) |v| break :blk v;
|
||||
}
|
||||
break :blk null;
|
||||
},
|
||||
.unary_op => |u| switch (u.op) {
|
||||
.negate => {
|
||||
const v = evalConstFloatExpr(u.operand, ctx) orelse return null;
|
||||
@@ -295,6 +320,10 @@ pub fn evalConstFloatExpr(node: *const Node, ctx: anytype) ?f64 {
|
||||
.sub => l - r,
|
||||
.mul => l * r,
|
||||
.div => if (r == 0.0) null else l / r,
|
||||
// `%` mirrors `evalConstIntExpr`'s `.mod` (and codegen's `frem`):
|
||||
// `@rem` truncated remainder, so `5.5 % 2.0` = 1.5 surfaces as a
|
||||
// non-integral float instead of silently truncating.
|
||||
.mod => if (r == 0.0) null else @rem(l, r),
|
||||
else => null,
|
||||
};
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user