error inference in fallible functions

This commit is contained in:
2026-08-09 16:17:49 +02:00
parent 572ffe7d07
commit a7a32894f3
5 changed files with 217 additions and 37 deletions
+90 -27
View File
@@ -105,6 +105,7 @@ Build_Ctx :: struct {
pkg: ast.Package_Id,
file: ast.File_Id,
result: types.Type,
infer_error: bool,
local_types: []types.Type,
locals: ^[dynamic]Build_Local,
hir_locals: ^[dynamic]hir.Local,
@@ -253,7 +254,7 @@ Checker :: struct {
sink_symbol: symbol.Id,
type_symbol: symbol.Id,
current_result: types.Type,
inferred_test_error: ^types.Type,
inferred_error: ^types.Type,
current_build_ctx: ^Build_Ctx,
current_comptime_values: []Comptime_Value,
static_state: Ct_State,
@@ -1308,12 +1309,22 @@ function_channel_type :: proc(checker: ^Checker, function: ast.Function) -> type
if function.pkg == 0 && function.name == checker.main_symbol && result == types.INT {
result = types.I32
}
if function.infer_error {
return types.fallible(&checker.module.types, result, types.INVALID)
}
if types.is_valid(function.error) {
return types.fallible(&checker.module.types, result, type_from_syntax(checker, function.error, function.pkg, function.file))
}
return result
}
is_error_channel_type :: proc(checker: ^Checker, value: types.Type) -> bool {
resolved := types.resolve_alias(value, &checker.module.types)
return types.is_enum(resolved, &checker.module.types) ||
types.is_struct(resolved, &checker.module.types) ||
types.is_tagged_union(resolved, &checker.module.types)
}
is_runtime_type :: proc(checker: ^Checker, value: types.Type) -> bool {
return types.is_runtime_value(value, &checker.module.types)
}
@@ -1726,8 +1737,8 @@ configure_entry_point :: proc(checker: ^Checker) {
type_from_syntax(checker, function.result, function.pkg, function.file),
&checker.module.types,
)
if function.package_hidden && function.has_body && !function.c_abi && len(function.params) == 0 &&
!types.is_valid(function.error) && types.equal(result, io_type) {
if function.package_hidden && function.has_body && !function.c_abi && !function.infer_error &&
len(function.params) == 0 && !types.is_valid(function.error) && types.equal(result, io_type) {
provider = ast.function_id(function_id)
}
}
@@ -3335,7 +3346,7 @@ resolve_type_factory_call :: proc(checker: ^Checker, expr_id: ast.Expr_Id, pkg:
return types.INVALID
}
function := checker.ast_module.functions[template]
if !is_type_metatype_syntax(checker, function.result) || types.is_valid(function.error) {
if !is_type_metatype_syntax(checker, function.result) || function.infer_error || types.is_valid(function.error) {
source.addf(checker.diagnostics, expr.span, "function '%s' does not return a type", symbol_text(checker, expr.name))
return types.INVALID
}
@@ -3588,7 +3599,7 @@ function_value_signature :: proc(
if function_has_comptime_params(function) {
return nil, types.INVALID, false
}
if function.c_abi && types.is_valid(function.error) {
if function.c_abi && (function.infer_error || types.is_valid(function.error)) {
return nil, types.INVALID, false
}
if !function.c_abi && (!function.has_body || function.variadic) {
@@ -3855,7 +3866,7 @@ validate_external_globals :: proc(checker: ^Checker) {
}
runtime_write_declaration_matches :: proc(checker: ^Checker, function: ast.Function) -> bool {
if function.variadic || len(function.params) != 3 || types.is_valid(function.error) {
if function.variadic || len(function.params) != 3 || function.infer_error || types.is_valid(function.error) {
return false
}
store := &checker.module.types
@@ -3977,11 +3988,25 @@ validate_declarations :: proc(checker: ^Checker) {
)
}
}
if function.infer_error && !signature_poisoned {
root_main := function.pkg == 0 && function.name == checker.main_symbol
if function.c_abi {
checker.template_diagnostics[function_id] = source.add(
checker.diagnostics,
function.span,
"inferred error channels are not allowed on 'c_func'",
)
} else if !function.package_hidden && !root_main {
checker.template_diagnostics[function_id] = source.add(
checker.diagnostics,
function.span,
"inferred error channels are only allowed on hidden functions and root main",
)
}
}
if types.is_valid(function.error) && !signature_poisoned {
error_type := type_from_syntax(checker, function.error, function.pkg, function.file)
error_channel := types.is_enum(error_type, &checker.module.types) ||
types.is_struct(error_type, &checker.module.types) ||
types.is_tagged_union(error_type, &checker.module.types)
error_channel := is_error_channel_type(checker, error_type)
if function.c_abi {
checker.template_diagnostics[function_id] = source.add(
checker.diagnostics,
@@ -4780,10 +4805,16 @@ Infer_Frame :: struct {
reverse_operands: bool,
}
merge_inferred_test_error :: proc(checker: ^Checker, incoming: types.Type) {
current := checker.inferred_test_error
if current == nil || !types.is_valid(incoming) ||
types.can_sum_widen(incoming, current^, &checker.module.types) {
merge_inferred_error :: proc(checker: ^Checker, incoming: types.Type) {
current := checker.inferred_error
if current == nil || !types.is_valid(incoming) {
return
}
if !types.is_valid(current^) {
current^ = incoming
return
}
if types.can_sum_widen(incoming, current^, &checker.module.types) {
return
}
if merged, err := types.compose_sum(&checker.module.types, current^, incoming); err == .None {
@@ -5028,7 +5059,7 @@ infer_compound_expr :: proc(
case .Try:
left_expected := expected if checker.ast_module.exprs[expr.left].kind == .Call else types.INVALID
value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types, left_expected)
merge_inferred_test_error(checker, types.fallible_error(value, store))
merge_inferred_error(checker, types.fallible_error(value, store))
return types.fallible_success(value, store) if types.kind(value, store) == .Fallible else types.INVALID
case .Catch:
left_expected := expected if checker.ast_module.exprs[expr.left].kind == .Call else types.INVALID
@@ -6103,6 +6134,12 @@ infer_statements :: proc(
}
if statement.expr != ast.INVALID_EXPR {
returned := infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded, local_types, result_hint)
if checker.inferred_error != nil &&
is_error_channel_type(checker, returned) &&
!can_implicitly_convert_type(checker, returned, result_hint) {
merge_inferred_error(checker, returned)
continue
}
if is_runtime_type(checker, result_hint) {
_ = record_demand_shallow(checker, statement.expr, result_hint, locals^[:], local_types, pkg, file)
expr := checker.ast_module.exprs[statement.expr]
@@ -6372,23 +6409,31 @@ infer_spec_locals_and_result :: proc(
}
result := types.INVALID
test_error := function.error
previous_test_error := checker.inferred_test_error
checker.inferred_test_error = &test_error if function.test else nil
infer_statements(checker, function.body, &locals, local_types, function.pkg, function.file, demanded, &result, result_hint)
checker.inferred_test_error = previous_test_error
if function.test && !types.equal(function.error, test_error) {
checker.ast_module.functions[spec.template].error = test_error
success := types.fallible_success(checker.specs[id].result, &checker.module.types)
checker.specs[id].result = types.fallible(&checker.module.types, success, test_error)
inferred_error := function.error
if function.infer_error {
inferred_error = types.fallible_error(checker.specs[id].result, &checker.module.types)
}
previous_inferred_error := checker.inferred_error
checker.inferred_error = &inferred_error if function.test || function.infer_error else nil
infer_statements(checker, function.body, &locals, local_types, function.pkg, function.file, demanded, &result, result_hint)
checker.inferred_error = previous_inferred_error
if function.test && !types.equal(function.error, inferred_error) {
checker.ast_module.functions[spec.template].error = inferred_error
success := types.fallible_success(checker.specs[id].result, &checker.module.types)
checker.specs[id].result = types.fallible(&checker.module.types, success, inferred_error)
}
resolved_result := declared
if types.is_constraint(declared) {
// Narrow the inferred result to the constraint's family; an out-of-family
// result (e.g. returning a non-integer from an `int` function) yields
// INVALID and is rejected downstream.
return local_types, types.constraint_target(declared, result, &checker.module.types)
resolved_result = types.constraint_target(declared, result, &checker.module.types)
}
return local_types, declared
if function.infer_error {
checker.specs[id].result = types.fallible(&checker.module.types, resolved_result, inferred_error)
return local_types, checker.specs[id].result
}
return local_types, resolved_result
}
infer_spec_result :: proc(checker: ^Checker, id: Spec_Id, demanded: ^[dynamic]Spec_Id = nil) -> types.Type {
@@ -11465,7 +11510,16 @@ build_block :: proc(
success_has := types.sum_has_name(store, success, u32(expr_ast.name))
error_has := types.sum_has_name(store, error_type, u32(expr_ast.name))
if error_has && !success_has {
error_exit = true
if ctx.infer_error {
id := source.add(
checker.diagnostics,
expr_ast.span,
"inferred error returns require a concretely typed error value",
)
value = invalid_hir_expr(checker, expr_ast.span, id, ctx.result)
} else {
error_exit = true
}
} else if error_has && success_has {
id := source.add(checker.diagnostics, expr_ast.span, "ambiguous fallible return member")
value = invalid_hir_expr(checker, expr_ast.span, id, ctx.result)
@@ -13901,7 +13955,15 @@ build_function :: proc(checker: ^Checker, id: Spec_Id) {
signature_diagnostic := source.INVALID_DIAGNOSTIC
unresolved_result := !types.is_void(spec.result) && !types.is_noreturn(spec.result) && !is_runtime_type(checker, spec.result)
if unresolved_result {
if types.is_comptime_only(spec.result, &checker.module.types) {
if function.infer_error &&
!types.is_valid(types.fallible_error(spec.result, &checker.module.types)) {
signature_diagnostic = source.addf(
checker.diagnostics,
function.span,
"could not infer a named error channel for '%s'; propagate one with 'try' or return a concretely typed error value",
symbol_text(checker, function.name),
)
} else if types.is_comptime_only(spec.result, &checker.module.types) {
signature_diagnostic = source.addf(
checker.diagnostics,
function.span,
@@ -14050,6 +14112,7 @@ build_function :: proc(checker: ^Checker, id: Spec_Id) {
pkg = function.pkg,
file = function.file,
result = spec.result,
infer_error = function.infer_error,
local_types = local_types,
locals = &locals,
hir_locals = &hir_locals,