feat: execute break and continue in while loops
This commit is contained in:
@@ -40,6 +40,7 @@ Role :: enum {
|
||||
if_stmt,
|
||||
assignment_stmt,
|
||||
while_stmt,
|
||||
loop_control,
|
||||
}
|
||||
|
||||
@hide:file
|
||||
@@ -510,11 +511,11 @@ collect_function proc(state @mut State, id ast.NodeId) void ! (mem.AllocError |
|
||||
state.cells[usize(id)].type = invalid_type(binding.data.diagnostic)
|
||||
state.cells[usize(id)].type.function = id
|
||||
}
|
||||
try collect_block(state, function.body, function.return_type)
|
||||
try collect_block(state, function.body, function.return_type, false)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
collect_block proc(state @mut State, id, return_annotation ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
collect_block proc(state @mut State, id, return_annotation ast.NodeId, in_loop bool) void ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
tree :: state.tree
|
||||
state.cells[usize(id)].role = .body
|
||||
for (0..ast.block_statement_count(tree, id)) |i| {
|
||||
@@ -540,10 +541,10 @@ collect_block proc(state @mut State, id, return_annotation ast.NodeId) void ! (m
|
||||
state.cells[usize(statement)].type = Type{ kind = .void }
|
||||
try collect_expression(state, conditional.condition)
|
||||
try depend(state, conditional.condition, statement)
|
||||
try collect_block(state, conditional.then_block, return_annotation)
|
||||
try collect_block(state, conditional.then_block, return_annotation, in_loop)
|
||||
try depend(state, conditional.then_block, id)
|
||||
if (conditional.else_block) |other| {
|
||||
try collect_block(state, other, return_annotation)
|
||||
try collect_block(state, other, return_annotation, in_loop)
|
||||
try depend(state, other, id)
|
||||
}
|
||||
}
|
||||
@@ -573,9 +574,22 @@ collect_block proc(state @mut State, id, return_annotation ast.NodeId) void ! (m
|
||||
state.cells[usize(statement)].type = Type{ kind = .void }
|
||||
try collect_expression(state, loop.condition)
|
||||
try depend(state, loop.condition, statement)
|
||||
try collect_block(state, loop.body, return_annotation)
|
||||
try collect_block(state, loop.body, return_annotation, true)
|
||||
try depend(state, loop.body, id)
|
||||
}
|
||||
.break_stmt, .continue_stmt: {
|
||||
state.cells[usize(statement)].role = .loop_control
|
||||
state.cells[usize(statement)].type = Type{ kind = .void }
|
||||
if (!in_loop) {
|
||||
code :: if (ast.stmt_kind(tree, statement) == .break_stmt)
|
||||
diagnostics.Code.break_outside_loop
|
||||
else
|
||||
diagnostics.Code.continue_outside_loop
|
||||
state.cells[usize(statement)].type = invalid_type(try diagnostics.add(
|
||||
state.diagnostics, code, ast.node_span(tree, statement),
|
||||
))
|
||||
}
|
||||
}
|
||||
.invalid: collect_invalid(state, statement)
|
||||
}
|
||||
source :: if (ast.stmt_kind(tree, statement) == .expression) ast.statement_expr(tree, statement) else statement
|
||||
@@ -889,7 +903,7 @@ block_falls_through proc(state @State, id ast.NodeId) bool {
|
||||
source :: if (kind == .expression) ast.statement_expr(tree, statement) else statement
|
||||
if (state.types[usize(source)].kind == .invalid) return false
|
||||
match kind {
|
||||
.return_stmt, .invalid: return false
|
||||
.return_stmt, .break_stmt, .continue_stmt, .invalid: return false
|
||||
.if_stmt: {
|
||||
conditional :: ast.if_stmt(tree, statement)
|
||||
then_falls :: block_falls_through(state, conditional.then_block)
|
||||
|
||||
@@ -1548,3 +1548,147 @@ short_circuit_skip_paths_do_not_guarantee_a_return test {
|
||||
try testing.expect_equal(3, code_count(&diagnostic_store, .invalid_operand))
|
||||
try testing.expect_equal(18, diagnostics.count(&diagnostic_store))
|
||||
}
|
||||
|
||||
loop_controls_follow_lexical_bodies_and_check_unreachable_code test {
|
||||
strpool.strings = strpool.init(mem.c_allocator)
|
||||
defer strpool.deinit(&strpool.strings)
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
source ::
|
||||
`scopes proc(flag bool) void {
|
||||
` while flag {
|
||||
` if (flag) break else continue
|
||||
` while flag {
|
||||
` if (flag) continue else break
|
||||
` }
|
||||
` absent
|
||||
` }
|
||||
` break
|
||||
` continue
|
||||
`}
|
||||
`caller proc() void {
|
||||
` while true {
|
||||
` callee()
|
||||
` break
|
||||
` }
|
||||
`}
|
||||
`callee proc() void { continue }
|
||||
`dead proc() void {
|
||||
` return
|
||||
` break
|
||||
` continue
|
||||
`}
|
||||
`arms proc() void {
|
||||
` if (false) break else continue
|
||||
`}
|
||||
scan_state := lexer.init(mem.c_allocator)
|
||||
defer lexer.deinit(&scan_state)
|
||||
try lexer.scan(&scan_state, source, &diagnostic_store)
|
||||
tree := try parser.parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
|
||||
defer ast.deinit(&tree)
|
||||
resolution := try sema.resolve(mem.c_allocator, &tree, &diagnostic_store)
|
||||
defer sema.deinit(&resolution)
|
||||
result := try check(mem.c_allocator, &tree, &resolution, &diagnostic_store)
|
||||
defer deinit(&result)
|
||||
|
||||
scopes_body :: ast.function_decl(&tree, ast.package_decl(&tree, 0)).body
|
||||
outer_statement :: ast.block_statement(&tree, scopes_body, 0)
|
||||
outer :: ast.while_stmt(&tree, outer_statement)
|
||||
outer_if :: ast.if_stmt(&tree, ast.block_statement(&tree, outer.body, 0))
|
||||
inner_statement :: ast.block_statement(&tree, outer.body, 1)
|
||||
inner :: ast.while_stmt(&tree, inner_statement)
|
||||
inner_if :: ast.if_stmt(&tree, ast.block_statement(&tree, inner.body, 0))
|
||||
caller_body :: ast.function_decl(&tree, ast.package_decl(&tree, 1)).body
|
||||
caller_loop :: ast.while_stmt(&tree, ast.block_statement(&tree, caller_body, 0))
|
||||
valid [5]ast.NodeId :: [
|
||||
ast.block_statement(&tree, outer_if.then_block, 0),
|
||||
ast.block_statement(&tree, outer_if.else_block?, 0),
|
||||
ast.block_statement(&tree, inner_if.then_block, 0),
|
||||
ast.block_statement(&tree, inner_if.else_block?, 0),
|
||||
ast.block_statement(&tree, caller_loop.body, 1),
|
||||
]
|
||||
for (valid) |statement| try expect_kind(&result, statement, .void)
|
||||
try expect_kind(&result, outer_statement, .void)
|
||||
try expect_kind(&result, inner_statement, .void)
|
||||
unknown :: ast.statement_expr(&tree, ast.block_statement(&tree, outer.body, 2))
|
||||
original :: sema.binding(&resolution, unknown).data.diagnostic
|
||||
try testing.expect_equal(original, type_of(&result, unknown).diagnostic)
|
||||
outside_break :: ast.block_statement(&tree, scopes_body, 1)
|
||||
outside_continue :: ast.block_statement(&tree, scopes_body, 2)
|
||||
_ = try expect_error(&result, &diagnostic_store, outside_break, .break_outside_loop, ast.node_span(&tree, outside_break))
|
||||
_ = try expect_error(&result, &diagnostic_store, outside_continue, .continue_outside_loop, ast.node_span(&tree, outside_continue))
|
||||
callee_body :: ast.function_decl(&tree, ast.package_decl(&tree, 2)).body
|
||||
callee_control :: ast.block_statement(&tree, callee_body, 0)
|
||||
_ = try expect_error(&result, &diagnostic_store, callee_control, .continue_outside_loop, ast.node_span(&tree, callee_control))
|
||||
dead_body :: ast.function_decl(&tree, ast.package_decl(&tree, 3)).body
|
||||
dead_break :: ast.block_statement(&tree, dead_body, 1)
|
||||
dead_continue :: ast.block_statement(&tree, dead_body, 2)
|
||||
_ = try expect_error(&result, &diagnostic_store, dead_break, .break_outside_loop, ast.node_span(&tree, dead_break))
|
||||
_ = try expect_error(&result, &diagnostic_store, dead_continue, .continue_outside_loop, ast.node_span(&tree, dead_continue))
|
||||
arms_body :: ast.function_decl(&tree, ast.package_decl(&tree, 4)).body
|
||||
arms :: ast.if_stmt(&tree, ast.block_statement(&tree, arms_body, 0))
|
||||
arm_break :: ast.block_statement(&tree, arms.then_block, 0)
|
||||
arm_continue :: ast.block_statement(&tree, arms.else_block?, 0)
|
||||
_ = try expect_error(&result, &diagnostic_store, arm_break, .break_outside_loop, ast.node_span(&tree, arm_break))
|
||||
_ = try expect_error(&result, &diagnostic_store, arm_continue, .continue_outside_loop, ast.node_span(&tree, arm_continue))
|
||||
for (usize(0)..usize(5)) |index| try expect_kind(&result, ast.package_decl(&tree, index), .function)
|
||||
try testing.expect_equal(3, code_count(&diagnostic_store, .break_outside_loop))
|
||||
try testing.expect_equal(4, code_count(&diagnostic_store, .continue_outside_loop))
|
||||
try testing.expect_equal(1, code_count(&diagnostic_store, .unknown_name))
|
||||
try testing.expect_equal(8, diagnostics.count(&diagnostic_store))
|
||||
}
|
||||
|
||||
loop_controls_do_not_guarantee_function_returns test {
|
||||
strpool.strings = strpool.init(mem.c_allocator)
|
||||
defer strpool.deinit(&strpool.strings)
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
source ::
|
||||
`breaking proc() int { while true { break } }
|
||||
`continuing proc() int { while false { continue } }
|
||||
`mixed proc(flag bool) int {
|
||||
` while flag {
|
||||
` if (flag) return 42 else break
|
||||
` }
|
||||
`}
|
||||
`closed proc(flag bool) int {
|
||||
` while flag {
|
||||
` if (flag) break else continue
|
||||
` -"bad"
|
||||
` }
|
||||
` return 42
|
||||
`}
|
||||
`invalid proc() int { break }
|
||||
scan_state := lexer.init(mem.c_allocator)
|
||||
defer lexer.deinit(&scan_state)
|
||||
try lexer.scan(&scan_state, source, &diagnostic_store)
|
||||
tree := try parser.parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
|
||||
defer ast.deinit(&tree)
|
||||
resolution := try sema.resolve(mem.c_allocator, &tree, &diagnostic_store)
|
||||
defer sema.deinit(&resolution)
|
||||
result := try check(mem.c_allocator, &tree, &resolution, &diagnostic_store)
|
||||
defer deinit(&result)
|
||||
|
||||
for (usize(0)..usize(3)) |index| {
|
||||
body :: ast.function_decl(&tree, ast.package_decl(&tree, index)).body
|
||||
_ = try expect_error(&result, &diagnostic_store, body, .missing_return, ast.node_span(&tree, body))
|
||||
try expect_kind(&result, ast.block_statement(&tree, body, 0), .void)
|
||||
}
|
||||
closed_body :: ast.function_decl(&tree, ast.package_decl(&tree, 3)).body
|
||||
loop_statement :: ast.block_statement(&tree, closed_body, 0)
|
||||
loop :: ast.while_stmt(&tree, loop_statement)
|
||||
dead_expression :: ast.statement_expr(&tree, ast.block_statement(&tree, loop.body, 1))
|
||||
original :: try expect_error(&result, &diagnostic_store, dead_expression, .invalid_operand, ast.node_span(&tree, dead_expression))
|
||||
try testing.expect_equal(original, type_of(&result, closed_body).diagnostic)
|
||||
try expect_kind(&result, loop_statement, .void)
|
||||
try expect_kind(&result, ast.block_statement(&tree, closed_body, 1), .void)
|
||||
invalid_body :: ast.function_decl(&tree, ast.package_decl(&tree, 4)).body
|
||||
invalid_control :: ast.block_statement(&tree, invalid_body, 0)
|
||||
control_error :: try expect_error(&result, &diagnostic_store, invalid_control, .break_outside_loop, ast.node_span(&tree, invalid_control))
|
||||
try testing.expect_equal(control_error, type_of(&result, invalid_body).diagnostic)
|
||||
for (usize(0)..usize(5)) |index| try expect_kind(&result, ast.package_decl(&tree, index), .function)
|
||||
try testing.expect_equal(3, code_count(&diagnostic_store, .missing_return))
|
||||
try testing.expect_equal(1, code_count(&diagnostic_store, .invalid_operand))
|
||||
try testing.expect_equal(1, code_count(&diagnostic_store, .break_outside_loop))
|
||||
try testing.expect_equal(5, diagnostics.count(&diagnostic_store))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user