refine returned match errors
This commit is contained in:
@@ -95,6 +95,11 @@ Defer_Entry :: struct {
|
||||
capture: hir.Local_Id,
|
||||
}
|
||||
|
||||
Error_Refinement :: struct {
|
||||
local: hir.Local_Id,
|
||||
variants: []u32,
|
||||
}
|
||||
|
||||
Build_Ctx :: struct {
|
||||
checker: ^Checker,
|
||||
pkg: ast.Package_Id,
|
||||
@@ -109,6 +114,7 @@ Build_Ctx :: struct {
|
||||
global_reads: ^[dynamic]hir.Global_Id,
|
||||
calls: ^[dynamic]hir.Function_Id,
|
||||
problematic: ^bool,
|
||||
error_refinements: ^[dynamic]Error_Refinement,
|
||||
// Stack of labeled value-loops being built (innermost last); see Yield_Target.
|
||||
yield_targets: ^[dynamic]Yield_Target,
|
||||
// `defer` lowering. Deferred statements are built once at the `defer` site and
|
||||
@@ -7738,6 +7744,63 @@ fallible_aggregate :: proc(
|
||||
})
|
||||
}
|
||||
|
||||
refined_error_projection :: proc(
|
||||
ctx: ^Build_Ctx,
|
||||
expr_id: hir.Expr_Id,
|
||||
target: types.Type,
|
||||
span: source.Span,
|
||||
) -> (hir.Expr_Id, bool) {
|
||||
checker := ctx.checker
|
||||
if expr_id == hir.INVALID_EXPR || int(expr_id) >= len(checker.module.exprs) {
|
||||
return hir.INVALID_EXPR, false
|
||||
}
|
||||
expr := checker.module.exprs[expr_id]
|
||||
if expr.kind != .Local {
|
||||
return hir.INVALID_EXPR, false
|
||||
}
|
||||
local := hir.as_local(expr.target)
|
||||
if local == hir.INVALID_LOCAL || int(local) >= len(ctx.hir_locals^) || ctx.hir_locals^[local].mutable {
|
||||
return hir.INVALID_EXPR, false
|
||||
}
|
||||
candidates: []u32
|
||||
for index := len(ctx.error_refinements^) - 1; index >= 0; index -= 1 {
|
||||
refinement := ctx.error_refinements^[index]
|
||||
if refinement.local == local {
|
||||
candidates = refinement.variants
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(candidates) == 0 {
|
||||
return hir.INVALID_EXPR, false
|
||||
}
|
||||
selected: [dynamic]u32
|
||||
selected.allocator = checker.allocator
|
||||
defer delete(selected)
|
||||
for candidate in candidates {
|
||||
allowed := true
|
||||
for refinement in ctx.error_refinements^ {
|
||||
if refinement.local != local {
|
||||
continue
|
||||
}
|
||||
found := false
|
||||
for variant in refinement.variants {
|
||||
found = found || variant == candidate
|
||||
}
|
||||
allowed = allowed && found
|
||||
}
|
||||
if allowed {
|
||||
append(&selected, candidate)
|
||||
}
|
||||
}
|
||||
if !types.selected_sum_fits(expr.type, target, selected[:], &checker.module.types) {
|
||||
return hir.INVALID_EXPR, false
|
||||
}
|
||||
return add_hir_expr(checker, hir.Expr{
|
||||
kind=.Sum_Project, span=span, type=target, left=expr_id,
|
||||
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
}), true
|
||||
}
|
||||
|
||||
build_compound_expr :: proc(
|
||||
checker: ^Checker,
|
||||
expr: ast.Expr,
|
||||
@@ -10837,11 +10900,24 @@ build_block :: proc(
|
||||
types.INVALID, ctx.pkg, ctx.file,
|
||||
)
|
||||
probe_type := checker.module.exprs[probe].type
|
||||
if can_implicitly_convert_type(checker, probe_type, error_type) &&
|
||||
!can_implicitly_convert_type(checker, probe_type, success) {
|
||||
returns_success := can_implicitly_convert_type(checker, probe_type, success)
|
||||
returns_error := can_implicitly_convert_type(checker, probe_type, error_type)
|
||||
if returns_error && !returns_success {
|
||||
error_exit = true
|
||||
value = probe
|
||||
} else if can_implicitly_convert_type(checker, probe_type, success) {
|
||||
} else if !returns_success {
|
||||
if projected, refined := refined_error_projection(ctx, probe, error_type, statement.span); refined {
|
||||
error_exit = true
|
||||
value = projected
|
||||
} else if types.is_enum(probe_type, store) || types.is_tagged_union(probe_type, store) {
|
||||
id := source.addf(
|
||||
checker.diagnostics, statement.span,
|
||||
"cannot return %s as success type %s or error type %s",
|
||||
type_label(checker, probe_type), type_label(checker, success), type_label(checker, error_type),
|
||||
)
|
||||
value = invalid_hir_expr(checker, statement.span, id, success)
|
||||
}
|
||||
} else {
|
||||
value = probe
|
||||
}
|
||||
}
|
||||
@@ -12155,6 +12231,14 @@ emit_match :: proc(
|
||||
kind = .Declaration, span = span, local = subj_local, expr = spill_value,
|
||||
diagnostic = source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
refinement_local := hir.INVALID_LOCAL
|
||||
if checker.module.exprs[subject].kind == .Local {
|
||||
candidate := hir.as_local(checker.module.exprs[subject].target)
|
||||
if candidate != hir.INVALID_LOCAL && int(candidate) < len(ctx.hir_locals^) &&
|
||||
!ctx.hir_locals^[candidate].mutable {
|
||||
refinement_local = candidate
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Dispatch key: a tagged union reads its discriminant into its own temp; an enum
|
||||
// or scalar compares the subject directly.
|
||||
@@ -12261,7 +12345,7 @@ emit_match :: proc(
|
||||
}
|
||||
arm_body, body_ok := build_match_arm_body(
|
||||
ctx, body_arm, subject_type, subj_local, subj_is_pointer, ptr_type, subj_writable,
|
||||
field_index, field.type, as_value, slot, slot_type, span,
|
||||
field_index, field.type, refinement_local, []u32{u32(name)}, as_value, slot, slot_type, span,
|
||||
)
|
||||
pop_static_bindings(checker, static_start)
|
||||
ok = body_ok && ok
|
||||
@@ -12289,7 +12373,7 @@ emit_match :: proc(
|
||||
body_arm.captures = nil
|
||||
arm_body, body_ok := build_match_arm_body(
|
||||
ctx, body_arm, subject_type, subj_local, subj_is_pointer, ptr_type, subj_writable,
|
||||
-1, types.INVALID, as_value, slot, slot_type, span,
|
||||
-1, types.INVALID, refinement_local, []u32{u32(name)}, as_value, slot, slot_type, span,
|
||||
)
|
||||
pop_static_bindings(checker, static_start)
|
||||
ok = body_ok && ok
|
||||
@@ -12308,6 +12392,8 @@ emit_match :: proc(
|
||||
field_index := -1
|
||||
payload_type := types.INVALID
|
||||
has_capture := len(arm.captures) > 0
|
||||
refinement_variants: [dynamic]u32
|
||||
refinement_variants.allocator = checker.allocator
|
||||
|
||||
if is_else {
|
||||
if has_capture {
|
||||
@@ -12315,6 +12401,12 @@ emit_match :: proc(
|
||||
ok = false
|
||||
}
|
||||
has_else = true
|
||||
member_enum := tag_enum if is_tagged else subject_type
|
||||
for member in types.enum_members_for(store, member_enum) {
|
||||
if !contains_name(covered[:], symbol.Id(member.name)) {
|
||||
append(&refinement_variants, member.name)
|
||||
}
|
||||
}
|
||||
} else if is_tagged || is_enum_subject {
|
||||
// The capture payload (if any) must be one type across every listed variant.
|
||||
capture_field := -1
|
||||
@@ -12338,6 +12430,7 @@ emit_match :: proc(
|
||||
ok = false
|
||||
} else {
|
||||
append(&covered, pattern.name)
|
||||
append(&refinement_variants, u32(pattern.name))
|
||||
}
|
||||
if is_tagged {
|
||||
index, field, found := find_struct_field(checker, subject_type, pattern.name)
|
||||
@@ -12432,7 +12525,11 @@ emit_match :: proc(
|
||||
}
|
||||
}
|
||||
|
||||
arm_body, body_ok := build_match_arm_body(ctx, arm, subject_type, subj_local, subj_is_pointer, ptr_type, subj_writable, field_index, payload_type, as_value, slot, slot_type, span)
|
||||
arm_body, body_ok := build_match_arm_body(
|
||||
ctx, arm, subject_type, subj_local, subj_is_pointer, ptr_type, subj_writable,
|
||||
field_index, payload_type, refinement_local, refinement_variants[:], as_value, slot, slot_type, span,
|
||||
)
|
||||
delete(refinement_variants)
|
||||
if !body_ok {
|
||||
ok = false
|
||||
}
|
||||
@@ -12541,6 +12638,8 @@ build_match_arm_body :: proc(
|
||||
subj_writable: bool,
|
||||
field_index: int,
|
||||
payload_type: types.Type,
|
||||
refinement_local: hir.Local_Id,
|
||||
refinement_variants: []u32,
|
||||
as_value: bool,
|
||||
slot: ^hir.Local_Id,
|
||||
slot_type: ^types.Type,
|
||||
@@ -12550,6 +12649,10 @@ build_match_arm_body :: proc(
|
||||
result: [dynamic]hir.Stmt_Id
|
||||
result.allocator = checker.allocator
|
||||
capture_start := len(ctx.locals^)
|
||||
refinement_start := len(ctx.error_refinements^)
|
||||
if refinement_local != hir.INVALID_LOCAL && len(refinement_variants) > 0 {
|
||||
append(ctx.error_refinements, Error_Refinement{local=refinement_local, variants=refinement_variants})
|
||||
}
|
||||
capture_ok := true
|
||||
|
||||
if len(arm.captures) > 0 && field_index >= 0 {
|
||||
@@ -12603,6 +12706,7 @@ build_match_arm_body :: proc(
|
||||
body_ok = build_value_arm(ctx, &result, arm.body, slot, slot_type, span) && body_ok
|
||||
}
|
||||
resize(ctx.locals, capture_start)
|
||||
resize(ctx.error_refinements, refinement_start)
|
||||
return result[:], body_ok
|
||||
}
|
||||
|
||||
@@ -13339,6 +13443,8 @@ build_function :: proc(checker: ^Checker, id: Spec_Id) {
|
||||
loop_is_loop.allocator = checker.allocator
|
||||
yield_targets: [dynamic]Yield_Target
|
||||
yield_targets.allocator = checker.allocator
|
||||
error_refinements: [dynamic]Error_Refinement
|
||||
error_refinements.allocator = checker.allocator
|
||||
ctx := Build_Ctx{
|
||||
checker = checker,
|
||||
pkg = function.pkg,
|
||||
@@ -13353,6 +13459,7 @@ build_function :: proc(checker: ^Checker, id: Spec_Id) {
|
||||
global_reads = &global_reads,
|
||||
calls = &calls,
|
||||
problematic = &problematic,
|
||||
error_refinements = &error_refinements,
|
||||
defers = &defers,
|
||||
loop_defer_starts = &loop_defer_starts,
|
||||
loop_labels = &loop_labels,
|
||||
@@ -13443,6 +13550,7 @@ build_function :: proc(checker: ^Checker, id: Spec_Id) {
|
||||
delete(loop_labels)
|
||||
delete(loop_is_loop)
|
||||
delete(yield_targets)
|
||||
delete(error_refinements)
|
||||
delete(locals)
|
||||
delete(local_spans)
|
||||
delete(local_used)
|
||||
|
||||
Reference in New Issue
Block a user