add constcast and immutable free

This commit is contained in:
2026-07-22 00:44:35 +02:00
parent 5f343ad2d3
commit 9c6215776e
12 changed files with 256 additions and 11 deletions
+142
View File
@@ -2184,6 +2184,148 @@ main func() void {
testing.expect(t, found_target_type)
}
@(test)
constcast_restores_pointer_and_slice_mutability :: proc(t: ^testing.T) {
text := `main func() i32 {
values [2]mut u8 = [1, 2]
immutable_slice []u8 = values[..]
mutable_slice []mut u8 = constcast!(immutable_slice)
mutable_slice[0] = 3
immutable_many *u8 = immutable_slice.ptr
mutable_many *mut u8 = constcast!(immutable_many)
mutable_many[1] = 4
number i32 = 5
immutable_single @i32 = &number
mutable_single @mut i32 = constcast!(immutable_single)
mutable_single^ = 6
maybe ?@i32 = immutable_single
mutable_maybe ?@mut i32 = constcast!(maybe)
if mutable_maybe |pointer| { pointer^ = 7 }
already_mutable []mut u8 = constcast!(mutable_slice)
if already_mutable[0] != 3 or already_mutable[1] != 4 or number != 7 { return 1 }
return 0
}
`
source_file := source.Source{path="constcast.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)
hir_casts, ir_casts := 0, 0
for expr in hir_module.exprs {
hir_casts += 1 if expr.kind == .Const_Cast else 0
}
for function in ir_module.functions {
for instruction in function.instructions {
ir_casts += 1 if instruction.op == .Const_Cast else 0
}
}
testing.expect_value(t, len(diagnostics.items), 0)
testing.expect_value(t, hir_casts, 5)
testing.expect_value(t, ir_casts, 5)
directory := "/tmp/brolang-test-constcast"
main_path := "/tmp/brolang-test-constcast/main.bro"
output := "/tmp/brolang-test-constcast-output"
_ = 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)
invalid_constcasts_are_rejected :: proc(t: ^testing.T) {
text := `main func() void {
values [2]mut u8 = [1, 2]
_ = constcast!()
_ = constcast!(1, 2)
_ = constcast!(1)
_ = constcast!(values)
}
`
source_file := source.Source{path="invalid_constcast.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)
bad_arity, bad_operand := false, false
for diagnostic in diagnostics.items {
bad_arity = bad_arity || strings.contains(diagnostic.message, "constcast! expects 1 argument")
bad_operand = bad_operand || strings.contains(diagnostic.message, "constcast! operand must be a pointer, optional pointer, or slice")
}
testing.expect(t, bad_arity && bad_operand)
}
@(test)
immutable_allocations_can_be_freed_and_constcast_keeps_slice_bounds_checks :: proc(t: ^testing.T) {
free_text := `mem :: import "@std/mem"
main func() i32 {
memory []mut u8 :: mem.alloc(u8, mem.c_allocator, 4) catch |_| { return 1 }
memory[0] = 42
immutable []u8 = memory
mem.free(mem.c_allocator, immutable)
empty []u8 = mem.empty(u8)
mem.free(mem.c_allocator, empty)
return 0
}
`
directory := "/tmp/brolang-test-immutable-free"
main_path := "/tmp/brolang-test-immutable-free/main.bro"
output := "/tmp/brolang-test-immutable-free-output"
_ = 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)free_text))
testing.expect_value(t, compiler_core.compile_package(
directory, output, nil, target.DEFAULT, cimport.Options{}, ".",
), 0)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 0)
bounds_text := `main func() void {
values [2]mut u8 = [1, 2]
immutable []u8 = values[..]
mutable []mut u8 = constcast!(immutable)
_ = mutable[mutable.len]
}
`
testing.expect(t, os.write_entire_file(main_path, transmute([]byte)bounds_text))
testing.expect_value(t, compiler_core.compile_package(directory, output), 0)
bounds_state, stdout, stderr, err := os2.process_exec(
os2.Process_Desc{command=[]string{output}}, context.allocator,
)
defer delete(stdout)
defer delete(stderr)
testing.expect(t, err == nil)
testing.expect(t, !bounds_state.success)
testing.expect(t, strings.contains(string(stderr), "index out of bounds"))
}
@(test)
old_intrinsic_spellings_are_not_recognized :: proc(t: ^testing.T) {
text := `main func() void {