unify catch fallback value sources

This commit is contained in:
2026-07-22 00:37:48 +02:00
parent 17508ff751
commit 5f343ad2d3
12 changed files with 1048397 additions and 324314 deletions
+28 -19
View File
@@ -4737,23 +4737,21 @@ infer_compound_expr :: proc(
value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types, left_expected)
success := types.fallible_success(value, store)
error_type := types.fallible_error(value, store)
fallback_locals: [dynamic]Infer_Local
fallback_locals.allocator = checker.allocator
defer delete(fallback_locals)
append(&fallback_locals, ..locals)
if symbol.is_valid(expr.name) && expr.name != checker.sink_symbol && types.is_valid(error_type) {
append(&fallback_locals, Infer_Local{name=expr.name, type=error_type, declared=error_type, statement=ast.INVALID_STMT})
}
if expr.right != ast.INVALID_EXPR {
fallback := infer_nested_expr(checker, expr.right, locals, pkg, file, demanded, local_types)
fallback := infer_nested_expr(checker, expr.right, fallback_locals[:], pkg, file, demanded, local_types)
if types.is_valid(success) && types.is_valid(fallback) && !types.equal(success, fallback) {
return types.widest(success, fallback)
}
return success if types.is_valid(success) else fallback
}
block_locals: [dynamic]Infer_Local
block_locals.allocator = checker.allocator
defer delete(block_locals)
append(&block_locals, ..locals)
capture_start := len(block_locals)
if symbol.is_valid(expr.name) && expr.name != checker.sink_symbol && types.is_valid(error_type) {
append(&block_locals, Infer_Local{name=expr.name, type=error_type, declared=error_type, statement=ast.INVALID_STMT})
}
infer_statements(checker, expr.body, &block_locals, local_types, pkg, file, demanded, &success, success)
resize(&block_locals, capture_start)
infer_statements(checker, expr.body, &fallback_locals, local_types, pkg, file, demanded, &success, success)
return success
case .Struct_Literal:
value := types.INVALID
@@ -8139,26 +8137,37 @@ build_compound_expr :: proc(
block_handler := false
void_fallthrough := false
fallback := hir.INVALID_EXPR
ctx := checker.current_build_ctx
capture_start := 0
if symbol.is_valid(expr.name) && expr.name != checker.sink_symbol {
if ctx == nil {
id := source.add(checker.diagnostics, expr.span, "a captured catch fallback is only valid in a function body")
return invalid_hir_expr(checker, expr.span, id, success)
}
capture_start = len(ctx.locals^)
error_type := types.fallible_error(channel_type, store)
capture = append_build_local(ctx, expr.name, error_type, false, expr.span)
}
if expr.right != ast.INVALID_EXPR {
fallback = build_nested_expr(checker, expr.right, locals, global_reads, calls, success, pkg, file)
fallback_locals := locals
if capture != hir.INVALID_LOCAL {
fallback_locals = ctx.locals^[:]
}
fallback = build_nested_expr(checker, expr.right, fallback_locals, global_reads, calls, success, pkg, file)
fallback = coerce_expr(checker, fallback, success, checker.module.exprs[fallback].span)
} else {
block_handler = true
ctx := checker.current_build_ctx
if ctx == nil {
id := source.add(checker.diagnostics, expr.span, "catch block form is only valid in a function body")
return invalid_hir_expr(checker, expr.span, id, success)
}
capture_start := len(ctx.locals^)
error_type := types.fallible_error(channel_type, store)
if symbol.is_valid(expr.name) && expr.name != checker.sink_symbol {
capture = append_build_local(ctx, expr.name, error_type, false, expr.span)
}
handler: [dynamic]hir.Stmt_Id
handler.allocator = checker.allocator
fallback, _ = build_value_source(ctx, &handler, expr.body, success, expr.span, allow_exit=true)
fallback, _ = build_value_source(ctx, &handler, expr.body, success, expr.span, value_control_flow=expr.integer != 0, allow_exit=true)
void_fallthrough = fallback == hir.INVALID_EXPR && !all_paths_exit(&checker.module, handler[:])
body = handler[:]
}
if capture != hir.INVALID_LOCAL {
resize(ctx.locals, capture_start)
}
catch_mode := hir.CATCH_EXPRESSION
+5 -3
View File
@@ -3763,14 +3763,16 @@ ct_eval_catch_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Typ
if value.active == 0 {
return payload, ct_flow(.Normal), true
}
if expr.right != ast.INVALID_EXPR {
return ct_eval_expr(state, expr.right, success, depth+1)
}
scope_start := len(state.bindings)
if symbol.is_valid(expr.name) && expr.name != checker.sink_symbol && payload != INVALID_CT_VALUE {
error_type := types.fallible_error(value.type, &checker.module.types)
ct_bind_value(state, expr.name, error_type, payload, false)
}
if expr.right != ast.INVALID_EXPR {
result, result_flow, result_ok := ct_eval_expr(state, expr.right, success, depth+1)
ct_pop_bindings(state, scope_start)
return result, result_flow, result_ok
}
handler, handler_ok := ct_exec_statements(state, expr.body, true, depth+1)
ct_pop_bindings(state, scope_start)
if !handler_ok {
+4 -4
View File
@@ -468,9 +468,8 @@ lower_compound_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instructi
diagnostic=source.INVALID_DIAGNOSTIC,
})
} else {
if expr.integer != hir.CATCH_EXPRESSION {
capture := hir.as_local(expr.target)
if capture != hir.INVALID_LOCAL && int(capture) < len(state.func_locals) {
capture := hir.as_local(expr.target)
if capture != hir.INVALID_LOCAL && int(capture) < len(state.func_locals) {
error_type := state.func_locals[capture].type
error_value := append_instruction(state, ir.Instruction{
op=.Fallible_Error, span=expr.span, type=error_type,
@@ -489,7 +488,8 @@ lower_compound_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instructi
target=ir.INVALID_REF, a=capture_slot, b=error_value,
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
}
if expr.integer != hir.CATCH_EXPRESSION {
lower_statements(state, expr.body)
if expr.integer == hir.CATCH_VOID_FALLTHROUGH {
append_instruction(state, ir.Instruction{
+21 -1
View File
@@ -1470,8 +1470,9 @@ parse_expression_bp :: proc(parser: ^Parser, minimum_binding_power, nesting: int
skip_newlines(parser)
if operator.kind == .Keyword_Catch {
left_expr := parser.module.exprs[left]
capture := token.Token{}
if _, pipe_ok := allow(parser, .Pipe); pipe_ok {
capture := current(parser)
capture = current(parser)
if capture.kind != .Identifier && capture.kind != .Underscore {
source.add(parser.diagnostics, capture.span, "expected a catch capture name")
} else {
@@ -1480,6 +1481,9 @@ parse_expression_bp :: proc(parser: ^Parser, minimum_binding_power, nesting: int
if _, close_ok := allow(parser, .Pipe); !close_ok {
source.add(parser.diagnostics, current(parser).span, "expected '|' after catch capture")
}
skip_newlines(parser)
}
if current(parser).kind == .Left_Brace {
body := parse_block(parser)
end := previous(parser)
left = add_expr(parser, ast.Expr{
@@ -1493,11 +1497,27 @@ parse_expression_bp :: proc(parser: ^Parser, minimum_binding_power, nesting: int
})
continue
}
if cf, is_cf := parse_value_control_flow(parser); is_cf {
body := make([]ast.Stmt_Id, 1, parser.module.allocator)
body[0] = cf
left = add_expr(parser, ast.Expr{
kind=.Catch,
span=span_from(left_expr.span, parser.module.statements[cf].span),
name=capture.symbol,
left=left,
right=ast.INVALID_EXPR,
body=body,
integer=1,
diagnostic=source.INVALID_DIAGNOSTIC,
})
continue
}
right := parse_expression_bp(parser, right_power, nesting+1)
right_expr := parser.module.exprs[right]
left = add_expr(parser, ast.Expr{
kind=.Catch,
span=span_from(left_expr.span, right_expr.span),
name=capture.symbol,
left=left,
right=right,
diagnostic=source.INVALID_DIAGNOSTIC,