fallible ergonomics
This commit is contained in:
@@ -576,10 +576,12 @@
|
|||||||
merge/conflict/widening/rejections and the `.Yield` inference regression
|
merge/conflict/widening/rejections and the `.Yield` inference regression
|
||||||
- deferred follow-ups are split below; 23.5 keeps the next user-visible slice small
|
- deferred follow-ups are split below; 23.5 keeps the next user-visible slice small
|
||||||
|
|
||||||
23.5. fallible ergonomics: catch blocks + composable try widening
|
23.5. fallible ergonomics: catch blocks + composable try widening (implemented)
|
||||||
- implement `expr catch |e| { ... }` with `e : E`
|
- `expr catch |e| { ... }` binds `e : E` in the handler and uses the existing value-block
|
||||||
- allow `try` to widen `T ! E1` into an enclosing `T ! (E1 | E2)` channel
|
`yield` rules to produce the fallback success value
|
||||||
- add focused tests/examples for both
|
- `try` still requires the same success type `T`, but now propagates either the exact error
|
||||||
|
channel or a sum-widenable `E1` into an enclosing `E1 | E2`
|
||||||
|
- focused coverage lives in `examples/programs/errors` and the fallible ergonomics compiler test
|
||||||
- leave ABI/layout/lint/design polish for later milestones
|
- leave ABI/layout/lint/design polish for later milestones
|
||||||
|
|
||||||
23.6. contextual payload construction + inline error types
|
23.6. contextual payload construction + inline error types
|
||||||
@@ -974,7 +976,7 @@ message :: match code {
|
|||||||
|
|
||||||
Brolang handles errors as values. There is no hidden control flow — a function that can fail declares this in its signature, and the caller must explicitly handle the possibility of failure.
|
Brolang handles errors as values. There is no hidden control flow — a function that can fail declares this in its signature, and the caller must explicitly handle the possibility of failure.
|
||||||
|
|
||||||
Milestone 23 v1 implements named error channels, native sum composition, `return`-based error dispatch, exact-channel `try`, and fallback `catch`. It intentionally defers the `error` keyword shorthand, inline error types, `catch |e|` blocks, and `try` widening across different-but-composable error channels.
|
Milestone 23 v1 implements named error channels, native sum composition, `return`-based error dispatch, exact-channel `try`, and fallback `catch`. Milestone 23.5 adds `catch |e|` blocks and `try` widening across composable error channels. It still defers the `error` keyword shorthand, inline error types, contextual payload construction, and match-on-error shorthand.
|
||||||
|
|
||||||
### Fallible Functions
|
### Fallible Functions
|
||||||
|
|
||||||
@@ -1093,11 +1095,11 @@ process func(path []u8) Ast ! ProcessError {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
In v1, `try` propagates only when the callee's error channel exactly matches the enclosing function's error channel. Widening narrower error types into a composed return channel is a planned follow-up.
|
`try` propagates when the success type matches the enclosing fallible function and the callee's error channel either exactly matches or can widen into the enclosing composed error channel.
|
||||||
|
|
||||||
### Handling with `catch`
|
### Handling with `catch`
|
||||||
|
|
||||||
The `catch` keyword handles errors and provides a value to continue with. Milestone 23 v1 implements the fallback-value form.
|
The `catch` keyword handles errors and provides a value to continue with. It supports both fallback values and block handlers.
|
||||||
|
|
||||||
**Provide a fallback value:**
|
**Provide a fallback value:**
|
||||||
|
|
||||||
@@ -1105,7 +1107,7 @@ The `catch` keyword handles errors and provides a value to continue with. Milest
|
|||||||
data :: read_file(path) catch default_data
|
data :: read_file(path) catch default_data
|
||||||
```
|
```
|
||||||
|
|
||||||
**Planned block form using** `yield` (deferred in v1):
|
**Block form using** `yield`:
|
||||||
|
|
||||||
```
|
```
|
||||||
data :: read_file(path) catch |e| {
|
data :: read_file(path) catch |e| {
|
||||||
@@ -1156,9 +1158,9 @@ data :: read_file(path) catch |e| match e {
|
|||||||
| `return e` | Exit function via error channel when `e : E` |
|
| `return e` | Exit function via error channel when `e : E` |
|
||||||
| `error e` | Planned shorthand, not v1 |
|
| `error e` | Planned shorthand, not v1 |
|
||||||
| `error .variant{...}` | Planned shorthand, not v1 |
|
| `error .variant{...}` | Planned shorthand, not v1 |
|
||||||
| `try expr` | Unwrap success or propagate an exact matching error channel |
|
| `try expr` | Unwrap success or propagate an exact/sum-widenable error channel |
|
||||||
| `expr catch fallback` | Provide fallback value on error |
|
| `expr catch fallback` | Provide fallback value on error |
|
||||||
| `expr catch |e| { ... }` | Planned block handler, not v1 |
|
| `expr catch |e| { ... }` | Bind `e : E` and yield a fallback value from the handler |
|
||||||
| `yield value` | Provide value from innermost block |
|
| `yield value` | Provide value from innermost block |
|
||||||
| `yield :label value` | Provide value from labeled block |
|
| `yield :label value` | Provide value from labeled block |
|
||||||
| `return` / `return value` | Exit the current function; in fallible functions, return value dispatches by type |
|
| `return` / `return value` | Exit the current function; in fallible functions, return value dispatches by type |
|
||||||
|
|||||||
@@ -168,6 +168,7 @@ Checker :: struct {
|
|||||||
main_symbol: symbol.Id,
|
main_symbol: symbol.Id,
|
||||||
sink_symbol: symbol.Id,
|
sink_symbol: symbol.Id,
|
||||||
current_result: types.Type,
|
current_result: types.Type,
|
||||||
|
current_build_ctx: ^Build_Ctx,
|
||||||
target: target.Target,
|
target: target.Target,
|
||||||
allocator: mem.Allocator,
|
allocator: mem.Allocator,
|
||||||
}
|
}
|
||||||
@@ -3429,9 +3430,20 @@ build_compound_expr :: proc(
|
|||||||
id := source.add(checker.diagnostics, expr.span, "'try' requires a fallible expression")
|
id := source.add(checker.diagnostics, expr.span, "'try' requires a fallible expression")
|
||||||
return invalid_hir_expr(checker, expr.span, id)
|
return invalid_hir_expr(checker, expr.span, id)
|
||||||
}
|
}
|
||||||
if !types.equal(channel_type, checker.current_result) {
|
enclosing_success := types.fallible_success(checker.current_result, store)
|
||||||
// ponytail: exact channel propagation; add fallible-error widening when cross-error-set try matters.
|
enclosing_error := types.fallible_error(checker.current_result, store)
|
||||||
id := source.add(checker.diagnostics, expr.span, "'try' can only propagate the enclosing function's exact error channel in v1")
|
if !types.is_valid(enclosing_success) {
|
||||||
|
id := source.add(checker.diagnostics, expr.span, "'try' requires an enclosing fallible function")
|
||||||
|
return invalid_hir_expr(checker, expr.span, id, success)
|
||||||
|
}
|
||||||
|
if !types.equal(success, enclosing_success) {
|
||||||
|
id := source.add(checker.diagnostics, expr.span, "'try' success type must match the enclosing fallible result")
|
||||||
|
return invalid_hir_expr(checker, expr.span, id, success)
|
||||||
|
}
|
||||||
|
error_type := types.fallible_error(channel_type, store)
|
||||||
|
if !types.equal(error_type, enclosing_error) &&
|
||||||
|
!types.can_sum_widen(error_type, enclosing_error, store) {
|
||||||
|
id := source.add(checker.diagnostics, expr.span, "'try' error channel cannot be widened to the enclosing error channel")
|
||||||
return invalid_hir_expr(checker, expr.span, id, success)
|
return invalid_hir_expr(checker, expr.span, id, success)
|
||||||
}
|
}
|
||||||
return add_hir_expr(checker, hir.Expr{
|
return add_hir_expr(checker, hir.Expr{
|
||||||
@@ -3451,20 +3463,42 @@ build_compound_expr :: proc(
|
|||||||
id := source.add(checker.diagnostics, expr.span, "'catch' requires a fallible expression")
|
id := source.add(checker.diagnostics, expr.span, "'catch' requires a fallible expression")
|
||||||
return invalid_hir_expr(checker, expr.span, id)
|
return invalid_hir_expr(checker, expr.span, id)
|
||||||
}
|
}
|
||||||
if expr.right == ast.INVALID_EXPR {
|
body: []hir.Stmt_Id
|
||||||
// ponytail: catch blocks need Build_Ctx threading through expression build; fallback catch covers v1.
|
capture := hir.INVALID_LOCAL
|
||||||
id := source.add(checker.diagnostics, expr.span, "catch block form is not implemented in v1")
|
block_handler := false
|
||||||
|
fallback := hir.INVALID_EXPR
|
||||||
|
if expr.right != ast.INVALID_EXPR {
|
||||||
|
fallback = build_nested_expr(checker, expr.right, 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)
|
return invalid_hir_expr(checker, expr.span, id, success)
|
||||||
}
|
}
|
||||||
fallback := build_nested_expr(checker, expr.right, locals, global_reads, calls, success, pkg, file)
|
capture_start := len(ctx.locals^)
|
||||||
fallback = coerce_expr(checker, fallback, success, checker.module.exprs[fallback].span)
|
error_type := types.fallible_error(channel_type, store)
|
||||||
|
if symbol.is_valid(expr.name) && expr.name != checker.sink_symbol {
|
||||||
|
capture = hir.local_id(len(ctx.hir_locals^))
|
||||||
|
append(ctx.hir_locals, hir.Local{name=expr.name, type=error_type, mutable=false})
|
||||||
|
append(ctx.locals, Build_Local{name=expr.name, type=error_type, mutable=false, id=capture})
|
||||||
|
}
|
||||||
|
handler: [dynamic]hir.Stmt_Id
|
||||||
|
handler.allocator = checker.allocator
|
||||||
|
fallback, _ = build_value_source(ctx, &handler, expr.body, success, expr.span)
|
||||||
|
body = handler[:]
|
||||||
|
resize(ctx.locals, capture_start)
|
||||||
|
}
|
||||||
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,
|
||||||
left=channel,
|
left=channel,
|
||||||
right=fallback,
|
right=fallback,
|
||||||
target=hir.INVALID_REF,
|
body=body,
|
||||||
|
target=hir.local_ref(capture) if capture != hir.INVALID_LOCAL else hir.INVALID_REF,
|
||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
case .Range:
|
case .Range:
|
||||||
@@ -6727,9 +6761,12 @@ build_function :: proc(checker: ^Checker, id: Spec_Id) {
|
|||||||
yield_targets = &yield_targets,
|
yield_targets = &yield_targets,
|
||||||
}
|
}
|
||||||
previous_result := checker.current_result
|
previous_result := checker.current_result
|
||||||
|
previous_ctx := checker.current_build_ctx
|
||||||
checker.current_result = spec.result
|
checker.current_result = spec.result
|
||||||
|
checker.current_build_ctx = &ctx
|
||||||
block := build_block(&ctx, function.body)
|
block := build_block(&ctx, function.body)
|
||||||
checker.current_result = previous_result
|
checker.current_result = previous_result
|
||||||
|
checker.current_build_ctx = previous_ctx
|
||||||
returns := all_paths_return(&checker.module, block)
|
returns := all_paths_return(&checker.module, block)
|
||||||
for block_stmt in block {
|
for block_stmt in block {
|
||||||
append(&body, block_stmt)
|
append(&body, block_stmt)
|
||||||
|
|||||||
@@ -129,6 +129,9 @@ Expr :: struct {
|
|||||||
type: types.Type,
|
type: types.Type,
|
||||||
integer: i64,
|
integer: i64,
|
||||||
args: []Expr_Id,
|
args: []Expr_Id,
|
||||||
|
// `Catch` block handlers use `body` for the handler statements and `target`
|
||||||
|
// for the optional captured error local.
|
||||||
|
body: []Stmt_Id,
|
||||||
target: Ref,
|
target: Ref,
|
||||||
left: Expr_Id,
|
left: Expr_Id,
|
||||||
right: Expr_Id,
|
right: Expr_Id,
|
||||||
@@ -269,6 +272,7 @@ init_module :: proc(selected := target.DEFAULT, allocator := context.allocator)
|
|||||||
destroy_module :: proc(module: ^Module) {
|
destroy_module :: proc(module: ^Module) {
|
||||||
for expr in module.exprs {
|
for expr in module.exprs {
|
||||||
delete(expr.args, module.allocator)
|
delete(expr.args, module.allocator)
|
||||||
|
delete(expr.body, module.allocator)
|
||||||
}
|
}
|
||||||
for statement in module.statements {
|
for statement in module.statements {
|
||||||
delete(statement.unwraps, module.allocator)
|
delete(statement.unwraps, module.allocator)
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ Opcode :: enum u8 {
|
|||||||
Index_Address,
|
Index_Address,
|
||||||
Field_Address,
|
Field_Address,
|
||||||
Union_Tag,
|
Union_Tag,
|
||||||
|
Fallible_Error,
|
||||||
Load,
|
Load,
|
||||||
Store,
|
Store,
|
||||||
Fill,
|
Fill,
|
||||||
|
|||||||
+53
-1
@@ -254,7 +254,7 @@ valid_value :: proc(
|
|||||||
switch instructions[value_id].op {
|
switch instructions[value_id].op {
|
||||||
case .Param, .Const, .String, .Aggregate, .None, .Optional_Some,
|
case .Param, .Const, .String, .Aggregate, .None, .Optional_Some,
|
||||||
.Load_Global, .Function_Address, .Address_Of, .Load, .Union_Tag, .Slice, .Length, .Slice_Ptr,
|
.Load_Global, .Function_Address, .Address_Of, .Load, .Union_Tag, .Slice, .Length, .Slice_Ptr,
|
||||||
.Extract, .Select, .Unwrap,
|
.Fallible_Error, .Extract, .Select, .Unwrap,
|
||||||
.Optional_Is_Some, .Optional_Value, .Orelse,
|
.Optional_Is_Some, .Optional_Value, .Orelse,
|
||||||
.Widen, .Sum_Widen, .C_Coerce, .C_Vararg_Promote, .Retype, .Weaken_Pointer, .Weaken_Slice, .Decay_Array_Pointer,
|
.Widen, .Sum_Widen, .C_Coerce, .C_Vararg_Promote, .Retype, .Weaken_Pointer, .Weaken_Slice, .Decay_Array_Pointer,
|
||||||
.Neg_Checked, .Add_Checked, .Sub_Checked, .Mul_Checked, .Div_Checked, .Pointer_Add, .Not, .Compare, .Call:
|
.Neg_Checked, .Add_Checked, .Sub_Checked, .Mul_Checked, .Div_Checked, .Pointer_Add, .Not, .Compare, .Call:
|
||||||
@@ -1056,6 +1056,37 @@ emit_instruction_stream :: proc(
|
|||||||
llvm_type(instruction.type, &emitter.module.types),
|
llvm_type(instruction.type, &emitter.module.types),
|
||||||
instruction.a,
|
instruction.a,
|
||||||
)
|
)
|
||||||
|
case .Fallible_Error:
|
||||||
|
channel_type := instructions[instruction.a].type if valid_instruction(instructions, instruction.a) else types.INVALID
|
||||||
|
error_type := types.fallible_error(channel_type, &emitter.module.types)
|
||||||
|
if !types.equal(error_type, instruction.type) ||
|
||||||
|
!valid_address(instructions, instruction.a, channel_type, &emitter.module.types) {
|
||||||
|
emit_recovery_value(emitter, instruction_index, instruction, "invalid fallible error operand")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if types.is_enum(error_type, &emitter.module.types) {
|
||||||
|
fmt.sbprintf(&emitter.builder, " %%v%d = load %s, ptr %%v%d\n", instruction_index, llvm_type(error_type, &emitter.module.types), instruction.a)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if types.is_tagged_union(error_type, &emitter.module.types) {
|
||||||
|
type_name := llvm_type(error_type, &emitter.module.types)
|
||||||
|
align := types.alignment_of(error_type, &emitter.module.types, emitter.module.target)
|
||||||
|
fmt.sbprintf(&emitter.builder, " %%fallible_error_slot%d = alloca %s, align %d\n", instruction_index, type_name, align)
|
||||||
|
fmt.sbprintf(&emitter.builder, " store %s zeroinitializer, ptr %%fallible_error_slot%d\n", type_name, instruction_index)
|
||||||
|
fmt.sbprintf(&emitter.builder, " %%fallible_error_code%d = load i16, ptr %%v%d\n", instruction_index, instruction.a)
|
||||||
|
fmt.sbprintf(&emitter.builder, " store i16 %%fallible_error_code%d, ptr %%fallible_error_slot%d\n", instruction_index, instruction_index)
|
||||||
|
payload_size := types.sum_payload_size(error_type, &emitter.module.types, emitter.module.target)
|
||||||
|
if payload_size > 0 {
|
||||||
|
source_offset := types.fallible_payload_offset(channel_type, &emitter.module.types, emitter.module.target)
|
||||||
|
target_offset := types.union_payload_offset(error_type, &emitter.module.types, emitter.module.target)
|
||||||
|
fmt.sbprintf(&emitter.builder, " %%fallible_error_source%d = getelementptr i8, ptr %%v%d, i64 %d\n", instruction_index, instruction.a, source_offset)
|
||||||
|
fmt.sbprintf(&emitter.builder, " %%fallible_error_payload%d = getelementptr i8, ptr %%fallible_error_slot%d, i64 %d\n", instruction_index, instruction_index, target_offset)
|
||||||
|
fmt.sbprintf(&emitter.builder, " call void @llvm.memcpy.p0.p0.i64(ptr %%fallible_error_payload%d, ptr %%fallible_error_source%d, i64 %d, i1 false)\n", instruction_index, instruction_index, payload_size)
|
||||||
|
}
|
||||||
|
fmt.sbprintf(&emitter.builder, " %%v%d = load %s, ptr %%fallible_error_slot%d\n", instruction_index, type_name, instruction_index)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
emit_recovery_value(emitter, instruction_index, instruction, "unsupported fallible error type")
|
||||||
case .Store:
|
case .Store:
|
||||||
if !valid_address(instructions, instruction.a, instruction.type, &emitter.module.types) ||
|
if !valid_address(instructions, instruction.a, instruction.type, &emitter.module.types) ||
|
||||||
!valid_value(instructions, instruction.b, instruction.type, &emitter.module.types) {
|
!valid_value(instructions, instruction.b, instruction.type, &emitter.module.types) {
|
||||||
@@ -1324,6 +1355,27 @@ emit_instruction_stream :: proc(
|
|||||||
fmt.sbprintf(&emitter.builder, ", %s zeroinitializer\n", type_name)
|
fmt.sbprintf(&emitter.builder, ", %s zeroinitializer\n", type_name)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if types.is_enum(from_type, &emitter.module.types) && types.is_tagged_union(instruction.type, &emitter.module.types) {
|
||||||
|
to_name := llvm_type(instruction.type, &emitter.module.types)
|
||||||
|
to_align := types.alignment_of(instruction.type, &emitter.module.types, emitter.module.target)
|
||||||
|
fmt.sbprintf(&emitter.builder, " %%sum_to_slot%d = alloca %s, align %d\n", instruction_index, to_name, to_align)
|
||||||
|
fmt.sbprintf(&emitter.builder, " store %s zeroinitializer, ptr %%sum_to_slot%d\n", to_name, instruction_index)
|
||||||
|
fmt.sbprintf(&emitter.builder, " store i16 ")
|
||||||
|
write_operand(&emitter.builder, instructions, instruction.a, from_type, &emitter.module.types)
|
||||||
|
fmt.sbprintf(&emitter.builder, ", ptr %%sum_to_slot%d\n", instruction_index)
|
||||||
|
fmt.sbprintf(&emitter.builder, " %%v%d = load %s, ptr %%sum_to_slot%d\n", instruction_index, to_name, instruction_index)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if types.is_tagged_union(from_type, &emitter.module.types) && types.is_enum(instruction.type, &emitter.module.types) {
|
||||||
|
from_name := llvm_type(from_type, &emitter.module.types)
|
||||||
|
from_align := types.alignment_of(from_type, &emitter.module.types, emitter.module.target)
|
||||||
|
fmt.sbprintf(&emitter.builder, " %%sum_from_slot%d = alloca %s, align %d\n", instruction_index, from_name, from_align)
|
||||||
|
fmt.sbprintf(&emitter.builder, " store %s ", from_name)
|
||||||
|
write_operand(&emitter.builder, instructions, instruction.a, from_type, &emitter.module.types)
|
||||||
|
fmt.sbprintf(&emitter.builder, ", ptr %%sum_from_slot%d\n", instruction_index)
|
||||||
|
fmt.sbprintf(&emitter.builder, " %%v%d = load %s, ptr %%sum_from_slot%d\n", instruction_index, llvm_type(instruction.type, &emitter.module.types), instruction_index)
|
||||||
|
continue
|
||||||
|
}
|
||||||
if types.is_tagged_union(from_type, &emitter.module.types) && types.is_tagged_union(instruction.type, &emitter.module.types) {
|
if types.is_tagged_union(from_type, &emitter.module.types) && types.is_tagged_union(instruction.type, &emitter.module.types) {
|
||||||
from_name := llvm_type(from_type, &emitter.module.types)
|
from_name := llvm_type(from_type, &emitter.module.types)
|
||||||
to_name := llvm_type(instruction.type, &emitter.module.types)
|
to_name := llvm_type(instruction.type, &emitter.module.types)
|
||||||
|
|||||||
@@ -412,12 +412,60 @@ lower_compound_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instructi
|
|||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
if expr.kind == .Try {
|
if expr.kind == .Try {
|
||||||
|
result := channel
|
||||||
|
if !types.equal(channel_type, state.func_result) {
|
||||||
|
error_type := types.fallible_error(channel_type, &state.hir_module.types)
|
||||||
|
enclosing_error := types.fallible_error(state.func_result, &state.hir_module.types)
|
||||||
|
error_value := append_instruction(state, ir.Instruction{
|
||||||
|
op=.Fallible_Error, span=expr.span, type=error_type,
|
||||||
|
target=ir.INVALID_REF, a=channel_slot, b=ir.INVALID_INSTRUCTION,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
if !types.equal(error_type, enclosing_error) {
|
||||||
|
error_value = append_instruction(state, ir.Instruction{
|
||||||
|
op=.Sum_Widen, span=expr.span, type=enclosing_error,
|
||||||
|
target=ir.INVALID_REF, a=error_value, b=ir.INVALID_INSTRUCTION,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
args := make([]ir.Instruction_Id, 1, state.allocator)
|
||||||
|
args[0] = error_value
|
||||||
|
result = append_instruction(state, ir.Instruction{
|
||||||
|
op=.Aggregate, span=expr.span, type=state.func_result, integer=1,
|
||||||
|
args=args, target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION,
|
||||||
|
b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
}
|
||||||
append_instruction(state, ir.Instruction{
|
append_instruction(state, ir.Instruction{
|
||||||
op=.Return, span=expr.span, type=state.func_result,
|
op=.Return, span=expr.span, type=state.func_result,
|
||||||
target=ir.INVALID_REF, a=channel, b=ir.INVALID_INSTRUCTION,
|
target=ir.INVALID_REF, a=result, b=ir.INVALID_INSTRUCTION,
|
||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
if expr.integer != 0 {
|
||||||
|
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,
|
||||||
|
target=ir.INVALID_REF, a=channel_slot, b=ir.INVALID_INSTRUCTION,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
capture_slot := append_instruction(state, ir.Instruction{
|
||||||
|
op=.Alloca, span=expr.span, type=error_type,
|
||||||
|
target=ir.local_ref(ir.Local_Id(capture)),
|
||||||
|
a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
state.local_slots[capture] = capture_slot
|
||||||
|
append_instruction(state, ir.Instruction{
|
||||||
|
op=.Store, span=expr.span, type=error_type,
|
||||||
|
target=ir.INVALID_REF, a=capture_slot, b=error_value,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
lower_statements(state, expr.body)
|
||||||
|
}
|
||||||
fallback := lower_nested_expr(state, expr.right)
|
fallback := lower_nested_expr(state, expr.right)
|
||||||
append_instruction(state, ir.Instruction{
|
append_instruction(state, ir.Instruction{
|
||||||
op=.Store, span=expr.span, type=success,
|
op=.Store, span=expr.span, type=success,
|
||||||
|
|||||||
@@ -2439,6 +2439,81 @@ main func() i32 {
|
|||||||
testing.expect(t, len(hir_module.functions) > 1)
|
testing.expect(t, len(hir_module.functions) > 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
fallible_ergonomics_rejects_bad_try_and_catch_blocks :: proc(t: ^testing.T) {
|
||||||
|
text := `A :: enum {
|
||||||
|
a
|
||||||
|
}
|
||||||
|
B :: enum {
|
||||||
|
b
|
||||||
|
}
|
||||||
|
fa func() i32 ! A {
|
||||||
|
return .a
|
||||||
|
}
|
||||||
|
bad_error func() i32 ! B {
|
||||||
|
x :: try fa()
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
bad_catch func() i32 {
|
||||||
|
return fa() catch |e| {
|
||||||
|
_ = e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
main func() i32 {
|
||||||
|
_ = bad_error() catch 0
|
||||||
|
return bad_catch()
|
||||||
|
}
|
||||||
|
`
|
||||||
|
source_file := source.Source{path="fallible_ergonomics.bro", text=text}
|
||||||
|
diagnostics := source.init_diagnostics(&source_file)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
symbols := symbol.init_table()
|
||||||
|
defer symbol.destroy_table(&symbols)
|
||||||
|
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||||
|
defer delete(stream.items)
|
||||||
|
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||||
|
defer ast.destroy_module(&ast_module)
|
||||||
|
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||||
|
defer hir.destroy_module(&hir_module)
|
||||||
|
|
||||||
|
found_error := false
|
||||||
|
found_yield := false
|
||||||
|
for diagnostic in diagnostics.items {
|
||||||
|
found_error = found_error || strings.contains(diagnostic.message, "'try' error channel cannot be widened")
|
||||||
|
found_yield = found_yield || strings.contains(diagnostic.message, "a value block must end with an explicit 'yield'")
|
||||||
|
}
|
||||||
|
testing.expect(t, found_error)
|
||||||
|
testing.expect(t, found_yield)
|
||||||
|
|
||||||
|
success_text := `A :: enum {
|
||||||
|
a
|
||||||
|
}
|
||||||
|
B :: enum {
|
||||||
|
b
|
||||||
|
}
|
||||||
|
Both :: alias A | B
|
||||||
|
fs func() i64 ! A {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
bad_success func() i32 ! Both {
|
||||||
|
x :: try fs()
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
main func() i32 {
|
||||||
|
return bad_success() catch 0
|
||||||
|
}
|
||||||
|
`
|
||||||
|
directory := "/tmp/brolang-test-try-success-mismatch"
|
||||||
|
main_path := "/tmp/brolang-test-try-success-mismatch/main.bro"
|
||||||
|
output := "/tmp/brolang-test-try-success-mismatch-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)success_text))
|
||||||
|
testing.expect_value(t, compiler_core.compile_package(directory, output), 1)
|
||||||
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
errors_example_compiles_and_runs :: proc(t: ^testing.T) {
|
errors_example_compiles_and_runs :: proc(t: ^testing.T) {
|
||||||
output := "/tmp/brolang-test-errors"
|
output := "/tmp/brolang-test-errors"
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ DetailError :: union(enum) {
|
|||||||
empty void
|
empty void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BasicOrDetail :: alias BasicError | DetailError
|
||||||
|
|
||||||
Left :: enum {
|
Left :: enum {
|
||||||
left
|
left
|
||||||
}
|
}
|
||||||
@@ -30,6 +32,7 @@ Box :: alias BoxA | BoxB
|
|||||||
|
|
||||||
maybe func(value i32) i32 ! BasicError {
|
maybe func(value i32) i32 ! BasicError {
|
||||||
if (value == 0) return .bad
|
if (value == 0) return .bad
|
||||||
|
if (value < 0) return .worse
|
||||||
return value + 1
|
return value + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,6 +47,41 @@ with_detail func(value i32) i32 ! DetailError {
|
|||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
via_widen func(value i32) i32 ! BasicOrDetail {
|
||||||
|
unwrapped :: try maybe(value)
|
||||||
|
return unwrapped + 2
|
||||||
|
}
|
||||||
|
|
||||||
|
catch_basic func(value i32) i32 {
|
||||||
|
return maybe(value) catch |e| {
|
||||||
|
if (e == .bad) {
|
||||||
|
yield 21
|
||||||
|
} else {
|
||||||
|
yield 22
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
catch_detail func(value i32) i32 {
|
||||||
|
return with_detail(value) catch |e| {
|
||||||
|
match e {
|
||||||
|
.code |n|: yield n + 30
|
||||||
|
.empty: yield 40
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
catch_widen func(value i32) i32 {
|
||||||
|
return via_widen(value) catch |e| {
|
||||||
|
match e {
|
||||||
|
.bad: yield 50
|
||||||
|
.worse: yield 51
|
||||||
|
.code |n|: yield n
|
||||||
|
.empty: yield 52
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pick func(value Both) i32 {
|
pick func(value Both) i32 {
|
||||||
match value {
|
match value {
|
||||||
.left: return 10
|
.left: return 10
|
||||||
@@ -67,8 +105,15 @@ main func() i32 {
|
|||||||
d :: via_try(0) catch 11
|
d :: via_try(0) catch 11
|
||||||
e :: with_detail(0) catch 13
|
e :: with_detail(0) catch 13
|
||||||
f :: with_detail(2) catch 99
|
f :: with_detail(2) catch 99
|
||||||
|
g :: catch_basic(0)
|
||||||
|
h :: catch_basic(-1)
|
||||||
|
i :: catch_detail(0)
|
||||||
|
j :: catch_detail(1)
|
||||||
|
k :: via_widen(3) catch 99
|
||||||
|
l :: catch_widen(0)
|
||||||
|
m :: catch_widen(-1)
|
||||||
|
|
||||||
acc = acc + a + b + c + d + e + f
|
acc = acc + a + b + c + d + e + f + g + h + i + j + k + l + m
|
||||||
acc = acc + pick(.left)
|
acc = acc + pick(.left)
|
||||||
r Right = .right
|
r Right = .right
|
||||||
acc = acc + pick(r)
|
acc = acc + pick(r)
|
||||||
@@ -77,6 +122,6 @@ main func() i32 {
|
|||||||
empty BoxB = .b
|
empty BoxB = .b
|
||||||
acc = acc + payload(empty)
|
acc = acc + payload(empty)
|
||||||
|
|
||||||
# 7 + 5 + 5 + 11 + 13 + 2 + 10 + 20 + 8 + 3 = 84
|
# 7 + 5 + 5 + 11 + 13 + 2 + 21 + 22 + 35 + 40 + 6 + 50 + 51 + 10 + 20 + 8 + 3 = 309
|
||||||
return acc - 84
|
return acc - 309
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user