implicit address-taking for array slices

This commit is contained in:
2026-06-23 18:44:59 +02:00
parent 18e2a96b76
commit c7e171c438
5 changed files with 102 additions and 6 deletions
+7
View File
@@ -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.
+20 -3
View File
@@ -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 {
+72
View File
@@ -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
+1 -1
View File
@@ -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
+2 -2
View File
@@ -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
}