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:
22
specs.md
22
specs.md
@@ -893,6 +893,8 @@ A **count** is a compile-time integer used as an array dimension, a `Vector`
|
||||
lane count, or a generic value-param count. Every count must be **integral**: an
|
||||
integral float (`4.0`, or a float-typed const `N : f64 : 4.0`) folds to its
|
||||
integer (`[4.0]s64` ≡ `[4]s64`), while a non-integral float (`4.5`) is rejected.
|
||||
This is the same integral-float rule a typed binding's float→integer initializer
|
||||
follows (see "Implicit float → integer", §2 Type Conversions).
|
||||
The accepted *range* of a count is **context-dependent** — zero is legal for
|
||||
some counts and not others:
|
||||
|
||||
@@ -1418,13 +1420,27 @@ isReady : ValueListenable(bool) = map(
|
||||
- Unsigned to strictly wider signed (`u8` → `s16`)
|
||||
- Any integer to any float (`u8` → `f32`, `s32` → `f64`)
|
||||
- Float to wider float (`f32` → `f64`)
|
||||
- Integer and float literals can convert to any numeric type implicitly
|
||||
- Integer literals can convert to any numeric type implicitly
|
||||
|
||||
**Explicit (narrowing)** — requires `xx` prefix:
|
||||
**Implicit float → integer (the unified narrowing rule)** — a float flowing into
|
||||
an integer-typed binding without `xx`/`cast` is governed by the SAME rule an
|
||||
array dimension / lane count uses (see "Array dimensions are integral", §2):
|
||||
|
||||
- An **integral** compile-time float **folds** to its integer:
|
||||
`y : s64 = 4.0` ≡ `y : s64 = 4`, `n : s64 = -2.0` ≡ `-2`.
|
||||
- A **non-integral** compile-time float is a **compile error**:
|
||||
`y : s64 = 1.5` → "cannot implicitly narrow non-integral float '1.5' to 's64'".
|
||||
- This applies uniformly to a typed **local**, a function **param default**, a
|
||||
struct **field default**, and a typed module **constant**
|
||||
(`K : s64 : 4.0` → 4; `N : s64 : 1.5` → error; `K : s64 : M + 2.0` → 4 when
|
||||
the expression folds to an integer).
|
||||
|
||||
**Explicit (narrowing)** — requires `xx` prefix (or `cast(T)`):
|
||||
- Integer to narrower integer (`s32` → `u8`)
|
||||
- Signed to unsigned (`s32` → `u32`)
|
||||
- Float to narrower float (`f64` → `f32`)
|
||||
- Float to any integer (`f64` → `u16`)
|
||||
- Float to any integer (`f64` → `u16`) — always **truncates**, integral or not
|
||||
(`y : s64 = xx 1.5` → 1); this is the escape hatch from the implicit rule above
|
||||
- Unsigned to signed of same or narrower width (`u8` → `s8`)
|
||||
|
||||
The `xx` prefix operator marks an expression for auto-conversion to the expected type from context (assignment, declaration, argument, return):
|
||||
|
||||
Reference in New Issue
Block a user