diff --git a/TODO.md b/TODO.md index 156e60e..656e67b 100644 --- a/TODO.md +++ b/TODO.md @@ -144,6 +144,13 @@ 9. allow pointer field access pass-through (implemented) - having a pointer (`ptr`) to a struct, we should allow access through `ptr.field` as opposed to mandating `ptr^.field` +10. make slice expressions on array variables implicitly address-taking (implemented) + - zig's slice expression on an array variable handles the address-taking implicitly (nice ergonomics) + - `arr[a..b]` on an array variable now slices without the explicit `&`; the + explicit `(&arr)[a..b]` pointer-to-array form keeps working unchanged + - array rvalues (e.g. a by-value array return) are materialized into a + temporary before slicing, matching the for-loop iterable lowering + ## A word on multi-unwrap Unwrap multiple optionals with `and`. This **short-circuits**: if the first optional is none, subsequent expressions are not evaluated. diff --git a/compiler/lower/lower.odin b/compiler/lower/lower.odin index 9ec63f9..d153c3c 100644 --- a/compiler/lower/lower.odin +++ b/compiler/lower/lower.odin @@ -244,9 +244,26 @@ lower_compound_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instructi diagnostic=source.INVALID_DIAGNOSTIC, }) case .Slice: - container := lower_nested_expr(state, expr.left) - if types.is_array(state.hir_module.exprs[expr.left].type, &state.hir_module.types) { - container = lower_location(state, expr.left) + left_type := state.hir_module.exprs[expr.left].type + container: ir.Instruction_Id + if types.is_array(left_type, &state.hir_module.types) { + if hir_expr_is_location(state, expr.left) { + container = lower_location(state, expr.left) + } else { + value := lower_nested_expr(state, expr.left) + container = append_instruction(state, ir.Instruction{ + op=.Alloca, span=expr.span, type=left_type, + target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, + diagnostic=source.INVALID_DIAGNOSTIC, + }) + append_instruction(state, ir.Instruction{ + op=.Store, span=expr.span, type=left_type, + target=ir.INVALID_REF, a=container, b=value, + diagnostic=source.INVALID_DIAGNOSTIC, + }) + } + } else { + container = lower_nested_expr(state, expr.left) } args := make([]ir.Instruction_Id, len(expr.args), state.allocator) for arg, index in expr.args { diff --git a/compiler_tests.odin b/compiler_tests.odin index 944fa9c..681726f 100644 --- a/compiler_tests.odin +++ b/compiler_tests.odin @@ -1017,6 +1017,78 @@ immutable_pointer_and_slice_bindings_preserve_mutable_pointees :: proc(t: ^testi testing.expect_value(t, len(diagnostics.items), 0) } +@(test) +slicing_an_array_variable_takes_its_address_implicitly :: proc(t: ^testing.T) { + // Milestone 10: `arr[a..b]` on an array variable slices without an explicit + // `&`. The slice operand must be a pointer to the array (getelementptr off a + // `ptr`), not the array value. + text := `sink :: func(s []i32) i32 { + return s[0] +} +main :: func() i32 { + arr [4]i32 = [10, 20, 30, 40] + full :: sink(arr[..]) + part :: sink(arr[1..3]) + return full + part +} +` + 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) + ir_module := lower.lower(&hir_module) + defer ir.destroy_module(&ir_module) + llvm_text := llvm.emit(&ir_module, &diagnostics, &symbols) + defer delete(llvm_text) + + testing.expect_value(t, len(diagnostics.items), 0) + testing.expect(t, strings.contains(llvm_text, "getelementptr [4 x i32], ptr")) +} + +@(test) +slicing_an_array_rvalue_materializes_a_temporary :: proc(t: ^testing.T) { + // The checker accepts slicing a non-location array (here, a by-value array + // return). Lowering must store it into a temporary and slice that address; + // otherwise the slice operand is an array value, which is an invalid pointer. + text := `make_arr :: func() [4]i32 { + return [1, 2, 3, 4] +} +sink :: func(s []i32) i32 { + return s[0] +} +main :: func() i32 { + return sink(make_arr()[0..]) +} +` + 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) + ir_module := lower.lower(&hir_module) + defer ir.destroy_module(&ir_module) + llvm_text := llvm.emit(&ir_module, &diagnostics, &symbols) + defer delete(llvm_text) + + testing.expect_value(t, len(diagnostics.items), 0) + // The materialization store of the array rvalue into its temporary. + testing.expect(t, strings.contains(llvm_text, "store [4 x i32]")) + testing.expect(t, strings.contains(llvm_text, "getelementptr [4 x i32], ptr")) +} + @(test) c_variadic_calls_promote_extras_and_emit_variadic_llvm :: proc(t: ^testing.T) { text := `variadic :: c_func(tag c_int, ...) c_int diff --git a/examples/programs/for_loop/main.bro b/examples/programs/for_loop/main.bro index c63d038..2e91106 100644 --- a/examples/programs/for_loop/main.bro +++ b/examples/programs/for_loop/main.bro @@ -17,7 +17,7 @@ main :: func() i32 { item^ = item^ + 1 } - view []mut i32 = (&items)[..] + view []mut i32 = items[..] for view |@item, index| { item^ = item^ + 1 _ = index diff --git a/testbed/main.bro b/testbed/main.bro index c6597a9..dbfed2c 100644 --- a/testbed/main.bro +++ b/testbed/main.bro @@ -71,14 +71,14 @@ main :: func() i32 { Player { id = PlayerID(4), name = "Grace", tier = .gold, score = 33, streak = 5, active = true }, ] - apply_decay((&players)[..]) + apply_decay(players[..]) for (&players) |@player, index| { score :: projected_score(player^) _ = printf("%d: %s projected=%d\n", index, player.name, score) } - if best_player((&players)[..]) |winner : projected_score(winner^) >= 80| { + if best_player(players[..]) |winner : projected_score(winner^) >= 80| { _ = printf("winner: %s\n", winner.name) return 0 }