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(
19931993 }
19941994}
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
19962011fn transCCast(
19972012 rp: RestorePoint,
19982013 scope: *Scope,
......@@ -2005,40 +2020,45 @@ fn transCCast(
20052020 if (dst_type.eq(src_type)) return expr;
20062021 if (qualTypeIsPtr(dst_type) and qualTypeIsPtr(src_type))
20072022 return transCPtrCast(rp, loc, dst_type, src_type, expr);
2008 if (cIsInteger(dst_type) and cIsInteger(src_type)) {
2009 // 1. Extend or truncate without changing signed-ness.
2010 // 2. Bit-cast to correct signed-ness
2023 if (cIsInteger(dst_type) and (cIsInteger(src_type) or cIsEnum(src_type))) {
2024 // 1. If src_type is an enum, determine the underlying signed int type
2025 // 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
20122032 // @bitCast(dest_type, intermediate_value)
20132033 const cast_node = try rp.c.createBuiltinCall("@bitCast", 2);
20142034 cast_node.params()[0] = try transQualType(rp, dst_type, loc);
20152035 _ = try appendToken(rp.c, .Comma, ",");
20162036
2017 switch (cIntTypeCmp(dst_type, src_type)) {
2037 switch (cIntTypeCmp(dst_type, src_int_type)) {
20182038 .lt => {
2019 // @truncate(SameSignSmallerInt, src_type)
2039 // @truncate(SameSignSmallerInt, src_int_expr)
20202040 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);
20222042 trunc_node.params()[0] = ty_node;
20232043 _ = try appendToken(rp.c, .Comma, ",");
2024 trunc_node.params()[1] = expr;
2044 trunc_node.params()[1] = src_int_expr;
20252045 trunc_node.rparen_token = try appendToken(rp.c, .RParen, ")");
20262046
20272047 cast_node.params()[1] = &trunc_node.base;
20282048 },
20292049 .gt => {
2030 // @as(SameSignBiggerInt, src_type)
2050 // @as(SameSignBiggerInt, src_int_expr)
20312051 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);
20332053 as_node.params()[0] = ty_node;
20342054 _ = try appendToken(rp.c, .Comma, ",");
2035 as_node.params()[1] = expr;
2055 as_node.params()[1] = src_int_expr;
20362056 as_node.rparen_token = try appendToken(rp.c, .RParen, ")");
20372057
20382058 cast_node.params()[1] = &as_node.base;
20392059 },
20402060 .eq => {
2041 cast_node.params()[1] = expr;
2061 cast_node.params()[1] = src_int_expr;
20422062 },
20432063 }
20442064 cast_node.rparen_token = try appendToken(rp.c, .RParen, ")");
......@@ -2119,7 +2139,7 @@ fn transCCast(
21192139
21202140 return &cast_node.base;
21212141 }
2122 if (dst_type.getCanonicalType().getTypeClass() == .Enum) {
2142 if (cIsEnum(dst_type)) {
21232143 const builtin_node = try rp.c.createBuiltinCall("@intToEnum", 2);
21242144 builtin_node.params()[0] = try transQualType(rp, dst_type, loc);
21252145 _ = try appendToken(rp.c, .Comma, ",");
......@@ -2127,13 +2147,8 @@ fn transCCast(
21272147 builtin_node.rparen_token = try appendToken(rp.c, .RParen, ")");
21282148 return &builtin_node.base;
21292149 }
2130 if (src_type.getCanonicalType().getTypeClass() == .Enum and
2131 dst_type.getCanonicalType().getTypeClass() != .Enum)
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;
2150 if (cIsEnum(src_type) and !cIsEnum(dst_type)) {
2151 return transEnumToInt(rp.c, expr);
21372152 }
21382153 const cast_node = try rp.c.createBuiltinCall("@as", 2);
21392154 cast_node.params()[0] = try transQualType(rp, dst_type, loc);
......@@ -2143,6 +2158,13 @@ fn transCCast(
21432158 return &cast_node.base;
21442159}
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
21462168fn transExpr(
21472169 rp: RestorePoint,
21482170 scope: *Scope,
test/run_translated_c.zig+113
......@@ -371,4 +371,117 @@ pub fn addCases(cases: *tests.RunTranslatedCContext) void {
371371 \\ return 0;
372372 \\}
373373 , "");
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 , "");
374487}
test/translate_c.zig+6-6
......@@ -2000,9 +2000,9 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
20002000 \\ int h = (a || b);
20012001 \\ int i = (b || c);
20022002 \\ int j = (a || c);
2003 \\ int k = (a || d);
2004 \\ int l = (d && b);
2005 \\ int m = (c || d);
2003 \\ int k = (a || (int)d);
2004 \\ int l = ((int)d && b);
2005 \\ int m = (c || (unsigned int)d);
20062006 \\ SomeTypedef td = 44;
20072007 \\ int o = (td || b);
20082008 \\ int p = (c && td);
......@@ -2027,9 +2027,9 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
20272027 \\ var h: c_int = @boolToInt(((a != 0) or (b != 0)));
20282028 \\ var i: c_int = @boolToInt(((b != 0) or (c != null)));
20292029 \\ var j: c_int = @boolToInt(((a != 0) or (c != null)));
2030 \\ var k: c_int = @boolToInt(((a != 0) or (@enumToInt(d) != 0)));
2031 \\ var l: c_int = @boolToInt(((@enumToInt(d) != 0) and (b != 0)));
2032 \\ var m: c_int = @boolToInt(((c != null) 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(((@bitCast(c_int, @enumToInt(d)) != 0) and (b != 0)));
2032 \\ var m: c_int = @boolToInt(((c != null) or (@bitCast(c_uint, @enumToInt(d)) != 0)));
20332033 \\ var td: SomeTypedef = 44;
20342034 \\ var o: c_int = @boolToInt(((td != 0) or (b != 0)));
20352035 \\ var p: c_int = @boolToInt(((c != null) and (td != 0)));