From d4d21dd46d69961300cc796abdebe352dfe6eae8 Mon Sep 17 00:00:00 2001 From: Evan Haas Date: Tue, 13 Apr 2021 20:57:50 -0700 Subject: [PATCH] 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 --- lib/std/meta.zig | 18 +++++++++++++++--- src/translate_c.zig | 4 ++-- test/run_translated_c.zig | 14 ++++++++++++++ test/translate_c.zig | 8 ++++---- 4 files changed, 35 insertions(+), 9 deletions(-) diff --git a/lib/std/meta.zig b/lib/std/meta.zig index 860b6874c09062c74721baf6c0ba2c9c4f4e9a5c..c6f800ca2cc314af834d3799ea7402c6f6d0101a 100644 --- a/lib/std/meta.zig +++ b/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. diff --git a/src/translate_c.zig b/src/translate_c.zig index 9a1215abd6a55d50d37995328042bbff22f357ae..8a2a1b62231a8efdfdf4a1d5c898ef006a737117 100644 --- a/src/translate_c.zig +++ b/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) diff --git a/test/run_translated_c.zig b/test/run_translated_c.zig index 7e5d0da367f24f5d8410bdaa4240504406d7e53d..35686c11f9aff4c01df953e83adaf86c14bc88e2 100644 --- a/test/run_translated_c.zig +++ b/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 + \\#include + \\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; + \\} + , ""); } diff --git a/test/translate_c.zig b/test/translate_c.zig index 142579c92cdf492392d7d84411c8d49d25cdc7f9..415d303f31d3425c96269372573d21cd98045b7e 100644 --- a/test/translate_c.zig +++ b/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)); -- 2.54.0