diff --git a/compiler/checker/checker.odin b/compiler/checker/checker.odin index 884ca0a..9f67486 100644 --- a/compiler/checker/checker.odin +++ b/compiler/checker/checker.odin @@ -38,12 +38,12 @@ Infer_Local :: struct { declared: types.Type, statement: ast.Stmt_Id, mutable: bool, - // open_const/open_float mark a local whose initializer is an unannotated numeric - // constant: like an open-constant global, it can adopt a backward demand from use. - open_const: bool, - open_float: bool, - const_value: i128, - demanded: bool, + // Open numeric constants and value sources can adopt a backward demand from use. + open_const: bool, + open_float: bool, + open_value_source: bool, + const_value: i128, + demanded: bool, } Build_Local :: struct { @@ -5125,6 +5125,9 @@ infer_compound_expr :: proc( } field_expected := field.type if is_runtime_type(checker, field.type) else types.INVALID actual := infer_nested_expr(checker, keyed_expr.left, locals, pkg, file, demanded, local_types, field_expected) + _ = record_demand( + checker, keyed_expr.left, field_expected, locals, local_types, pkg, file, + ) if !is_inferred_record_field(checker, slot) { continue } @@ -5960,7 +5963,7 @@ infer_statements :: proc( block_type := declared_block if is_runtime_type(checker, declared_block) else types.INVALID local := Infer_Local{ name=statement.name, type=block_type, declared=declared_block, - statement=statement_id, mutable=!statement.immutable, + statement=statement_id, mutable=!statement.immutable, open_value_source=true, } append(locals, local) record_infer_local_type(local, local_types) @@ -6488,10 +6491,12 @@ merge_global_demand :: proc(checker: ^Checker, global: ast.Global_Id, demand: ty return changed } -// merge_local_demand records a concrete numeric demand onto an open-constant local. -// The first demand replaces the literal's default type; later demands may only widen -// within the chosen family. +// merge_local_demand records a concrete demand onto an unresolved value source or +// open numeric constant. Numeric demands retain their literal-family restrictions. merge_local_demand :: proc(checker: ^Checker, local: ^Infer_Local, demand: types.Type, local_types: []types.Type) -> bool { + if local.open_value_source { + return merge_infer_local_type(checker, local, demand, local_types) + } if !(local.open_const && open_integer_accepts_demand(checker, local.const_value, demand) || local.open_float && open_float_accepts_demand(checker, demand)) { return false @@ -10924,10 +10929,12 @@ build_block :: proc( // leaves `expr` invalid and stashes the block in `body`. Build it, then // declare the local from the yielded value (its type for an untyped `::`). if statement.expr == ast.INVALID_EXPR { - expected := types.INVALID - typed := is_runtime_type(checker, type_from_syntax(checker, statement.type, ctx.pkg, ctx.file)) - if typed { - expected = type_from_syntax(checker, statement.type, ctx.pkg, ctx.file) + declared := type_from_syntax(checker, statement.type, ctx.pkg, ctx.file) + expected := declared if is_runtime_type(checker, declared) else types.INVALID + if !is_runtime_type(checker, expected) && + int(statement_id) < len(ctx.local_types) && + is_runtime_type(checker, ctx.local_types[statement_id]) { + expected = ctx.local_types[statement_id] } value, value_type := build_value_source(ctx, &body, statement.body, expected, statement.span, statement.label, statement.value_control_flow) if _, found := find_build_local(ctx.locals^[duplicate_start:], statement.name); found { diff --git a/compiler/parser/parser.odin b/compiler/parser/parser.odin index 3613f6e..fb49343 100644 --- a/compiler/parser/parser.odin +++ b/compiler/parser/parser.odin @@ -29,6 +29,8 @@ Parser :: struct { // At the top level of if/for headers, `|` begins captures. Bitwise OR in // those headers remains available inside parentheses. capture_pipe: bool, + // A parenthesized `if` condition ends before a leading-dot brace-less body. + if_condition: bool, hidden_names: [dynamic]symbol.Id, } @@ -1331,6 +1333,10 @@ parse_expression_bp :: proc(parser: ^Parser, minimum_binding_power, nesting: int }) continue } + if parser.if_condition && parser.delimiter_depth == 0 && + parser.module.exprs[left].parenthesized && current(parser).kind == .Dot { + break + } if current(parser).kind == .Dot { advance(parser) if current(parser).kind == .Integer { @@ -2160,8 +2166,11 @@ parse_if :: proc(parser: ^Parser) -> ast.Stmt_Id { saved := parser.no_struct_literal parser.no_struct_literal = true saved_capture_pipe := parser.capture_pipe + saved_if_condition := parser.if_condition parser.capture_pipe = true + parser.if_condition = true condition := parse_expression(parser) + parser.if_condition = saved_if_condition parser.capture_pipe = saved_capture_pipe parser.no_struct_literal = saved captures: [dynamic]symbol.Id diff --git a/compiler_tests.odin b/compiler_tests.odin index e140614..2cc98f5 100644 --- a/compiler_tests.odin +++ b/compiler_tests.odin @@ -10812,6 +10812,33 @@ main func() void { testing.expect_value(t, module.statements[module.functions[0].body[1]].kind, ast.Stmt_Kind.If) } +@(test) +braceless_value_if_infers_shorthand_enum_from_use :: proc(t: ^testing.T) { + directory := "/tmp/brolang-test-value-if-enum" + main_path := "/tmp/brolang-test-value-if-enum/main.bro" + output := "/tmp/brolang-test-value-if-enum-output" + text := `Kind :: enum { first, second } +Box :: struct { kind Kind } +choose func(first bool) Kind { + kind :: if (first) .first else .second + return Box{kind = kind}.kind +} +main func() i32 { + if (choose(true) != .first) return 1 + if (choose(false) != .second) return 2 + return 0 +} +` + _ = os2.remove_all(directory) + defer _ = os2.remove_all(directory) + defer _ = os.remove(output) + testing.expect(t, os.make_directory(directory) == nil) + 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) parser_accepts_braceless_while_bodies :: proc(t: ^testing.T) { text := `ready func() bool { return false }