break and continue in loops
This commit is contained in:
@@ -68,6 +68,9 @@ Build_Ctx :: struct {
|
||||
global_reads: ^[dynamic]hir.Global_Id,
|
||||
calls: ^[dynamic]hir.Function_Id,
|
||||
problematic: ^bool,
|
||||
// Number of enclosing loops being built. `break`/`continue` are only valid
|
||||
// when this is > 0; bumped around loop-body builds in `build_block`.
|
||||
loop_depth: int,
|
||||
}
|
||||
|
||||
Constant_Kind :: enum {
|
||||
@@ -734,6 +737,7 @@ mark_block_imports_used :: proc(checker: ^Checker, statements: []ast.Stmt_Id, fi
|
||||
case .For:
|
||||
mark_expr_imports_used(checker, statement.expr, file)
|
||||
mark_block_imports_used(checker, statement.body, file)
|
||||
case .Break, .Continue:
|
||||
case .Invalid:
|
||||
}
|
||||
}
|
||||
@@ -4439,7 +4443,9 @@ build_block :: proc(
|
||||
condition = invalid_hir_expr(checker, statement.span, id, types.BOOL)
|
||||
ctx.problematic^ = true
|
||||
}
|
||||
ctx.loop_depth += 1
|
||||
loop_body := build_block(ctx, statement.body)
|
||||
ctx.loop_depth -= 1
|
||||
update := hir.INVALID_STMT
|
||||
if statement.update != ast.INVALID_STMT {
|
||||
update_ast := [1]ast.Stmt_Id{statement.update}
|
||||
@@ -4531,7 +4537,9 @@ build_block :: proc(
|
||||
append(ctx.locals, Build_Local{name=statement.index_name, type=types.USIZE, mutable=false, id=index_local})
|
||||
}
|
||||
}
|
||||
ctx.loop_depth += 1
|
||||
loop_body := build_block(ctx, statement.body, capture_start)
|
||||
ctx.loop_depth -= 1
|
||||
resize(ctx.locals, capture_start)
|
||||
|
||||
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||
@@ -4562,6 +4570,24 @@ build_block :: proc(
|
||||
})
|
||||
ctx.problematic^ = true
|
||||
}
|
||||
case .Break, .Continue:
|
||||
if ctx.loop_depth == 0 {
|
||||
keyword := "break" if statement.kind == .Break else "continue"
|
||||
id := source.addf(checker.diagnostics, statement.span, "'%s' outside of a loop", keyword)
|
||||
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||
append(&checker.module.statements, hir.Stmt{
|
||||
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
|
||||
local = hir.INVALID_LOCAL, diagnostic = id,
|
||||
})
|
||||
ctx.problematic^ = true
|
||||
continue
|
||||
}
|
||||
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||
append(&checker.module.statements, hir.Stmt{
|
||||
kind = .Break if statement.kind == .Break else .Continue,
|
||||
span = statement.span, expr = hir.INVALID_EXPR,
|
||||
local = hir.INVALID_LOCAL, diagnostic = source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
case .Invalid:
|
||||
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||
append(&checker.module.statements, hir.Stmt{
|
||||
@@ -4593,9 +4619,12 @@ all_paths_return :: proc(module: ^hir.Module, stmts: []hir.Stmt_Id) -> bool {
|
||||
return true
|
||||
}
|
||||
case .While:
|
||||
// A literal `while true` makes the end of the block unreachable —
|
||||
// unless its body can `break` out of this loop.
|
||||
if statement.expr != hir.INVALID_EXPR && int(statement.expr) < len(module.exprs) {
|
||||
condition := module.exprs[statement.expr]
|
||||
if condition.kind == .Bool && condition.integer != 0 {
|
||||
if condition.kind == .Bool && condition.integer != 0 &&
|
||||
!loop_body_breaks(module, statement.then_body) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -4604,6 +4633,25 @@ all_paths_return :: proc(module: ^hir.Module, stmts: []hir.Stmt_Id) -> bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Reports whether `stmts` contains a `break` that targets the enclosing loop:
|
||||
// a `.Break` at this level or inside `if`/`else` branches counts, but a `break`
|
||||
// inside a nested `.While`/`.For` targets that inner loop, so we do not descend.
|
||||
loop_body_breaks :: proc(module: ^hir.Module, stmts: []hir.Stmt_Id) -> bool {
|
||||
for id in stmts {
|
||||
statement := module.statements[id]
|
||||
#partial switch statement.kind {
|
||||
case .Break:
|
||||
return true
|
||||
case .If:
|
||||
if loop_body_breaks(module, statement.then_body) ||
|
||||
loop_body_breaks(module, statement.else_body) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
build_function :: proc(checker: ^Checker, id: Spec_Id) {
|
||||
spec := checker.specs[id]
|
||||
function := checker.ast_module.functions[spec.template]
|
||||
|
||||
Reference in New Issue
Block a user