fallible ergonomics
This commit is contained in:
@@ -168,6 +168,7 @@ Checker :: struct {
|
||||
main_symbol: symbol.Id,
|
||||
sink_symbol: symbol.Id,
|
||||
current_result: types.Type,
|
||||
current_build_ctx: ^Build_Ctx,
|
||||
target: target.Target,
|
||||
allocator: mem.Allocator,
|
||||
}
|
||||
@@ -3429,9 +3430,20 @@ build_compound_expr :: proc(
|
||||
id := source.add(checker.diagnostics, expr.span, "'try' requires a fallible expression")
|
||||
return invalid_hir_expr(checker, expr.span, id)
|
||||
}
|
||||
if !types.equal(channel_type, checker.current_result) {
|
||||
// ponytail: exact channel propagation; add fallible-error widening when cross-error-set try matters.
|
||||
id := source.add(checker.diagnostics, expr.span, "'try' can only propagate the enclosing function's exact error channel in v1")
|
||||
enclosing_success := types.fallible_success(checker.current_result, store)
|
||||
enclosing_error := types.fallible_error(checker.current_result, store)
|
||||
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 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")
|
||||
return invalid_hir_expr(checker, expr.span, id)
|
||||
}
|
||||
if expr.right == ast.INVALID_EXPR {
|
||||
// ponytail: catch blocks need Build_Ctx threading through expression build; fallback catch covers v1.
|
||||
id := source.add(checker.diagnostics, expr.span, "catch block form is not implemented in v1")
|
||||
return invalid_hir_expr(checker, expr.span, id, success)
|
||||
body: []hir.Stmt_Id
|
||||
capture := hir.INVALID_LOCAL
|
||||
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)
|
||||
}
|
||||
capture_start := len(ctx.locals^)
|
||||
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)
|
||||
}
|
||||
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)
|
||||
return add_hir_expr(checker, hir.Expr{
|
||||
kind=.Catch,
|
||||
span=expr.span,
|
||||
type=success,
|
||||
integer=1 if block_handler else 0,
|
||||
left=channel,
|
||||
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,
|
||||
})
|
||||
case .Range:
|
||||
@@ -6727,9 +6761,12 @@ build_function :: proc(checker: ^Checker, id: Spec_Id) {
|
||||
yield_targets = &yield_targets,
|
||||
}
|
||||
previous_result := checker.current_result
|
||||
previous_ctx := checker.current_build_ctx
|
||||
checker.current_result = spec.result
|
||||
checker.current_build_ctx = &ctx
|
||||
block := build_block(&ctx, function.body)
|
||||
checker.current_result = previous_result
|
||||
checker.current_build_ctx = previous_ctx
|
||||
returns := all_paths_return(&checker.module, block)
|
||||
for block_stmt in block {
|
||||
append(&body, block_stmt)
|
||||
|
||||
@@ -129,6 +129,9 @@ Expr :: struct {
|
||||
type: types.Type,
|
||||
integer: i64,
|
||||
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,
|
||||
left: Expr_Id,
|
||||
right: Expr_Id,
|
||||
@@ -269,6 +272,7 @@ init_module :: proc(selected := target.DEFAULT, allocator := context.allocator)
|
||||
destroy_module :: proc(module: ^Module) {
|
||||
for expr in module.exprs {
|
||||
delete(expr.args, module.allocator)
|
||||
delete(expr.body, module.allocator)
|
||||
}
|
||||
for statement in module.statements {
|
||||
delete(statement.unwraps, module.allocator)
|
||||
|
||||
@@ -80,6 +80,7 @@ Opcode :: enum u8 {
|
||||
Index_Address,
|
||||
Field_Address,
|
||||
Union_Tag,
|
||||
Fallible_Error,
|
||||
Load,
|
||||
Store,
|
||||
Fill,
|
||||
|
||||
+53
-1
@@ -254,7 +254,7 @@ valid_value :: proc(
|
||||
switch instructions[value_id].op {
|
||||
case .Param, .Const, .String, .Aggregate, .None, .Optional_Some,
|
||||
.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,
|
||||
.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:
|
||||
@@ -1056,6 +1056,37 @@ emit_instruction_stream :: proc(
|
||||
llvm_type(instruction.type, &emitter.module.types),
|
||||
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:
|
||||
if !valid_address(instructions, instruction.a, 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)
|
||||
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) {
|
||||
from_name := llvm_type(from_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,
|
||||
})
|
||||
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{
|
||||
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,
|
||||
})
|
||||
} 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)
|
||||
append_instruction(state, ir.Instruction{
|
||||
op=.Store, span=expr.span, type=success,
|
||||
|
||||
Reference in New Issue
Block a user