From 6fd0dddf186f6435f422f2992f44ec9a35e09f20 Mon Sep 17 00:00:00 2001 From: Vexu Date: Wed, 15 Jan 2020 20:23:49 +0200 Subject: [PATCH] implement non-exhaustive enums --- src/all_types.hpp | 2 + src/analyze.cpp | 18 +++++++- src/codegen.cpp | 2 +- src/ir.cpp | 110 ++++++++++++++++++++++++++++++++-------------- 4 files changed, 97 insertions(+), 35 deletions(-) diff --git a/src/all_types.hpp b/src/all_types.hpp index 0fed73f7b..89af4082d 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -1383,6 +1383,7 @@ struct ZigTypeEnum { ContainerLayout layout; ResolveStatus resolve_status; + bool non_exhaustive; bool resolve_loop_flag; }; @@ -3665,6 +3666,7 @@ struct IrInstructionCheckSwitchProngs { IrInstructionCheckSwitchProngsRange *ranges; size_t range_count; bool have_else_prong; + bool have_underscore_prong; }; struct IrInstructionCheckStatementIsVoid { diff --git a/src/analyze.cpp b/src/analyze.cpp index b7838003c..a62e0414e 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -2572,6 +2572,7 @@ static Error resolve_enum_zero_bits(CodeGen *g, ZigType *enum_type) { enum_type->data.enumeration.src_field_count = field_count; enum_type->data.enumeration.fields = allocate(field_count); enum_type->data.enumeration.fields_by_name.init(field_count); + enum_type->data.enumeration.non_exhaustive = false; Scope *scope = &enum_type->data.enumeration.decls_scope->base; @@ -2648,6 +2649,21 @@ static Error resolve_enum_zero_bits(CodeGen *g, ZigType *enum_type) { buf_sprintf("consider 'union(enum)' here")); } + AstNode *tag_value = field_node->data.struct_field.value; + + if (buf_eql_str(type_enum_field->name, "_")) { + if (field_i != field_count - 1) { + add_node_error(g, field_node, buf_sprintf("'_' field of non-exhaustive enum must be last")); + enum_type->data.enumeration.resolve_status = ResolveStatusInvalid; + } + if (tag_value != nullptr) { + add_node_error(g, field_node, buf_sprintf("value assigned to '_' field of non-exhaustive enum")); + enum_type->data.enumeration.resolve_status = ResolveStatusInvalid; + } + enum_type->data.enumeration.non_exhaustive = true; + continue; + } + auto field_entry = enum_type->data.enumeration.fields_by_name.put_unique(type_enum_field->name, type_enum_field); if (field_entry != nullptr) { ErrorMsg *msg = add_node_error(g, field_node, @@ -2657,8 +2673,6 @@ static Error resolve_enum_zero_bits(CodeGen *g, ZigType *enum_type) { continue; } - AstNode *tag_value = field_node->data.struct_field.value; - if (tag_value != nullptr) { // A user-specified value is available ZigValue *result = analyze_const_value(g, scope, tag_value, tag_int_type, diff --git a/src/codegen.cpp b/src/codegen.cpp index 9bab5fd87..0dc820be5 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -3356,7 +3356,7 @@ static LLVMValueRef ir_render_int_to_enum(CodeGen *g, IrExecutable *executable, LLVMValueRef tag_int_value = gen_widen_or_shorten(g, ir_want_runtime_safety(g, &instruction->base), instruction->target->value->type, tag_int_type, target_val); - if (ir_want_runtime_safety(g, &instruction->base) && wanted_type->data.enumeration.layout != ContainerLayoutExtern) { + if (ir_want_runtime_safety(g, &instruction->base) && !wanted_type->data.enumeration.non_exhaustive) { LLVMBasicBlockRef bad_value_block = LLVMAppendBasicBlock(g->cur_fn_val, "BadValue"); LLVMBasicBlockRef ok_value_block = LLVMAppendBasicBlock(g->cur_fn_val, "OkValue"); size_t field_count = wanted_type->data.enumeration.src_field_count; diff --git a/src/ir.cpp b/src/ir.cpp index d871aa27a..687621451 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -3451,7 +3451,7 @@ static IrInstruction *ir_build_err_to_int(IrBuilder *irb, Scope *scope, AstNode static IrInstruction *ir_build_check_switch_prongs(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *target_value, IrInstructionCheckSwitchProngsRange *ranges, size_t range_count, - bool have_else_prong) + bool have_else_prong, bool have_underscore_prong) { IrInstructionCheckSwitchProngs *instruction = ir_build_instruction( irb, scope, source_node); @@ -3459,6 +3459,7 @@ static IrInstruction *ir_build_check_switch_prongs(IrBuilder *irb, Scope *scope, instruction->ranges = ranges; instruction->range_count = range_count; instruction->have_else_prong = have_else_prong; + instruction->have_underscore_prong = have_underscore_prong; ir_ref_instruction(target_value, irb->current_basic_block); for (size_t i = 0; i < range_count; i += 1) { @@ -8090,34 +8091,11 @@ static IrInstruction *ir_gen_switch_expr(IrBuilder *irb, Scope *scope, AstNode * Scope *subexpr_scope = create_runtime_scope(irb->codegen, node, scope, is_comptime); Scope *comptime_scope = create_comptime_scope(irb->codegen, node, scope); AstNode *else_prong = nullptr; + AstNode *underscore_prong = nullptr; for (size_t prong_i = 0; prong_i < prong_count; prong_i += 1) { AstNode *prong_node = node->data.switch_expr.prongs.at(prong_i); size_t prong_item_count = prong_node->data.switch_prong.items.length; - if (prong_item_count == 0) { - ResultLocPeer *this_peer_result_loc = create_peer_result(peer_parent); - if (else_prong) { - ErrorMsg *msg = add_node_error(irb->codegen, prong_node, - buf_sprintf("multiple else prongs in switch expression")); - add_error_note(irb->codegen, msg, else_prong, - buf_sprintf("previous else prong is here")); - return irb->codegen->invalid_instruction; - } - else_prong = prong_node; - - IrBasicBlock *prev_block = irb->current_basic_block; - if (peer_parent->peers.length > 0) { - peer_parent->peers.last()->next_bb = else_block; - } - peer_parent->peers.append(this_peer_result_loc); - ir_set_cursor_at_end_and_append_block(irb, else_block); - if (!ir_gen_switch_prong_expr(irb, subexpr_scope, node, prong_node, end_block, - is_comptime, var_is_comptime, target_value_ptr, nullptr, 0, &incoming_blocks, &incoming_values, - &switch_else_var, LValNone, &this_peer_result_loc->base)) - { - return irb->codegen->invalid_instruction; - } - ir_set_cursor_at_end(irb, prev_block); - } else if (prong_node->data.switch_prong.any_items_are_range) { + if (prong_node->data.switch_prong.any_items_are_range) { ResultLocPeer *this_peer_result_loc = create_peer_result(peer_parent); IrInstruction *ok_bit = nullptr; @@ -8195,6 +8173,59 @@ static IrInstruction *ir_gen_switch_expr(IrBuilder *irb, Scope *scope, AstNode * } ir_set_cursor_at_end_and_append_block(irb, range_block_no); + } else { + if (prong_item_count == 0) { + if (else_prong) { + ErrorMsg *msg = add_node_error(irb->codegen, prong_node, + buf_sprintf("multiple else prongs in switch expression")); + add_error_note(irb->codegen, msg, else_prong, + buf_sprintf("previous else prong is here")); + return irb->codegen->invalid_instruction; + } + else_prong = prong_node; + if (underscore_prong) { + ErrorMsg *msg = add_node_error(irb->codegen, prong_node, + buf_sprintf("else and '_' prong in switch expression")); + add_error_note(irb->codegen, msg, underscore_prong, + buf_sprintf("'_' prong is here")); + return irb->codegen->invalid_instruction; + } + } else if (prong_item_count == 1 && + prong_node->data.switch_prong.items.at(0)->type == NodeTypeSymbol && + buf_eql_str(prong_node->data.switch_prong.items.at(0)->data.symbol_expr.symbol, "_")) { + if (underscore_prong) { + ErrorMsg *msg = add_node_error(irb->codegen, prong_node, + buf_sprintf("multiple '_' prongs in switch expression")); + add_error_note(irb->codegen, msg, underscore_prong, + buf_sprintf("previous '_' prong is here")); + return irb->codegen->invalid_instruction; + } + underscore_prong = prong_node; + if (else_prong) { + ErrorMsg *msg = add_node_error(irb->codegen, prong_node, + buf_sprintf("else and '_' prong in switch expression")); + add_error_note(irb->codegen, msg, else_prong, + buf_sprintf("else prong is here")); + return irb->codegen->invalid_instruction; + } + } else { + continue; + } + ResultLocPeer *this_peer_result_loc = create_peer_result(peer_parent); + + IrBasicBlock *prev_block = irb->current_basic_block; + if (peer_parent->peers.length > 0) { + peer_parent->peers.last()->next_bb = else_block; + } + peer_parent->peers.append(this_peer_result_loc); + ir_set_cursor_at_end_and_append_block(irb, else_block); + if (!ir_gen_switch_prong_expr(irb, subexpr_scope, node, prong_node, end_block, + is_comptime, var_is_comptime, target_value_ptr, nullptr, 0, &incoming_blocks, &incoming_values, + &switch_else_var, LValNone, &this_peer_result_loc->base)) + { + return irb->codegen->invalid_instruction; + } + ir_set_cursor_at_end(irb, prev_block); } } @@ -8206,6 +8237,8 @@ static IrInstruction *ir_gen_switch_expr(IrBuilder *irb, Scope *scope, AstNode * continue; if (prong_node->data.switch_prong.any_items_are_range) continue; + if (underscore_prong == prong_node) + continue; ResultLocPeer *this_peer_result_loc = create_peer_result(peer_parent); @@ -8249,7 +8282,7 @@ static IrInstruction *ir_gen_switch_expr(IrBuilder *irb, Scope *scope, AstNode * } IrInstruction *switch_prongs_void = ir_build_check_switch_prongs(irb, scope, node, target_value, - check_ranges.items, check_ranges.length, else_prong != nullptr); + check_ranges.items, check_ranges.length, else_prong != nullptr, underscore_prong != nullptr); IrInstruction *br_instruction; if (cases.length == 0) { @@ -8269,7 +8302,7 @@ static IrInstruction *ir_gen_switch_expr(IrBuilder *irb, Scope *scope, AstNode * peer_parent->peers.at(i)->base.source_instruction = peer_parent->base.source_instruction; } - if (!else_prong) { + if (!else_prong && !underscore_prong) { if (peer_parent->peers.length != 0) { peer_parent->peers.last()->next_bb = else_block; } @@ -12790,7 +12823,7 @@ static IrInstruction *ir_analyze_int_to_enum(IrAnalyze *ira, IrInstruction *sour return ira->codegen->invalid_instruction; TypeEnumField *field = find_enum_field_by_tag(wanted_type, &val->data.x_bigint); - if (field == nullptr && wanted_type->data.enumeration.layout != ContainerLayoutExtern) { + if (field == nullptr && !wanted_type->data.enumeration.non_exhaustive) { Buf *val_buf = buf_alloc(); bigint_append_buf(val_buf, &val->data.x_bigint, 10); ErrorMsg *msg = ir_add_error(ira, source_instr, @@ -26433,10 +26466,23 @@ static IrInstruction *ir_analyze_instruction_check_switch_prongs(IrAnalyze *ira, bigint_incr(&field_index); } } - if (!instruction->have_else_prong) { - if (switch_type->data.enumeration.layout == ContainerLayoutExtern) { + if (switch_type->data.enumeration.non_exhaustive && instruction->have_underscore_prong) { + for (uint32_t i = 0; i < switch_type->data.enumeration.src_field_count; i += 1) { + TypeEnumField *enum_field = &switch_type->data.enumeration.fields[i]; + if (buf_eql_str(enum_field->name, "_")) + continue; + + auto entry = field_prev_uses.maybe_get(enum_field->value); + if (!entry) { + ir_add_error(ira, &instruction->base, + buf_sprintf("enumeration value '%s.%s' not handled in switch", buf_ptr(&switch_type->name), + buf_ptr(enum_field->name))); + } + } + } else if (!instruction->have_else_prong) { + if (switch_type->data.enumeration.non_exhaustive) { ir_add_error(ira, &instruction->base, - buf_sprintf("switch on an extern enum must have an else prong")); + buf_sprintf("switch on non-exhaustive enum must include `else` or `_` prong")); } for (uint32_t i = 0; i < switch_type->data.enumeration.src_field_count; i += 1) { TypeEnumField *enum_field = &switch_type->data.enumeration.fields[i];