authorgravatar for evan@lagerdata.comEvan Haas <evan@lagerdata.com> 2020-12-07 14:20:45-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-12-10 15:47:56-05:00
log55cac65f957fc374e4e369e26bd338f11b8b37ee
tree2bcef192dc9c996494117fee01a11fbbebd49933
parent5a5389128d3d74f02d3697dd317b977c01c5e8c6

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.

3 files changed, 160 insertions(+), 25 deletions(-)

src/translate_c.zig+41-19
...@@ -1993,6 +1993,21 @@ fn transStringLiteral(...@@ -1993,6 +1993,21 @@ fn transStringLiteral(
1993 }1993 }
1994}1994}
19951995
1996fn cIsEnum(qt: clang.QualType) bool {
1997 return qt.getCanonicalType().getTypeClass() == .Enum;
1998}
1999
2000/// Get the underlying int type of an enum. The C compiler chooses a signed int
2001/// type that is large enough to hold all of the enum's values. It is not required
2002/// to be the smallest possible type that can hold all the values.
2003fn cIntTypeForEnum(enum_qt: clang.QualType) clang.QualType {
2004 assert(cIsEnum(enum_qt));
2005 const ty = enum_qt.getCanonicalType().getTypePtr();
2006 const enum_ty = @ptrCast(*const clang.EnumType, ty);
2007 const enum_decl = enum_ty.getDecl();
2008 return enum_decl.getIntegerType();
2009}
2010
1996fn transCCast(2011fn transCCast(
1997 rp: RestorePoint,2012 rp: RestorePoint,
1998 scope: *Scope,2013 scope: *Scope,
...@@ -2005,40 +2020,45 @@ fn transCCast(...@@ -2005,40 +2020,45 @@ fn transCCast(
2005 if (dst_type.eq(src_type)) return expr;2020 if (dst_type.eq(src_type)) return expr;
2006 if (qualTypeIsPtr(dst_type) and qualTypeIsPtr(src_type))2021 if (qualTypeIsPtr(dst_type) and qualTypeIsPtr(src_type))
2007 return transCPtrCast(rp, loc, dst_type, src_type, expr);2022 return transCPtrCast(rp, loc, dst_type, src_type, expr);
2008 if (cIsInteger(dst_type) and cIsInteger(src_type)) {2023 if (cIsInteger(dst_type) and (cIsInteger(src_type) or cIsEnum(src_type))) {
2009 // 1. Extend or truncate without changing signed-ness.2024 // 1. If src_type is an enum, determine the underlying signed int type
2010 // 2. Bit-cast to correct signed-ness2025 // 2. Extend or truncate without changing signed-ness.
2026 // 3. Bit-cast to correct signed-ness
2027
2028 const src_type_is_signed = cIsSignedInteger(src_type) or cIsEnum(src_type);
2029 const src_int_type = if (cIsInteger(src_type)) src_type else cIntTypeForEnum(src_type);
2030 const src_int_expr = if (cIsInteger(src_type)) expr else try transEnumToInt(rp.c, expr);
20112031
2012 // @bitCast(dest_type, intermediate_value)2032 // @bitCast(dest_type, intermediate_value)
2013 const cast_node = try rp.c.createBuiltinCall("@bitCast", 2);2033 const cast_node = try rp.c.createBuiltinCall("@bitCast", 2);
2014 cast_node.params()[0] = try transQualType(rp, dst_type, loc);2034 cast_node.params()[0] = try transQualType(rp, dst_type, loc);
2015 _ = try appendToken(rp.c, .Comma, ",");2035 _ = try appendToken(rp.c, .Comma, ",");
20162036
2017 switch (cIntTypeCmp(dst_type, src_type)) {2037 switch (cIntTypeCmp(dst_type, src_int_type)) {
2018 .lt => {2038 .lt => {
2019 // @truncate(SameSignSmallerInt, src_type)2039 // @truncate(SameSignSmallerInt, src_int_expr)
2020 const trunc_node = try rp.c.createBuiltinCall("@truncate", 2);2040 const trunc_node = try rp.c.createBuiltinCall("@truncate", 2);
2021 const ty_node = try transQualTypeIntWidthOf(rp.c, dst_type, cIsSignedInteger(src_type));2041 const ty_node = try transQualTypeIntWidthOf(rp.c, dst_type, src_type_is_signed);
2022 trunc_node.params()[0] = ty_node;2042 trunc_node.params()[0] = ty_node;
2023 _ = try appendToken(rp.c, .Comma, ",");2043 _ = try appendToken(rp.c, .Comma, ",");
2024 trunc_node.params()[1] = expr;2044 trunc_node.params()[1] = src_int_expr;
2025 trunc_node.rparen_token = try appendToken(rp.c, .RParen, ")");2045 trunc_node.rparen_token = try appendToken(rp.c, .RParen, ")");
20262046
2027 cast_node.params()[1] = &trunc_node.base;2047 cast_node.params()[1] = &trunc_node.base;
2028 },2048 },
2029 .gt => {2049 .gt => {
2030 // @as(SameSignBiggerInt, src_type)2050 // @as(SameSignBiggerInt, src_int_expr)
2031 const as_node = try rp.c.createBuiltinCall("@as", 2);2051 const as_node = try rp.c.createBuiltinCall("@as", 2);
2032 const ty_node = try transQualTypeIntWidthOf(rp.c, dst_type, cIsSignedInteger(src_type));2052 const ty_node = try transQualTypeIntWidthOf(rp.c, dst_type, src_type_is_signed);
2033 as_node.params()[0] = ty_node;2053 as_node.params()[0] = ty_node;
2034 _ = try appendToken(rp.c, .Comma, ",");2054 _ = try appendToken(rp.c, .Comma, ",");
2035 as_node.params()[1] = expr;2055 as_node.params()[1] = src_int_expr;
2036 as_node.rparen_token = try appendToken(rp.c, .RParen, ")");2056 as_node.rparen_token = try appendToken(rp.c, .RParen, ")");
20372057
2038 cast_node.params()[1] = &as_node.base;2058 cast_node.params()[1] = &as_node.base;
2039 },2059 },
2040 .eq => {2060 .eq => {
2041 cast_node.params()[1] = expr;2061 cast_node.params()[1] = src_int_expr;
2042 },2062 },
2043 }2063 }
2044 cast_node.rparen_token = try appendToken(rp.c, .RParen, ")");2064 cast_node.rparen_token = try appendToken(rp.c, .RParen, ")");
...@@ -2119,7 +2139,7 @@ fn transCCast(...@@ -2119,7 +2139,7 @@ fn transCCast(
21192139
2120 return &cast_node.base;2140 return &cast_node.base;
2121 }2141 }
2122 if (dst_type.getCanonicalType().getTypeClass() == .Enum) {2142 if (cIsEnum(dst_type)) {
2123 const builtin_node = try rp.c.createBuiltinCall("@intToEnum", 2);2143 const builtin_node = try rp.c.createBuiltinCall("@intToEnum", 2);
2124 builtin_node.params()[0] = try transQualType(rp, dst_type, loc);2144 builtin_node.params()[0] = try transQualType(rp, dst_type, loc);
2125 _ = try appendToken(rp.c, .Comma, ",");2145 _ = try appendToken(rp.c, .Comma, ",");
...@@ -2127,13 +2147,8 @@ fn transCCast(...@@ -2127,13 +2147,8 @@ fn transCCast(
2127 builtin_node.rparen_token = try appendToken(rp.c, .RParen, ")");2147 builtin_node.rparen_token = try appendToken(rp.c, .RParen, ")");
2128 return &builtin_node.base;2148 return &builtin_node.base;
2129 }2149 }
2130 if (src_type.getCanonicalType().getTypeClass() == .Enum and2150 if (cIsEnum(src_type) and !cIsEnum(dst_type)) {
2131 dst_type.getCanonicalType().getTypeClass() != .Enum)2151 return transEnumToInt(rp.c, expr);
2132 {
2133 const builtin_node = try rp.c.createBuiltinCall("@enumToInt", 1);
2134 builtin_node.params()[0] = expr;
2135 builtin_node.rparen_token = try appendToken(rp.c, .RParen, ")");
2136 return &builtin_node.base;
2137 }2152 }
2138 const cast_node = try rp.c.createBuiltinCall("@as", 2);2153 const cast_node = try rp.c.createBuiltinCall("@as", 2);
2139 cast_node.params()[0] = try transQualType(rp, dst_type, loc);2154 cast_node.params()[0] = try transQualType(rp, dst_type, loc);
...@@ -2143,6 +2158,13 @@ fn transCCast(...@@ -2143,6 +2158,13 @@ fn transCCast(
2143 return &cast_node.base;2158 return &cast_node.base;
2144}2159}
21452160
2161fn transEnumToInt(c: *Context, enum_expr: *ast.Node) TypeError!*ast.Node {
2162 const builtin_node = try c.createBuiltinCall("@enumToInt", 1);
2163 builtin_node.params()[0] = enum_expr;
2164 builtin_node.rparen_token = try appendToken(c, .RParen, ")");
2165 return &builtin_node.base;
2166}
2167
2146fn transExpr(2168fn transExpr(
2147 rp: RestorePoint,2169 rp: RestorePoint,
2148 scope: *Scope,2170 scope: *Scope,
test/run_translated_c.zig+113
...@@ -371,4 +371,117 @@ pub fn addCases(cases: *tests.RunTranslatedCContext) void {...@@ -371,4 +371,117 @@ pub fn addCases(cases: *tests.RunTranslatedCContext) void {
371 \\ return 0;371 \\ return 0;
372 \\}372 \\}
373 , "");373 , "");
374
375 cases.add("assign enum to uint, no explicit cast",
376 \\#include <stdlib.h>
377 \\typedef enum {
378 \\ ENUM_0 = 0,
379 \\ ENUM_1 = 1,
380 \\} my_enum_t;
381 \\
382 \\int main() {
383 \\ my_enum_t val = ENUM_1;
384 \\ unsigned int x = val;
385 \\ if (x != 1) abort();
386 \\ return 0;
387 \\}
388 , "");
389
390 cases.add("assign enum to int",
391 \\#include <stdlib.h>
392 \\typedef enum {
393 \\ ENUM_0 = 0,
394 \\ ENUM_1 = 1,
395 \\} my_enum_t;
396 \\
397 \\int main() {
398 \\ my_enum_t val = ENUM_1;
399 \\ int x = val;
400 \\ if (x != 1) abort();
401 \\ return 0;
402 \\}
403 , "");
404
405 cases.add("cast enum to smaller uint",
406 \\#include <stdlib.h>
407 \\#include <stdint.h>
408 \\typedef enum {
409 \\ ENUM_0 = 0,
410 \\ ENUM_257 = 257,
411 \\} my_enum_t;
412 \\
413 \\int main() {
414 \\ my_enum_t val = ENUM_257;
415 \\ uint8_t x = (uint8_t)val;
416 \\ if (x != (uint8_t)257) abort();
417 \\ return 0;
418 \\}
419 , "");
420
421 cases.add("cast enum to smaller signed int",
422 \\#include <stdlib.h>
423 \\#include <stdint.h>
424 \\typedef enum {
425 \\ ENUM_0 = 0,
426 \\ ENUM_384 = 384,
427 \\} my_enum_t;
428 \\
429 \\int main() {
430 \\ my_enum_t val = ENUM_384;
431 \\ int8_t x = (int8_t)val;
432 \\ if (x != (int8_t)384) abort();
433 \\ return 0;
434 \\}
435 , "");
436
437 cases.add("cast negative enum to smaller signed int",
438 \\#include <stdlib.h>
439 \\#include <stdint.h>
440 \\typedef enum {
441 \\ ENUM_MINUS_1 = -1,
442 \\ ENUM_384 = 384,
443 \\} my_enum_t;
444 \\
445 \\int main() {
446 \\ my_enum_t val = ENUM_MINUS_1;
447 \\ int8_t x = (int8_t)val;
448 \\ if (x != -1) abort();
449 \\ return 0;
450 \\}
451 , "");
452
453 cases.add("cast negative enum to smaller unsigned int",
454 \\#include <stdlib.h>
455 \\#include <stdint.h>
456 \\typedef enum {
457 \\ ENUM_MINUS_1 = -1,
458 \\ ENUM_384 = 384,
459 \\} my_enum_t;
460 \\
461 \\int main() {
462 \\ my_enum_t val = ENUM_MINUS_1;
463 \\ uint8_t x = (uint8_t)val;
464 \\ if (x != (uint8_t)-1) abort();
465 \\ return 0;
466 \\}
467 , "");
468
469 cases.add("implicit enum cast in boolean expression",
470 \\#include <stdlib.h>
471 \\enum Foo {
472 \\ FooA,
473 \\ FooB,
474 \\ FooC,
475 \\};
476 \\int main() {
477 \\ int a = 0;
478 \\ float b = 0;
479 \\ void *c = 0;
480 \\ enum Foo d = FooA;
481 \\ if (a || d) abort();
482 \\ if (d && b) abort();
483 \\ if (c || d) abort();
484 \\ return 0;
485 \\}
486 , "");
374}487}
test/translate_c.zig+6-6
...@@ -2000,9 +2000,9 @@ pub fn addCases(cases: *tests.TranslateCContext) void {...@@ -2000,9 +2000,9 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
2000 \\ int h = (a || b);2000 \\ int h = (a || b);
2001 \\ int i = (b || c);2001 \\ int i = (b || c);
2002 \\ int j = (a || c);2002 \\ int j = (a || c);
2003 \\ int k = (a || d);2003 \\ int k = (a || (int)d);
2004 \\ int l = (d && b);2004 \\ int l = ((int)d && b);
2005 \\ int m = (c || d);2005 \\ int m = (c || (unsigned int)d);
2006 \\ SomeTypedef td = 44;2006 \\ SomeTypedef td = 44;
2007 \\ int o = (td || b);2007 \\ int o = (td || b);
2008 \\ int p = (c && td);2008 \\ int p = (c && td);
...@@ -2027,9 +2027,9 @@ pub fn addCases(cases: *tests.TranslateCContext) void {...@@ -2027,9 +2027,9 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
2027 \\ var h: c_int = @boolToInt(((a != 0) or (b != 0)));2027 \\ var h: c_int = @boolToInt(((a != 0) or (b != 0)));
2028 \\ var i: c_int = @boolToInt(((b != 0) or (c != null)));2028 \\ var i: c_int = @boolToInt(((b != 0) or (c != null)));
2029 \\ var j: c_int = @boolToInt(((a != 0) or (c != null)));2029 \\ var j: c_int = @boolToInt(((a != 0) or (c != null)));
2030 \\ var k: c_int = @boolToInt(((a != 0) or (@enumToInt(d) != 0)));2030 \\ var k: c_int = @boolToInt(((a != 0) or (@bitCast(c_int, @enumToInt(d)) != 0)));
2031 \\ var l: c_int = @boolToInt(((@enumToInt(d) != 0) and (b != 0)));2031 \\ var l: c_int = @boolToInt(((@bitCast(c_int, @enumToInt(d)) != 0) and (b != 0)));
2032 \\ var m: c_int = @boolToInt(((c != null) or (@enumToInt(d) != 0)));2032 \\ var m: c_int = @boolToInt(((c != null) or (@bitCast(c_uint, @enumToInt(d)) != 0)));
2033 \\ var td: SomeTypedef = 44;2033 \\ var td: SomeTypedef = 44;
2034 \\ var o: c_int = @boolToInt(((td != 0) or (b != 0)));2034 \\ var o: c_int = @boolToInt(((td != 0) or (b != 0)));
2035 \\ var p: c_int = @boolToInt(((c != null) and (td != 0)));2035 \\ var p: c_int = @boolToInt(((c != null) and (td != 0)));