fix(ir): float / folds as FLOAT division under the unified narrowing rule — int folder refuses a float-operand / [F0.11]

The shared compile-time integer folder (`evalConstIntExpr`) accepts an
integral float literal/const as an integer leaf (`[4.0]` → 4) and then
applied INTEGER arithmetic to the whole expression — so `5.0 / 2.0` folded
as `divTrunc(5,2)` = 2 instead of float division (`2.5`). The bug fired at
all FIVE unified-rule sites (typed local, field default, param default,
typed const, array dimension), because the typed sites evaluate through
`evalConstFloatExpr` (which delegates the node to the int folder) and the
count sites through `foldCountI64` (int folder first).

Fix at the single root: `evalConstIntExpr`'s `.div` arm refuses to fold a
division whose lhs/rhs is float-valued (`isFloatValuedExpr`), so the value
surfaces through `evalConstFloatExpr` + the unified rule — an integral
quotient (`6.0 / 2.0` → 3) folds, a non-integral one (`5.0 / 2.0` = 2.5,
mixed `5 / 2.0`, float-const `F / G`) errors. Genuine integer `/` (`5 / 2`
→ 2) is unchanged; `*`/`+`/`-` need no guard (they agree between int and
float for the integral operands the int folder ever sees).

`isFloatValuedExpr` judges a const-leaf by VALUE (`moduleConstIsFloatTyped`
recurses into the const's value with the existing cycle-guard frame), so an
untyped float-EXPRESSION const (`ME :: 4.0 + 1.0`, placeholder type s64) is
caught at both the count path and — via `foldComptimeFloatInit`'s guard —
the typed-binding path. A backtick RAW receiver (`` `f64.epsilon ``) is a
field read, not a float limit (is_raw check, issues 0092/0093).

Regression: examples/1147 (negative — `5.0 / 2.0` errors at all five sites
plus untyped float-EXPR const div); 0168 extended (positive — `6.0 / 2.0`,
`12.0 / 4.0`, `[6.0/2.0]`, `xx (5.0/2.0)` → 2); unit tests "the int folder
refuses a FLOAT division" and "moduleConstIsFloatTyped judges a const by
VALUE". specs.md + readme.md state the float-`/` rule.
This commit is contained in:
agra
2026-06-05 19:26:22 +03:00
parent 74f675ac0b
commit e442cdf5e7
13 changed files with 393 additions and 18 deletions

View File

@@ -141,6 +141,30 @@
> 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).
>
> **Completion (F0.11 attempt 6)** — attempt 5 reached evaluator parity for
> leaves/operators, but a structural hole remained in the SHARED integer folder:
> `evalConstIntExpr` accepts an integral float literal/const as an integer leaf
> (`[4.0]` → 4) and then applies INTEGER arithmetic to the whole expression — so a
> float DIVISION with integral-looking operands (`5.0 / 2.0`) folded as integer
> truncating division (`divTrunc(5,2)` = 2) instead of float division (`2.5`). The
> bug fired at ALL FIVE sites (`5.0 / 2.0` printed `2` at a typed local, field
> default, param default, typed const, and array dimension), because the typed
> sites evaluate through `evalConstFloatExpr` (which delegates the whole node to
> the int folder) and the count sites through `foldCountI64` (which tries the int
> folder first). Closed at the single root: `evalConstIntExpr`'s `.div` arm now
> REFUSES to fold a division whose lhs/rhs is float-valued (a new
> `isFloatValuedExpr` predicate, resolving a float-typed const leaf through each
> ctx's `nameIsFloatTyped`) — so the division surfaces through `evalConstFloatExpr`
> (float `/`) + the unified rule: an integral quotient (`6.0 / 2.0` → 3) folds, a
> non-integral one (`5.0 / 2.0` = 2.5, mixed `5 / 2.0`, float-const `F / G`)
> errors. Genuine integer `/` (`5 / 2` → 2) is unchanged; `*`/`+`/`-` need no guard
> (they agree between int and float for the integral operands the int folder ever
> sees). Regression: `examples/1147-diagnostics-float-division-narrowing.sx`
> (negative — `5.0 / 2.0` errors at all five sites), the integral-`/` positives
> added to `examples/0168` (`6.0 / 2.0` local/field, `12.0 / 4.0` const, `[6.0 /
> 2.0]` dim, `xx (5.0 / 2.0)` → 2), and unit
> `program_index.test.zig` "the int folder refuses a FLOAT division".
## Symptom
A typed LOCAL (and likely typed param/field) silently truncates a floating-point