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:
agra
2026-06-05 18:15:17 +03:00
parent b73363ca4c
commit 74f675ac0b
9 changed files with 189 additions and 44 deletions

View File

@@ -95,6 +95,28 @@
> path. This relaxes the F0.4 `examples/1132` wording (a non-integral float const
> dim now reports the precise "non-integral float" message; it still errors).
>
> **Completion (F0.11 attempt 5)** — attempts 14 unified all five sites for
> literal / int-const-expr / float-const-leaf forms, but `evalConstFloatExpr` still
> LAGGED `evalConstIntExpr`: the int evaluator resolves a numeric-limit field-access
> leaf (`f64.true_min`, `f64.max`) via `type_resolver.integerLimitFor`, but the
> float evaluator had no parallel arm, so `y : s64 = f64.true_min + 0.5` (= 0.5)
> truncated silently to 0 (the direct `f64.true_min` already errored via the IR-level
> `constFloatInfo` path, but the *expression* form escaped). Closed by bringing the
> two evaluators to PARITY:
> - `evalConstFloatExpr` gains a `.field_access` arm that resolves a builtin FLOAT
> numeric-limit accessor through `type_resolver.TypeResolver.floatLimitFor` (the
> SAME facility `lowerNumericLimit` uses) — the float twin of the int evaluator's
> `integerLimitFor` arm. Integer limits / `<pack>.len` are still resolved by the
> int delegation, so only the float-limit case lands here.
> - The audit also surfaced a missing `%` arm: the int evaluator folds `.mod` but
> the float one did not, so `y : s64 = 5.5 % 2.0` (= 1.5) truncated silently to 1.
> `evalConstFloatExpr` now handles `.mod` via `@rem` (matching `evalConstIntExpr`
> and codegen's `frem`; `6.0 % 4.0` folds to 2 via the int delegation, `5.5 % 2.0`
> = 1.5 is rejected). The two evaluators are now at full leaf/operator parity, so
> no compile-time-const float shape escapes the rule at one site while folding at
> another. (A comptime-fn returning float is a genuinely new form for BOTH and is
> out of scope.)
>
> Regression tests: `examples/0168-types-integral-float-to-int.sx` (positive —
> local/field/param/const fold, integral int-const-EXPRESSION (`M + 2.0`) AND
> float-const-LEAF (`F + 1.5`, `F : f64 : 2.5`) fold at local/field/param/const,
@@ -110,9 +132,15 @@
> len 4; 1146 adds `[F + 0.25]s64` erroring; `examples/1132` now expects the
> precise non-integral-float dim wording. Unit:
> `program_index.test.zig` "evalConstFloatExpr folds comptime float expressions"
> (covers the float-const leaf: `F` → 2.5, `F + 0.25` → 2.75, `F + 1.5` → 4.0) and
> "foldCountI64 / foldDimU32 fold an integral float count, reject a non-integral
> one" (the count fold + the `non_integral_float` / `below_min` distinction).
> (covers the float-const leaf: `F` → 2.5, `F + 0.25` → 2.75, `F + 1.5` → 4.0;
> attempt 5 adds the numeric-limit leaf `f64.max`/`f64.true_min`/`f32.epsilon`,
> `f64.max - f64.max` → 0, `f64.true_min + 0.5` → 0.5, and the `%` arm `5.5 % 2.0`
> → 1.5 / `% 0.0` → null) and "foldCountI64 / foldDimU32 fold an integral float
> count, reject a non-integral one" (the count fold + the `non_integral_float` /
> `below_min` distinction). Attempt 5 also extends 0168 (positive: `f64.max -
> f64.max` → 0, `6.0 % 4.0` → 2, integer-limit `s8.max`/`[u8.max]` unregressed,
> `xx` escapes for both new forms) and 1146 (negative: `f64.true_min + 0.5` and
> `5.5 % 2.0` error at a binding site).
## Symptom
A typed LOCAL (and likely typed param/field) silently truncates a floating-point