mirror of
https://github.com/ziglang/zig.git
synced 2024-11-26 14:20:25 +00:00
Support casting enums to all int types.
In C, enums are represented as signed integers, so casting from an enum to an integer should use the "cast integer to integer" translation code path. Previously it used the "cast enum to generic non-enum" code path, because enums were not being treated as integers. Ultimately this can produce zig code that fails to compile if the destination type does not support the full range of enum values (e.g. translated C code that casts an enum value to an unsigned integer would fail to compile since enums are signed integers, and unsigned integers cannot represent the full range of values that signed ones can). One interesting thing that came up during testing is that the implicit enum-to-int cast that occurs when an enum is used in a boolean expression was parsed as an (int) by some versions of the zig compiler, and an (unsigned int) cast by others. Specifically, the following code: ```c enum Foo {Bar, Baz}; // ... enum Foo foo = Bar; if (0 || foo) { // do something } ``` When tested on MacOS, Linux, and Windows using a compiler built from the Windows Zig Compiler Dev Kit, the above code would emit a cast to c_uint: `if (false or (@bitCast(c_uint, @enumToInt(foo)) != 0)) {}` However when tested on Windows with a Zig compiler built using MSVC, it produces: `if (false or (@bitCast(c_int, @enumToInt(foo)) != 0)) {}` In this particular case I don't think it matters, since a c_int and c_uint will have the same representation for zero, but I'm not sure if this is ultimately the result of implementation-defined behavior or something else. Because of this, I added explicit casts in the `translate_c.zig` tests, to ensure that the emitted zig source exactly matches across platforms. I also added a behavior test in `run_translated_c.zig` that uses the old implicit casts from `translate_c.zig` to ensure that the emitted Zig code behaves the same as the C code regardless of what cast is used.
This commit is contained in:
parent
5a5389128d
commit
55cac65f95
@ -1993,6 +1993,21 @@ fn transStringLiteral(
|
||||
}
|
||||
}
|
||||
|
||||
fn cIsEnum(qt: clang.QualType) bool {
|
||||
return qt.getCanonicalType().getTypeClass() == .Enum;
|
||||
}
|
||||
|
||||
/// Get the underlying int type of an enum. The C compiler chooses a signed int
|
||||
/// type that is large enough to hold all of the enum's values. It is not required
|
||||
/// to be the smallest possible type that can hold all the values.
|
||||
fn cIntTypeForEnum(enum_qt: clang.QualType) clang.QualType {
|
||||
assert(cIsEnum(enum_qt));
|
||||
const ty = enum_qt.getCanonicalType().getTypePtr();
|
||||
const enum_ty = @ptrCast(*const clang.EnumType, ty);
|
||||
const enum_decl = enum_ty.getDecl();
|
||||
return enum_decl.getIntegerType();
|
||||
}
|
||||
|
||||
fn transCCast(
|
||||
rp: RestorePoint,
|
||||
scope: *Scope,
|
||||
@ -2005,40 +2020,45 @@ fn transCCast(
|
||||
if (dst_type.eq(src_type)) return expr;
|
||||
if (qualTypeIsPtr(dst_type) and qualTypeIsPtr(src_type))
|
||||
return transCPtrCast(rp, loc, dst_type, src_type, expr);
|
||||
if (cIsInteger(dst_type) and cIsInteger(src_type)) {
|
||||
// 1. Extend or truncate without changing signed-ness.
|
||||
// 2. Bit-cast to correct signed-ness
|
||||
if (cIsInteger(dst_type) and (cIsInteger(src_type) or cIsEnum(src_type))) {
|
||||
// 1. If src_type is an enum, determine the underlying signed int type
|
||||
// 2. Extend or truncate without changing signed-ness.
|
||||
// 3. Bit-cast to correct signed-ness
|
||||
|
||||
const src_type_is_signed = cIsSignedInteger(src_type) or cIsEnum(src_type);
|
||||
const src_int_type = if (cIsInteger(src_type)) src_type else cIntTypeForEnum(src_type);
|
||||
const src_int_expr = if (cIsInteger(src_type)) expr else try transEnumToInt(rp.c, expr);
|
||||
|
||||
// @bitCast(dest_type, intermediate_value)
|
||||
const cast_node = try rp.c.createBuiltinCall("@bitCast", 2);
|
||||
cast_node.params()[0] = try transQualType(rp, dst_type, loc);
|
||||
_ = try appendToken(rp.c, .Comma, ",");
|
||||
|
||||
switch (cIntTypeCmp(dst_type, src_type)) {
|
||||
switch (cIntTypeCmp(dst_type, src_int_type)) {
|
||||
.lt => {
|
||||
// @truncate(SameSignSmallerInt, src_type)
|
||||
// @truncate(SameSignSmallerInt, src_int_expr)
|
||||
const trunc_node = try rp.c.createBuiltinCall("@truncate", 2);
|
||||
const ty_node = try transQualTypeIntWidthOf(rp.c, dst_type, cIsSignedInteger(src_type));
|
||||
const ty_node = try transQualTypeIntWidthOf(rp.c, dst_type, src_type_is_signed);
|
||||
trunc_node.params()[0] = ty_node;
|
||||
_ = try appendToken(rp.c, .Comma, ",");
|
||||
trunc_node.params()[1] = expr;
|
||||
trunc_node.params()[1] = src_int_expr;
|
||||
trunc_node.rparen_token = try appendToken(rp.c, .RParen, ")");
|
||||
|
||||
cast_node.params()[1] = &trunc_node.base;
|
||||
},
|
||||
.gt => {
|
||||
// @as(SameSignBiggerInt, src_type)
|
||||
// @as(SameSignBiggerInt, src_int_expr)
|
||||
const as_node = try rp.c.createBuiltinCall("@as", 2);
|
||||
const ty_node = try transQualTypeIntWidthOf(rp.c, dst_type, cIsSignedInteger(src_type));
|
||||
const ty_node = try transQualTypeIntWidthOf(rp.c, dst_type, src_type_is_signed);
|
||||
as_node.params()[0] = ty_node;
|
||||
_ = try appendToken(rp.c, .Comma, ",");
|
||||
as_node.params()[1] = expr;
|
||||
as_node.params()[1] = src_int_expr;
|
||||
as_node.rparen_token = try appendToken(rp.c, .RParen, ")");
|
||||
|
||||
cast_node.params()[1] = &as_node.base;
|
||||
},
|
||||
.eq => {
|
||||
cast_node.params()[1] = expr;
|
||||
cast_node.params()[1] = src_int_expr;
|
||||
},
|
||||
}
|
||||
cast_node.rparen_token = try appendToken(rp.c, .RParen, ")");
|
||||
@ -2119,7 +2139,7 @@ fn transCCast(
|
||||
|
||||
return &cast_node.base;
|
||||
}
|
||||
if (dst_type.getCanonicalType().getTypeClass() == .Enum) {
|
||||
if (cIsEnum(dst_type)) {
|
||||
const builtin_node = try rp.c.createBuiltinCall("@intToEnum", 2);
|
||||
builtin_node.params()[0] = try transQualType(rp, dst_type, loc);
|
||||
_ = try appendToken(rp.c, .Comma, ",");
|
||||
@ -2127,13 +2147,8 @@ fn transCCast(
|
||||
builtin_node.rparen_token = try appendToken(rp.c, .RParen, ")");
|
||||
return &builtin_node.base;
|
||||
}
|
||||
if (src_type.getCanonicalType().getTypeClass() == .Enum and
|
||||
dst_type.getCanonicalType().getTypeClass() != .Enum)
|
||||
{
|
||||
const builtin_node = try rp.c.createBuiltinCall("@enumToInt", 1);
|
||||
builtin_node.params()[0] = expr;
|
||||
builtin_node.rparen_token = try appendToken(rp.c, .RParen, ")");
|
||||
return &builtin_node.base;
|
||||
if (cIsEnum(src_type) and !cIsEnum(dst_type)) {
|
||||
return transEnumToInt(rp.c, expr);
|
||||
}
|
||||
const cast_node = try rp.c.createBuiltinCall("@as", 2);
|
||||
cast_node.params()[0] = try transQualType(rp, dst_type, loc);
|
||||
@ -2143,6 +2158,13 @@ fn transCCast(
|
||||
return &cast_node.base;
|
||||
}
|
||||
|
||||
fn transEnumToInt(c: *Context, enum_expr: *ast.Node) TypeError!*ast.Node {
|
||||
const builtin_node = try c.createBuiltinCall("@enumToInt", 1);
|
||||
builtin_node.params()[0] = enum_expr;
|
||||
builtin_node.rparen_token = try appendToken(c, .RParen, ")");
|
||||
return &builtin_node.base;
|
||||
}
|
||||
|
||||
fn transExpr(
|
||||
rp: RestorePoint,
|
||||
scope: *Scope,
|
||||
|
@ -371,4 +371,117 @@ pub fn addCases(cases: *tests.RunTranslatedCContext) void {
|
||||
\\ return 0;
|
||||
\\}
|
||||
, "");
|
||||
|
||||
cases.add("assign enum to uint, no explicit cast",
|
||||
\\#include <stdlib.h>
|
||||
\\typedef enum {
|
||||
\\ ENUM_0 = 0,
|
||||
\\ ENUM_1 = 1,
|
||||
\\} my_enum_t;
|
||||
\\
|
||||
\\int main() {
|
||||
\\ my_enum_t val = ENUM_1;
|
||||
\\ unsigned int x = val;
|
||||
\\ if (x != 1) abort();
|
||||
\\ return 0;
|
||||
\\}
|
||||
, "");
|
||||
|
||||
cases.add("assign enum to int",
|
||||
\\#include <stdlib.h>
|
||||
\\typedef enum {
|
||||
\\ ENUM_0 = 0,
|
||||
\\ ENUM_1 = 1,
|
||||
\\} my_enum_t;
|
||||
\\
|
||||
\\int main() {
|
||||
\\ my_enum_t val = ENUM_1;
|
||||
\\ int x = val;
|
||||
\\ if (x != 1) abort();
|
||||
\\ return 0;
|
||||
\\}
|
||||
, "");
|
||||
|
||||
cases.add("cast enum to smaller uint",
|
||||
\\#include <stdlib.h>
|
||||
\\#include <stdint.h>
|
||||
\\typedef enum {
|
||||
\\ ENUM_0 = 0,
|
||||
\\ ENUM_257 = 257,
|
||||
\\} my_enum_t;
|
||||
\\
|
||||
\\int main() {
|
||||
\\ my_enum_t val = ENUM_257;
|
||||
\\ uint8_t x = (uint8_t)val;
|
||||
\\ if (x != (uint8_t)257) abort();
|
||||
\\ return 0;
|
||||
\\}
|
||||
, "");
|
||||
|
||||
cases.add("cast enum to smaller signed int",
|
||||
\\#include <stdlib.h>
|
||||
\\#include <stdint.h>
|
||||
\\typedef enum {
|
||||
\\ ENUM_0 = 0,
|
||||
\\ ENUM_384 = 384,
|
||||
\\} my_enum_t;
|
||||
\\
|
||||
\\int main() {
|
||||
\\ my_enum_t val = ENUM_384;
|
||||
\\ int8_t x = (int8_t)val;
|
||||
\\ if (x != (int8_t)384) abort();
|
||||
\\ return 0;
|
||||
\\}
|
||||
, "");
|
||||
|
||||
cases.add("cast negative enum to smaller signed int",
|
||||
\\#include <stdlib.h>
|
||||
\\#include <stdint.h>
|
||||
\\typedef enum {
|
||||
\\ ENUM_MINUS_1 = -1,
|
||||
\\ ENUM_384 = 384,
|
||||
\\} my_enum_t;
|
||||
\\
|
||||
\\int main() {
|
||||
\\ my_enum_t val = ENUM_MINUS_1;
|
||||
\\ int8_t x = (int8_t)val;
|
||||
\\ if (x != -1) abort();
|
||||
\\ return 0;
|
||||
\\}
|
||||
, "");
|
||||
|
||||
cases.add("cast negative enum to smaller unsigned int",
|
||||
\\#include <stdlib.h>
|
||||
\\#include <stdint.h>
|
||||
\\typedef enum {
|
||||
\\ ENUM_MINUS_1 = -1,
|
||||
\\ ENUM_384 = 384,
|
||||
\\} my_enum_t;
|
||||
\\
|
||||
\\int main() {
|
||||
\\ my_enum_t val = ENUM_MINUS_1;
|
||||
\\ uint8_t x = (uint8_t)val;
|
||||
\\ if (x != (uint8_t)-1) abort();
|
||||
\\ return 0;
|
||||
\\}
|
||||
, "");
|
||||
|
||||
cases.add("implicit enum cast in boolean expression",
|
||||
\\#include <stdlib.h>
|
||||
\\enum Foo {
|
||||
\\ FooA,
|
||||
\\ FooB,
|
||||
\\ FooC,
|
||||
\\};
|
||||
\\int main() {
|
||||
\\ int a = 0;
|
||||
\\ float b = 0;
|
||||
\\ void *c = 0;
|
||||
\\ enum Foo d = FooA;
|
||||
\\ if (a || d) abort();
|
||||
\\ if (d && b) abort();
|
||||
\\ if (c || d) abort();
|
||||
\\ return 0;
|
||||
\\}
|
||||
, "");
|
||||
}
|
||||
|
@ -2000,9 +2000,9 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
|
||||
\\ int h = (a || b);
|
||||
\\ int i = (b || c);
|
||||
\\ int j = (a || c);
|
||||
\\ int k = (a || d);
|
||||
\\ int l = (d && b);
|
||||
\\ int m = (c || d);
|
||||
\\ int k = (a || (int)d);
|
||||
\\ int l = ((int)d && b);
|
||||
\\ int m = (c || (unsigned int)d);
|
||||
\\ SomeTypedef td = 44;
|
||||
\\ int o = (td || b);
|
||||
\\ int p = (c && td);
|
||||
@ -2027,9 +2027,9 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
|
||||
\\ var h: c_int = @boolToInt(((a != 0) or (b != 0)));
|
||||
\\ var i: c_int = @boolToInt(((b != 0) or (c != null)));
|
||||
\\ var j: c_int = @boolToInt(((a != 0) or (c != null)));
|
||||
\\ var k: c_int = @boolToInt(((a != 0) or (@enumToInt(d) != 0)));
|
||||
\\ var l: c_int = @boolToInt(((@enumToInt(d) != 0) and (b != 0)));
|
||||
\\ var m: c_int = @boolToInt(((c != null) or (@enumToInt(d) != 0)));
|
||||
\\ var k: c_int = @boolToInt(((a != 0) or (@bitCast(c_int, @enumToInt(d)) != 0)));
|
||||
\\ var l: c_int = @boolToInt(((@bitCast(c_int, @enumToInt(d)) != 0) and (b != 0)));
|
||||
\\ var m: c_int = @boolToInt(((c != null) or (@bitCast(c_uint, @enumToInt(d)) != 0)));
|
||||
\\ var td: SomeTypedef = 44;
|
||||
\\ var o: c_int = @boolToInt(((td != 0) or (b != 0)));
|
||||
\\ var p: c_int = @boolToInt(((c != null) and (td != 0)));
|
||||
|
Loading…
Reference in New Issue
Block a user