fix: reject inexact integer literals in float context
This commit is contained in:
@@ -48,6 +48,7 @@ Code :: enum {
|
||||
missing_entry,
|
||||
invalid_entry,
|
||||
literal_out_of_range,
|
||||
inexact_integer_literal,
|
||||
invalid_escape,
|
||||
read_before_initialization,
|
||||
write_before_initialization,
|
||||
@@ -131,6 +132,7 @@ messages :: $enummap.init(Code, []u8, {
|
||||
missing_entry = "program requires a main function",
|
||||
invalid_entry = "main must be a function with no parameters",
|
||||
literal_out_of_range = "numeric literal is outside its runtime type's range",
|
||||
inexact_integer_literal = "integer literal is not exactly representable as float; use a floating literal to allow rounding",
|
||||
invalid_escape = "invalid string escape",
|
||||
read_before_initialization = "local value was read before initialization",
|
||||
write_before_initialization = "local value was assigned before initialization",
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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| {
|
||||
|
||||
Reference in New Issue
Block a user