condtional multi-unwrap and guard clauses
This commit is contained in:
+181
-2
@@ -3823,11 +3823,121 @@ conditional_unwrap_compiles_and_runs :: proc(t: ^testing.T) {
|
||||
status := compiler_core.compile_package("examples/programs/conditional_unwrap", output)
|
||||
testing.expect_value(t, status, 0)
|
||||
state := run_executable(output)
|
||||
// present scalar binds and unwraps (40), none takes the else (+2), a present
|
||||
// optional pointer binds and derefs (+0), a none optional pointer is skipped.
|
||||
// Single unwrap, guarded two/three-value unwraps, optional pointers, false
|
||||
// guards, and failed short-circuit chains preserve the expected total.
|
||||
testing.expect_value(t, state.exit_code, 42)
|
||||
}
|
||||
|
||||
@(test)
|
||||
conditional_unwrap_parser_captures_guard_and_parenthesized_chain :: proc(t: ^testing.T) {
|
||||
text := `main :: func() void {
|
||||
first ?i32 = 1
|
||||
second ?i32 = 2
|
||||
if (first and second) |a, b : a == 1 and b == 2| {
|
||||
_ = a
|
||||
_ = 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)
|
||||
module := parser.parse(&stream, &source_file, &diagnostics)
|
||||
defer ast.destroy_module(&module)
|
||||
|
||||
testing.expect_value(t, len(diagnostics.items), 0)
|
||||
statement := module.statements[module.functions[0].body[2]]
|
||||
testing.expect_value(t, statement.kind, ast.Stmt_Kind.If)
|
||||
testing.expect_value(t, len(statement.captures), 2)
|
||||
testing.expect_value(t, module.exprs[statement.expr].kind, ast.Expr_Kind.And)
|
||||
testing.expect(t, module.exprs[statement.expr].parenthesized)
|
||||
testing.expect(t, statement.guard != ast.INVALID_EXPR)
|
||||
testing.expect_value(t, module.exprs[statement.guard].kind, ast.Expr_Kind.And)
|
||||
}
|
||||
|
||||
@(test)
|
||||
conditional_unwrap_allows_sink_captures :: proc(t: ^testing.T) {
|
||||
text := `main :: func() void {
|
||||
first ?i32 = 1
|
||||
second ?i32 = 2
|
||||
if first and second |_, value : value == 2| {
|
||||
_ = value
|
||||
}
|
||||
if first |_| {}
|
||||
}
|
||||
`
|
||||
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)
|
||||
|
||||
testing.expect_value(t, len(diagnostics.items), 0)
|
||||
main := hir_module.functions[0]
|
||||
first_if := hir_module.statements[main.body[2]]
|
||||
second_if := hir_module.statements[main.body[3]]
|
||||
testing.expect_value(t, first_if.unwraps[0].local, hir.INVALID_LOCAL)
|
||||
testing.expect(t, first_if.unwraps[1].local != hir.INVALID_LOCAL)
|
||||
testing.expect_value(t, second_if.unwraps[0].local, hir.INVALID_LOCAL)
|
||||
}
|
||||
|
||||
@(test)
|
||||
parser_diagnoses_malformed_conditional_unwrap_captures_and_guards :: proc(t: ^testing.T) {
|
||||
cases := [4]struct {
|
||||
text: string,
|
||||
needle: string,
|
||||
}{
|
||||
{`main :: func() void {
|
||||
value ?i32 = 1
|
||||
if value || {}
|
||||
}
|
||||
`, "expected an unwrap capture name"},
|
||||
{`main :: func() void {
|
||||
value ?i32 = 1
|
||||
if value |capture,| {}
|
||||
}
|
||||
`, "expected an unwrap capture after ','"},
|
||||
{`main :: func() void {
|
||||
value ?i32 = 1
|
||||
if value |capture :| {}
|
||||
}
|
||||
`, "expected a guard expression after ':'"},
|
||||
{`main :: func() void {
|
||||
value ?i32 = 1
|
||||
if value |capture {}
|
||||
}
|
||||
`, "expected '|' to close unwrap captures"},
|
||||
}
|
||||
for test_case in cases {
|
||||
source_file := source.Source{path="test.bro", text=test_case.text}
|
||||
diagnostics := source.init_diagnostics(&source_file)
|
||||
symbols := symbol.init_table()
|
||||
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||
module := parser.parse(&stream, &source_file, &diagnostics)
|
||||
|
||||
found := false
|
||||
for diagnostic in diagnostics.items {
|
||||
found = found || strings.contains(diagnostic.message, test_case.needle)
|
||||
}
|
||||
testing.expect(t, found)
|
||||
|
||||
ast.destroy_module(&module)
|
||||
delete(stream.items)
|
||||
symbol.destroy_table(&symbols)
|
||||
source.destroy_diagnostics(&diagnostics)
|
||||
}
|
||||
}
|
||||
|
||||
@(test)
|
||||
if_unwrap_on_non_optional_is_diagnosed :: proc(t: ^testing.T) {
|
||||
text := `main :: func() i32 {
|
||||
@@ -3857,6 +3967,75 @@ if_unwrap_on_non_optional_is_diagnosed :: proc(t: ^testing.T) {
|
||||
testing.expect(t, found)
|
||||
}
|
||||
|
||||
@(test)
|
||||
conditional_unwrap_diagnostics_cover_counts_guards_and_capture_scope :: proc(t: ^testing.T) {
|
||||
text := `main :: func() void {
|
||||
first ?i32 = 1
|
||||
second ?i32 = 2
|
||||
plain i32 = 3
|
||||
|
||||
if first and second |one| {}
|
||||
if first |one, two| {}
|
||||
if first and second |same, same| {}
|
||||
if plain |value| {}
|
||||
if first |value : value| {}
|
||||
if first and earlier |earlier, later| {}
|
||||
|
||||
if first |value| {
|
||||
value = 2
|
||||
}
|
||||
if first |value| {
|
||||
value i32 = 2
|
||||
_ = value
|
||||
}
|
||||
if first |value| {
|
||||
_ = value
|
||||
} else {
|
||||
_ = value
|
||||
}
|
||||
_ = value
|
||||
}
|
||||
`
|
||||
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)
|
||||
|
||||
count_mismatches := 0
|
||||
duplicate := false
|
||||
non_optional := false
|
||||
guard := false
|
||||
outer_operand_scope := false
|
||||
immutable := false
|
||||
redeclaration := false
|
||||
capture_scope := 0
|
||||
for diagnostic in diagnostics.items {
|
||||
count_mismatches += 1 if strings.contains(diagnostic.message, "unwrap has") else 0
|
||||
duplicate = duplicate || strings.contains(diagnostic.message, "unwrap captures must have distinct names")
|
||||
non_optional = non_optional || strings.contains(diagnostic.message, "unwrap requires an optional value")
|
||||
guard = guard || strings.contains(diagnostic.message, "unwrap guard must be a bool")
|
||||
outer_operand_scope = outer_operand_scope || strings.contains(diagnostic.message, "unresolved global 'earlier'")
|
||||
immutable = immutable || strings.contains(diagnostic.message, "cannot assign immutable local 'value'")
|
||||
redeclaration = redeclaration || strings.contains(diagnostic.message, "duplicate local 'value'")
|
||||
capture_scope += 1 if strings.contains(diagnostic.message, "unresolved global 'value'") else 0
|
||||
}
|
||||
testing.expect_value(t, count_mismatches, 2)
|
||||
testing.expect(t, duplicate)
|
||||
testing.expect(t, non_optional)
|
||||
testing.expect(t, guard)
|
||||
testing.expect(t, outer_operand_scope)
|
||||
testing.expect(t, immutable)
|
||||
testing.expect(t, redeclaration)
|
||||
testing.expect_value(t, capture_scope, 2)
|
||||
}
|
||||
|
||||
@(test)
|
||||
if_unwrap_binding_is_scoped_to_then_block :: proc(t: ^testing.T) {
|
||||
// The binding `v` is usable in the then-block but not in the else-block.
|
||||
|
||||
Reference in New Issue
Block a user