error inference in fallible functions
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user