From 6fd0dddf186f6435f422f2992f44ec9a35e09f20 Mon Sep 17 00:00:00 2001 From: Vexu Date: Wed, 15 Jan 2020 20:23:49 +0200 Subject: [PATCH 01/11] 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 0fed73f7b2..89af4082d8 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 b7838003c8..a62e0414e0 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 9bab5fd878..0dc820be51 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 d871aa27a0..6876214510 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]; From b971c7d0ff1c0ef86ac8d6816eb5e115f0d648fa Mon Sep 17 00:00:00 2001 From: Vexu Date: Wed, 15 Jan 2020 20:40:56 +0200 Subject: [PATCH 02/11] update tests and translate-c --- src-self-hosted/translate_c.zig | 27 +++++++++++----------- test/compile_errors.zig | 41 ++++++++++++++++++--------------- test/stage1/behavior/cast.zig | 1 - test/stage1/behavior/enum.zig | 40 +++++++++++++++++++++++++------- test/translate_c.zig | 11 +++++++++ 5 files changed, 78 insertions(+), 42 deletions(-) diff --git a/src-self-hosted/translate_c.zig b/src-self-hosted/translate_c.zig index a876b20b68..ad58b6a916 100644 --- a/src-self-hosted/translate_c.zig +++ b/src-self-hosted/translate_c.zig @@ -440,7 +440,6 @@ fn visitFnDecl(c: *Context, fn_decl: *const ZigClangFunctionDecl) Error!void { .PrivateExtern => return failDecl(c, fn_decl_loc, fn_name, "unsupported storage class: private extern", .{}), .Auto => unreachable, // Not legal on functions .Register => unreachable, // Not legal on functions - else => unreachable, }, }; @@ -953,6 +952,19 @@ fn transEnumDecl(c: *Context, enum_decl: *const ZigClangEnumDecl) Error!?*ast.No tld_node.semicolon_token = try appendToken(c, .Semicolon, ";"); try addTopLevelDecl(c, field_name, &tld_node.base); } + // make non exhaustive + const field_node = try c.a().create(ast.Node.ContainerField); + field_node.* = .{ + .doc_comments = null, + .comptime_token = null, + .name_token = try appendIdentifier(c, "_"), + .type_expr = null, + .value_expr = null, + .align_expr = null, + }; + + try container_node.fields_and_decls.push(&field_node.base); + _ = try appendToken(c, .Comma, ","); container_node.rbrace_token = try appendToken(c, .RBrace, "}"); break :blk &container_node.base; @@ -1231,18 +1243,6 @@ fn transBinaryOperator( op_id = .BitOr; op_token = try appendToken(rp.c, .Pipe, "|"); }, - .Assign, - .MulAssign, - .DivAssign, - .RemAssign, - .AddAssign, - .SubAssign, - .ShlAssign, - .ShrAssign, - .AndAssign, - .XorAssign, - .OrAssign, - => unreachable, else => unreachable, } @@ -1678,7 +1678,6 @@ fn transStringLiteral( "TODO: support string literal kind {}", .{kind}, ), - else => unreachable, } } diff --git a/test/compile_errors.zig b/test/compile_errors.zig index 3ce5bd8801..e7ef53452a 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -2,6 +2,28 @@ const tests = @import("tests.zig"); const builtin = @import("builtin"); pub fn addCases(cases: *tests.CompileErrorContext) void { + cases.addTest("non-exhaustive enums", + \\const E = enum { + \\ a, + \\ b, + \\ _, + \\}; + \\pub export fn entry() void { + \\ var e: E = .b; + \\ switch (e) { // error: switch not handling the tag `b` + \\ .a => {}, + \\ _ => {}, + \\ } + \\ switch (e) { // error: switch on non-exhaustive enum must include `else` or `_` prong + \\ .a => {}, + \\ .b => {}, + \\ } + \\} + , &[_][]const u8{ + "tmp.zig:8:5: error: enumeration value 'E.b' not handled in switch", + "tmp.zig:12:5: error: switch on non-exhaustive enum must include `else` or `_` prong", + }); + cases.addTest("@export with empty name string", \\pub export fn entry() void { } \\comptime { @@ -139,25 +161,6 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { "tmp.zig:2:13: error: pointer type '[*]align(4) u8' requires aligned address", }); - cases.add("switch on extern enum missing else prong", - \\const i = extern enum { - \\ n = 0, - \\ o = 2, - \\ p = 4, - \\ q = 4, - \\}; - \\pub fn main() void { - \\ var x = @intToEnum(i, 52); - \\ switch (x) { - \\ .n, - \\ .o, - \\ .p => unreachable, - \\ } - \\} - , &[_][]const u8{ - "tmp.zig:9:5: error: switch on an extern enum must have an else prong", - }); - cases.add("invalid float literal", \\const std = @import("std"); \\ diff --git a/test/stage1/behavior/cast.zig b/test/stage1/behavior/cast.zig index ae2530af61..fe55b445a8 100644 --- a/test/stage1/behavior/cast.zig +++ b/test/stage1/behavior/cast.zig @@ -618,7 +618,6 @@ test "peer resolution of string literals" { .b => "two", .c => "three", .d => "four", - else => unreachable, }; expect(mem.eql(u8, cmd, "two")); } diff --git a/test/stage1/behavior/enum.zig b/test/stage1/behavior/enum.zig index d5bb2ddbb2..c888722f27 100644 --- a/test/stage1/behavior/enum.zig +++ b/test/stage1/behavior/enum.zig @@ -11,17 +11,41 @@ test "extern enum" { }; fn doTheTest(y: c_int) void { var x = i.o; - expect(@enumToInt(x) == 2); - x = @intToEnum(i, 12); - expect(@enumToInt(x) == 12); - x = @intToEnum(i, y); - expect(@enumToInt(x) == 52); switch (x) { - .n, - .o, - .p => unreachable, + .n, .p => unreachable, + .o => {}, + } + } + }; + S.doTheTest(52); + comptime S.doTheTest(52); +} + +test "non-exhaustive enum" { + const S = struct { + const E = enum(u8) { + a, + b, + _, + }; + fn doTheTest(y: u8) void { + var e: E = .b; + switch (e) { + .a => {}, + .b => {}, + _ => {}, + } + + switch (e) { + .a => {}, + .b => {}, else => {}, } + expect(@enumToInt(e) == 1); + e = @intToEnum(E, 12); + expect(@enumToInt(e) == 12); + e = @intToEnum(E, y); + expect(@enumToInt(e) == 52); } }; S.doTheTest(52); diff --git a/test/translate_c.zig b/test/translate_c.zig index 16caa6e323..13779cb2fa 100644 --- a/test/translate_c.zig +++ b/test/translate_c.zig @@ -629,6 +629,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\ VAL21 = 6917529027641081853, \\ VAL22 = 0, \\ VAL23 = -1, + \\ _, \\}; }); } @@ -990,6 +991,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\pub const FOO = @enumToInt(enum_enum_ty.FOO); \\pub const enum_enum_ty = extern enum { \\ FOO, + \\ _, \\}; \\pub extern var my_enum: enum_enum_ty; }); @@ -1106,6 +1108,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\ a, \\ b, \\ c, + \\ _, \\}; \\pub const d = enum_unnamed_1; \\pub const e = @enumToInt(enum_unnamed_2.e); @@ -1115,6 +1118,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\ e = 0, \\ f = 4, \\ g = 5, + \\ _, \\}; \\pub export var h: enum_unnamed_2 = @intToEnum(enum_unnamed_2, e); \\pub const i = @enumToInt(enum_unnamed_3.i); @@ -1124,6 +1128,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\ i, \\ j, \\ k, + \\ _, \\}; \\pub const struct_Baz = extern struct { \\ l: enum_unnamed_3, @@ -1136,6 +1141,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\ n, \\ o, \\ p, + \\ _, \\}; , \\pub const Baz = struct_Baz; @@ -1566,6 +1572,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\const enum_unnamed_1 = extern enum { \\ One, \\ Two, + \\ _, \\}; }); @@ -1669,6 +1676,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\ A, \\ B, \\ C, + \\ _, \\}; \\pub const SomeTypedef = c_int; \\pub export fn and_or_non_bool(arg_a: c_int, arg_b: f32, arg_c: ?*c_void) c_int { @@ -1713,6 +1721,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\pub const enum_Bar = extern enum { \\ A, \\ B, + \\ _, \\}; \\pub extern fn func(a: [*c]struct_Foo, b: [*c][*c]enum_Bar) void; , @@ -1977,6 +1986,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\ A, \\ B, \\ C, + \\ _, \\}; \\pub export fn if_none_bool(arg_a: c_int, arg_b: f32, arg_c: ?*c_void, arg_d: enum_SomeEnum) c_int { \\ var a = arg_a; @@ -2418,6 +2428,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\ A = 2, \\ B = 5, \\ @"1" = 6, + \\ _, \\}; , \\pub const Foo = enum_Foo; From f3d174aa616401117927988dfc499a1762db01a3 Mon Sep 17 00:00:00 2001 From: Vexu Date: Wed, 15 Jan 2020 21:38:11 +0200 Subject: [PATCH 03/11] require size for non-exhaustive enums --- src-self-hosted/translate_c.zig | 63 ++++++++++++++------------------- src/analyze.cpp | 8 +++++ test/compile_errors.zig | 25 ++++++++++++- test/translate_c.zig | 20 +++++------ 4 files changed, 68 insertions(+), 48 deletions(-) diff --git a/src-self-hosted/translate_c.zig b/src-self-hosted/translate_c.zig index ad58b6a916..c726bf8cbf 100644 --- a/src-self-hosted/translate_c.zig +++ b/src-self-hosted/translate_c.zig @@ -289,8 +289,7 @@ pub fn translate( tree.errors = ast.Tree.ErrorList.init(arena); tree.root_node = try arena.create(ast.Node.Root); - tree.root_node.* = ast.Node.Root{ - .base = ast.Node{ .id = ast.Node.Id.Root }, + tree.root_node.* = .{ .decls = ast.Node.Root.DeclList.init(arena), // initialized with the eof token at the end .eof_token = undefined, @@ -876,25 +875,20 @@ fn transEnumDecl(c: *Context, enum_decl: *const ZigClangEnumDecl) Error!?*ast.No // types, while that's not ISO-C compliant many compilers allow this and // default to the usual integer type used for all the enums. - // TODO only emit this tag type if the enum tag type is not the default. - // I don't know what the default is, need to figure out how clang is deciding. - // it appears to at least be different across gcc/msvc - if (int_type.ptr != null and - !isCBuiltinType(int_type, .UInt) and - !isCBuiltinType(int_type, .Int)) - { - _ = try appendToken(c, .LParen, "("); - container_node.init_arg_expr = .{ - .Type = transQualType(rp, int_type, enum_loc) catch |err| switch (err) { + _ = try appendToken(c, .LParen, "("); + container_node.init_arg_expr = .{ + .Type = if (int_type.ptr != null) + transQualType(rp, int_type, enum_loc) catch |err| switch (err) { error.UnsupportedType => { try failDecl(c, enum_loc, name, "unable to translate enum tag type", .{}); return null; }, else => |e| return e, - }, - }; - _ = try appendToken(c, .RParen, ")"); - } + } + else + try transCreateNodeIdentifier(c, "c_int"), + }; + _ = try appendToken(c, .RParen, ")"); container_node.lbrace_token = try appendToken(c, .LBrace, "{"); @@ -2198,6 +2192,19 @@ fn transDoWhileLoop( .id = .Loop, }; + // if (!cond) break; + const if_node = try transCreateNodeIf(rp.c); + var cond_scope = Scope{ + .parent = scope, + .id = .Condition, + }; + const prefix_op = try transCreateNodePrefixOp(rp.c, .BoolNot, .Bang, "!"); + prefix_op.rhs = try transBoolExpr(rp, &cond_scope, @ptrCast(*const ZigClangExpr, ZigClangDoStmt_getCond(stmt)), .used, .r_value, true); + _ = try appendToken(rp.c, .RParen, ")"); + if_node.condition = &prefix_op.base; + if_node.body = &(try transCreateNodeBreak(rp.c, null)).base; + _ = try appendToken(rp.c, .Semicolon, ";"); + const body_node = if (ZigClangStmt_getStmtClass(ZigClangDoStmt_getBody(stmt)) == .CompoundStmtClass) blk: { // there's already a block in C, so we'll append our condition to it. // c: do { @@ -2209,10 +2216,7 @@ fn transDoWhileLoop( // zig: b; // zig: if (!cond) break; // zig: } - const body = (try transStmt(rp, &loop_scope, ZigClangDoStmt_getBody(stmt), .unused, .r_value)).cast(ast.Node.Block).?; - // if this is used as an expression in Zig it needs to be immediately followed by a semicolon - _ = try appendToken(rp.c, .Semicolon, ";"); - break :blk body; + break :blk (try transStmt(rp, &loop_scope, ZigClangDoStmt_getBody(stmt), .unused, .r_value)).cast(ast.Node.Block).?; } else blk: { // the C statement is without a block, so we need to create a block to contain it. // c: do @@ -2228,19 +2232,6 @@ fn transDoWhileLoop( break :blk block; }; - // if (!cond) break; - const if_node = try transCreateNodeIf(rp.c); - var cond_scope = Scope{ - .parent = scope, - .id = .Condition, - }; - const prefix_op = try transCreateNodePrefixOp(rp.c, .BoolNot, .Bang, "!"); - prefix_op.rhs = try transBoolExpr(rp, &cond_scope, @ptrCast(*const ZigClangExpr, ZigClangDoStmt_getCond(stmt)), .used, .r_value, true); - _ = try appendToken(rp.c, .RParen, ")"); - if_node.condition = &prefix_op.base; - if_node.body = &(try transCreateNodeBreak(rp.c, null)).base; - _ = try appendToken(rp.c, .Semicolon, ";"); - try body_node.statements.push(&if_node.base); if (new) body_node.rbrace = try appendToken(rp.c, .RBrace, "}"); @@ -4775,8 +4766,7 @@ fn appendIdentifier(c: *Context, name: []const u8) !ast.TokenIndex { fn transCreateNodeIdentifier(c: *Context, name: []const u8) !*ast.Node { const token_index = try appendIdentifier(c, name); const identifier = try c.a().create(ast.Node.Identifier); - identifier.* = ast.Node.Identifier{ - .base = ast.Node{ .id = ast.Node.Id.Identifier }, + identifier.* = .{ .token = token_index, }; return &identifier.base; @@ -4915,8 +4905,7 @@ fn transMacroFnDefine(c: *Context, it: *ctok.TokenList.Iterator, name: []const u const token_index = try appendToken(c, .Keyword_var, "var"); const identifier = try c.a().create(ast.Node.Identifier); - identifier.* = ast.Node.Identifier{ - .base = ast.Node{ .id = ast.Node.Id.Identifier }, + identifier.* = .{ .token = token_index, }; diff --git a/src/analyze.cpp b/src/analyze.cpp index a62e0414e0..0b2b6ddeaa 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -2652,6 +2652,14 @@ static Error resolve_enum_zero_bits(CodeGen *g, ZigType *enum_type) { AstNode *tag_value = field_node->data.struct_field.value; if (buf_eql_str(type_enum_field->name, "_")) { + if (decl_node->data.container_decl.init_arg_expr == nullptr) { + add_node_error(g, field_node, buf_sprintf("non-exhaustive enum must specify size")); + enum_type->data.enumeration.resolve_status = ResolveStatusInvalid; + } + if (log2_u64(field_count - 1) == enum_type->size_in_bits) { + add_node_error(g, field_node, buf_sprintf("non-exhaustive enum specifies every value")); + enum_type->data.enumeration.resolve_status = ResolveStatusInvalid; + } 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; diff --git a/test/compile_errors.zig b/test/compile_errors.zig index e7ef53452a..9a76319af5 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -3,7 +3,30 @@ const builtin = @import("builtin"); pub fn addCases(cases: *tests.CompileErrorContext) void { cases.addTest("non-exhaustive enums", - \\const E = enum { + \\const A = enum { + \\ a, + \\ b, + \\ _ = 1, + \\}; + \\const B = enum(u1) { + \\ a, + \\ b, + \\ _, + \\ c, + \\}; + \\pub export fn entry() void { + \\ _ = A; + \\ _ = B; + \\} + , &[_][]const u8{ + "tmp.zig:4:5: error: non-exhaustive enum must specify size", + "error: value assigned to '_' field of non-exhaustive enum", + "error: non-exhaustive enum specifies every value", + "error: '_' field of non-exhaustive enum must be last", + }); + + cases.addTest("switching with non-exhaustive enums", + \\const E = enum(u8) { \\ a, \\ b, \\ _, diff --git a/test/translate_c.zig b/test/translate_c.zig index 13779cb2fa..adee4c9a4d 100644 --- a/test/translate_c.zig +++ b/test/translate_c.zig @@ -989,7 +989,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\enum enum_ty { FOO }; , &[_][]const u8{ \\pub const FOO = @enumToInt(enum_enum_ty.FOO); - \\pub const enum_enum_ty = extern enum { + \\pub const enum_enum_ty = extern enum(c_int) { \\ FOO, \\ _, \\}; @@ -1104,7 +1104,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\pub const a = @enumToInt(enum_unnamed_1.a); \\pub const b = @enumToInt(enum_unnamed_1.b); \\pub const c = @enumToInt(enum_unnamed_1.c); - \\const enum_unnamed_1 = extern enum { + \\const enum_unnamed_1 = extern enum(c_uint) { \\ a, \\ b, \\ c, @@ -1114,7 +1114,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\pub const e = @enumToInt(enum_unnamed_2.e); \\pub const f = @enumToInt(enum_unnamed_2.f); \\pub const g = @enumToInt(enum_unnamed_2.g); - \\const enum_unnamed_2 = extern enum { + \\const enum_unnamed_2 = extern enum(c_uint) { \\ e = 0, \\ f = 4, \\ g = 5, @@ -1124,7 +1124,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\pub const i = @enumToInt(enum_unnamed_3.i); \\pub const j = @enumToInt(enum_unnamed_3.j); \\pub const k = @enumToInt(enum_unnamed_3.k); - \\const enum_unnamed_3 = extern enum { + \\const enum_unnamed_3 = extern enum(c_uint) { \\ i, \\ j, \\ k, @@ -1137,7 +1137,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\pub const n = @enumToInt(enum_i.n); \\pub const o = @enumToInt(enum_i.o); \\pub const p = @enumToInt(enum_i.p); - \\pub const enum_i = extern enum { + \\pub const enum_i = extern enum(c_uint) { \\ n, \\ o, \\ p, @@ -1569,7 +1569,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { , &[_][]const u8{ \\pub const One = @enumToInt(enum_unnamed_1.One); \\pub const Two = @enumToInt(enum_unnamed_1.Two); - \\const enum_unnamed_1 = extern enum { + \\const enum_unnamed_1 = extern enum(c_uint) { \\ One, \\ Two, \\ _, @@ -1672,7 +1672,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\ return ((((((((((e + f) + g) + h) + i) + j) + k) + l) + m) + o) + p); \\} , &[_][]const u8{ - \\pub const enum_Foo = extern enum { + \\pub const enum_Foo = extern enum(c_uint) { \\ A, \\ B, \\ C, @@ -1718,7 +1718,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\ y: c_int, \\}; , - \\pub const enum_Bar = extern enum { + \\pub const enum_Bar = extern enum(c_uint) { \\ A, \\ B, \\ _, @@ -1982,7 +1982,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\ return 4; \\} , &[_][]const u8{ - \\pub const enum_SomeEnum = extern enum { + \\pub const enum_SomeEnum = extern enum(c_uint) { \\ A, \\ B, \\ C, @@ -2424,7 +2424,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\pub const FooA = @enumToInt(enum_Foo.A); \\pub const FooB = @enumToInt(enum_Foo.B); \\pub const Foo1 = @enumToInt(enum_Foo.@"1"); - \\pub const enum_Foo = extern enum { + \\pub const enum_Foo = extern enum(c_uint) { \\ A = 2, \\ B = 5, \\ @"1" = 6, From c57784aa15b50a9f38482154170924babab19c03 Mon Sep 17 00:00:00 2001 From: Vexu Date: Wed, 15 Jan 2020 21:50:12 +0200 Subject: [PATCH 04/11] add is_exhaustive field to typeinfo --- lib/std/builtin.zig | 1 + src/ir.cpp | 7 ++++++- test/stage1/behavior/enum.zig | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/std/builtin.zig b/lib/std/builtin.zig index dbd19fbadf..4b80b38100 100644 --- a/lib/std/builtin.zig +++ b/lib/std/builtin.zig @@ -253,6 +253,7 @@ pub const TypeInfo = union(enum) { tag_type: type, fields: []EnumField, decls: []Declaration, + is_exhaustive: bool, }; /// This data structure is used by the Zig language code generation and diff --git a/src/ir.cpp b/src/ir.cpp index 6876214510..08f80e0647 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -23107,7 +23107,7 @@ static Error ir_make_type_info_value(IrAnalyze *ira, IrInstruction *source_instr result->special = ConstValSpecialStatic; result->type = ir_type_info_get_type(ira, "Enum", nullptr); - ZigValue **fields = alloc_const_vals_ptrs(4); + ZigValue **fields = alloc_const_vals_ptrs(5); result->data.x_struct.fields = fields; // layout: ContainerLayout @@ -23153,6 +23153,11 @@ static Error ir_make_type_info_value(IrAnalyze *ira, IrInstruction *source_instr { return err; } + // is_exhaustive: bool + ensure_field_index(result->type, "is_exhaustive", 4); + fields[4]->special = ConstValSpecialStatic; + fields[4]->type = ira->codegen->builtin_types.entry_bool; + fields[4]->data.x_bool = !type_entry->data.enumeration.non_exhaustive; break; } diff --git a/test/stage1/behavior/enum.zig b/test/stage1/behavior/enum.zig index c888722f27..82e57a3a38 100644 --- a/test/stage1/behavior/enum.zig +++ b/test/stage1/behavior/enum.zig @@ -46,6 +46,7 @@ test "non-exhaustive enum" { expect(@enumToInt(e) == 12); e = @intToEnum(E, y); expect(@enumToInt(e) == 52); + expect(@typeInfo(E).Enum.is_exhaustive == false); } }; S.doTheTest(52); From 5c2238fc4ad1d10f0620c931d369005b53742eb7 Mon Sep 17 00:00:00 2001 From: Vexu Date: Wed, 15 Jan 2020 22:09:19 +0200 Subject: [PATCH 05/11] small fixes * error for '_' prong on exhaustive enum * todo panic for `@tagName` on non-exhaustive enum * don't require '_' field on tagged unions --- src/analyze.cpp | 2 +- src/codegen.cpp | 2 ++ src/ir.cpp | 8 +++++++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/analyze.cpp b/src/analyze.cpp index 0b2b6ddeaa..7669e0890b 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -3293,7 +3293,7 @@ static Error resolve_union_zero_bits(CodeGen *g, ZigType *union_type) { } else if (enum_type_node != nullptr) { for (uint32_t i = 0; i < tag_type->data.enumeration.src_field_count; i += 1) { TypeEnumField *enum_field = &tag_type->data.enumeration.fields[i]; - if (!covered_enum_fields[i]) { + if (!covered_enum_fields[i] && !buf_eql_str(enum_field->name, "_")) { AstNode *enum_decl_node = tag_type->data.enumeration.decl_node; AstNode *field_node = enum_decl_node->data.container_decl.fields.at(i); ErrorMsg *msg = add_node_error(g, decl_node, diff --git a/src/codegen.cpp b/src/codegen.cpp index 0dc820be51..42fd188824 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -5065,6 +5065,8 @@ static LLVMValueRef ir_render_enum_tag_name(CodeGen *g, IrExecutable *executable { ZigType *enum_type = instruction->target->value->type; assert(enum_type->id == ZigTypeIdEnum); + if (enum_type->data.enumeration.non_exhaustive) + zig_panic("TODO @tagName on non-exhaustive enum"); LLVMValueRef enum_name_function = get_enum_tag_name_function(g, enum_type); diff --git a/src/ir.cpp b/src/ir.cpp index 08f80e0647..7aa3243c34 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -22357,6 +22357,8 @@ static IrInstruction *ir_analyze_instruction_enum_tag_name(IrAnalyze *ira, IrIns if (instr_is_comptime(target)) { if ((err = type_resolve(ira->codegen, target->value->type, ResolveStatusZeroBitsKnown))) return ira->codegen->invalid_instruction; + if (target->value->type->data.enumeration.non_exhaustive) + zig_panic("TODO @tagName on non-exhaustive enum"); TypeEnumField *field = find_enum_field_by_tag(target->value->type, &target->value->data.x_bigint); ZigValue *array_val = create_const_str_lit(ira->codegen, field->name)->data.x_ptr.data.ref.pointee; IrInstruction *result = ir_const(ira, &instruction->base, nullptr); @@ -26471,7 +26473,11 @@ static IrInstruction *ir_analyze_instruction_check_switch_prongs(IrAnalyze *ira, bigint_incr(&field_index); } } - if (switch_type->data.enumeration.non_exhaustive && instruction->have_underscore_prong) { + if (instruction->have_underscore_prong) { + if (!switch_type->data.enumeration.non_exhaustive){ + ir_add_error(ira, &instruction->base, + buf_sprintf("switch on non-exhaustive enum has `_` 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, "_")) From 02e5cb1cd4203219ae753e94c7e14cd18a918b49 Mon Sep 17 00:00:00 2001 From: Vexu Date: Wed, 15 Jan 2020 23:05:52 +0200 Subject: [PATCH 06/11] add non-exhaustive enum to langref --- doc/langref.html.in | 44 +++++++++++++++++++++++++++++++++++++++++ test/compile_errors.zig | 3 +-- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/doc/langref.html.in b/doc/langref.html.in index 442e4ac52b..dbe98ce708 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -2893,6 +2893,50 @@ test "switch using enum literals" { } {#code_end#} {#header_close#} + + {#header_open|Non-exhaustive enum#} +

+ A Non-exhaustive enum can be created by adding a trailing '_' field. + It must specify a tag type and cannot consume every enumeration value. +

+

+ {#link|@intToEnum#} on a non-exhaustive enum cannot fail. +

+

+ A switch on a non-exhaustive enum can include a '_' prong with the following properties: +

    +
  • makes it a compile error if all the known tag names are not handled by the switch
  • +
  • allows omitting {#syntax#}else{#endsyntax#}
  • +
+

+ {#code_begin|test#} +const std = @import("std"); +const assert = std.debug.assert; + +const Number = enum(u8) { + One, + Two, + Three, + _, +}; + +test "switch on non-exhaustive enum" { + const number = Number.One; + const result = switch (number) { + .One => true, + .Two, + .Three => false, + _ => false, + }; + assert(result); + const is_one = switch (number) { + .One => true, + else => false, + }; + assert(is_one); +} + {#code_end#} + {#header_close#} {#header_close#} {#header_open|union#} diff --git a/test/compile_errors.zig b/test/compile_errors.zig index 9a76319af5..702cc76524 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -10,9 +10,8 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { \\}; \\const B = enum(u1) { \\ a, - \\ b, \\ _, - \\ c, + \\ b, \\}; \\pub export fn entry() void { \\ _ = A; From d84569895c80136d9b081a319301a737f342d251 Mon Sep 17 00:00:00 2001 From: Vexu Date: Thu, 16 Jan 2020 09:04:11 +0200 Subject: [PATCH 07/11] turn panics into compile errors, require at least 1 field in non-exhaustive enum --- doc/langref.html.in | 7 ++----- src/analyze.cpp | 3 ++- src/codegen.cpp | 7 +++++-- src/ir.cpp | 32 ++++++++++++++++---------------- 4 files changed, 25 insertions(+), 24 deletions(-) diff --git a/doc/langref.html.in b/doc/langref.html.in index dbe98ce708..cac01c5686 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -2903,11 +2903,8 @@ test "switch using enum literals" { {#link|@intToEnum#} on a non-exhaustive enum cannot fail.

- A switch on a non-exhaustive enum can include a '_' prong with the following properties: -

    -
  • makes it a compile error if all the known tag names are not handled by the switch
  • -
  • allows omitting {#syntax#}else{#endsyntax#}
  • -
+ A switch on a non-exhaustive enum can include a '_' prong as an alternative to an {#syntax#}else{#endsyntax#} prong + with the difference being that it makes it a compile error if all the known tag names are not handled by the switch.

{#code_begin|test#} const std = @import("std"); diff --git a/src/analyze.cpp b/src/analyze.cpp index 7669e0890b..a9aaf74a85 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -2560,7 +2560,8 @@ static Error resolve_enum_zero_bits(CodeGen *g, ZigType *enum_type) { assert(!enum_type->data.enumeration.fields); uint32_t field_count = (uint32_t)decl_node->data.container_decl.fields.length; - if (field_count == 0) { + if (field_count == 0 || (field_count == 1 && + buf_eql_str(decl_node->data.container_decl.fields.at(0)->data.struct_field.name, "_"))) { add_node_error(g, decl_node, buf_sprintf("enums must have 1 or more fields")); enum_type->data.enumeration.src_field_count = field_count; diff --git a/src/codegen.cpp b/src/codegen.cpp index 42fd188824..030e892d45 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -5065,8 +5065,11 @@ static LLVMValueRef ir_render_enum_tag_name(CodeGen *g, IrExecutable *executable { ZigType *enum_type = instruction->target->value->type; assert(enum_type->id == ZigTypeIdEnum); - if (enum_type->data.enumeration.non_exhaustive) - zig_panic("TODO @tagName on non-exhaustive enum"); + if (enum_type->data.enumeration.non_exhaustive) { + add_node_error(g, instruction->base.source_node, + buf_sprintf("TODO @tagName on non-exhaustive enum https://github.com/ziglang/zig/issues/3991")); + codegen_report_errors_and_exit(g); + } LLVMValueRef enum_name_function = get_enum_tag_name_function(g, enum_type); diff --git a/src/ir.cpp b/src/ir.cpp index 7aa3243c34..43ca01113b 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -8183,13 +8183,6 @@ static IrInstruction *ir_gen_switch_expr(IrBuilder *irb, Scope *scope, AstNode * 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, "_")) { @@ -8201,16 +8194,20 @@ static IrInstruction *ir_gen_switch_expr(IrBuilder *irb, Scope *scope, AstNode * 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; } + if (underscore_prong && else_prong) { + ErrorMsg *msg = add_node_error(irb->codegen, prong_node, + buf_sprintf("else and '_' prong in switch expression")); + if (underscore_prong == prong_node) + add_error_note(irb->codegen, msg, else_prong, + buf_sprintf("else prong is here")); + else + add_error_note(irb->codegen, msg, underscore_prong, + buf_sprintf("'_' prong is here")); + return irb->codegen->invalid_instruction; + } ResultLocPeer *this_peer_result_loc = create_peer_result(peer_parent); IrBasicBlock *prev_block = irb->current_basic_block; @@ -22357,8 +22354,11 @@ static IrInstruction *ir_analyze_instruction_enum_tag_name(IrAnalyze *ira, IrIns if (instr_is_comptime(target)) { if ((err = type_resolve(ira->codegen, target->value->type, ResolveStatusZeroBitsKnown))) return ira->codegen->invalid_instruction; - if (target->value->type->data.enumeration.non_exhaustive) - zig_panic("TODO @tagName on non-exhaustive enum"); + if (target->value->type->data.enumeration.non_exhaustive) { + add_node_error(ira->codegen, instruction->base.source_node, + buf_sprintf("TODO @tagName on non-exhaustive enum https://github.com/ziglang/zig/issues/3991")); + return ira->codegen->invalid_instruction; + } TypeEnumField *field = find_enum_field_by_tag(target->value->type, &target->value->data.x_bigint); ZigValue *array_val = create_const_str_lit(ira->codegen, field->name)->data.x_ptr.data.ref.pointee; IrInstruction *result = ir_const(ira, &instruction->base, nullptr); From cb257b4e11768ccce93d7cf11c416fa8e745f206 Mon Sep 17 00:00:00 2001 From: Vexu Date: Thu, 16 Jan 2020 09:23:26 +0200 Subject: [PATCH 08/11] allow non-exhaustive enums with no fields --- src/analyze.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/analyze.cpp b/src/analyze.cpp index a9aaf74a85..18dc7e032c 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -2560,8 +2560,7 @@ static Error resolve_enum_zero_bits(CodeGen *g, ZigType *enum_type) { assert(!enum_type->data.enumeration.fields); uint32_t field_count = (uint32_t)decl_node->data.container_decl.fields.length; - if (field_count == 0 || (field_count == 1 && - buf_eql_str(decl_node->data.container_decl.fields.at(0)->data.struct_field.name, "_"))) { + if (field_count == 0) { add_node_error(g, decl_node, buf_sprintf("enums must have 1 or more fields")); enum_type->data.enumeration.src_field_count = field_count; @@ -2657,7 +2656,7 @@ static Error resolve_enum_zero_bits(CodeGen *g, ZigType *enum_type) { add_node_error(g, field_node, buf_sprintf("non-exhaustive enum must specify size")); enum_type->data.enumeration.resolve_status = ResolveStatusInvalid; } - if (log2_u64(field_count - 1) == enum_type->size_in_bits) { + if (field_count > 1 && log2_u64(field_count - 1) == enum_type->size_in_bits) { add_node_error(g, field_node, buf_sprintf("non-exhaustive enum specifies every value")); enum_type->data.enumeration.resolve_status = ResolveStatusInvalid; } From 6450736c5f9039565802e1f898a8fd402ba12c63 Mon Sep 17 00:00:00 2001 From: Vexu Date: Thu, 16 Jan 2020 12:50:44 +0200 Subject: [PATCH 09/11] translate-c default enum tag type to c_int --- src-self-hosted/translate_c.zig | 5 ++++- test/translate_c.zig | 18 +++++++++--------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src-self-hosted/translate_c.zig b/src-self-hosted/translate_c.zig index c726bf8cbf..2d5774f452 100644 --- a/src-self-hosted/translate_c.zig +++ b/src-self-hosted/translate_c.zig @@ -875,9 +875,12 @@ fn transEnumDecl(c: *Context, enum_decl: *const ZigClangEnumDecl) Error!?*ast.No // types, while that's not ISO-C compliant many compilers allow this and // default to the usual integer type used for all the enums. + // default to c_int since msvc and gcc default to different types _ = try appendToken(c, .LParen, "("); container_node.init_arg_expr = .{ - .Type = if (int_type.ptr != null) + .Type = if (int_type.ptr != null and + !isCBuiltinType(int_type, .UInt) and + !isCBuiltinType(int_type, .Int)) transQualType(rp, int_type, enum_loc) catch |err| switch (err) { error.UnsupportedType => { try failDecl(c, enum_loc, name, "unable to translate enum tag type", .{}); diff --git a/test/translate_c.zig b/test/translate_c.zig index adee4c9a4d..df33d9b145 100644 --- a/test/translate_c.zig +++ b/test/translate_c.zig @@ -1104,7 +1104,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\pub const a = @enumToInt(enum_unnamed_1.a); \\pub const b = @enumToInt(enum_unnamed_1.b); \\pub const c = @enumToInt(enum_unnamed_1.c); - \\const enum_unnamed_1 = extern enum(c_uint) { + \\const enum_unnamed_1 = extern enum(c_int) { \\ a, \\ b, \\ c, @@ -1114,7 +1114,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\pub const e = @enumToInt(enum_unnamed_2.e); \\pub const f = @enumToInt(enum_unnamed_2.f); \\pub const g = @enumToInt(enum_unnamed_2.g); - \\const enum_unnamed_2 = extern enum(c_uint) { + \\const enum_unnamed_2 = extern enum(c_int) { \\ e = 0, \\ f = 4, \\ g = 5, @@ -1124,7 +1124,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\pub const i = @enumToInt(enum_unnamed_3.i); \\pub const j = @enumToInt(enum_unnamed_3.j); \\pub const k = @enumToInt(enum_unnamed_3.k); - \\const enum_unnamed_3 = extern enum(c_uint) { + \\const enum_unnamed_3 = extern enum(c_int) { \\ i, \\ j, \\ k, @@ -1137,7 +1137,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\pub const n = @enumToInt(enum_i.n); \\pub const o = @enumToInt(enum_i.o); \\pub const p = @enumToInt(enum_i.p); - \\pub const enum_i = extern enum(c_uint) { + \\pub const enum_i = extern enum(c_int) { \\ n, \\ o, \\ p, @@ -1569,7 +1569,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { , &[_][]const u8{ \\pub const One = @enumToInt(enum_unnamed_1.One); \\pub const Two = @enumToInt(enum_unnamed_1.Two); - \\const enum_unnamed_1 = extern enum(c_uint) { + \\const enum_unnamed_1 = extern enum(c_int) { \\ One, \\ Two, \\ _, @@ -1672,7 +1672,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\ return ((((((((((e + f) + g) + h) + i) + j) + k) + l) + m) + o) + p); \\} , &[_][]const u8{ - \\pub const enum_Foo = extern enum(c_uint) { + \\pub const enum_Foo = extern enum(c_int) { \\ A, \\ B, \\ C, @@ -1718,7 +1718,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\ y: c_int, \\}; , - \\pub const enum_Bar = extern enum(c_uint) { + \\pub const enum_Bar = extern enum(c_int) { \\ A, \\ B, \\ _, @@ -1982,7 +1982,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\ return 4; \\} , &[_][]const u8{ - \\pub const enum_SomeEnum = extern enum(c_uint) { + \\pub const enum_SomeEnum = extern enum(c_int) { \\ A, \\ B, \\ C, @@ -2424,7 +2424,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\pub const FooA = @enumToInt(enum_Foo.A); \\pub const FooB = @enumToInt(enum_Foo.B); \\pub const Foo1 = @enumToInt(enum_Foo.@"1"); - \\pub const enum_Foo = extern enum(c_uint) { + \\pub const enum_Foo = extern enum(c_int) { \\ A = 2, \\ B = 5, \\ @"1" = 6, From 6c8f01dcde03380806338758d489c3f2a78e5b5b Mon Sep 17 00:00:00 2001 From: Vexu Date: Thu, 16 Jan 2020 22:48:01 +0200 Subject: [PATCH 10/11] correct field count --- src/analyze.cpp | 60 ++++++++++++++++++----------------- test/compile_errors.zig | 6 ++++ test/stage1/behavior/enum.zig | 1 + 3 files changed, 38 insertions(+), 29 deletions(-) diff --git a/src/analyze.cpp b/src/analyze.cpp index 18dc7e032c..e064677a09 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -2569,16 +2569,8 @@ static Error resolve_enum_zero_bits(CodeGen *g, ZigType *enum_type) { return ErrorSemanticAnalyzeFail; } - 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; - HashMap occupied_tag_values = {}; - occupied_tag_values.init(field_count); - ZigType *tag_int_type; if (enum_type->data.enumeration.layout == ContainerLayoutExtern) { tag_int_type = get_c_int_type(g, CIntTypeInt); @@ -2620,6 +2612,7 @@ static Error resolve_enum_zero_bits(CodeGen *g, ZigType *enum_type) { } } + enum_type->data.enumeration.non_exhaustive = false; enum_type->data.enumeration.tag_int_type = tag_int_type; enum_type->size_in_bits = tag_int_type->size_in_bits; enum_type->abi_size = tag_int_type->abi_size; @@ -2628,6 +2621,31 @@ static Error resolve_enum_zero_bits(CodeGen *g, ZigType *enum_type) { BigInt bi_one; bigint_init_unsigned(&bi_one, 1); + AstNode *last_field_node = decl_node->data.container_decl.fields.at(field_count - 1); + if (buf_eql_str(last_field_node->data.struct_field.name, "_")) { + field_count -= 1; + if (field_count > 1 && log2_u64(field_count) == enum_type->size_in_bits) { + add_node_error(g, last_field_node, buf_sprintf("non-exhaustive enum specifies every value")); + enum_type->data.enumeration.resolve_status = ResolveStatusInvalid; + } + if (decl_node->data.container_decl.init_arg_expr == nullptr) { + add_node_error(g, last_field_node, buf_sprintf("non-exhaustive enum must specify size")); + enum_type->data.enumeration.resolve_status = ResolveStatusInvalid; + } + if (last_field_node->data.struct_field.value != nullptr) { + add_node_error(g, last_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; + } + + 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); + + HashMap occupied_tag_values = {}; + occupied_tag_values.init(field_count); + TypeEnumField *last_enum_field = nullptr; for (uint32_t field_i = 0; field_i < field_count; field_i += 1) { @@ -2649,27 +2667,9 @@ 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 (decl_node->data.container_decl.init_arg_expr == nullptr) { - add_node_error(g, field_node, buf_sprintf("non-exhaustive enum must specify size")); - enum_type->data.enumeration.resolve_status = ResolveStatusInvalid; - } - if (field_count > 1 && log2_u64(field_count - 1) == enum_type->size_in_bits) { - add_node_error(g, field_node, buf_sprintf("non-exhaustive enum specifies every value")); - enum_type->data.enumeration.resolve_status = ResolveStatusInvalid; - } - 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; + add_node_error(g, field_node, buf_sprintf("'_' field of non-exhaustive enum must be last")); + enum_type->data.enumeration.resolve_status = ResolveStatusInvalid; } auto field_entry = enum_type->data.enumeration.fields_by_name.put_unique(type_enum_field->name, type_enum_field); @@ -2681,6 +2681,8 @@ 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, @@ -3293,7 +3295,7 @@ static Error resolve_union_zero_bits(CodeGen *g, ZigType *union_type) { } else if (enum_type_node != nullptr) { for (uint32_t i = 0; i < tag_type->data.enumeration.src_field_count; i += 1) { TypeEnumField *enum_field = &tag_type->data.enumeration.fields[i]; - if (!covered_enum_fields[i] && !buf_eql_str(enum_field->name, "_")) { + if (!covered_enum_fields[i]) { AstNode *enum_decl_node = tag_type->data.enumeration.decl_node; AstNode *field_node = enum_decl_node->data.container_decl.fields.at(i); ErrorMsg *msg = add_node_error(g, decl_node, diff --git a/test/compile_errors.zig b/test/compile_errors.zig index 702cc76524..be2a40d74d 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -13,9 +13,15 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { \\ _, \\ b, \\}; + \\const C = enum(u1) { + \\ a, + \\ b, + \\ _, + \\}; \\pub export fn entry() void { \\ _ = A; \\ _ = B; + \\ _ = C; \\} , &[_][]const u8{ "tmp.zig:4:5: error: non-exhaustive enum must specify size", diff --git a/test/stage1/behavior/enum.zig b/test/stage1/behavior/enum.zig index 82e57a3a38..62b7d51c26 100644 --- a/test/stage1/behavior/enum.zig +++ b/test/stage1/behavior/enum.zig @@ -41,6 +41,7 @@ test "non-exhaustive enum" { .b => {}, else => {}, } + expect(@typeInfo(E).Enum.fields.len == 2); expect(@enumToInt(e) == 1); e = @intToEnum(E, 12); expect(@enumToInt(e) == 12); From 39f92a9ee4ea109628e1f7d5a65bb53575e53194 Mon Sep 17 00:00:00 2001 From: Vexu Date: Fri, 17 Jan 2020 09:50:20 +0200 Subject: [PATCH 11/11] improve behavior test --- test/stage1/behavior/enum.zig | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/test/stage1/behavior/enum.zig b/test/stage1/behavior/enum.zig index 62b7d51c26..83ad76b72c 100644 --- a/test/stage1/behavior/enum.zig +++ b/test/stage1/behavior/enum.zig @@ -30,19 +30,30 @@ test "non-exhaustive enum" { }; fn doTheTest(y: u8) void { var e: E = .b; - switch (e) { - .a => {}, - .b => {}, - _ => {}, - } + expect(switch (e) { + .a => false, + .b => true, + _ => false, + }); + e = @intToEnum(E, 12); + expect(switch (e) { + .a => false, + .b => false, + _ => true, + }); + + expect(switch (e) { + .a => false, + .b => false, + else => true, + }); + e = .b; + expect(switch (e) { + .a => false, + else => true, + }); - switch (e) { - .a => {}, - .b => {}, - else => {}, - } expect(@typeInfo(E).Enum.fields.len == 2); - expect(@enumToInt(e) == 1); e = @intToEnum(E, 12); expect(@enumToInt(e) == 12); e = @intToEnum(E, y);