From f9ea04416907071e57333e00b27b2a6842ba5e2c Mon Sep 17 00:00:00 2001 From: hl-valdemar Date: Fri, 26 Jun 2026 17:47:14 +0200 Subject: [PATCH] broaden type inference from context (assignment statements) --- TODO.md | 2 +- compiler/checker/checker.odin | 8 +++++++- compiler_tests.odin | 27 +++++++++++++++++++++++++++ 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/TODO.md b/TODO.md index a4b55e3..b31ca76 100644 --- a/TODO.md +++ b/TODO.md @@ -253,7 +253,7 @@ ``` 17. for if statements, allow `if (cond) one-line statement` (instead of forcing either `if (cond) { block }` or `if cond { block }`) - - if statements without a bracketed body must enclose the condition in parentheses + - if statements without a bracketed body must wrap the condition in parentheses UNLESS it's a function call 18. add `defer` statement (inspired by zig) diff --git a/compiler/checker/checker.odin b/compiler/checker/checker.odin index 62cefeb..5874a7f 100644 --- a/compiler/checker/checker.odin +++ b/compiler/checker/checker.odin @@ -1727,7 +1727,7 @@ infer_statements :: proc( case .Assignment: value_type := infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded, local_types) if statement.target != ast.INVALID_EXPR { - _ = infer_expr(checker, statement.target, locals^[:], pkg, file, demanded, local_types) + target_type := infer_expr(checker, statement.target, locals^[:], pkg, file, demanded, local_types) target_expr := checker.ast_module.exprs[statement.target] if target_expr.kind == .Name && !symbol.is_valid(target_expr.qualifier) { if local_index, ok := find_infer_local_index(locals^[:], target_expr.name); ok && @@ -1735,10 +1735,16 @@ infer_statements :: proc( _ = merge_infer_local_type(checker, &locals^[local_index], value_type, local_types) } } + // Push the target's concrete type backward onto the RHS so a const used + // only in an assignment (e.g. `x += speed`) resolves, mirroring how + // declarations and returns demand their context (record_demand self-gates + // on a concrete demand and only touches fitting open slots). + _ = record_demand(checker, statement.expr, target_type, locals^[:], local_types, pkg, file) } else if statement.name != checker.sink_symbol { if local_index, ok := find_infer_local_index(locals^[:], statement.name); ok && locals^[local_index].mutable { _ = merge_infer_local_type(checker, &locals^[local_index], value_type, local_types) + _ = record_demand(checker, statement.expr, locals^[local_index].type, locals^[:], local_types, pkg, file) } } case .Expression: diff --git a/compiler_tests.odin b/compiler_tests.odin index 57e7975..ac2a786 100644 --- a/compiler_tests.odin +++ b/compiler_tests.odin @@ -6603,6 +6603,33 @@ main :: func() void { testing.expect(t, types.equal(hir_module.globals[0].type, types.U16)) } +@(test) +contextual_inference_flows_through_compound_assignment :: proc(t: ^testing.T) { + text := `main :: func() void { + s :: 5 + v u16 = 0 + v += s + _ = v +} +` + 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) + + // `s` is used only as the RHS of `v += s`. The assignment target's type (u16) is + // demanded backward onto `s`, resolving the open constant; without it the compound + // assignment would report "arithmetic requires compatible numeric operands". + testing.expect_value(t, len(diagnostics.items), 0) +} + @(test) contextual_inference_rejects_local_constant_that_does_not_fit :: proc(t: ^testing.T) { text := `main :: func() void {