fix(ir): unify float→int narrowing — integral folds, non-integral errors [F0.11]

Issue 0095: a typed local/param/field silently TRUNCATED a float initializer
to an integer annotation (`y : s64 = 1.5` → 1) with no diagnostic. Agra ruled
the UNIFIED rule (Option B): an implicit float→int in a typed binding behaves
like the array-dimension rule —

  - an INTEGRAL compile-time float FOLDS to its int (`4.0` → 4, `-2.0` → -2);
  - a NON-integral float is a COMPILE ERROR (`1.5`, `4.5`);
  - explicit `xx` / `cast(T)` ALWAYS truncates (the escape hatch).

Applied consistently to typed local / param-default / field-default, typed
module CONST, and array dim — all reusing the single
`program_index.floatToIntExact` / `evalConstIntExpr` facility (no second
integral check).

- `Builder.constFloatInfo` reads a compile-time `const_float` back from its
  Ref (value + span).
- `coerceToType` is now the IMPLICIT path: its `.float_to_int` arm folds an
  integral const-float to `constInt`, else emits the narrowing diagnostic.
  `coerceExplicit` is the raw truncating path; `xx` (lowerXX) and `cast(T)`
  route through it so the escape still truncates.
- Field-default lowering (struct-literal pad, named-field default,
  buildDefaultValue) now coerces the default to the field type at the IR level
  (was silently bit-coerced by emitStructInit).
- Const path: `typedConstInitFits` accepts an integral float (literal or a
  `M + 2.0`-style expression folding via `evalComptimeInt`); `emitModuleConst`
  / `constExprValue` / `globalInitValue` fold an integral float to its int and
  reject a non-integral one — relaxing F0.7's blanket float rejection.

Tests: examples/0168 (positive: local/field/param/const fold, xx/cast
truncate), examples/1146 (negative: local/param/field error), integral-float
const cases added to examples/0162; non-integral const cases in 1143 stay
errors. specs.md + readme.md document the unified rule, cross-referencing the
array-dim rule. issues/0095 marked RESOLVED.
This commit is contained in:
agra
2026-06-05 15:34:33 +03:00
parent 341b62c197
commit 4c12e1de38
16 changed files with 362 additions and 14 deletions

View File

@@ -123,6 +123,16 @@ int+float arithmetic promotes to the float in either operand order (`n + 0.5` an
`0.5 + n` are both `f64`), so `C : s64 : M + 0.5` is rejected regardless of order
while `F : f64 : M + 0.5` folds to `2.5`.
**Float → integer narrowing (unified rule).** A float flowing into an
integer-typed binding *without* a cast follows the same integral-fold rule an
array dimension uses: an **integral** float folds to its integer, a
**non-integral** float is a compile error. This is uniform across a typed local,
a parameter default, a struct field default, and a typed constant —
`y : s64 = 4.0` and `K : s64 : 4.0` both give `4` (and `K : s64 : M + 2.0` folds
to `4`), while `y : s64 = 1.5` and `N : s64 : 1.5` both error. An explicit
`xx` / `cast(s64)` is the escape hatch and always truncates (`y : s64 = xx 1.5`
→ `1`).
Builtin type names (`s2`, `u8`, `bool`, `string`, …) are reserved and a *bare*
spelling can't be used as an identifier at a **value-binding or declaration-name**
site — a value binding (`:=` / typed local / parameter), a `::` constant or