bug fixes
This commit is contained in:
+1
-1
@@ -37,7 +37,7 @@ roadmap and milestone history.
|
|||||||
- source-order native structs, opaque nominal records with `Name :: opaque`, complete `c_struct { ... }`, keyed record literals, native untagged unions, and native tagged unions `union(Enum)` / `union(enum)`
|
- source-order native structs, opaque nominal records with `Name :: opaque`, complete `c_struct { ... }`, keyed record literals, native untagged unions, and native tagged unions `union(Enum)` / `union(enum)`
|
||||||
- void-payload tagged-union variants, anonymous struct payloads, contextual `.variant`, `.variant{payload}`, and `.variant{field = value}` construction
|
- void-payload tagged-union variants, anonymous struct payloads, contextual `.variant`, `.variant{payload}`, and `.variant{field = value}` construction
|
||||||
- native sum composition with `A | B` for unbacked enums and tagged unions, using program-global `u16` variant ids
|
- native sum composition with `A | B` for unbacked enums and tagged unions, using program-global `u16` variant ids
|
||||||
- fallible channel types `T ! E`, where `E` is a native enum/tagged union or supported sum composition
|
- fallible channel types `T ! E`, where `E` is a native enum/tagged union or supported sum composition; `void ! E` functions complete successfully on fallthrough, and void-success `catch` handlers may fall through without `yield`
|
||||||
|
|
||||||
#### native record constraint fields
|
#### native record constraint fields
|
||||||
|
|
||||||
|
|||||||
@@ -5913,6 +5913,7 @@ build_compound_expr :: proc(
|
|||||||
body: []hir.Stmt_Id
|
body: []hir.Stmt_Id
|
||||||
capture := hir.INVALID_LOCAL
|
capture := hir.INVALID_LOCAL
|
||||||
block_handler := false
|
block_handler := false
|
||||||
|
void_fallthrough := false
|
||||||
fallback := hir.INVALID_EXPR
|
fallback := hir.INVALID_EXPR
|
||||||
if expr.right != ast.INVALID_EXPR {
|
if expr.right != ast.INVALID_EXPR {
|
||||||
fallback = build_nested_expr(checker, expr.right, locals, global_reads, calls, success, pkg, file)
|
fallback = build_nested_expr(checker, expr.right, locals, global_reads, calls, success, pkg, file)
|
||||||
@@ -5932,14 +5933,19 @@ build_compound_expr :: proc(
|
|||||||
handler: [dynamic]hir.Stmt_Id
|
handler: [dynamic]hir.Stmt_Id
|
||||||
handler.allocator = checker.allocator
|
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, allow_exit=true)
|
||||||
|
void_fallthrough = fallback == hir.INVALID_EXPR && !all_paths_exit(&checker.module, handler[:])
|
||||||
body = handler[:]
|
body = handler[:]
|
||||||
resize(ctx.locals, capture_start)
|
resize(ctx.locals, capture_start)
|
||||||
}
|
}
|
||||||
|
catch_mode := hir.CATCH_EXPRESSION
|
||||||
|
if block_handler {
|
||||||
|
catch_mode = hir.CATCH_VOID_FALLTHROUGH if void_fallthrough else hir.CATCH_BLOCK
|
||||||
|
}
|
||||||
return add_hir_expr(checker, hir.Expr{
|
return add_hir_expr(checker, hir.Expr{
|
||||||
kind=.Catch,
|
kind=.Catch,
|
||||||
span=expr.span,
|
span=expr.span,
|
||||||
type=success,
|
type=success,
|
||||||
integer=1 if block_handler else 0,
|
integer=catch_mode,
|
||||||
left=channel,
|
left=channel,
|
||||||
right=fallback,
|
right=fallback,
|
||||||
body=body,
|
body=body,
|
||||||
@@ -8236,10 +8242,11 @@ build_block :: proc(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// build_value_block builds a `{ ... yield v }` value block whose final statement
|
// build_value_block builds a `{ ... yield v }` value block whose final statement
|
||||||
// must be a `yield`. Catch handlers may instead exit on every path, in which case
|
// must be a `yield`. Catch handlers may instead exit on every path or complete with
|
||||||
// `allow_exit` leaves the fallback expression invalid. Otherwise it builds the leading
|
// void, in which case `allow_exit` leaves the fallback expression invalid. Otherwise
|
||||||
// statements inline, evaluates the yield in their scope, then captures the value before
|
// it builds the leading statements inline, evaluates the yield in their scope, then
|
||||||
// running defers. `expected` is the binding's type (INVALID for an untyped `::`).
|
// captures the value before running defers. `expected` is the binding's type (INVALID
|
||||||
|
// for an untyped `::`).
|
||||||
build_value_block :: proc(
|
build_value_block :: proc(
|
||||||
ctx: ^Build_Ctx,
|
ctx: ^Build_Ctx,
|
||||||
body: ^[dynamic]hir.Stmt_Id,
|
body: ^[dynamic]hir.Stmt_Id,
|
||||||
@@ -8257,7 +8264,7 @@ build_value_block :: proc(
|
|||||||
for s in inner {
|
for s in inner {
|
||||||
append(body, s)
|
append(body, s)
|
||||||
}
|
}
|
||||||
if allow_exit && all_paths_exit(&checker.module, inner) {
|
if allow_exit && (types.is_void(expected) || all_paths_exit(&checker.module, inner)) {
|
||||||
delete(inner, checker.allocator)
|
delete(inner, checker.allocator)
|
||||||
return hir.INVALID_EXPR, expected
|
return hir.INVALID_EXPR, expected
|
||||||
}
|
}
|
||||||
@@ -9785,7 +9792,16 @@ build_function :: proc(checker: ^Checker, id: Spec_Id) {
|
|||||||
}
|
}
|
||||||
delete(block, checker.allocator)
|
delete(block, checker.allocator)
|
||||||
|
|
||||||
if !types.is_void(spec.result) && !returns {
|
fallible_void := types.kind(spec.result, &checker.module.types) == .Fallible &&
|
||||||
|
types.is_void(types.fallible_success(spec.result, &checker.module.types))
|
||||||
|
if !returns && fallible_void {
|
||||||
|
value := fallible_aggregate(checker, function.span, spec.result, hir.INVALID_EXPR, false)
|
||||||
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||||
|
append(&checker.module.statements, hir.Stmt{
|
||||||
|
kind=.Return, span=function.span, expr=value, local=hir.INVALID_LOCAL,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
} else if !types.is_void(spec.result) && !returns {
|
||||||
id := source.addf(
|
id := source.addf(
|
||||||
checker.diagnostics,
|
checker.diagnostics,
|
||||||
function.span,
|
function.span,
|
||||||
@@ -9944,8 +9960,8 @@ build_globals :: proc(checker: ^Checker) {
|
|||||||
}
|
}
|
||||||
expr := build_expr(checker, global.expr, nil, &dependencies, &calls, expected, global.pkg, global.file)
|
expr := build_expr(checker, global.expr, nil, &dependencies, &calls, expected, global.pkg, global.file)
|
||||||
global_type := checker.global_types[global_index]
|
global_type := checker.global_types[global_index]
|
||||||
if is_runtime_type(checker, declared) {
|
if is_runtime_type(checker, checker.global_types[global_index]) {
|
||||||
expr = coerce_expr(checker, expr, declared, global.span)
|
expr = coerce_expr(checker, expr, checker.global_types[global_index], global.span)
|
||||||
global_type = checker.module.exprs[expr].type
|
global_type = checker.module.exprs[expr].type
|
||||||
} else if is_runtime_type(checker, checker.module.exprs[expr].type) {
|
} else if is_runtime_type(checker, checker.module.exprs[expr].type) {
|
||||||
global_type = checker.module.exprs[expr].type
|
global_type = checker.module.exprs[expr].type
|
||||||
|
|||||||
@@ -2196,6 +2196,11 @@ ct_eval_template_call :: proc(
|
|||||||
result := ct_add_value(state, Ct_Value{kind=.Void, type=types.VOID})
|
result := ct_add_value(state, Ct_Value{kind=.Void, type=types.VOID})
|
||||||
return result, ct_flow(.Normal), true
|
return result, ct_flow(.Normal), true
|
||||||
}
|
}
|
||||||
|
if flow.kind == .Normal && types.kind(result_type, &checker.module.types) == .Fallible &&
|
||||||
|
types.is_void(types.fallible_success(result_type, &checker.module.types)) {
|
||||||
|
result := ct_make_fallible(state, result_type, INVALID_CT_VALUE, false)
|
||||||
|
return result, ct_flow(.Normal), true
|
||||||
|
}
|
||||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, span, "comptime function '%s' did not return a value", symbol_text(checker, function.name))
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, span, "comptime function '%s' did not return a value", symbol_text(checker, function.name))
|
||||||
}
|
}
|
||||||
result := flow.value
|
result := flow.value
|
||||||
@@ -2304,6 +2309,10 @@ ct_eval_catch_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Typ
|
|||||||
}
|
}
|
||||||
return handler.value, ct_flow(.Normal), true
|
return handler.value, ct_flow(.Normal), true
|
||||||
}
|
}
|
||||||
|
if handler.kind == .Normal && types.is_void(success) {
|
||||||
|
value := ct_add_value(state, Ct_Value{kind=.Void, type=types.VOID})
|
||||||
|
return value, ct_flow(.Normal), true
|
||||||
|
}
|
||||||
return INVALID_CT_VALUE, handler, ct_fail(state, .Not_Comptime, expr.span, "catch block must yield a value")
|
return INVALID_CT_VALUE, handler, ct_fail(state, .Not_Comptime, expr.span, "catch block must yield a value")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -132,6 +132,10 @@ Expr_Kind :: enum u8 {
|
|||||||
Call,
|
Call,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CATCH_EXPRESSION :: i64(0)
|
||||||
|
CATCH_BLOCK :: i64(1)
|
||||||
|
CATCH_VOID_FALLTHROUGH :: i64(2)
|
||||||
|
|
||||||
Expr :: struct {
|
Expr :: struct {
|
||||||
span: source.Span,
|
span: source.Span,
|
||||||
type: types.Type,
|
type: types.Type,
|
||||||
@@ -140,9 +144,9 @@ Expr :: struct {
|
|||||||
// encoded as Expr_Id because it otherwise has no args.
|
// encoded as Expr_Id because it otherwise has no args.
|
||||||
args: []Expr_Id,
|
args: []Expr_Id,
|
||||||
// `Catch` block handlers use `body` for the handler statements and `target`
|
// `Catch` block handlers use `body` for the handler statements and `target`
|
||||||
// for the optional captured error local. A missing `right` means the handler
|
// for the optional captured error local. `integer` selects the catch mode;
|
||||||
// exits on every path and therefore has no fallback value. `Try` uses `body`
|
// a missing `right` means the block exits or completes with void. `Try` uses
|
||||||
// for active error-exit cleanup.
|
// `body` for active error-exit cleanup.
|
||||||
body: []Stmt_Id,
|
body: []Stmt_Id,
|
||||||
target: Ref,
|
target: Ref,
|
||||||
left: Expr_Id,
|
left: Expr_Id,
|
||||||
|
|||||||
@@ -468,7 +468,7 @@ lower_compound_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instructi
|
|||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
if expr.integer != 0 {
|
if expr.integer != hir.CATCH_EXPRESSION {
|
||||||
capture := hir.as_local(expr.target)
|
capture := hir.as_local(expr.target)
|
||||||
if capture != hir.INVALID_LOCAL && int(capture) < len(state.func_locals) {
|
if capture != hir.INVALID_LOCAL && int(capture) < len(state.func_locals) {
|
||||||
error_type := state.func_locals[capture].type
|
error_type := state.func_locals[capture].type
|
||||||
@@ -491,6 +491,13 @@ lower_compound_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instructi
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
lower_statements(state, expr.body)
|
lower_statements(state, expr.body)
|
||||||
|
if expr.integer == hir.CATCH_VOID_FALLTHROUGH {
|
||||||
|
append_instruction(state, ir.Instruction{
|
||||||
|
op=.Br, span=expr.span, type=types.VOID, integer=merge_lbl,
|
||||||
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if expr.right != hir.INVALID_EXPR {
|
if expr.right != hir.INVALID_EXPR {
|
||||||
fallback := lower_nested_expr(state, expr.right)
|
fallback := lower_nested_expr(state, expr.right)
|
||||||
|
|||||||
+64
-3
@@ -2585,9 +2585,8 @@ main func() i32 {
|
|||||||
|
|
||||||
testing.expect_value(t, len(diagnostics.items), 0)
|
testing.expect_value(t, len(diagnostics.items), 0)
|
||||||
testing.expect_value(t, len(hir_module.functions), 3)
|
testing.expect_value(t, len(hir_module.functions), 3)
|
||||||
for global in hir_module.globals {
|
testing.expect(t, types.equal(hir_module.globals[0].type, types.I32))
|
||||||
testing.expect(t, types.equal(global.type, types.I8))
|
testing.expect(t, types.equal(hir_module.globals[1].type, types.I8))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
@@ -4249,6 +4248,50 @@ main func() i32 {
|
|||||||
testing.expect_value(t, state.exit_code, 0)
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
fallible_void_fallthrough_compiles_at_runtime_and_comptime :: proc(t: ^testing.T) {
|
||||||
|
text := `Failure :: enum {
|
||||||
|
bad
|
||||||
|
}
|
||||||
|
succeed func() void ! Failure {}
|
||||||
|
fail func() void ! Failure { return .bad }
|
||||||
|
maybe func(should_fail bool) void ! Failure {
|
||||||
|
if should_fail { return .bad }
|
||||||
|
}
|
||||||
|
recover func(exit bool) i32 {
|
||||||
|
fail() catch |_| {
|
||||||
|
if exit { return 7 }
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
comptime_scenario func() i32 {
|
||||||
|
succeed() catch |_| {}
|
||||||
|
fail() catch |_| {}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
main func() i32 {
|
||||||
|
comptime_result i32 :: $comptime_scenario()
|
||||||
|
handled bool = false
|
||||||
|
fail() catch |_| { handled = true }
|
||||||
|
if !handled { return 3 }
|
||||||
|
succeed() catch |_| { return 1 }
|
||||||
|
maybe(false) catch |_| { return 2 }
|
||||||
|
return comptime_result + recover(false) + recover(true) - 7
|
||||||
|
}
|
||||||
|
`
|
||||||
|
directory := "/tmp/brolang-test-fallible-void-fallthrough"
|
||||||
|
main_path := "/tmp/brolang-test-fallible-void-fallthrough/main.bro"
|
||||||
|
output := "/tmp/brolang-test-fallible-void-fallthrough-output"
|
||||||
|
_ = os2.remove_all(directory)
|
||||||
|
defer _ = os2.remove_all(directory)
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
testing.expect(t, os.make_directory(directory) == nil)
|
||||||
|
testing.expect(t, os.write_entire_file(main_path, transmute([]byte)text))
|
||||||
|
testing.expect_value(t, compiler_core.compile_package(directory, output), 0)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
conversion_diagnostics_render_source_types :: proc(t: ^testing.T) {
|
conversion_diagnostics_render_source_types :: proc(t: ^testing.T) {
|
||||||
text := `Allocator :: struct {
|
text := `Allocator :: struct {
|
||||||
@@ -6739,6 +6782,24 @@ valid_runtime_global_initializes_before_main :: proc(t: ^testing.T) {
|
|||||||
testing.expect_value(t, state.exit_code, 0)
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
inferred_string_global_decays_to_demanded_slice :: proc(t: ^testing.T) {
|
||||||
|
text := "consume func(value []u8) usize { return value.len }\n" +
|
||||||
|
"program ::\n\t`abc\n" +
|
||||||
|
"main func() i32 {\n\tif consume(program) == 3 { return 0 }\n\treturn 1\n}\n"
|
||||||
|
directory := "/tmp/brolang-test-inferred-string-global"
|
||||||
|
main_path := "/tmp/brolang-test-inferred-string-global/main.bro"
|
||||||
|
output := "/tmp/brolang-test-inferred-string-global-output"
|
||||||
|
_ = os2.remove_all(directory)
|
||||||
|
defer _ = os2.remove_all(directory)
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
testing.expect(t, os.make_directory(directory) == nil)
|
||||||
|
testing.expect(t, os.write_entire_file(main_path, transmute([]byte)text))
|
||||||
|
testing.expect_value(t, compiler_core.compile_package(directory, output), 0)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
mutable_runtime_globals_compile_and_run :: proc(t: ^testing.T) {
|
mutable_runtime_globals_compile_and_run :: proc(t: ^testing.T) {
|
||||||
output := "/tmp/brolang-test-mutable-global"
|
output := "/tmp/brolang-test-mutable-global"
|
||||||
|
|||||||
Reference in New Issue
Block a user