fix: reject inexact integer literals in float context

This commit is contained in:
2026-10-24 01:02:20 +02:00
parent 9c1bd2a20e
commit 5fd8d9eb89
5 changed files with 58 additions and 4 deletions
+15
View File
@@ -872,6 +872,17 @@ integer_magnitude proc(text []u8, limit u64) ?u64 {
return value
}
@hide:file
exact_float_integer proc(text []u8, value f64) bool {
# every finite f64 integer has at most 309 decimal digits, plus a terminator.
decimal [310]mut u8 := undefined
count :: c.snprintf(ptrcast!((&decimal).ptr), c_ulong(decimal.len), "%.0f", value)
if (count < 0 or usize(count) >= decimal.len) return false
start := usize(0)
while (start + 1 < text.len and text[start] == '0') start += 1
return mem.eql(text[start..], decimal[..usize(count)])
}
@hide:file
lower_number proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
span :: ast.node_span(state.tree, id)
@@ -894,6 +905,10 @@ lower_number proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError
value :: f64(c.strtod(ptrcast!(buffer.ptr), null))
# libc reports ERANGE for representable subnormals as well as zero/infinity.
if (c.__error()?^ != c.ERANGE or (value > 0 and value < 1)) {
if (ast.expr_kind(state.tree, id) == .literal_int and !exact_float_integer(text, value)) {
trap(state, try diagnostics.add(state.diagnostics, .inexact_integer_literal, span), span)
return ir.no_value
}
return try constant(state, ir.Value{ type = .float, data = ir.ValueData{ real = value } }, span)
}
}
+38
View File
@@ -357,6 +357,44 @@ parses_float_context_without_integer_range_limits_and_rejects_float_overflow tes
try expect_trap(trapped, &diagnostic_store, .literal_out_of_range)
}
rejects_inexact_integer_literals_in_float_context test {
sources [4][]u8 :: [
"main proc() f64 {\n return 9007199254740993\n}\n",
"main proc() f64 {\n value :: 9007199254740993\n return value\n}\n",
"main proc() f64 {\n return 9007199254740993 / 1\n}\n",
"main proc() f64 {\n values [1]f64 :: [9007199254740993]\n return values[0]\n}\n",
]
for (sources) |source| {
diagnostic_store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&diagnostic_store)
module := try compile_owned(source, &diagnostic_store)
defer ir.deinit(&module)
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
defer interpreter.deinit(&outcome)
try expect_trap(outcome, &diagnostic_store, .inexact_integer_literal)
}
}
preserves_exact_large_integers_and_explicit_float_rounding test {
sources [3][]u8 :: [
"main proc() f64 {\n return 9007199254740994 - 9007199254740992\n}\n",
"main proc() f64 {\n return 9007199254740993.0 - 9007199254740992\n}\n",
"main proc() f64 {\n return 0003\n}\n",
]
expected [3]f64 :: [2, 0, 3]
for sources |source, i| {
diagnostic_store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&diagnostic_store)
module := try compile_owned(source, &diagnostic_store)
defer ir.deinit(&module)
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
defer interpreter.deinit(&outcome)
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
try testing.expect(outcome.trap == null)
try testing.expect_equal(expected[i], outcome.value.data.real)
}
}
accepts_representable_subnormals_and_rejects_underflow_to_zero test {
zeros [2]usize :: [307, 400]
for (zeros) |count| {