void-payloads, multi-pattern arms, and range patterns (match statements)

This commit is contained in:
2026-06-29 19:52:05 +02:00
parent 462632554c
commit c82f070d55
8 changed files with 499 additions and 106 deletions
+125
View File
@@ -2402,6 +2402,131 @@ main :: func() i32 {
testing.expect(t, found_unknown)
}
@(test)
match_range_arm_emits_bounds :: proc(t: ^testing.T) {
// A scalar range arm `lo..hi:` desugars to `key >= lo and key < hi` (inclusive uses
// `<=`), emitted as signed integer comparisons for an i32 subject.
text := `main :: func() i32 {
n i32 = 5
out i32 = 0
match n {
0..10: out = 1
10..=20: out = 2
else: out = 3
}
return out
}
`
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, "icmp sge i32")) // key >= lo
testing.expect(t, strings.contains(llvm_text, "icmp slt i32")) // key < hi (exclusive)
testing.expect(t, strings.contains(llvm_text, "icmp sle i32")) // key <= hi (inclusive)
}
@(test)
match_extended_misuse_is_diagnosed :: proc(t: ^testing.T) {
// Five rejected forms from milestone 22.5: (1) a capture on a void variant, (2) a value
// given to a void variant in construction, (3) a bare key on a non-void field, (4) a
// range pattern on an enum subject, and (5) a multi-pattern capture whose variants have
// different payload types. (Pointer capture on an rvalue subject is also rejected, but
// match-on-call-result is a separate pre-existing gap so it isn't exercised here.)
text := `Animal :: enum {
dog
cat
bird
}
Point :: struct {
x i32
y i32
}
Box :: union(enum) {
point Point
count i32
empty void
}
void_capture :: func(b Box) i32 {
match b {
.point |p|: { return p.x }
.count |c|: { return c }
.empty |x|: { return 0 }
}
return 0
}
void_value :: func() i32 {
b Box = Box{ empty = 5 }
return 0
}
bare_on_nonvoid :: func() i32 {
b Box = Box{ count }
return 0
}
range_on_enum :: func(a Animal) i32 {
match a {
0..2: { return 1 }
else: { return 0 }
}
return 0
}
incompatible_capture :: func(b Box) i32 {
match b {
.point, .count |v|: { return 0 }
.empty: { return 0 }
}
return 0
}
main :: func() i32 {
b Box = Box{ count = 1 }
return void_capture(b) + void_value() + bare_on_nonvoid() + range_on_enum(.dog) +
incompatible_capture(b)
}
`
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)
found_void_capture := false
found_void_value := false
found_bare := false
found_range_enum := false
found_incompatible := false
for diagnostic in diagnostics.items {
found_void_capture = found_void_capture || strings.contains(diagnostic.message, "void payload")
found_void_value = found_void_value || strings.contains(diagnostic.message, "void variant 'empty' takes no value")
found_bare = found_bare || strings.contains(diagnostic.message, "field 'count' requires a value")
found_range_enum = found_range_enum || strings.contains(diagnostic.message, "range patterns only apply to scalar")
found_incompatible = found_incompatible || strings.contains(diagnostic.message, "capture group with incompatible types")
}
testing.expect(t, found_void_capture)
testing.expect(t, found_void_value)
testing.expect(t, found_bare)
testing.expect(t, found_range_enum)
testing.expect(t, found_incompatible)
}
@(test)
yield_misuse_is_diagnosed :: proc(t: ^testing.T) {
// A value block that does not end in `yield`, and a `yield` nested inside an