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
This commit is contained in:
Evan Haas 2021-04-13 20:57:50 -07:00 committed by Andrew Kelley
parent ccdf55310b
commit d4d21dd46d
4 changed files with 35 additions and 9 deletions

View File

@ -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.

View File

@ -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)

View File

@ -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;
\\}
, "");
}

View File

@ -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));