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:
Evan Haas 2020-12-07 14:20:45 -08:00 committed by Andrew Kelley
parent 5a5389128d
commit 55cac65f95
3 changed files with 160 additions and 25 deletions

View File

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

View File

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

View File

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