refine returned match errors
This commit is contained in:
@@ -286,6 +286,11 @@ Ct_Binding :: struct {
|
||||
mutable: bool,
|
||||
}
|
||||
|
||||
Ct_Error_Refinement :: struct {
|
||||
cell: Ct_Cell_Id,
|
||||
variants: []u32,
|
||||
}
|
||||
|
||||
Ct_Flow_Kind :: enum u8 {
|
||||
Normal,
|
||||
Return,
|
||||
@@ -311,8 +316,10 @@ Ct_State :: struct {
|
||||
places: [dynamic]Ct_Place,
|
||||
paths: [dynamic]Ct_Path_Elem,
|
||||
bindings: [dynamic]Ct_Binding,
|
||||
error_refinements: [dynamic]Ct_Error_Refinement,
|
||||
defers: [dynamic]Ct_Defer,
|
||||
defer_depth: int,
|
||||
value_return_depth: int,
|
||||
steps: int,
|
||||
error: Ct_Error_Kind,
|
||||
diagnostic: source.Diagnostic_Id,
|
||||
@@ -350,6 +357,7 @@ ct_state_make :: proc(
|
||||
state.places.allocator = checker.allocator
|
||||
state.paths.allocator = checker.allocator
|
||||
state.bindings.allocator = checker.allocator
|
||||
state.error_refinements.allocator = checker.allocator
|
||||
state.defers.allocator = checker.allocator
|
||||
for value in values {
|
||||
if value.kind == .Integer {
|
||||
@@ -387,6 +395,7 @@ ct_state_destroy :: proc(state: ^Ct_State) {
|
||||
delete(state.places)
|
||||
delete(state.paths)
|
||||
delete(state.bindings)
|
||||
delete(state.error_refinements)
|
||||
delete(state.defers)
|
||||
}
|
||||
|
||||
@@ -3776,7 +3785,9 @@ ct_eval_catch_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Typ
|
||||
ct_pop_bindings(state, scope_start)
|
||||
return result, result_flow, result_ok
|
||||
}
|
||||
state.value_return_depth += 1
|
||||
handler, handler_ok := ct_exec_statements(state, expr.body, true, depth+1)
|
||||
state.value_return_depth -= 1
|
||||
ct_pop_bindings(state, scope_start)
|
||||
if !handler_ok {
|
||||
return INVALID_CT_VALUE, handler, false
|
||||
@@ -3791,6 +3802,9 @@ ct_eval_catch_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Typ
|
||||
value := ct_add_value(state, Ct_Value{kind=.Void, type=types.VOID})
|
||||
return value, ct_flow(.Normal), true
|
||||
}
|
||||
if handler.kind == .Return {
|
||||
return INVALID_CT_VALUE, handler, true
|
||||
}
|
||||
return INVALID_CT_VALUE, handler, ct_fail(state, .Not_Comptime, expr.span, "catch block must yield a value")
|
||||
}
|
||||
|
||||
@@ -3802,6 +3816,99 @@ ct_make_fallible :: proc(state: ^Ct_State, result_type: types.Type, payload: Ct_
|
||||
})
|
||||
}
|
||||
|
||||
ct_refined_error_return :: proc(state: ^Ct_State, expr_id: ast.Expr_Id, target: types.Type) -> bool {
|
||||
checker := state.checker
|
||||
if expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) {
|
||||
return false
|
||||
}
|
||||
expr := checker.ast_module.exprs[expr_id]
|
||||
if expr.kind != .Name || symbol.is_valid(expr.qualifier) {
|
||||
return false
|
||||
}
|
||||
index, found := ct_find_binding_index(state, expr.name)
|
||||
if !found || state.bindings[index].mutable {
|
||||
return false
|
||||
}
|
||||
binding := state.bindings[index]
|
||||
candidates: []u32
|
||||
for refinement_index := len(state.error_refinements) - 1; refinement_index >= 0; refinement_index -= 1 {
|
||||
refinement := state.error_refinements[refinement_index]
|
||||
if refinement.cell == binding.cell {
|
||||
candidates = refinement.variants
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(candidates) == 0 {
|
||||
return false
|
||||
}
|
||||
selected: [dynamic]u32
|
||||
selected.allocator = checker.allocator
|
||||
defer delete(selected)
|
||||
for candidate in candidates {
|
||||
allowed := true
|
||||
for refinement in state.error_refinements {
|
||||
if refinement.cell != binding.cell {
|
||||
continue
|
||||
}
|
||||
found_variant := false
|
||||
for variant in refinement.variants {
|
||||
found_variant = found_variant || variant == candidate
|
||||
}
|
||||
allowed = allowed && found_variant
|
||||
}
|
||||
if allowed {
|
||||
append(&selected, candidate)
|
||||
}
|
||||
}
|
||||
return types.selected_sum_fits(binding.type, target, selected[:], &checker.module.types)
|
||||
}
|
||||
|
||||
ct_project_sum_value :: proc(state: ^Ct_State, value_id: Ct_Value_Id, target: types.Type, span: source.Span) -> (Ct_Value_Id, bool) {
|
||||
checker := state.checker
|
||||
if value_id == INVALID_CT_VALUE || int(value_id) >= len(state.values) {
|
||||
return INVALID_CT_VALUE, false
|
||||
}
|
||||
value := state.values[value_id]
|
||||
name := symbol.INVALID
|
||||
if types.is_enum(value.type, &checker.module.types) && value.kind == .Integer {
|
||||
for member in types.enum_members_for(&checker.module.types, value.type) {
|
||||
if member.value == value.integer {
|
||||
name = symbol.Id(member.name)
|
||||
break
|
||||
}
|
||||
}
|
||||
} else if types.is_tagged_union(value.type, &checker.module.types) && value.kind == .Struct {
|
||||
fields := types.fields_for(&checker.module.types, value.type)
|
||||
if value.active >= 0 && int(value.active) < len(fields) {
|
||||
name = symbol.Id(fields[value.active].name)
|
||||
}
|
||||
}
|
||||
if !symbol.is_valid(name) {
|
||||
return INVALID_CT_VALUE, ct_fail(state, .Not_Comptime, span, "invalid refined error value")
|
||||
}
|
||||
if types.is_enum(target, &checker.module.types) {
|
||||
member, found := find_enum_member(checker, target, name)
|
||||
if !found {
|
||||
return INVALID_CT_VALUE, ct_fail(state, .Not_Comptime, span, "refined error is not in the declared error type")
|
||||
}
|
||||
return ct_add_value(state, Ct_Value{kind=.Integer, type=target, integer=member.value}), true
|
||||
}
|
||||
if !types.is_tagged_union(target, &checker.module.types) {
|
||||
return INVALID_CT_VALUE, ct_fail(state, .Not_Comptime, span, "declared error type is not a sum")
|
||||
}
|
||||
field_index, _, found := find_struct_field(checker, target, name)
|
||||
if !found {
|
||||
return INVALID_CT_VALUE, ct_fail(state, .Not_Comptime, span, "refined error is not in the declared error type")
|
||||
}
|
||||
start := len(state.children)
|
||||
if value.kind == .Struct {
|
||||
append(&state.children, ..ct_child_slice(state, value))
|
||||
}
|
||||
return ct_add_value(state, Ct_Value{
|
||||
kind=.Struct, type=target, active=i64(field_index), start=u32(start), count=u32(len(state.children)-start),
|
||||
}), true
|
||||
}
|
||||
|
||||
ct_return_value :: proc(state: ^Ct_State, expr_id: ast.Expr_Id, span: source.Span, depth: int) -> (Ct_Flow, bool) {
|
||||
checker := state.checker
|
||||
if types.is_void(state.result) {
|
||||
@@ -3822,15 +3929,33 @@ ct_return_value :: proc(state: ^Ct_State, expr_id: ast.Expr_Id, span: source.Spa
|
||||
expr := checker.ast_module.exprs[expr_id]
|
||||
error_path := false
|
||||
expected := success
|
||||
project_error := false
|
||||
if expr.kind == .Enum_Literal && types.sum_has_name(&checker.module.types, error_type, u32(expr.name)) {
|
||||
error_path = true
|
||||
expected = error_type
|
||||
} else if expr.kind == .Name && !symbol.is_valid(expr.qualifier) {
|
||||
if index, found := ct_find_binding_index(state, expr.name); found {
|
||||
actual := state.bindings[index].type
|
||||
if can_implicitly_convert_type(checker, actual, error_type) &&
|
||||
!can_implicitly_convert_type(checker, actual, success) {
|
||||
error_path = true
|
||||
expected = error_type
|
||||
} else if ct_refined_error_return(state, expr_id, error_type) {
|
||||
error_path = true
|
||||
project_error = true
|
||||
expected = types.INVALID
|
||||
}
|
||||
}
|
||||
}
|
||||
value, flow, ok := ct_eval_expr(state, expr_id, expected, depth+1)
|
||||
if !ok || flow.kind != .Normal {
|
||||
return flow, ok
|
||||
}
|
||||
value, ok = ct_coerce_value(state, value, expected, span)
|
||||
if project_error {
|
||||
value, ok = ct_project_sum_value(state, value, error_type, span)
|
||||
} else {
|
||||
value, ok = ct_coerce_value(state, value, expected, span)
|
||||
}
|
||||
if !ok {
|
||||
return ct_flow(.Normal), false
|
||||
}
|
||||
@@ -3913,7 +4038,7 @@ ct_exec_statements :: proc(
|
||||
case .Expression:
|
||||
_, flow, ok = ct_eval_expr(state, statement.expr, types.INVALID, depth+1)
|
||||
case .Return:
|
||||
if yield_returns {
|
||||
if yield_returns && state.value_return_depth == 0 {
|
||||
ok = ct_fail(state, .Not_Comptime, statement.span, "'return' is not valid in this comptime block")
|
||||
} else if statement.value_control_flow {
|
||||
value_flow, value_ok := ct_exec_statements(state, statement.body, true, depth+1)
|
||||
@@ -4526,6 +4651,13 @@ ct_select_match_arm :: proc(
|
||||
|
||||
ct_exec_match :: proc(state: ^Ct_State, statement: ast.Stmt, yield_returns: bool, depth: int) -> (Ct_Flow, bool) {
|
||||
checker := state.checker
|
||||
refinement_cell := INVALID_CT_CELL
|
||||
subject_expr := checker.ast_module.exprs[statement.expr]
|
||||
if subject_expr.kind == .Name && !symbol.is_valid(subject_expr.qualifier) {
|
||||
if index, found := ct_find_binding_index(state, subject_expr.name); found && !state.bindings[index].mutable {
|
||||
refinement_cell = state.bindings[index].cell
|
||||
}
|
||||
}
|
||||
subject, flow, ok := ct_eval_expr(state, statement.expr, types.INVALID, depth+1)
|
||||
if !ok || flow.kind != .Normal {
|
||||
return flow, ok
|
||||
@@ -4552,6 +4684,63 @@ ct_exec_match :: proc(state: ^Ct_State, statement: ast.Stmt, yield_returns: bool
|
||||
selection, selected := ct_select_match_arm(state, statement, subject, depth+1)
|
||||
if selected {
|
||||
arm := checker.ast_module.statements[selection.arm]
|
||||
refinement_variants: [dynamic]u32
|
||||
refinement_variants.allocator = checker.allocator
|
||||
defer delete(refinement_variants)
|
||||
subject_type := state.values[subject].type
|
||||
if refinement_cell != INVALID_CT_CELL &&
|
||||
(types.is_enum(subject_type, &checker.module.types) || types.is_tagged_union(subject_type, &checker.module.types)) {
|
||||
if arm.expand {
|
||||
if types.is_enum(subject_type, &checker.module.types) {
|
||||
for member in types.enum_members_for(&checker.module.types, subject_type) {
|
||||
if member.value == state.values[subject].integer {
|
||||
append(&refinement_variants, member.name)
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fields := types.fields_for(&checker.module.types, subject_type)
|
||||
active := state.values[subject].active
|
||||
if active >= 0 && int(active) < len(fields) {
|
||||
append(&refinement_variants, fields[active].name)
|
||||
}
|
||||
}
|
||||
} else if len(arm.patterns) > 0 {
|
||||
for pattern_id in arm.patterns {
|
||||
pattern := checker.ast_module.exprs[pattern_id]
|
||||
if pattern.kind == .Enum_Literal {
|
||||
append(&refinement_variants, u32(pattern.name))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
covered: [dynamic]symbol.Id
|
||||
covered.allocator = checker.allocator
|
||||
defer delete(covered)
|
||||
for candidate_id in statement.body {
|
||||
if candidate_id == selection.arm {
|
||||
break
|
||||
}
|
||||
candidate := checker.ast_module.statements[candidate_id]
|
||||
for pattern_id in candidate.patterns {
|
||||
pattern := checker.ast_module.exprs[pattern_id]
|
||||
if pattern.kind == .Enum_Literal {
|
||||
append(&covered, pattern.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
member_enum := types.union_tag_enum(subject_type, &checker.module.types) if types.is_tagged_union(subject_type, &checker.module.types) else subject_type
|
||||
for member in types.enum_members_for(&checker.module.types, member_enum) {
|
||||
if !contains_name(covered[:], symbol.Id(member.name)) {
|
||||
append(&refinement_variants, member.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
refinement_start := len(state.error_refinements)
|
||||
if refinement_cell != INVALID_CT_CELL && len(refinement_variants) > 0 {
|
||||
append(&state.error_refinements, Ct_Error_Refinement{cell=refinement_cell, variants=refinement_variants[:]})
|
||||
}
|
||||
defer resize(&state.error_refinements, refinement_start)
|
||||
scope_start := len(state.bindings)
|
||||
if arm.expand && types.is_enum(state.values[subject].type, &checker.module.types) {
|
||||
if len(arm.captures) > 0 && arm.captures[0] != checker.sink_symbol {
|
||||
|
||||
Reference in New Issue
Block a user