match statements
This commit is contained in:
@@ -2251,6 +2251,157 @@ main :: func() i32 {
|
||||
testing.expect(t, found_variant)
|
||||
}
|
||||
|
||||
@(test)
|
||||
match_compiles_and_runs :: proc(t: ^testing.T) {
|
||||
output := "/tmp/brolang-test-match"
|
||||
defer _ = os.remove(output)
|
||||
status := compiler_core.compile_package("examples/programs/match", output)
|
||||
testing.expect_value(t, status, 0)
|
||||
state := run_executable(output)
|
||||
// Exercises every match form — tagged-union statement match with payload capture,
|
||||
// tagged-union value match (implicit + explicit yield), enum statement/value match,
|
||||
// and integer match with `else` — all summed to a self-checking 0 on success.
|
||||
testing.expect_value(t, state.exit_code, 0)
|
||||
}
|
||||
|
||||
@(test)
|
||||
match_dispatches_on_the_tag :: proc(t: ^testing.T) {
|
||||
// A tagged-union match desugars to: read the discriminant once (a load of the tag
|
||||
// enum at the union's offset 0), then compare it against each variant's tag value.
|
||||
// `Animal` is dense in a u8 backing (dog=0, bird=2), so the dog arm compares the
|
||||
// loaded i8 tag against 0.
|
||||
text := `Animal :: enum {
|
||||
dog
|
||||
cat
|
||||
bird
|
||||
}
|
||||
Data :: union(Animal) {
|
||||
dog i32
|
||||
bird i32
|
||||
}
|
||||
main :: func() i32 {
|
||||
d Data = Data{ bird = 7 }
|
||||
out i32 = 0
|
||||
match d {
|
||||
.dog |v|: out = v
|
||||
.bird |v|: out = v + 1
|
||||
}
|
||||
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)
|
||||
// The tag is loaded as an i8 and compared (icmp eq) against the dog variant's tag (0).
|
||||
testing.expect(t, strings.contains(llvm_text, "load i8"))
|
||||
testing.expect(t, strings.contains(llvm_text, "icmp eq i8"))
|
||||
}
|
||||
|
||||
@(test)
|
||||
match_misuse_is_diagnosed :: proc(t: ^testing.T) {
|
||||
// Five rejected matches: (1) a non-exhaustive enum match with no `else`, (2) a scalar
|
||||
// match missing its mandatory `else`, (3) a payload capture on a non-union arm, (4) a
|
||||
// redundant `else` on an already-exhaustive match, and (5) an unknown variant.
|
||||
text := `Animal :: enum {
|
||||
dog
|
||||
cat
|
||||
bird
|
||||
}
|
||||
Data :: union(Animal) {
|
||||
dog i32
|
||||
bird i32
|
||||
}
|
||||
not_exhaustive :: func(a Animal) i32 {
|
||||
match a {
|
||||
.dog: { return 1 }
|
||||
.cat: { return 2 }
|
||||
}
|
||||
return 0
|
||||
}
|
||||
missing_else :: func(n i32) i32 {
|
||||
match n {
|
||||
0: { return 1 }
|
||||
1: { return 2 }
|
||||
}
|
||||
return 0
|
||||
}
|
||||
bad_capture :: func(a Animal) i32 {
|
||||
match a {
|
||||
.dog |v|: { return 1 }
|
||||
.cat: { return 2 }
|
||||
.bird: { return 3 }
|
||||
}
|
||||
return 0
|
||||
}
|
||||
redundant_else :: func(a Animal) i32 {
|
||||
match a {
|
||||
.dog: { return 1 }
|
||||
.cat: { return 2 }
|
||||
.bird: { return 3 }
|
||||
else: { return 4 }
|
||||
}
|
||||
return 0
|
||||
}
|
||||
unknown_variant :: func(d Data) i32 {
|
||||
match d {
|
||||
.dog: { return 1 }
|
||||
.snake: { return 2 }
|
||||
else: { return 3 }
|
||||
}
|
||||
return 0
|
||||
}
|
||||
main :: func() i32 {
|
||||
# Functions are specialized on use, so call each so its body is type-checked.
|
||||
d Data = Data{ dog = 0 }
|
||||
return not_exhaustive(.dog) + missing_else(0) + bad_capture(.dog) +
|
||||
redundant_else(.dog) + unknown_variant(d)
|
||||
}
|
||||
`
|
||||
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_exhaustive := false
|
||||
found_missing_else := false
|
||||
found_capture := false
|
||||
found_redundant := false
|
||||
found_unknown := false
|
||||
for diagnostic in diagnostics.items {
|
||||
found_exhaustive = found_exhaustive || strings.contains(diagnostic.message, "is not exhaustive")
|
||||
found_missing_else = found_missing_else || strings.contains(diagnostic.message, "requires an 'else' arm")
|
||||
found_capture = found_capture || strings.contains(diagnostic.message, "only tagged-union variants can capture")
|
||||
found_redundant = found_redundant || strings.contains(diagnostic.message, "redundant 'else'")
|
||||
found_unknown = found_unknown || strings.contains(diagnostic.message, "unknown variant '.snake'")
|
||||
}
|
||||
testing.expect(t, found_exhaustive)
|
||||
testing.expect(t, found_missing_else)
|
||||
testing.expect(t, found_capture)
|
||||
testing.expect(t, found_redundant)
|
||||
testing.expect(t, found_unknown)
|
||||
}
|
||||
|
||||
@(test)
|
||||
yield_misuse_is_diagnosed :: proc(t: ^testing.T) {
|
||||
// A value block that does not end in `yield`, and a `yield` nested inside an
|
||||
|
||||
Reference in New Issue
Block a user