comptime expandable match statements
This commit is contained in:
+403
-69
@@ -59,7 +59,7 @@ Static_Binding :: struct {
|
||||
value: Ct_Value_Id,
|
||||
}
|
||||
|
||||
Inline_Expansion :: struct {
|
||||
Expand_Expansion :: struct {
|
||||
statement: ast.Stmt_Id,
|
||||
index: u32,
|
||||
}
|
||||
@@ -175,7 +175,7 @@ Type_Factory_Origin :: struct {
|
||||
Call_Resolution :: struct {
|
||||
expr: ast.Expr_Id,
|
||||
ctx: []Comptime_Value,
|
||||
inline_ctx: []Inline_Expansion,
|
||||
expand_ctx: []Expand_Expansion,
|
||||
mapping: []int,
|
||||
comptime_values: []Comptime_Value,
|
||||
runtime_types: []types.Type,
|
||||
@@ -235,7 +235,7 @@ Checker :: struct {
|
||||
static_bindings: [dynamic]Static_Binding,
|
||||
comptime_keys: [dynamic]string,
|
||||
comptime_static_values: [dynamic]Ct_Value_Id,
|
||||
inline_context: [dynamic]Inline_Expansion,
|
||||
expand_context: [dynamic]Expand_Expansion,
|
||||
type_factories: [dynamic]Type_Factory_Entry,
|
||||
generated_types: [dynamic]Generated_Type_Entry,
|
||||
type_factory_origins: [dynamic]Type_Factory_Origin,
|
||||
@@ -549,7 +549,21 @@ persistent_field_value :: proc(checker: ^Checker, root: Ct_Value_Id, name: symbo
|
||||
}
|
||||
|
||||
build_static_value :: proc(checker: ^Checker, value: Ct_Value, span: source.Span, expected: types.Type) -> hir.Expr_Id {
|
||||
if value.kind == .Void {
|
||||
return add_hir_expr(checker, hir.Expr{
|
||||
kind=.Void, span=span, type=types.VOID,
|
||||
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
}
|
||||
if value.kind == .Integer {
|
||||
if types.is_enum(value.type, &checker.module.types) {
|
||||
return add_hir_expr(checker, hir.Expr{
|
||||
kind=.Integer, span=span, type=value.type, integer=i64(value.integer),
|
||||
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
}
|
||||
expr := ast.Expr{
|
||||
kind=.Integer, span=span, integer=u64(value.integer),
|
||||
left=ast.INVALID_EXPR, right=ast.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
@@ -628,6 +642,28 @@ build_static_value :: proc(checker: ^Checker, value: Ct_Value, span: source.Span
|
||||
return invalid_hir_expr(checker, span, id, expected)
|
||||
}
|
||||
|
||||
push_static_integer_binding :: proc(checker: ^Checker, name: symbol.Id, value_type: types.Type, value: i128) -> int {
|
||||
start := len(checker.static_bindings)
|
||||
if symbol.is_valid(name) && name != checker.sink_symbol {
|
||||
id := ct_add_value(&checker.static_state, Ct_Value{kind=.Integer, type=value_type, integer=value})
|
||||
append(&checker.static_bindings, Static_Binding{name=name, type=value_type, value=id})
|
||||
}
|
||||
return start
|
||||
}
|
||||
|
||||
push_static_void_binding :: proc(checker: ^Checker, name: symbol.Id) -> int {
|
||||
start := len(checker.static_bindings)
|
||||
if symbol.is_valid(name) && name != checker.sink_symbol {
|
||||
id := ct_add_value(&checker.static_state, Ct_Value{kind=.Void, type=types.VOID})
|
||||
append(&checker.static_bindings, Static_Binding{name=name, type=types.VOID, value=id})
|
||||
}
|
||||
return start
|
||||
}
|
||||
|
||||
pop_static_bindings :: proc(checker: ^Checker, start: int) {
|
||||
resize(&checker.static_bindings, start)
|
||||
}
|
||||
|
||||
comptime_string_argument :: proc(
|
||||
checker: ^Checker,
|
||||
id: ast.Expr_Id,
|
||||
@@ -856,6 +892,104 @@ build_type_builtin :: proc(
|
||||
)
|
||||
}
|
||||
|
||||
tag_result_type :: proc(checker: ^Checker, value: types.Type) -> (types.Type, bool) {
|
||||
if !types.is_tagged_union(value, &checker.module.types) {
|
||||
return types.INVALID, false
|
||||
}
|
||||
return types.union_tag_enum(value, &checker.module.types), true
|
||||
}
|
||||
|
||||
enum_member_name_from_value :: proc(checker: ^Checker, enum_type: types.Type, value: i128) -> (string, bool) {
|
||||
if !types.is_enum(enum_type, &checker.module.types) {
|
||||
return "", false
|
||||
}
|
||||
for member in types.enum_members_for(&checker.module.types, enum_type) {
|
||||
if member.value == value {
|
||||
return symbol_text(checker, symbol.Id(member.name)), true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
build_tag_intrinsic :: proc(
|
||||
checker: ^Checker,
|
||||
expr: ast.Expr,
|
||||
locals: []Build_Local,
|
||||
global_reads: ^[dynamic]hir.Global_Id,
|
||||
calls: ^[dynamic]hir.Function_Id,
|
||||
pkg: ast.Package_Id,
|
||||
file: ast.File_Id,
|
||||
) -> hir.Expr_Id {
|
||||
if len(expr.args) != 1 {
|
||||
id := source.addf(checker.diagnostics, expr.span, "tag! expects 1 argument, got %d", len(expr.args))
|
||||
return invalid_hir_expr(checker, expr.span, id)
|
||||
}
|
||||
state := ct_state_make(checker, pkg, file, values=checker.current_comptime_values, diagnose=false)
|
||||
value_id, flow, comptime_ok := ct_eval_expr(&state, expr.args[0], types.INVALID, 0)
|
||||
if comptime_ok && flow.kind == .Normal && value_id != INVALID_CT_VALUE && int(value_id) < len(state.values) {
|
||||
value := state.values[value_id]
|
||||
if tag_type, tagged := tag_result_type(checker, value.type); tagged && value.kind == .Struct &&
|
||||
value.active >= 0 {
|
||||
fields := types.fields_for(&checker.module.types, value.type)
|
||||
if int(value.active) < len(fields) {
|
||||
if member, found := find_enum_member(checker, tag_type, symbol.Id(fields[value.active].name)); found {
|
||||
ct_state_destroy(&state)
|
||||
return add_hir_expr(checker, hir.Expr{
|
||||
kind=.Integer, span=expr.span, type=tag_type, integer=i64(member.value),
|
||||
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ct_state_destroy(&state)
|
||||
value := build_nested_expr(checker, expr.args[0], locals, global_reads, calls, types.INVALID, pkg, file)
|
||||
if checker.module.exprs[value].kind == .Invalid {
|
||||
return value
|
||||
}
|
||||
tag_type, ok := tag_result_type(checker, checker.module.exprs[value].type)
|
||||
if !ok {
|
||||
id := source.add(checker.diagnostics, expr.span, "tag! requires a tagged-union value")
|
||||
return invalid_hir_expr(checker, expr.span, id)
|
||||
}
|
||||
return add_hir_expr(checker, hir.Expr{
|
||||
kind=.Union_Tag, span=expr.span, type=tag_type, left=value,
|
||||
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
}
|
||||
|
||||
build_tagname_intrinsic :: proc(
|
||||
checker: ^Checker,
|
||||
expr: ast.Expr,
|
||||
pkg: ast.Package_Id,
|
||||
file: ast.File_Id,
|
||||
) -> hir.Expr_Id {
|
||||
if len(expr.args) != 1 {
|
||||
id := source.addf(checker.diagnostics, expr.span, "tagname! expects 1 argument, got %d", len(expr.args))
|
||||
return invalid_hir_expr(checker, expr.span, id)
|
||||
}
|
||||
state := ct_state_make(checker, pkg, file, values=checker.current_comptime_values, diagnose=false)
|
||||
defer ct_state_destroy(&state)
|
||||
value_id, flow, ok := ct_eval_expr(&state, expr.args[0], types.INVALID, 0)
|
||||
if !ok || flow.kind != .Normal || value_id == INVALID_CT_VALUE || int(value_id) >= len(state.values) {
|
||||
id := source.add(checker.diagnostics, expr.span, "tagname! requires a comptime-known enum value")
|
||||
return invalid_hir_expr(checker, expr.span, id)
|
||||
}
|
||||
value := state.values[value_id]
|
||||
name, name_ok := enum_member_name_from_value(checker, value.type, value.integer)
|
||||
if value.kind != .Integer || !name_ok {
|
||||
id := source.add(checker.diagnostics, expr.span, "tagname! requires a comptime-known enum value")
|
||||
return invalid_hir_expr(checker, expr.span, id)
|
||||
}
|
||||
string_id := intern_comptime_string(checker, name)
|
||||
return add_hir_expr(checker, hir.Expr{
|
||||
kind=.String, span=expr.span, type=string_literal_type(checker, string_id), integer=i64(string_id),
|
||||
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
}
|
||||
|
||||
is_type_metatype_syntax :: proc(checker: ^Checker, value: ast.Type_Syntax) -> bool {
|
||||
item, ok := types.node(&checker.module.types, value)
|
||||
return ok && item.name == u32(checker.type_symbol) && item.qualifier == 0
|
||||
@@ -2554,7 +2688,7 @@ clone_comptime_values :: proc(values: []Comptime_Value, allocator: mem.Allocator
|
||||
return result
|
||||
}
|
||||
|
||||
inline_expansions_equal :: proc(left, right: []Inline_Expansion) -> bool {
|
||||
expand_expansions_equal :: proc(left, right: []Expand_Expansion) -> bool {
|
||||
if len(left) != len(right) {
|
||||
return false
|
||||
}
|
||||
@@ -2574,7 +2708,7 @@ find_call_resolution :: proc(
|
||||
entry := checker.call_resolutions[index]
|
||||
if entry.expr == expr &&
|
||||
comptime_values_equal(entry.ctx, checker.current_comptime_values) &&
|
||||
inline_expansions_equal(entry.inline_ctx, checker.inline_context[:]) {
|
||||
expand_expansions_equal(entry.expand_ctx, checker.expand_context[:]) {
|
||||
return index, true
|
||||
}
|
||||
}
|
||||
@@ -2591,7 +2725,7 @@ store_call_resolution :: proc(
|
||||
entry := Call_Resolution{
|
||||
expr=expr,
|
||||
ctx=clone_comptime_values(checker.current_comptime_values, checker.allocator),
|
||||
inline_ctx=slice.clone(checker.inline_context[:], checker.allocator),
|
||||
expand_ctx=slice.clone(checker.expand_context[:], checker.allocator),
|
||||
mapping=slice.clone(mapping, checker.allocator),
|
||||
comptime_values=clone_comptime_values(comptime_values, checker.allocator),
|
||||
runtime_types=slice.clone(runtime_types, checker.allocator),
|
||||
@@ -2599,7 +2733,7 @@ store_call_resolution :: proc(
|
||||
if index, ok := find_call_resolution(checker, expr); ok {
|
||||
previous := checker.call_resolutions[index]
|
||||
delete(previous.ctx, checker.allocator)
|
||||
delete(previous.inline_ctx, checker.allocator)
|
||||
delete(previous.expand_ctx, checker.allocator)
|
||||
delete(previous.mapping, checker.allocator)
|
||||
delete(previous.comptime_values, checker.allocator)
|
||||
delete(previous.runtime_types, checker.allocator)
|
||||
@@ -4276,7 +4410,11 @@ infer_expr :: proc(
|
||||
}
|
||||
expr := checker.ast_module.exprs[frame.expr]
|
||||
if frame.stage == 0 {
|
||||
constant := eval_constant(checker, frame.expr)
|
||||
constant := Constant{}
|
||||
_, static_name := current_static_binding(checker, expr.name)
|
||||
if expr.kind != .Name || symbol.is_valid(expr.qualifier) || !static_name {
|
||||
constant = eval_constant(checker, frame.expr)
|
||||
}
|
||||
if constant.kind == .Overflow || constant.kind == .Div_By_Zero ||
|
||||
(constant.kind == .Value && !fits_i64(constant.value)) {
|
||||
last = types.I64
|
||||
@@ -4502,6 +4640,21 @@ infer_expr :: proc(
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
if is_intrinsic_call(checker, expr, "tag") {
|
||||
if len(expr.args) == 1 {
|
||||
value_type := infer_nested_expr(checker, expr.args[0], locals, pkg, file, demanded, local_types)
|
||||
last, _ = tag_result_type(checker, value_type)
|
||||
} else {
|
||||
last = types.INVALID
|
||||
}
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
if is_intrinsic_call(checker, expr, "tagname") {
|
||||
last = types.slice(&checker.module.types, types.U8, false)
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
if is_ptrcast_call(checker, expr) {
|
||||
if len(expr.args) != 2 {
|
||||
last = types.INVALID
|
||||
@@ -5087,21 +5240,21 @@ infer_statements :: proc(
|
||||
infer_statements(checker, update[:], locals, local_types, pkg, file, demanded, result, result_hint)
|
||||
}
|
||||
case .For:
|
||||
if statement.inline {
|
||||
bindings, inline_error := inline_field_bindings(checker, statement.expr, statement.name, pkg, file)
|
||||
if inline_error == .None {
|
||||
for binding, inline_index in bindings {
|
||||
binding_start := push_inline_binding(checker, binding, statement.index_name, inline_index, statement_id)
|
||||
if statement.expand {
|
||||
bindings, expand_error := expand_field_bindings(checker, statement.expr, statement.name, pkg, file)
|
||||
if expand_error == .None {
|
||||
for binding, expand_index in bindings {
|
||||
binding_start := push_expand_binding(checker, binding, statement.index_name, expand_index, statement_id)
|
||||
iteration: [dynamic]ast.Stmt_Id
|
||||
iteration.allocator = checker.allocator
|
||||
control := flatten_inline_iteration(
|
||||
control := flatten_expand_iteration(
|
||||
checker, statement.body, pkg, file, &iteration, statement.label, nil,
|
||||
)
|
||||
if control != .Invalid {
|
||||
infer_statements(checker, iteration[:], locals, local_types, pkg, file, demanded, result, result_hint)
|
||||
}
|
||||
delete(iteration)
|
||||
pop_inline_binding(checker, binding_start)
|
||||
pop_expand_binding(checker, binding_start)
|
||||
if control == .Break || control == .Invalid {
|
||||
break
|
||||
}
|
||||
@@ -5164,13 +5317,65 @@ infer_statements :: proc(
|
||||
// (e.g. `match get()`). Mirror the `.For`/unwrap-`.If` capture handling.
|
||||
subject_type := infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded, local_types)
|
||||
is_tagged := types.is_tagged_union(subject_type, &checker.module.types)
|
||||
is_enum_subject := types.is_enum(subject_type, &checker.module.types)
|
||||
covered: [dynamic]symbol.Id
|
||||
covered.allocator = checker.allocator
|
||||
for arm_id in statement.body {
|
||||
arm := checker.ast_module.statements[arm_id]
|
||||
if arm.kind != .Match_Arm {
|
||||
continue
|
||||
}
|
||||
if arm.expand && (is_tagged || is_enum_subject) {
|
||||
tag_type := types.union_tag_enum(subject_type, &checker.module.types) if is_tagged else subject_type
|
||||
if is_tagged {
|
||||
for field in types.fields_for(&checker.module.types, subject_type) {
|
||||
name := symbol.Id(field.name)
|
||||
if contains_name(covered[:], name) {
|
||||
continue
|
||||
}
|
||||
member, found := find_enum_member(checker, tag_type, name)
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
capture_start := len(locals^)
|
||||
static_start := len(checker.static_bindings)
|
||||
if len(arm.captures) > 1 {
|
||||
_ = push_static_integer_binding(checker, arm.captures[1], tag_type, member.value)
|
||||
}
|
||||
if len(arm.captures) > 0 && arm.captures[0] != checker.sink_symbol {
|
||||
if types.is_void(field.type) && !arm.pointer_capture {
|
||||
_ = push_static_void_binding(checker, arm.captures[0])
|
||||
} else {
|
||||
capture_type := field.type
|
||||
if arm.pointer_capture {
|
||||
capture_type = types.pointer(&checker.module.types, field.type, true, false)
|
||||
}
|
||||
append(locals, Infer_Local{name=arm.captures[0], type=capture_type, declared=capture_type, statement=ast.INVALID_STMT})
|
||||
}
|
||||
}
|
||||
infer_statements(checker, arm.body, locals, local_types, pkg, file, demanded, result, result_hint)
|
||||
resize(locals, capture_start)
|
||||
pop_static_bindings(checker, static_start)
|
||||
}
|
||||
} else {
|
||||
for member in types.enum_members_for(&checker.module.types, subject_type) {
|
||||
name := symbol.Id(member.name)
|
||||
if contains_name(covered[:], name) {
|
||||
continue
|
||||
}
|
||||
static_start := push_static_integer_binding(checker, arm.captures[0] if len(arm.captures) > 0 else symbol.INVALID, tag_type, member.value)
|
||||
infer_statements(checker, arm.body, locals, local_types, pkg, file, demanded, result, result_hint)
|
||||
pop_static_bindings(checker, static_start)
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
for pattern in arm.patterns {
|
||||
_ = infer_expr(checker, pattern, locals^[:], pkg, file, demanded, local_types)
|
||||
pattern_expr := checker.ast_module.exprs[pattern]
|
||||
if pattern_expr.kind == .Enum_Literal {
|
||||
append(&covered, pattern_expr.name)
|
||||
}
|
||||
}
|
||||
capture_start := len(locals^)
|
||||
if len(arm.captures) > 0 && is_tagged && len(arm.patterns) > 0 {
|
||||
@@ -5194,6 +5399,7 @@ infer_statements :: proc(
|
||||
infer_statements(checker, arm.body, locals, local_types, pkg, file, demanded, result, result_hint)
|
||||
resize(locals, capture_start)
|
||||
}
|
||||
delete(covered)
|
||||
}
|
||||
}
|
||||
resize(locals, scope_start)
|
||||
@@ -7592,7 +7798,11 @@ build_expr :: proc(
|
||||
}
|
||||
expr := checker.ast_module.exprs[frame.expr]
|
||||
if frame.stage == 0 {
|
||||
constant := eval_constant(checker, frame.expr)
|
||||
constant := Constant{}
|
||||
_, static_name := current_static_binding(checker, expr.name)
|
||||
if expr.kind != .Name || symbol.is_valid(expr.qualifier) || !static_name {
|
||||
constant = eval_constant(checker, frame.expr)
|
||||
}
|
||||
if constant.kind == .Value || constant.kind == .Overflow || constant.kind == .Div_By_Zero || constant.kind == .Non_Exact {
|
||||
last = build_constant_expr(checker, expr, constant, frame.expected)
|
||||
_ = pop(&stack)
|
||||
@@ -7846,6 +8056,16 @@ build_expr :: proc(
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
if is_intrinsic_call(checker, expr, "tag") {
|
||||
last = build_tag_intrinsic(checker, expr, locals, global_reads, calls, pkg, file)
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
if is_intrinsic_call(checker, expr, "tagname") {
|
||||
last = build_tagname_intrinsic(checker, expr, pkg, file)
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
if is_ptrcast_call(checker, expr) {
|
||||
if len(expr.args) != 2 {
|
||||
id := source.addf(checker.diagnostics, expr.span, "ptrcast! expects 2 arguments, got %d", len(expr.args))
|
||||
@@ -8642,6 +8862,9 @@ specialization_match_body :: proc(
|
||||
return nil, {}, false, false
|
||||
}
|
||||
arm := checker.ast_module.statements[selection.arm]
|
||||
if arm.expand {
|
||||
return nil, {}, false, false
|
||||
}
|
||||
if arm.pointer_capture {
|
||||
return nil, {}, false, false
|
||||
}
|
||||
@@ -8656,21 +8879,21 @@ specialization_match_body :: proc(
|
||||
return arm.body, {}, false, true
|
||||
}
|
||||
|
||||
Inline_Binding_Error :: enum u8 {
|
||||
Expand_Binding_Error :: enum u8 {
|
||||
None,
|
||||
Invalid,
|
||||
Quota,
|
||||
Diagnosed,
|
||||
}
|
||||
|
||||
inline_field_bindings :: proc(
|
||||
expand_field_bindings :: proc(
|
||||
checker: ^Checker,
|
||||
expr: ast.Expr_Id,
|
||||
capture: symbol.Id,
|
||||
pkg: ast.Package_Id,
|
||||
file: ast.File_Id,
|
||||
diagnose := false,
|
||||
) -> ([]Static_Binding, Inline_Binding_Error) {
|
||||
) -> ([]Static_Binding, Expand_Binding_Error) {
|
||||
state := ct_state_make(checker, pkg, file, values=checker.current_comptime_values, diagnose=diagnose)
|
||||
defer ct_state_destroy(&state)
|
||||
value_id, flow, ok := ct_eval_expr(&state, expr, types.INVALID, 0)
|
||||
@@ -8680,7 +8903,7 @@ inline_field_bindings :: proc(
|
||||
value := state.values[value_id]
|
||||
if ct_value_contains_undefined(&state, value_id) {
|
||||
if diagnose {
|
||||
_ = ct_fail(&state, .Not_Comptime, checker.ast_module.exprs[expr].span, "inline for cannot expand an undefined comptime value")
|
||||
_ = ct_fail(&state, .Not_Comptime, checker.ast_module.exprs[expr].span, "expand for cannot expand an undefined comptime value")
|
||||
}
|
||||
return nil, .Diagnosed if state.diagnostic != source.INVALID_DIAGNOSTIC else .Invalid
|
||||
}
|
||||
@@ -8748,7 +8971,7 @@ inline_field_bindings :: proc(
|
||||
return bindings, .None
|
||||
}
|
||||
|
||||
push_inline_binding :: proc(
|
||||
push_expand_binding :: proc(
|
||||
checker: ^Checker,
|
||||
binding: Static_Binding,
|
||||
index_name: symbol.Id,
|
||||
@@ -8757,7 +8980,7 @@ push_inline_binding :: proc(
|
||||
) -> int {
|
||||
start := len(checker.static_bindings)
|
||||
append(&checker.static_bindings, binding)
|
||||
append(&checker.inline_context, Inline_Expansion{statement=statement, index=u32(index)})
|
||||
append(&checker.expand_context, Expand_Expansion{statement=statement, index=u32(index)})
|
||||
if symbol.is_valid(index_name) {
|
||||
value := ct_add_value(&checker.static_state, Ct_Value{kind=.Integer, type=types.USIZE, integer=i128(index)})
|
||||
append(&checker.static_bindings, Static_Binding{name=index_name, type=types.USIZE, value=value})
|
||||
@@ -8765,19 +8988,19 @@ push_inline_binding :: proc(
|
||||
return start
|
||||
}
|
||||
|
||||
pop_inline_binding :: proc(checker: ^Checker, start: int) {
|
||||
pop_expand_binding :: proc(checker: ^Checker, start: int) {
|
||||
resize(&checker.static_bindings, start)
|
||||
_ = pop(&checker.inline_context)
|
||||
_ = pop(&checker.expand_context)
|
||||
}
|
||||
|
||||
Inline_Control :: enum u8 {
|
||||
Expand_Control :: enum u8 {
|
||||
Normal,
|
||||
Break,
|
||||
Continue,
|
||||
Invalid,
|
||||
}
|
||||
|
||||
inline_control_target :: proc(statement: ast.Stmt, target_label: symbol.Id, allow_unlabeled: bool) -> Inline_Control {
|
||||
expand_control_target :: proc(statement: ast.Stmt, target_label: symbol.Id, allow_unlabeled: bool) -> Expand_Control {
|
||||
if statement.kind != .Break && statement.kind != .Continue {
|
||||
return .Normal
|
||||
}
|
||||
@@ -8791,7 +9014,7 @@ inline_control_target :: proc(statement: ast.Stmt, target_label: symbol.Id, allo
|
||||
return .Break if statement.kind == .Break else .Continue
|
||||
}
|
||||
|
||||
contains_inline_control :: proc(
|
||||
contains_expand_control :: proc(
|
||||
checker: ^Checker,
|
||||
statements: []ast.Stmt_Id,
|
||||
target_label: symbol.Id,
|
||||
@@ -8799,32 +9022,32 @@ contains_inline_control :: proc(
|
||||
) -> bool {
|
||||
for statement_id in statements {
|
||||
statement := checker.ast_module.statements[statement_id]
|
||||
if inline_control_target(statement, target_label, allow_unlabeled) != .Normal {
|
||||
if expand_control_target(statement, target_label, allow_unlabeled) != .Normal {
|
||||
return true
|
||||
}
|
||||
#partial switch statement.kind {
|
||||
case .Block:
|
||||
if contains_inline_control(checker, statement.body, target_label, allow_unlabeled) {
|
||||
if contains_expand_control(checker, statement.body, target_label, allow_unlabeled) {
|
||||
return true
|
||||
}
|
||||
case .If:
|
||||
if contains_inline_control(checker, statement.body, target_label, allow_unlabeled) ||
|
||||
contains_inline_control(checker, statement.else_body, target_label, allow_unlabeled) {
|
||||
if contains_expand_control(checker, statement.body, target_label, allow_unlabeled) ||
|
||||
contains_expand_control(checker, statement.else_body, target_label, allow_unlabeled) {
|
||||
return true
|
||||
}
|
||||
case .Match, .Match_Arm:
|
||||
if contains_inline_control(checker, statement.body, target_label, allow_unlabeled) {
|
||||
if contains_expand_control(checker, statement.body, target_label, allow_unlabeled) {
|
||||
return true
|
||||
}
|
||||
case .For, .While:
|
||||
// Unlabelled control belongs to the nested loop. A labelled jump can still
|
||||
// name the surrounding inline loop and is therefore relevant here.
|
||||
if contains_inline_control(checker, statement.body, target_label, false) {
|
||||
// name the surrounding expand loop and is therefore relevant here.
|
||||
if contains_expand_control(checker, statement.body, target_label, false) {
|
||||
return true
|
||||
}
|
||||
case .Defer:
|
||||
if statement.update != ast.INVALID_STMT &&
|
||||
contains_inline_control(checker, []ast.Stmt_Id{statement.update}, target_label, false) {
|
||||
contains_expand_control(checker, []ast.Stmt_Id{statement.update}, target_label, false) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -8861,7 +9084,7 @@ clone_statement_body :: proc(checker: ^Checker, statement: ast.Stmt, body: []ast
|
||||
return id
|
||||
}
|
||||
|
||||
append_inline_block :: proc(checker: ^Checker, span: source.Span, body: []ast.Stmt_Id, out: ^[dynamic]ast.Stmt_Id) {
|
||||
append_expand_block :: proc(checker: ^Checker, span: source.Span, body: []ast.Stmt_Id, out: ^[dynamic]ast.Stmt_Id) {
|
||||
if len(body) == 0 {
|
||||
return
|
||||
}
|
||||
@@ -8891,7 +9114,7 @@ same_stmt_ids :: proc(left, right: []ast.Stmt_Id) -> bool {
|
||||
return true
|
||||
}
|
||||
|
||||
flatten_inline_iteration :: proc(
|
||||
flatten_expand_iteration :: proc(
|
||||
checker: ^Checker,
|
||||
statements: []ast.Stmt_Id,
|
||||
pkg: ast.Package_Id,
|
||||
@@ -8899,10 +9122,10 @@ flatten_inline_iteration :: proc(
|
||||
out: ^[dynamic]ast.Stmt_Id,
|
||||
target_label: symbol.Id,
|
||||
diagnostic: ^source.Diagnostic_Id,
|
||||
) -> Inline_Control {
|
||||
) -> Expand_Control {
|
||||
for statement_id in statements {
|
||||
statement := checker.ast_module.statements[statement_id]
|
||||
if control := inline_control_target(statement, target_label, true); control != .Normal {
|
||||
if control := expand_control_target(statement, target_label, true); control != .Normal {
|
||||
return control
|
||||
}
|
||||
if statement.kind == .If && len(statement.captures) == 0 {
|
||||
@@ -8910,20 +9133,20 @@ flatten_inline_iteration :: proc(
|
||||
selected_body := statement.body if selected else statement.else_body
|
||||
branch: [dynamic]ast.Stmt_Id
|
||||
branch.allocator = checker.allocator
|
||||
flow := flatten_inline_iteration(checker, selected_body, pkg, file, &branch, target_label, diagnostic)
|
||||
append_inline_block(checker, statement.span, branch[:], out)
|
||||
flow := flatten_expand_iteration(checker, selected_body, pkg, file, &branch, target_label, diagnostic)
|
||||
append_expand_block(checker, statement.span, branch[:], out)
|
||||
delete(branch)
|
||||
if flow != .Normal {
|
||||
return flow
|
||||
}
|
||||
continue
|
||||
}
|
||||
if contains_inline_control(checker, statement.body, target_label, true) ||
|
||||
contains_inline_control(checker, statement.else_body, target_label, true) {
|
||||
if contains_expand_control(checker, statement.body, target_label, true) ||
|
||||
contains_expand_control(checker, statement.else_body, target_label, true) {
|
||||
if diagnostic != nil {
|
||||
diagnostic^ = source.add(
|
||||
checker.diagnostics, statement.span,
|
||||
"break or continue targeting an inline loop must be compile-time-resolvable",
|
||||
"break or continue targeting an expand loop must be compile-time-resolvable",
|
||||
)
|
||||
}
|
||||
return .Invalid
|
||||
@@ -8933,7 +9156,7 @@ flatten_inline_iteration :: proc(
|
||||
if selected_body, _, _, comptime_ok := specialization_match_body(checker, statement, pkg, file); comptime_ok {
|
||||
selected: [dynamic]ast.Stmt_Id
|
||||
selected.allocator = checker.allocator
|
||||
flow := flatten_inline_iteration(checker, selected_body, pkg, file, &selected, target_label, diagnostic)
|
||||
flow := flatten_expand_iteration(checker, selected_body, pkg, file, &selected, target_label, diagnostic)
|
||||
if flow != .Normal {
|
||||
arm_index := -1
|
||||
for arm_id, index in statement.body {
|
||||
@@ -8958,11 +9181,11 @@ flatten_inline_iteration :: proc(
|
||||
append(out, statement_id)
|
||||
continue
|
||||
}
|
||||
if contains_inline_control(checker, statement.body, target_label, true) {
|
||||
if contains_expand_control(checker, statement.body, target_label, true) {
|
||||
if diagnostic != nil {
|
||||
diagnostic^ = source.add(
|
||||
checker.diagnostics, statement.span,
|
||||
"break or continue targeting an inline loop must be compile-time-resolvable",
|
||||
"break or continue targeting an expand loop must be compile-time-resolvable",
|
||||
)
|
||||
}
|
||||
return .Invalid
|
||||
@@ -8971,11 +9194,11 @@ flatten_inline_iteration :: proc(
|
||||
if statement.kind == .Block {
|
||||
block: [dynamic]ast.Stmt_Id
|
||||
block.allocator = checker.allocator
|
||||
flow := flatten_inline_iteration(checker, statement.body, pkg, file, &block, target_label, diagnostic)
|
||||
flow := flatten_expand_iteration(checker, statement.body, pkg, file, &block, target_label, diagnostic)
|
||||
if flow == .Normal {
|
||||
append(out, statement_id)
|
||||
} else {
|
||||
append_inline_block(checker, statement.span, block[:], out)
|
||||
append_expand_block(checker, statement.span, block[:], out)
|
||||
}
|
||||
delete(block)
|
||||
if flow != .Normal {
|
||||
@@ -8984,11 +9207,11 @@ flatten_inline_iteration :: proc(
|
||||
continue
|
||||
}
|
||||
if (statement.kind == .For || statement.kind == .While || statement.kind == .Defer) &&
|
||||
contains_inline_control(checker, []ast.Stmt_Id{statement_id}, target_label, false) {
|
||||
contains_expand_control(checker, []ast.Stmt_Id{statement_id}, target_label, false) {
|
||||
if diagnostic != nil {
|
||||
diagnostic^ = source.add(
|
||||
checker.diagnostics, statement.span,
|
||||
"break or continue targeting an inline loop must be compile-time-resolvable",
|
||||
"break or continue targeting an expand loop must be compile-time-resolvable",
|
||||
)
|
||||
}
|
||||
return .Invalid
|
||||
@@ -9821,19 +10044,19 @@ build_block :: proc(
|
||||
})
|
||||
ctx.problematic^ = ctx.problematic^ || checker.module.exprs[condition].kind == .Invalid
|
||||
case .For:
|
||||
if statement.inline {
|
||||
if statement.expand {
|
||||
if statement.pointer_capture {
|
||||
id := source.add(checker.diagnostics, statement.span, "inline for does not support pointer captures")
|
||||
id := source.add(checker.diagnostics, statement.span, "expand for does not support pointer captures")
|
||||
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||
append(&checker.module.statements, hir.Stmt{kind=.Trap, span=statement.span, diagnostic=id})
|
||||
ctx.problematic^ = true
|
||||
continue
|
||||
}
|
||||
bindings, inline_error := inline_field_bindings(checker, statement.expr, statement.name, ctx.pkg, ctx.file, true)
|
||||
if inline_error != .None && inline_error != .Diagnosed {
|
||||
message := "inline for requires a comptime tuple, fixed array, range, slice, or reflection value"
|
||||
if inline_error == .Quota {
|
||||
message = "inline for expansion exceeds the compile-time evaluation quota"
|
||||
bindings, expand_error := expand_field_bindings(checker, statement.expr, statement.name, ctx.pkg, ctx.file, true)
|
||||
if expand_error != .None && expand_error != .Diagnosed {
|
||||
message := "expand for requires a comptime tuple, fixed array, range, slice, or reflection value"
|
||||
if expand_error == .Quota {
|
||||
message = "expand for expansion exceeds the compile-time evaluation quota"
|
||||
}
|
||||
id := source.add(
|
||||
checker.diagnostics, statement.span,
|
||||
@@ -9842,13 +10065,13 @@ build_block :: proc(
|
||||
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||
append(&checker.module.statements, hir.Stmt{kind=.Trap, span=statement.span, diagnostic=id})
|
||||
ctx.problematic^ = true
|
||||
} else if inline_error == .None {
|
||||
for binding, inline_index in bindings {
|
||||
binding_start := push_inline_binding(checker, binding, statement.index_name, inline_index, statement_id)
|
||||
} else if expand_error == .None {
|
||||
for binding, expand_index in bindings {
|
||||
binding_start := push_expand_binding(checker, binding, statement.index_name, expand_index, statement_id)
|
||||
iteration: [dynamic]ast.Stmt_Id
|
||||
iteration.allocator = checker.allocator
|
||||
diagnostic := source.INVALID_DIAGNOSTIC
|
||||
control := flatten_inline_iteration(
|
||||
control := flatten_expand_iteration(
|
||||
checker, statement.body, ctx.pkg, ctx.file, &iteration, statement.label, &diagnostic,
|
||||
)
|
||||
if control == .Invalid {
|
||||
@@ -9863,7 +10086,7 @@ build_block :: proc(
|
||||
delete(expanded, checker.allocator)
|
||||
}
|
||||
delete(iteration)
|
||||
pop_inline_binding(checker, binding_start)
|
||||
pop_expand_binding(checker, binding_start)
|
||||
if control == .Break || control == .Invalid {
|
||||
break
|
||||
}
|
||||
@@ -10846,6 +11069,7 @@ emit_match :: proc(
|
||||
covered.allocator = checker.allocator
|
||||
defer delete(covered)
|
||||
has_else := false
|
||||
has_expand := false
|
||||
|
||||
for arm_id in statement.body {
|
||||
arm := checker.ast_module.statements[arm_id]
|
||||
@@ -10853,10 +11077,107 @@ emit_match :: proc(
|
||||
ok = false
|
||||
continue
|
||||
}
|
||||
if has_else {
|
||||
source.add(checker.diagnostics, arm.span, "arms after 'else' are unreachable")
|
||||
if has_else || has_expand {
|
||||
message := "arms after 'else' are unreachable" if has_else else "arms after 'expand' are unreachable"
|
||||
source.add(checker.diagnostics, arm.span, message)
|
||||
ok = false
|
||||
}
|
||||
if arm.expand {
|
||||
if !is_tagged && !is_enum_subject {
|
||||
source.add(checker.diagnostics, arm.span, "'expand' requires an enum or tagged-union match subject")
|
||||
ok = false
|
||||
continue
|
||||
}
|
||||
expected_captures := 1 if is_enum_subject else 2
|
||||
if len(arm.captures) == 0 || len(arm.captures) > expected_captures {
|
||||
description := "exactly one capture" if is_enum_subject else "one or two captures"
|
||||
source.addf(checker.diagnostics, arm.span, "expanded match on '%s' requires %s", type_label(checker, subject_type), description)
|
||||
ok = false
|
||||
}
|
||||
if is_enum_subject && arm.pointer_capture {
|
||||
source.add(checker.diagnostics, arm.span, "enum expansion does not support pointer captures")
|
||||
ok = false
|
||||
}
|
||||
if len(arm.captures) > 1 && arm.captures[0] != checker.sink_symbol && arm.captures[0] == arm.captures[1] {
|
||||
source.add(checker.diagnostics, arm.span, "expand captures must have distinct names")
|
||||
ok = false
|
||||
}
|
||||
remaining := 0
|
||||
member_enum := tag_enum if is_tagged else subject_type
|
||||
if is_tagged {
|
||||
for field, field_index in types.fields_for(store, subject_type) {
|
||||
name := symbol.Id(field.name)
|
||||
if contains_name(covered[:], name) {
|
||||
continue
|
||||
}
|
||||
member, found := find_enum_member(checker, member_enum, name)
|
||||
if !found {
|
||||
ok = false
|
||||
continue
|
||||
}
|
||||
remaining += 1
|
||||
append(&covered, name)
|
||||
member_expr := enum_member_hir(checker, member_enum, name, arm.span)
|
||||
condition := add_hir_expr(checker, hir.Expr{
|
||||
kind=.Eq, span=arm.span, type=types.BOOL,
|
||||
left=slot_read(checker, key_local, key_type, arm.span), right=member_expr,
|
||||
target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
static_start := len(checker.static_bindings)
|
||||
if len(arm.captures) > 1 {
|
||||
_ = push_static_integer_binding(checker, arm.captures[1], member_enum, member.value)
|
||||
}
|
||||
body_arm := arm
|
||||
if types.is_void(field.type) && !arm.pointer_capture {
|
||||
if len(arm.captures) > 0 {
|
||||
_ = push_static_void_binding(checker, arm.captures[0])
|
||||
}
|
||||
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,
|
||||
field_index, field.type, as_value, slot, slot_type, span,
|
||||
)
|
||||
pop_static_bindings(checker, static_start)
|
||||
ok = body_ok && ok
|
||||
append(&built, Match_Built_Arm{condition=condition, body=arm_body})
|
||||
}
|
||||
} else {
|
||||
for member in types.enum_members_for(store, subject_type) {
|
||||
name := symbol.Id(member.name)
|
||||
if contains_name(covered[:], name) {
|
||||
continue
|
||||
}
|
||||
remaining += 1
|
||||
append(&covered, name)
|
||||
member_expr := enum_member_hir(checker, subject_type, name, arm.span)
|
||||
condition := add_hir_expr(checker, hir.Expr{
|
||||
kind=.Eq, span=arm.span, type=types.BOOL,
|
||||
left=slot_read(checker, key_local, key_type, arm.span), right=member_expr,
|
||||
target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
static_start := push_static_integer_binding(
|
||||
checker, arm.captures[0] if len(arm.captures) > 0 else symbol.INVALID,
|
||||
subject_type, member.value,
|
||||
)
|
||||
body_arm := arm
|
||||
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,
|
||||
)
|
||||
pop_static_bindings(checker, static_start)
|
||||
ok = body_ok && ok
|
||||
append(&built, Match_Built_Arm{condition=condition, body=arm_body})
|
||||
}
|
||||
}
|
||||
if remaining == 0 {
|
||||
source.add(checker.diagnostics, arm.span, "redundant 'expand': the 'match' already covers every variant")
|
||||
ok = false
|
||||
}
|
||||
has_expand = true
|
||||
continue
|
||||
}
|
||||
is_else := len(arm.patterns) == 0
|
||||
condition := hir.INVALID_EXPR
|
||||
field_index := -1
|
||||
@@ -11214,7 +11535,20 @@ build_value_match :: proc(
|
||||
}
|
||||
subtree: [dynamic]hir.Stmt_Id
|
||||
subtree.allocator = checker.allocator
|
||||
ok := emit_match(ctx, &subtree, statement, true, &slot, &slot_type)
|
||||
ok := false
|
||||
if selected_body, capture, has_capture, comptime_ok := specialization_match_body(
|
||||
checker, statement, ctx.pkg, ctx.file,
|
||||
); comptime_ok {
|
||||
if has_capture {
|
||||
append(&checker.static_bindings, capture)
|
||||
}
|
||||
ok = build_value_arm(ctx, &subtree, selected_body, &slot, &slot_type, span)
|
||||
if has_capture {
|
||||
_ = pop(&checker.static_bindings)
|
||||
}
|
||||
} else {
|
||||
ok = emit_match(ctx, &subtree, statement, true, &slot, &slot_type)
|
||||
}
|
||||
if !ok || slot == hir.INVALID_LOCAL {
|
||||
for s in subtree {
|
||||
append(body, s)
|
||||
@@ -12391,7 +12725,7 @@ check :: proc(
|
||||
checker.static_bindings.allocator = allocator
|
||||
checker.comptime_keys.allocator = allocator
|
||||
checker.comptime_static_values.allocator = allocator
|
||||
checker.inline_context.allocator = allocator
|
||||
checker.expand_context.allocator = allocator
|
||||
checker.static_state = ct_state_make(&checker, 0, ast.INVALID_FILE)
|
||||
build_symbol_indexes(&checker)
|
||||
checker.global_types = make([]types.Type, len(ast_module.globals), allocator)
|
||||
@@ -12467,7 +12801,7 @@ check :: proc(
|
||||
}
|
||||
for resolution in checker.call_resolutions {
|
||||
delete(resolution.ctx, allocator)
|
||||
delete(resolution.inline_ctx, allocator)
|
||||
delete(resolution.expand_ctx, allocator)
|
||||
delete(resolution.mapping, allocator)
|
||||
delete(resolution.comptime_values, allocator)
|
||||
delete(resolution.runtime_types, allocator)
|
||||
@@ -12483,7 +12817,7 @@ check :: proc(
|
||||
}
|
||||
delete(checker.comptime_keys)
|
||||
delete(checker.comptime_static_values)
|
||||
delete(checker.inline_context)
|
||||
delete(checker.expand_context)
|
||||
}
|
||||
|
||||
for function, index in ast_module.functions {
|
||||
|
||||
@@ -2515,6 +2515,50 @@ ct_eval_call_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Type
|
||||
}
|
||||
return ct_typeinfo_value(state, target, expr.span)
|
||||
}
|
||||
if is_intrinsic_call(checker, expr, "tag") {
|
||||
if len(expr.args) != 1 {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "tag! expects 1 argument, got %d", len(expr.args))
|
||||
}
|
||||
value_id, flow, ok := ct_eval_expr(state, expr.args[0], types.INVALID, depth+1)
|
||||
if !ok || flow.kind != .Normal {
|
||||
return INVALID_CT_VALUE, flow, ok
|
||||
}
|
||||
if value_id == INVALID_CT_VALUE || int(value_id) >= len(state.values) {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
||||
}
|
||||
value := state.values[value_id]
|
||||
tag_type, tag_ok := tag_result_type(checker, value.type)
|
||||
if !tag_ok || value.kind != .Struct || value.active < 0 {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "tag! requires a tagged-union value")
|
||||
}
|
||||
fields := types.fields_for(&checker.module.types, value.type)
|
||||
if int(value.active) >= len(fields) {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
||||
}
|
||||
member, found := find_enum_member(checker, tag_type, symbol.Id(fields[value.active].name))
|
||||
if !found {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
||||
}
|
||||
return ct_add_value(state, Ct_Value{kind=.Integer, type=tag_type, integer=member.value}), ct_flow(.Normal), true
|
||||
}
|
||||
if is_intrinsic_call(checker, expr, "tagname") {
|
||||
if len(expr.args) != 1 {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "tagname! expects 1 argument, got %d", len(expr.args))
|
||||
}
|
||||
value_id, flow, ok := ct_eval_expr(state, expr.args[0], types.INVALID, depth+1)
|
||||
if !ok || flow.kind != .Normal {
|
||||
return INVALID_CT_VALUE, flow, ok
|
||||
}
|
||||
if value_id == INVALID_CT_VALUE || int(value_id) >= len(state.values) {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
||||
}
|
||||
value := state.values[value_id]
|
||||
name, found := enum_member_name_from_value(checker, value.type, value.integer)
|
||||
if value.kind != .Integer || !found {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "tagname! requires a comptime-known enum value")
|
||||
}
|
||||
return ct_reflection_string(state, name), ct_flow(.Normal), true
|
||||
}
|
||||
if builtin := type_builtin_call(checker, expr); builtin != .None {
|
||||
if len(expr.args) != 1 {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "%s! expects 1 argument, got %d", symbol_text(checker, expr.name), len(expr.args))
|
||||
@@ -3660,6 +3704,7 @@ ct_exec_for :: proc(state: ^Ct_State, statement: ast.Stmt, yield_returns: bool,
|
||||
Ct_Match_Selection :: struct {
|
||||
arm: ast.Stmt_Id,
|
||||
payload: Ct_Value_Id,
|
||||
tag: Ct_Value_Id,
|
||||
payload_field: int,
|
||||
payload_type: types.Type,
|
||||
}
|
||||
@@ -3684,9 +3729,40 @@ ct_select_match_arm :: proc(
|
||||
selection := Ct_Match_Selection{
|
||||
arm=arm_id,
|
||||
payload=INVALID_CT_VALUE,
|
||||
tag=INVALID_CT_VALUE,
|
||||
payload_field=-1,
|
||||
payload_type=types.INVALID,
|
||||
}
|
||||
if arm.expand {
|
||||
if subject_value.kind == .Struct && types.is_tagged_union(subject_value.type, &checker.module.types) {
|
||||
fields := types.fields_for(&checker.module.types, subject_value.type)
|
||||
if subject_value.active < 0 || int(subject_value.active) >= len(fields) {
|
||||
return {}, false
|
||||
}
|
||||
field_index := int(subject_value.active)
|
||||
field := fields[field_index]
|
||||
selection.payload_field = field_index
|
||||
selection.payload_type = field.type
|
||||
children := ct_child_slice(state, subject_value)
|
||||
if len(children) > 0 {
|
||||
selection.payload = children[0]
|
||||
} else if types.is_void(field.type) {
|
||||
selection.payload = ct_add_value(state, Ct_Value{kind=.Void, type=types.VOID})
|
||||
}
|
||||
tag_type := types.union_tag_enum(subject_value.type, &checker.module.types)
|
||||
member, found := find_enum_member(checker, tag_type, symbol.Id(field.name))
|
||||
if !found {
|
||||
return {}, false
|
||||
}
|
||||
selection.tag = ct_add_value(state, Ct_Value{kind=.Integer, type=tag_type, integer=member.value})
|
||||
return selection, true
|
||||
}
|
||||
if subject_value.kind == .Integer && types.is_enum(subject_value.type, &checker.module.types) {
|
||||
selection.tag = subject
|
||||
return selection, true
|
||||
}
|
||||
return {}, false
|
||||
}
|
||||
if !matched {
|
||||
for pattern_id in arm.patterns {
|
||||
pattern := checker.ast_module.exprs[pattern_id]
|
||||
@@ -3765,7 +3841,11 @@ ct_exec_match :: proc(state: ^Ct_State, statement: ast.Stmt, yield_returns: bool
|
||||
if selected {
|
||||
arm := checker.ast_module.statements[selection.arm]
|
||||
scope_start := len(state.bindings)
|
||||
if len(arm.captures) > 0 && selection.payload != INVALID_CT_VALUE {
|
||||
if arm.expand && types.is_enum(state.values[subject].type, &checker.module.types) {
|
||||
if len(arm.captures) > 0 && arm.captures[0] != checker.sink_symbol {
|
||||
ct_bind_value(state, arm.captures[0], state.values[subject].type, subject, false)
|
||||
}
|
||||
} else if len(arm.captures) > 0 && selection.payload != INVALID_CT_VALUE {
|
||||
capture := arm.captures[0]
|
||||
if arm.pointer_capture {
|
||||
if subject_place == INVALID_CT_PLACE || selection.payload_field < 0 || !types.is_valid(selection.payload_type) {
|
||||
@@ -3785,6 +3865,10 @@ ct_exec_match :: proc(state: ^Ct_State, statement: ast.Stmt, yield_returns: bool
|
||||
ct_bind_value(state, capture, state.values[selection.payload].type, selection.payload, false)
|
||||
}
|
||||
}
|
||||
if arm.expand && len(arm.captures) > 1 && arm.captures[1] != checker.sink_symbol &&
|
||||
selection.tag != INVALID_CT_VALUE {
|
||||
ct_bind_value(state, arm.captures[1], state.values[selection.tag].type, selection.tag, false)
|
||||
}
|
||||
if yield_returns && len(arm.body) == 1 && checker.ast_module.statements[arm.body[0]].kind == .Expression {
|
||||
expr_stmt := checker.ast_module.statements[arm.body[0]]
|
||||
value, expr_flow, expr_ok := ct_eval_expr(state, expr_stmt.expr, types.INVALID, depth+1)
|
||||
|
||||
Reference in New Issue
Block a user