comptime expandable match statements

This commit is contained in:
2026-07-17 14:29:50 +02:00
parent 1f25e6cd1d
commit 97f1c06057
22 changed files with 120821 additions and 119318 deletions
+111 -15
View File
@@ -2404,7 +2404,7 @@ main func(init process.Init) void { _ = init }
}
@(test)
milestone_37_tuples_reflection_inline_for_and_debug_print_compile_and_run :: proc(t: ^testing.T) {
milestone_37_tuples_reflection_expand_for_and_debug_print_compile_and_run :: proc(t: ^testing.T) {
sources := source.init_store()
defer source.destroy_store(&sources)
diagnostics := source.init_store_diagnostics(&sources)
@@ -2445,6 +2445,102 @@ milestone_37_tuples_reflection_inline_for_and_debug_print_compile_and_run :: pro
testing.expect_value(t, string(stderr), "hello!\ntuple=40/bro, limits=-9223372036854775808/18446744073709551615")
}
@(test)
expanded_matches_and_tag_intrinsics_compile_and_run :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-expand"
defer _ = os.remove(output)
status := compiler_core.compile_package(
"examples/programs/expand", output, nil, target.DEFAULT, cimport.Options{}, ".",
)
testing.expect_value(t, status, 0)
state, stdout, stderr, _ := os2.process_exec(
os2.Process_Desc{command=[]string{output}}, context.allocator,
)
defer delete(stdout)
defer delete(stderr)
testing.expect_value(t, state.exit_code, 0)
testing.expect_value(t, string(stdout), "")
testing.expect_value(t, string(stderr), "")
}
@(test)
expanded_match_and_tag_diagnostics :: proc(t: ^testing.T) {
text := `E :: enum { a, b }
Plain :: union { value i32 }
main func() void {
inline for {1} |value| { _ = value }
n i32 = 1
match n { expand |value|: _ = value }
e E = .a
match e { expand |value, tag|: _ = value }
match e {
.a, .b: {}
expand |value|: _ = value
}
match e {
expand |value|: _ = value
.b: {}
}
match e {
else: {}
expand |value|: _ = value
}
u Plain = Plain{value = 1}
_ = tag!(u)
_ = tagname!(e)
match e { expand ||: {} }
match e { expand |a, b, c|: {} }
}
`
source_file := source.Source{path="expand_diagnostics.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)
found_old := false
found_subject := false
found_captures := false
found_redundant := false
found_after := false
found_missing := false
found_many := false
found_tag := false
found_tagname := false
for diagnostic in diagnostics.items {
message := diagnostic.message
found_old = found_old || strings.contains(message, "'inline for' was renamed to 'expand for'")
found_subject = found_subject || strings.contains(message, "'expand' requires an enum or tagged-union")
found_captures = found_captures || strings.contains(message, "requires exactly one capture")
found_redundant = found_redundant || strings.contains(message, "redundant 'expand'")
found_after = found_after || strings.contains(message, "arms after 'expand' are unreachable") || strings.contains(message, "arms after 'else' are unreachable")
found_missing = found_missing || strings.contains(message, "expected an expand value capture")
found_many = found_many || strings.contains(message, "at most two captures")
found_tag = found_tag || strings.contains(message, "tag! requires a tagged-union value")
found_tagname = found_tagname || strings.contains(message, "tagname! requires a comptime-known enum value")
}
testing.expect(t, found_old)
testing.expect(t, found_subject)
testing.expect(t, found_captures)
testing.expect(t, found_redundant)
testing.expect(t, found_after)
testing.expect(t, found_missing)
testing.expect(t, found_many)
testing.expect(t, found_tag)
testing.expect(t, found_tagname)
}
@(test)
milestone_37_format_errors_are_reported_at_comptime :: proc(t: ^testing.T) {
directory := "/tmp/brolang-test-format-errors"
@@ -2620,10 +2716,10 @@ main func() void {
}
@(test)
milestone_37_inline_loop_control_must_be_statically_resolvable :: proc(t: ^testing.T) {
milestone_37_expand_loop_control_must_be_statically_resolvable :: proc(t: ^testing.T) {
text := `main func() void {
total i32 = 0
inline for {1, 2} |value| {
expand for {1, 2} |value| {
if total == 0 {
break
}
@@ -2647,7 +2743,7 @@ milestone_37_inline_loop_control_must_be_statically_resolvable :: proc(t: ^testi
for diagnostic in diagnostics.items {
found = found || strings.contains(
diagnostic.message,
"break or continue targeting an inline loop must be compile-time-resolvable",
"break or continue targeting an expand loop must be compile-time-resolvable",
)
}
testing.expect(t, found)
@@ -2675,7 +2771,7 @@ read_initialized_sibling func() i32 {
answer :: $read_initialized_sibling()
main func() i32 {
total usize = 0
inline for make_tokens() |token| {
expand for make_tokens() |token| {
total += token.text.len + token.count
}
if answer != 42 or total != 8 {
@@ -2750,14 +2846,14 @@ main func() void {}
}
@(test)
milestone_37_inline_expansions_keep_distinct_call_resolutions :: proc(t: ^testing.T) {
milestone_37_expand_expansions_keep_distinct_call_resolutions :: proc(t: ^testing.T) {
text := `identity func($T type, value T) T {
return value
}
main func() i32 {
total i64 = 0
inline for {{i8(1), i16(2)}, {i32(3), i64(4)}} |row| {
inline for row |value| {
expand for {{i8(1), i16(2)}, {i32(3), i64(4)}} |row| {
expand for row |value| {
total += i64(identity(value))
}
}
@@ -2786,16 +2882,16 @@ main func() i32 {
}
@(test)
milestone_37_inline_control_prunes_inference_after_static_exit :: proc(t: ^testing.T) {
milestone_37_expand_control_prunes_inference_after_static_exit :: proc(t: ^testing.T) {
text := `take_i8 func(value i8) void { _ = value }
main func() void {
inline for {i8(1), "skip"} |value, index| {
expand for {i8(1), "skip"} |value, index| {
if index == 1 {
continue
}
take_i8(value)
}
inline for {i8(1), "stop"} |value, index| {
expand for {i8(1), "stop"} |value, index| {
if index == 1 {
break
}
@@ -2819,14 +2915,14 @@ main func() void {
}
@(test)
milestone_37_inline_match_specialization_prunes_unselected_arms :: proc(t: ^testing.T) {
milestone_37_expand_match_specialization_prunes_unselected_arms :: proc(t: ^testing.T) {
text := `Kind :: enum { integer, string, stop }
IntToken :: struct { kind Kind, value i8 }
StringToken :: struct { kind Kind, value []u8 }
StopToken :: struct { kind Kind, value bool }
take_i8 func(value i8) void { _ = value }
main func() void {
inline for {
expand for {
IntToken {kind = .integer, value = 1},
StringToken {kind = .string, value = "ok"},
StopToken {kind = .stop, value = false},
@@ -2840,7 +2936,7 @@ main func() void {
.stop: break
}
}
inline for {i8(2), "skip", "stop"} |value, index| {
expand for {i8(2), "skip", "stop"} |value, index| {
match index {
0: {}
1..=1, 7: continue
@@ -5416,7 +5512,7 @@ main func() i32 {
return a + b - 16
}
`
source_file := source.Source{path="inline_errors.bro", text=text}
source_file := source.Source{path="expand_errors.bro", text=text}
diagnostics := source.init_diagnostics(&source_file)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()