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:
@@ -247,6 +247,13 @@ pub const ImplTable = struct {
|
||||
// ── Builder ─────────────────────────────────────────────────────────────
|
||||
// Fluent API for constructing one function at a time.
|
||||
|
||||
/// A `const_float` instruction read back from its Ref: the compile-time value
|
||||
/// and the span it was emitted with.
|
||||
pub const ConstFloatInfo = struct {
|
||||
value: f64,
|
||||
span: Span,
|
||||
};
|
||||
|
||||
pub const Builder = struct {
|
||||
module: *Module,
|
||||
func: ?FuncId = null,
|
||||
@@ -347,6 +354,28 @@ pub const Builder = struct {
|
||||
return .unresolved;
|
||||
}
|
||||
|
||||
/// If `ref` points at a compile-time `const_float` instruction, return its
|
||||
/// value and the span it was emitted with; else null. The implicit
|
||||
/// float→int coercion rule reads this to fold an integral literal to its
|
||||
/// int (and to locate a non-integral one for its diagnostic).
|
||||
pub fn constFloatInfo(self: *Builder, ref: Ref) ?ConstFloatInfo {
|
||||
if (self.func == null) return null;
|
||||
const func = self.currentFunc();
|
||||
const ref_idx = @intFromEnum(ref);
|
||||
if (ref_idx < func.params.len) return null;
|
||||
for (func.blocks.items) |*block| {
|
||||
const first = block.first_ref;
|
||||
if (ref_idx >= first and ref_idx < first + @as(u32, @intCast(block.insts.items.len))) {
|
||||
const i = block.insts.items[ref_idx - first];
|
||||
return switch (i.op) {
|
||||
.const_float => |v| .{ .value = v, .span = i.span },
|
||||
else => null,
|
||||
};
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// ── Emit helpers ────────────────────────────────────────────────
|
||||
|
||||
pub fn emit(self: *Builder, op: Op, ty: TypeId) Ref {
|
||||
|
||||
Reference in New Issue
Block a user