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
+102
View File
@@ -3167,6 +3167,50 @@ main func() int ! Error { return try value() }
Error :: enum { failed }
fail func() void ! Error { return .failed }
main func(_ process.Init) void ! Error { try fail() }
`,
exit_code=1,
},
{
name="inferred-main-try",
source=`Error :: enum { failed }
fail func() void ! Error { return .failed }
main func() void! { try fail() }
`,
exit_code=1,
},
{
name="inferred-main-typed-return",
source=`Error :: enum { failed }
main func() void! {
err Error := .failed
return err
}
`,
exit_code=1,
},
{
name="inferred-hidden-sum",
source=`A :: enum { a }
B :: enum { b }
fail_a func() void ! A { return .a }
fail_b func() void ! B { return .b }
hide dispatch func(selector i32) void! {
if selector == 1 {
try fail_a()
return
}
try fail_b()
}
main func() void! { try dispatch(2) }
`,
exit_code=1,
},
{
name="inferred-process-main",
source=`process :: import "@std/process"
Error :: enum { failed }
fail func() void ! Error { return .failed }
main func(_ process.Init) void! { try fail() }
`,
exit_code=1,
},
@@ -3202,6 +3246,64 @@ main func(_ process.Init) void ! Error { try fail() }
}
}
@(test)
inferred_error_channels_reject_unstable_or_untyped_contracts :: proc(t: ^testing.T) {
Case :: struct {
source: string,
message: string,
}
cases := [?]Case{
{
source=`Error :: enum { failed }
fail func() void ! Error { return .failed }
visible func() void! { try fail() }
main func() void {}
`,
message="inferred error channels are only allowed on hidden functions and root main",
},
{
source=`Error :: enum { failed }
fail func() void ! Error { return .failed }
hide untyped func(flag bool) void! {
if flag {
try fail()
return
}
return .failed
}
main func() void { untyped(false) catch |_| {} }
`,
message="inferred error returns require a concretely typed error value",
},
{
source=`hide empty func() void! {}
main func() void { empty() catch |_| {} }
`,
message="could not infer a named error channel for 'empty'",
},
}
for test_case in cases {
source_file := source.Source{path="test.bro", text=test_case.source}
diagnostics := source.init_diagnostics(&source_file)
symbols := symbol.init_table()
stream := lexer.lex(&source_file, &diagnostics, &symbols)
ast_module := parser.parse(&stream, &source_file, &diagnostics)
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
found := false
for diagnostic in diagnostics.items {
found = found || strings.contains(diagnostic.message, test_case.message)
}
testing.expect(t, found)
hir.destroy_module(&hir_module)
ast.destroy_module(&ast_module)
delete(stream.items)
symbol.destroy_table(&symbols)
source.destroy_diagnostics(&diagnostics)
}
}
@(test)
milestone_33_injects_explicit_io_provider_and_runs_std_io :: proc(t: ^testing.T) {
sources := source.init_store()