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:
67
issues/0095-typed-local-float-int-narrowing.md
Normal file
67
issues/0095-typed-local-float-int-narrowing.md
Normal file
@@ -0,0 +1,67 @@
|
||||
# 0095 — typed local/decl silently truncates a float initializer to an integer annotation
|
||||
|
||||
> **RESOLVED (F0.11).** Agra ruled the UNIFIED rule (Option B): an implicit
|
||||
> float→int in a typed binding behaves exactly like the array-dimension rule —
|
||||
> an **integral** float FOLDS to its integer (`4.0` → 4, `-2.0` → -2), a
|
||||
> **non-integral** float is a COMPILE ERROR (`1.5`, `4.5`), and an explicit
|
||||
> `xx` / `cast(T)` ALWAYS truncates (the escape). Applied consistently across
|
||||
> 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).
|
||||
>
|
||||
> Fix (`src/ir/lower.zig`, `src/ir/module.zig`):
|
||||
> - `Builder.constFloatInfo` reads a compile-time `const_float` back from its
|
||||
> Ref (value + span).
|
||||
> - `coerceToType` now means IMPLICIT coercion: 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 that folds via `evalComptimeInt`); `emitModuleConst`
|
||||
> / `constExprValue` / `globalInitValue` fold an integral float to its int and
|
||||
> reject a non-integral one.
|
||||
>
|
||||
> Regression tests: `examples/0168-types-integral-float-to-int.sx` (positive —
|
||||
> local/field/param/const fold, `xx`/`cast` truncate), `examples/1146-diagnostics-
|
||||
> nonintegral-float-to-int.sx` (negative — local/param/field error), plus the
|
||||
> integral-float const cases added to `examples/0162-types-typed-module-const-
|
||||
> roundtrip.sx`. Non-integral const cases in `examples/1143` stay errors.
|
||||
|
||||
## Symptom
|
||||
A typed LOCAL (and likely typed param/field) silently truncates a floating-point
|
||||
initializer to an integer annotation instead of rejecting or requiring an explicit cast.
|
||||
|
||||
Observed:
|
||||
- `y : s64 = 1.5;` → y == 1 (float literal truncated, no diagnostic)
|
||||
- `y : s64 = 2 + 0.5;` → y == 2 (float-valued expr truncated, no diagnostic)
|
||||
|
||||
Expected: a type-mismatch / narrowing diagnostic (consistent with typed MODULE CONSTS,
|
||||
which after F0.7 reject `N : s64 : 1.5` and `N : s64 : M + 0.5`). Today consts are strict
|
||||
but locals are lenient — an inconsistency.
|
||||
|
||||
## Reproduction
|
||||
```sx
|
||||
#import "modules/std.sx";
|
||||
main :: () {
|
||||
y : s64 = 1.5;
|
||||
print("{}\n", y); // prints 1
|
||||
}
|
||||
```
|
||||
|
||||
## Investigation prompt
|
||||
Decide + implement the language rule for implicit float→int narrowing in a TYPED binding
|
||||
(local / param / field) initializer. Module consts already reject it (F0.7,
|
||||
registerTypedModuleConst + typedConstInitFits/constExprInitFits). Make typed-local/param/field
|
||||
assignment-coercion consistent: either reject a non-integral float→int initializer with a
|
||||
diagnostic (matching the const path) or require an explicit `xx`/cast. Suspected area: the
|
||||
assignment / typed-binding coercion path (coerceToType ladder, specs.md §"coercion") in
|
||||
src/ir/lower.zig. Verify `y : s64 = 1.5` errors (or requires a cast); confirm integral-float
|
||||
folding rules (specs.md: `4.0`→4 ok, `4.5` rejected) stay consistent. Then gate.
|
||||
|
||||
## Disposition
|
||||
Discovered during F0.7 (issue 0088) attempt-2 review. Agra ruled F0.7 fixes the
|
||||
inferExprType ROOT for binary-op promotion; this typed-LOCAL narrowing is a SEPARATE
|
||||
assignment-coercion concern -> its own scheduled step.
|
||||
Reference in New Issue
Block a user