finite-domain enum return analysis
This commit is contained in:
@@ -12712,12 +12712,88 @@ build_value_loop :: proc(
|
||||
return value, target.slot_type
|
||||
}
|
||||
|
||||
Return_Enum_Guard :: struct {
|
||||
local: hir.Local_Id,
|
||||
type: types.Type,
|
||||
value: i64,
|
||||
}
|
||||
|
||||
return_enum_guard :: proc(
|
||||
module: ^hir.Module,
|
||||
statement: hir.Stmt,
|
||||
locals: []hir.Local,
|
||||
) -> (Return_Enum_Guard, bool) {
|
||||
if statement.kind != .If || statement.else_body != nil ||
|
||||
statement.expr == hir.INVALID_EXPR || int(statement.expr) >= len(module.exprs) {
|
||||
return {}, false
|
||||
}
|
||||
condition := module.exprs[statement.expr]
|
||||
if condition.kind != .Eq || condition.left == hir.INVALID_EXPR || condition.right == hir.INVALID_EXPR ||
|
||||
int(condition.left) >= len(module.exprs) || int(condition.right) >= len(module.exprs) {
|
||||
return {}, false
|
||||
}
|
||||
left := module.exprs[condition.left]
|
||||
right := module.exprs[condition.right]
|
||||
if left.kind == .Integer {
|
||||
left, right = right, left
|
||||
}
|
||||
if left.kind != .Local || right.kind != .Integer ||
|
||||
!types.equal(left.type, right.type) || !types.is_enum(left.type, &module.types) {
|
||||
return {}, false
|
||||
}
|
||||
local := hir.as_local(left.target)
|
||||
if local == hir.INVALID_LOCAL || int(local) >= len(locals) || locals[local].mutable {
|
||||
return {}, false
|
||||
}
|
||||
if !all_paths_return(module, statement.then_body, locals) {
|
||||
return {}, false
|
||||
}
|
||||
return Return_Enum_Guard{local=local, type=left.type, value=right.integer}, true
|
||||
}
|
||||
|
||||
enum_guards_return :: proc(module: ^hir.Module, stmts: []hir.Stmt_Id, locals: []hir.Local) -> bool {
|
||||
guards: [dynamic]Return_Enum_Guard
|
||||
guards.allocator = module.allocator
|
||||
defer delete(guards)
|
||||
for id in stmts {
|
||||
if guard, ok := return_enum_guard(module, module.statements[id], locals); ok {
|
||||
append(&guards, guard)
|
||||
}
|
||||
}
|
||||
for guard, index in guards {
|
||||
seen := false
|
||||
for previous in guards[:index] {
|
||||
seen = seen || previous.local == guard.local
|
||||
}
|
||||
if seen {
|
||||
continue
|
||||
}
|
||||
members := types.enum_members_for(&module.types, guard.type)
|
||||
if len(members) == 0 {
|
||||
continue
|
||||
}
|
||||
complete := true
|
||||
for member in members {
|
||||
value := i64(member.value) if member.value < 0 else transmute(i64)u64(member.value)
|
||||
covered := false
|
||||
for candidate in guards {
|
||||
covered = covered || candidate.local == guard.local && candidate.value == value
|
||||
}
|
||||
complete = complete && covered
|
||||
}
|
||||
if complete {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Reports whether every control-flow path through `stmts` terminates (returns or traps),
|
||||
// so the end of the block is unreachable. A `.Return` or `.Trap` terminates outright; an
|
||||
// `.If` terminates only when it has an `else` and both arms terminate. A literal
|
||||
// `while true` cannot fall through because the language has no `break` statement.
|
||||
// Recursion into the branch slices handles nested ifs and `else if` chains.
|
||||
all_paths_return :: proc(module: ^hir.Module, stmts: []hir.Stmt_Id) -> bool {
|
||||
// Exhaustive equality guards over one immutable enum local also terminate collectively.
|
||||
all_paths_return :: proc(module: ^hir.Module, stmts: []hir.Stmt_Id, locals: []hir.Local = nil) -> bool {
|
||||
for id in stmts {
|
||||
statement := module.statements[id]
|
||||
#partial switch statement.kind {
|
||||
@@ -12725,8 +12801,8 @@ all_paths_return :: proc(module: ^hir.Module, stmts: []hir.Stmt_Id) -> bool {
|
||||
return true
|
||||
case .If:
|
||||
if statement.else_body != nil &&
|
||||
all_paths_return(module, statement.then_body) &&
|
||||
all_paths_return(module, statement.else_body) {
|
||||
all_paths_return(module, statement.then_body, locals) &&
|
||||
all_paths_return(module, statement.else_body, locals) {
|
||||
return true
|
||||
}
|
||||
case .While:
|
||||
@@ -12741,7 +12817,7 @@ all_paths_return :: proc(module: ^hir.Module, stmts: []hir.Stmt_Id) -> bool {
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
return enum_guards_return(module, stmts, locals)
|
||||
}
|
||||
|
||||
// Reports whether `stmts` contains a `break` that targets the enclosing loop:
|
||||
@@ -12967,7 +13043,7 @@ build_function :: proc(checker: ^Checker, id: Spec_Id) {
|
||||
block := build_block(&ctx, function.body)
|
||||
checker.current_result = previous_result
|
||||
checker.current_build_ctx = previous_ctx
|
||||
returns := all_paths_return(&checker.module, block)
|
||||
returns := all_paths_return(&checker.module, block, hir_locals[:])
|
||||
for block_stmt in block {
|
||||
append(&body, block_stmt)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user