Commit d4d21dd46d

Evan Haas <evan@lagerdata.com>
2021-04-14 05:57:50
translate-c: better handling of int -> enum casts
In std.meta.cast when casting to an enum type from an integer type, first do a C-style cast from the source value to the tag type of the enum. This ensures that we don't get an error due to the source value not being representable by the enum. In transCCast() use std.meta.cast instead of directly emitting the cast operation since the enum's underlying type may not be known at translation time due to an MSVC bug, see https://github.com/ziglang/zig/issues/8003 Fixes #6011
1 parent ccdf553
lib/std/meta.zig
@@ -888,7 +888,7 @@ pub fn Vector(comptime len: u32, comptime child: type) type {
 /// Given a type and value, cast the value to the type as c would.
 /// This is for translate-c and is not intended for general use.
 pub fn cast(comptime DestType: type, target: anytype) DestType {
-    // this function should behave like transCCast in translate-c, except it's for macros
+    // this function should behave like transCCast in translate-c, except it's for macros and enums
     const SourceType = @TypeOf(target);
     switch (@typeInfo(DestType)) {
         .Pointer => {
@@ -925,9 +925,10 @@ pub fn cast(comptime DestType: type, target: anytype) DestType {
                 }
             }
         },
-        .Enum => {
+        .Enum => |enum_type| {
             if (@typeInfo(SourceType) == .Int or @typeInfo(SourceType) == .ComptimeInt) {
-                return @intToEnum(DestType, target);
+                const intermediate = cast(enum_type.tag_type, target);
+                return @intToEnum(DestType, intermediate);
             }
         },
         .Int => {
@@ -1015,6 +1016,17 @@ test "std.meta.cast" {
     testing.expectEqual(@intToPtr(*u8, 2), cast(*u8, @intToPtr(*volatile u8, 2)));
 
     testing.expectEqual(@intToPtr(?*c_void, 2), cast(?*c_void, @intToPtr(*u8, 2)));
+
+    const C_ENUM = extern enum(c_int) {
+        A = 0,
+        B,
+        C,
+        _,
+    };
+    testing.expectEqual(cast(C_ENUM, @as(i64, -1)), @intToEnum(C_ENUM, -1));
+    testing.expectEqual(cast(C_ENUM, @as(i8, 1)), .B);
+    testing.expectEqual(cast(C_ENUM, @as(u64, 1)), .B);
+    testing.expectEqual(cast(C_ENUM, @as(u64, 42)), @intToEnum(C_ENUM, 42));
 }
 
 /// Given a value returns its size as C's sizeof operator would.
src/translate_c.zig
@@ -2161,8 +2161,8 @@ fn transCCast(
         return Tag.as.create(c.arena, .{ .lhs = dst_node, .rhs = bool_to_int });
     }
     if (cIsEnum(dst_type)) {
-        // @intToEnum(dest_type, val)
-        return Tag.int_to_enum.create(c.arena, .{ .lhs = dst_node, .rhs = expr });
+        // import("std").meta.cast(dest_type, val)
+        return Tag.std_meta_cast.create(c.arena, .{ .lhs = dst_node, .rhs = expr });
     }
     if (cIsEnum(src_type) and !cIsEnum(dst_type)) {
         // @enumToInt(val)
test/run_translated_c.zig
@@ -1453,4 +1453,18 @@ pub fn addCases(cases: *tests.RunTranslatedCContext) void {
         \\    return 0;
         \\}
     , "");
+
+    cases.add("Cast to enum from larger integral type. Issue #6011",
+        \\#include <stdint.h>
+        \\#include <stdlib.h>
+        \\enum Foo { A, B, C };
+        \\static inline enum Foo do_stuff(void) {
+        \\    int64_t i = 1;
+        \\    return (enum Foo)i;
+        \\}
+        \\int main(void) {
+        \\    if (do_stuff() != B) abort();
+        \\    return 0;
+        \\}
+    , "");
 }
test/translate_c.zig
@@ -111,7 +111,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
         \\    const A = @enumToInt(enum_Foo.A);
         \\    const B = @enumToInt(enum_Foo.B);
         \\    const C = @enumToInt(enum_Foo.C);
-        \\    var a: enum_Foo = @intToEnum(enum_Foo, B);
+        \\    var a: enum_Foo = @import("std").meta.cast(enum_Foo, B);
         \\    {
         \\        const enum_Foo = extern enum(c_int) {
         \\            A,
@@ -122,7 +122,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
         \\        const A_2 = @enumToInt(enum_Foo.A);
         \\        const B_3 = @enumToInt(enum_Foo.B);
         \\        const C_4 = @enumToInt(enum_Foo.C);
-        \\        var a_5: enum_Foo = @intToEnum(enum_Foo, B_3);
+        \\        var a_5: enum_Foo = @import("std").meta.cast(enum_Foo, B_3);
         \\    }
         \\}
     });
@@ -1676,7 +1676,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
         \\pub const e = @enumToInt(enum_unnamed_1.e);
         \\pub const f = @enumToInt(enum_unnamed_1.f);
         \\pub const g = @enumToInt(enum_unnamed_1.g);
-        \\pub export var h: enum_unnamed_1 = @intToEnum(enum_unnamed_1, e);
+        \\pub export var h: enum_unnamed_1 = @import("std").meta.cast(enum_unnamed_1, e);
         \\const enum_unnamed_2 = extern enum(c_int) {
         \\    i,
         \\    j,
@@ -2308,7 +2308,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
         \\    var a = arg_a;
         \\    var b = arg_b;
         \\    var c = arg_c;
-        \\    var d: enum_Foo = @intToEnum(enum_Foo, FooA);
+        \\    var d: enum_Foo = @import("std").meta.cast(enum_Foo, FooA);
         \\    var e: c_int = @boolToInt((a != 0) and (b != 0));
         \\    var f: c_int = @boolToInt((b != 0) and (c != null));
         \\    var g: c_int = @boolToInt((a != 0) and (c != null));