# 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.