broaden type inference from context (arithmetic expressions)

This commit is contained in:
2026-06-26 16:10:23 +02:00
parent ff69e1da83
commit cfc1b2cb42
3 changed files with 340 additions and 87 deletions
+117
View File
@@ -6629,3 +6629,120 @@ contextual_inference_rejects_local_constant_that_does_not_fit :: proc(t: ^testin
}
testing.expect(t, found)
}
@(test)
contextual_inference_flows_through_numeric_arithmetic :: proc(t: ^testing.T) {
text := `take_u16 :: func(v u16) void {}
take_f32 :: func(v f32) void {}
G :: 10
H u16 :: G + 2
GF :: 1.5
HF f32 :: GF + 2.5
CG :: 5
CFG :: 1.0
get :: func() f32 {
seed f32 :: 2.0
c :: seed + 3.0
d :: 4.0 + seed
return c + d
}
main :: func() void {
a :: 10
b u16 :: a + 2
x :: 1.5
y f32 :: x + 2.5
z f32 :: 2.5 + x
call_i :: 7
call_f :: 1.25
take_u16(call_i + 3)
take_f32(call_f + 3.0)
take_u16(CG + 1)
take_f32(CFG + 1.0)
_ = b
_ = y
_ = z
_ = H
_ = HF
_ = get()
}
`
source_file := source.Source{path="test.bro", text=text}
diagnostics := source.init_diagnostics(&source_file)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
stream := lexer.lex(&source_file, &diagnostics, &symbols)
defer delete(stream.items)
ast_module := parser.parse(&stream, &source_file, &diagnostics)
defer ast.destroy_module(&ast_module)
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
defer hir.destroy_module(&hir_module)
testing.expect_value(t, len(diagnostics.items), 0)
global_ok := 0
for global in hir_module.globals {
name := symbol.resolve(&symbols, global.name)
switch name {
case "G", "H", "CG":
global_ok += 1 if types.equal(global.type, types.U16) else 0
case "GF", "HF", "CFG":
global_ok += 1 if types.equal(global.type, types.F32) else 0
}
}
testing.expect_value(t, global_ok, 6)
main_ok := 0
get_ok := false
for function in hir_module.functions {
name := symbol.resolve(&symbols, function.name)
if name == "get" {
get_ok = types.equal(function.result, types.F32)
for local in function.locals {
local_name := symbol.resolve(&symbols, local.name)
if local_name == "c" || local_name == "d" {
main_ok += 1 if types.equal(local.type, types.F32) else 0
}
}
} else if name == "main" {
for local in function.locals {
local_name := symbol.resolve(&symbols, local.name)
switch local_name {
case "a", "call_i":
main_ok += 1 if types.equal(local.type, types.U16) else 0
case "x", "call_f":
main_ok += 1 if types.equal(local.type, types.F32) else 0
}
}
}
}
testing.expect(t, get_ok)
testing.expect_value(t, main_ok, 6)
}
@(test)
contextual_inference_rejects_non_fitting_arithmetic_demand :: proc(t: ^testing.T) {
text := `BIG :: 100000
C u8 :: BIG + 1
main :: func() void {
_ = C
}
`
source_file := source.Source{path="test.bro", text=text}
diagnostics := source.init_diagnostics(&source_file)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
stream := lexer.lex(&source_file, &diagnostics, &symbols)
defer delete(stream.items)
ast_module := parser.parse(&stream, &source_file, &diagnostics)
defer ast.destroy_module(&ast_module)
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
defer hir.destroy_module(&hir_module)
found := false
for diagnostic in diagnostics.items {
found = found || strings.contains(diagnostic.message, "cannot implicitly convert i32 to u8")
}
testing.expect(t, found)
}