fix: require exact contextual integer to float typing

This commit is contained in:
2026-10-24 01:07:20 +02:00
parent 8ebbf72388
commit 81531208b2
5 changed files with 240 additions and 14 deletions
+42 -3
View File
@@ -6,6 +6,7 @@ import "../source"
import "../symbol"
import "../target"
import "../types"
import "base:intrinsics"
import "core:fmt"
import "core:mem"
import "core:slice"
@@ -1206,6 +1207,33 @@ fits_integer_type :: proc(value: i128, value_type: types.Type, selected := targe
return fits_signed_type(value, value_type, selected) || fits_unsigned_type(value, value_type, selected)
}
integer_fits_float :: proc(value: i128, expected: types.Type, selected: target.Target) -> bool {
if !types.is_float(expected, selected) {
return false
}
precision := 0
switch types.bits(expected, selected) {
case 32: precision = 24
case 64: precision = 53
case: return false
}
if value == 0 {
return true
}
magnitude := u128(0)
if value < 0 {
magnitude = u128(-(value + 1)) + 1
} else {
magnitude = u128(value)
}
// Binary floats preserve the significant bits between the first and last one.
// Trailing zeros cost no precision, so large powers of two remain exact.
// Unsigned magnitude also handles i128's minimum without a float round-trip.
significant_bits := 128 - int(intrinsics.count_leading_zeros(magnitude)) -
int(intrinsics.count_trailing_zeros(magnitude))
return significant_bits <= precision
}
fits_i64 :: proc(value: i128) -> bool {
return fits_signed_type(value, types.I64)
}
@@ -7625,12 +7653,15 @@ build_constant_expr :: proc(
numeric_operation := false,
) -> hir.Expr_Id {
materialized := expected
for types.is_optional(materialized, &checker.module.types) {
materialized = types.child_type(materialized, &checker.module.types)
}
nominal := types.INVALID
if numeric_operation {
if representation, ok := types.distinct_scalar_backing(expected, &checker.module.types);
if representation, ok := types.distinct_scalar_backing(materialized, &checker.module.types);
ok && !types.is_bool(representation) {
nominal = materialized
materialized = representation
nominal = expected
}
}
recovery_type := types.I64
@@ -7640,7 +7671,15 @@ build_constant_expr :: proc(
// An integer constant in a float context (e.g. `pi float = 3`) folds to a
// float literal, mirroring build_float_expr's bit packing.
if constant.kind == .Value && types.is_float(materialized, checker.target) {
fval := f64(constant.value) // ponytail: silent precision loss past 2^53, like C int->double
if !integer_fits_float(constant.value, materialized, checker.target) {
id := source.addf(
checker.diagnostics, expr.span,
"integer constant %d is not exactly representable in %s",
constant.value, type_label(checker, materialized),
)
return invalid_hir_expr(checker, expr.span, id, expected)
}
fval := f64(constant.value)
bits := transmute(i64)fval
if types.bits(materialized, checker.target) == 32 {
bits = i64(transmute(u32)f32(fval))
+58 -11
View File
@@ -761,11 +761,20 @@ ct_coerce_value :: proc(state: ^Ct_State, id: Ct_Value_Id, expected: types.Type,
return ct_add_value(state, value), true
}
if types.is_float(expected, state.checker.target) {
if !integer_fits_float(value.integer, expected, state.checker.target) {
return INVALID_CT_VALUE, ct_failf(
state, .Not_Comptime, span, "integer constant %d is not exactly representable in %s",
value.integer, type_label(state.checker, expected),
)
}
return ct_add_value(state, Ct_Value{kind=.Float, type=expected, float=f64(value.integer)}), true
}
}
if value.kind == .Float && types.is_float(expected, state.checker.target) {
value.type = expected
if types.bits(expected, state.checker.target) == 32 {
value.float = f64(f32(value.float))
}
return ct_add_value(state, value), true
}
return INVALID_CT_VALUE, ct_failf(
@@ -1351,6 +1360,12 @@ ct_eval_expr :: proc(
if numeric_operation {
if representation, distinct_ok := types.distinct_scalar_backing(expected, store); distinct_ok {
if types.is_float(representation, checker.target) {
if !integer_fits_float(i128(expr.integer), representation, checker.target) {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(
state, .Not_Comptime, expr.span, "integer constant %d is not exactly representable in %s",
i128(expr.integer), type_label(checker, expected),
)
}
return ct_add_value(state, Ct_Value{
kind=.Float, type=expected, float=f64(expr.integer),
}), ct_flow(.Normal), true
@@ -1376,13 +1391,21 @@ ct_eval_expr :: proc(
if numeric_operation {
if representation, distinct_ok := types.distinct_scalar_backing(expected, store);
distinct_ok && types.is_float(representation, checker.target) {
value := transmute(f64)expr.integer
if types.bits(representation, checker.target) == 32 {
value = f64(f32(value))
}
return ct_add_value(state, Ct_Value{
kind=.Float, type=expected, float=transmute(f64)expr.integer,
kind=.Float, type=expected, float=value,
}), ct_flow(.Normal), true
}
}
value_type := expected if types.is_float(expected, checker.target) else types.F64
return ct_add_value(state, Ct_Value{kind=.Float, type=value_type, float=transmute(f64)expr.integer}), ct_flow(.Normal), true
value := transmute(f64)expr.integer
if types.bits(value_type, checker.target) == 32 {
value = f64(f32(value))
}
return ct_add_value(state, Ct_Value{kind=.Float, type=value_type, float=value}), ct_flow(.Normal), true
case .String:
return ct_add_value(state, Ct_Value{kind=.String, type=string_literal_type(checker, expr.integer), index=expr.integer}), ct_flow(.Normal), true
case .Name:
@@ -2679,6 +2702,30 @@ ct_eval_binary :: proc(state: ^Ct_State, op: ast.Expr_Kind, left_id, right_id: C
if distinct_operation && (!compatible || !types.is_float(operation.representation, state.checker.target)) {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "comptime binary expression requires compatible operands")
}
result_type := operation.result
if !distinct_operation {
result_type = types.widest(left.type, right.type)
if left.kind != .Float {
result_type = right.type
} else if right.kind != .Float {
result_type = left.type
}
if !types.is_float(result_type, state.checker.target) {
result_type = types.F64
}
}
representation := types.runtime_representation(result_type, store)
operands := [2]Ct_Value_Id{left_id, right_id}
for operand_id in operands {
operand := &state.values[operand_id]
if operand.kind != .Float &&
!integer_fits_float(operand.integer, representation, state.checker.target) {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(
state, .Not_Comptime, span, "integer constant %d is not exactly representable in %s",
operand.integer, type_label(state.checker, result_type),
)
}
}
lf := left.float if left.kind == .Float else f64(left.integer)
rf := right.float if right.kind == .Float else f64(right.integer)
if is_compare {
@@ -2702,14 +2749,7 @@ ct_eval_binary :: proc(state: ^Ct_State, op: ast.Expr_Kind, left_id, right_id: C
case:
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "comptime binary expression requires compatible operands")
}
result_type := operation.result
if !distinct_operation {
result_type = types.widest(left.type, right.type)
if !types.is_float(result_type, state.checker.target) {
result_type = types.F64
}
}
if types.bits(result_type, state.checker.target) == 32 {
if types.bits(representation, state.checker.target) == 32 {
result = f64(f32(result))
}
return ct_add_value(state, Ct_Value{kind=.Float, type=result_type, float=result}), ct_flow(.Normal), true
@@ -3144,13 +3184,20 @@ ct_scalar_cast :: proc(state: ^Ct_State, id: Ct_Value_Id, target: types.Type, sp
if value.kind == .Float {
if types.is_float(target, state.checker.target) {
value.type = target
if types.bits(target, state.checker.target) == 32 {
value.float = f64(f32(value.float))
}
return ct_add_value(state, value), ct_flow(.Normal), true
}
return ct_add_value(state, Ct_Value{kind=.Integer, type=target, integer=i128(value.float)}), ct_flow(.Normal), true
}
if ct_is_integer_like(value) {
if types.is_float(target, state.checker.target) {
return ct_add_value(state, Ct_Value{kind=.Float, type=target, float=f64(value.integer)}), ct_flow(.Normal), true
result := f64(value.integer)
if types.bits(target, state.checker.target) == 32 {
result = f64(f32(result))
}
return ct_add_value(state, Ct_Value{kind=.Float, type=target, float=result}), ct_flow(.Normal), true
}
value.kind = .Integer
value.type = target