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
+137
View File
@@ -15686,6 +15686,143 @@ main func() void {
testing.expect(t, found)
}
@(test)
contextual_integer_float_precision_loss_is_diagnosed :: proc(t: ^testing.T) {
Boundary :: struct {scalar, integer: string}
boundaries := []Boundary{
{"f32", "16777217"},
{"f32", "-16777217"},
{"f64", "9007199254740993"},
{"f64", "-9007199254740993"},
}
Context :: struct {declarations, body: string}
contexts := []Context{
{"bad Scalar :: VALUE", "_ = bad"},
{"bad Scalar := VALUE", "_ = bad"},
{"bad Scalar :: $VALUE", "_ = bad"},
{"bad Scalar :: ${ yield VALUE }", "_ = bad"},
{"", "value Scalar := VALUE\n_ = value"},
{"", "local :: VALUE\nvalue Scalar := local\n_ = value"},
{"", "local :: VALUE + 2 - 2\nvalue Scalar := local\n_ = value"},
{"folded :: VALUE + 2 - 2", "value Scalar := folded\n_ = value"},
{"", "value Scalar := G\n_ = value"},
{"", "value Scalar := VALUE + 2 - 2\n_ = value"},
{"", "_ = take(VALUE)"},
{"", "return VALUE"},
{"", "values [1]Scalar := [VALUE]\n_ = values"},
{"", "value ?Scalar := VALUE\n_ = value"},
{"", "value ?Scalar := some!(VALUE)\n_ = value"},
{"", "seed Nominal := Nominal(0.0)\n_ = seed + VALUE"},
{"", "seed Scalar := 0.0\n_ = seed + VALUE"},
{"", "local :: VALUE\nseed Scalar := 0.0\n_ = local + seed"},
{"", "seed Scalar := 0.0\n_ = seed == VALUE"},
}
for boundary in boundaries {
for test_context in contexts {
body, body_allocated := strings.replace_all(test_context.body, "VALUE", boundary.integer)
declarations, declarations_allocated := strings.replace_all(test_context.declarations, "VALUE", boundary.integer)
evaluation_modes := [2]bool{false, true}
for comptime in evaluation_modes {
invocation := "$exercise()" if comptime else "exercise()"
text := fmt.aprintf(
"Scalar :: alias %s\nNominal :: distinct %s\nG :: %s\n%s\n"+
"take func(value Scalar) Scalar {{ return value }}\n"+
"exercise func() Scalar {{\n%s\nreturn 0\n}}\n"+
"main func() void {{ _ = %s }}\n",
boundary.scalar, boundary.scalar, boundary.integer, declarations, body, invocation,
)
source_file := source.Source{path="test.bro", text=text}
diagnostics := source.init_diagnostics(&source_file)
symbols := symbol.init_table()
stream := lexer.lex(&source_file, &diagnostics, &symbols)
ast_module := parser.parse(&stream, &source_file, &diagnostics)
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
found := false
for diagnostic in diagnostics.items {
found = found || strings.contains(diagnostic.message, "not exactly representable")
}
testing.expect(t, found, text)
hir.destroy_module(&hir_module)
ast.destroy_module(&ast_module)
delete(stream.items)
symbol.destroy_table(&symbols)
source.destroy_diagnostics(&diagnostics)
delete(text)
}
if body_allocated { delete(body) }
if declarations_allocated { delete(declarations) }
}
}
}
@(test)
exact_contextual_integer_floats_and_explicit_rounding_compile_and_run :: proc(t: ^testing.T) {
directory := "/tmp/brolang-test-exact-integer-floats"
main_path := "/tmp/brolang-test-exact-integer-floats/main.bro"
output := "/tmp/brolang-test-exact-integer-floats-output"
_ = os2.remove_all(directory)
defer _ = os2.remove_all(directory)
defer _ = os.remove(output)
testing.expect(t, os2.make_directory_all(directory) == nil)
text := `Nominal :: distinct f32
G :: 16777216
H :: 9007199254740992
take32 func(value f32) f32 { return value }
take64 func(value f64) f64 { return value }
give32 func() f32 { return -16777216 }
give64 func() f64 { return -9007199254740992 }
exercise func() i32 {
local32 :: 16777216
local64 :: 9007199254740992
a f32 := local32
b f64 := local64
c f32 := G
d f64 := H
folded32 f32 := 16777215 + 1
folded64 f64 := 9007199254740991 + 1
power32 f32 := 9223372036854775808
power64 f64 := -9223372036854775808
next32 f32 := 16777218
next64 f64 := 9007199254740994
values32 [2]f32 := [16777216, -16777216]
values64 [2]f64 := [9007199254740992, -9007199254740992]
optional32 ?f32 := 16777216
optional64 ?f64 := some!(-9007199254740992)
nominal := Nominal(0.0) + 16777216
literal32 f32 := 16777217.0
literal64 f64 := 9007199254740993.0
cast32 f32 := f32(16777217)
cast64 f64 := f64(9007199254740993)
negative_literal64 f64 := -9007199254740993.0
negative_cast32 f32 := f32(-16777217)
negative_cast64 f64 := f64(-9007199254740993)
if a != 16777216.0 or b != 9007199254740992.0 { return 1 }
if c != a or d != b or folded32 != a or folded64 != b { return 2 }
if take32(16777216) != a or take64(9007199254740992) != b { return 3 }
if give32() != -a or give64() != -b { return 4 }
if values32[1] != -a or values64[1] != -b { return 5 }
if (optional32 orelse 0.0) != a or (optional64 orelse 0.0) != -b { return 6 }
if f32(nominal) != a { return 7 }
if literal32 != a or literal64 != b { return 8 }
if cast32 != a or cast64 != b { return 9 }
if power32 != 9223372036854775808.0 or power64 != -9223372036854775808.0 { return 10 }
if next32 != 16777218.0 or next64 != 9007199254740994.0 { return 11 }
if negative_literal64 != -b { return 12 }
if negative_cast32 != -a or negative_cast64 != -b { return 13 }
return 0
}
main func() i32 {
checked i32 :: $exercise()
if checked != 0 { return checked }
return exercise()
}
`
testing.expect(t, os.write_entire_file(main_path, transmute([]byte)text))
testing.expect_value(t, compiler_core.compile_package(directory, output), 0)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 0)
}
@(test)
missing_qualified_signature_symbol_reports_one_root_error :: proc(t: ^testing.T) {
directory := "/tmp/brolang-test-root-diagnostic"