Commit f3d174aa61

Vexu <git@vexu.eu>
2020-01-15 20:38:11
require size for non-exhaustive enums
1 parent b971c7d
Changed files (4)
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;
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,
         };
 
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,
         \\    _,
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,