zig/test/run_translated_c.zig
Evan Haas 55cac65f95 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.
2020-12-10 15:47:56 -05:00

488 lines
13 KiB
Zig

const std = @import("std");
const tests = @import("tests.zig");
const nl = std.cstr.line_sep;
pub fn addCases(cases: *tests.RunTranslatedCContext) void {
cases.add("variable shadowing type type",
\\#include <stdlib.h>
\\int main() {
\\ int type = 1;
\\ if (type != 1) abort();
\\}
, "");
cases.add("assignment as expression",
\\#include <stdlib.h>
\\int main() {
\\ int a, b, c, d = 5;
\\ int e = a = b = c = d;
\\ if (e != 5) abort();
\\}
, "");
cases.add("static variable in block scope",
\\#include <stdlib.h>
\\int foo() {
\\ static int bar;
\\ bar += 1;
\\ return bar;
\\}
\\int main() {
\\ foo();
\\ foo();
\\ if (foo() != 3) abort();
\\}
, "");
cases.add("array initializer",
\\#include <stdlib.h>
\\int main(int argc, char **argv) {
\\ int a0[4] = {1};
\\ int a1[4] = {1,2,3,4};
\\ int s0 = 0, s1 = 0;
\\ for (int i = 0; i < 4; i++) {
\\ s0 += a0[i];
\\ s1 += a1[i];
\\ }
\\ if (s0 != 1) abort();
\\ if (s1 != 10) abort();
\\}
, "");
cases.add("forward declarations",
\\#include <stdlib.h>
\\int foo(int);
\\int foo(int x) { return x + 1; }
\\int main(int argc, char **argv) {
\\ if (foo(2) != 3) abort();
\\ return 0;
\\}
, "");
cases.add("typedef and function pointer",
\\#include <stdlib.h>
\\typedef struct _Foo Foo;
\\typedef int Ret;
\\typedef int Param;
\\struct _Foo { Ret (*func)(Param p); };
\\static Ret add1(Param p) {
\\ return p + 1;
\\}
\\int main(int argc, char **argv) {
\\ Foo strct = { .func = add1 };
\\ if (strct.func(16) != 17) abort();
\\ return 0;
\\}
, "");
cases.add("ternary operator",
\\#include <stdlib.h>
\\static int cnt = 0;
\\int foo() { cnt++; return 42; }
\\int main(int argc, char **argv) {
\\ short q = 3;
\\ signed char z0 = q?:1;
\\ if (z0 != 3) abort();
\\ int z1 = 3?:1;
\\ if (z1 != 3) abort();
\\ int z2 = foo()?:-1;
\\ if (z2 != 42) abort();
\\ if (cnt != 1) abort();
\\ return 0;
\\}
, "");
cases.add("switch case",
\\#include <stdlib.h>
\\int lottery(unsigned int x) {
\\ switch (x) {
\\ case 3: return 0;
\\ case -1: return 3;
\\ case 8 ... 10: return x;
\\ default: return -1;
\\ }
\\}
\\int main(int argc, char **argv) {
\\ if (lottery(2) != -1) abort();
\\ if (lottery(3) != 0) abort();
\\ if (lottery(-1) != 3) abort();
\\ if (lottery(9) != 9) abort();
\\ return 0;
\\}
, "");
cases.add("boolean values and expressions",
\\#include <stdlib.h>
\\static const _Bool false_val = 0;
\\static const _Bool true_val = 1;
\\void foo(int x, int y) {
\\ _Bool r = x < y;
\\ if (!r) abort();
\\ _Bool self = foo;
\\ if (self == false_val) abort();
\\ if (((r) ? 'a' : 'b') != 'a') abort();
\\}
\\int main(int argc, char **argv) {
\\ foo(2, 5);
\\ if (false_val == true_val) abort();
\\ return 0;
\\}
, "");
cases.add("hello world",
\\#define _NO_CRT_STDIO_INLINE 1
\\#include <stdio.h>
\\int main(int argc, char **argv) {
\\ printf("hello, world!\n");
\\ return 0;
\\}
, "hello, world!" ++ nl);
cases.add("anon struct init",
\\#include <stdlib.h>
\\struct {int a; int b;} x = {1, 2};
\\int main(int argc, char **argv) {
\\ x.a += 2;
\\ x.b += 1;
\\ if (x.a != 3) abort();
\\ if (x.b != 3) abort();
\\ return 0;
\\}
, "");
cases.add("casting away const and volatile",
\\void foo(int *a) {}
\\void bar(const int *a) {
\\ foo((int *)a);
\\}
\\void baz(volatile int *a) {
\\ foo((int *)a);
\\}
\\int main(int argc, char **argv) {
\\ int a = 0;
\\ bar((const int *)&a);
\\ baz((volatile int *)&a);
\\ return 0;
\\}
, "");
cases.add("anonymous struct & unions",
\\#include <stdlib.h>
\\#include <stdint.h>
\\static struct { struct { uint16_t x, y; }; } x = { 1 };
\\static struct { union { uint32_t x; uint8_t y; }; } y = { 0x55AA55AA };
\\int main(int argc, char **argv) {
\\ if (x.x != 1) abort();
\\ if (x.y != 0) abort();
\\ if (y.x != 0x55AA55AA) abort();
\\ if (y.y != 0xAA) abort();
\\ return 0;
\\}
, "");
cases.add("array to pointer decay",
\\#include <stdlib.h>
\\int main(int argc, char **argv) {
\\ char data[3] = {'a','b','c'};
\\ if (2[data] != data[2]) abort();
\\ if ("abc"[1] != data[1]) abort();
\\ char *as_ptr = data;
\\ if (2[as_ptr] != as_ptr[2]) abort();
\\ if ("abc"[1] != as_ptr[1]) abort();
\\ return 0;
\\}
, "");
cases.add("struct initializer - packed",
\\#define _NO_CRT_STDIO_INLINE 1
\\#include <stdint.h>
\\#include <stdlib.h>
\\struct s {uint8_t x,y;
\\ uint32_t z;} __attribute__((packed)) s0 = {1, 2};
\\int main() {
\\ /* sizeof nor offsetof currently supported */
\\ if (((intptr_t)&s0.z - (intptr_t)&s0.x) != 2) abort();
\\ return 0;
\\}
, "");
cases.add("cast signed array index to unsigned",
\\#include <stdlib.h>
\\int main(int argc, char **argv) {
\\ int a[10], i = 0;
\\ a[i] = 0;
\\ if (a[i] != 0) abort();
\\ return 0;
\\}
, "");
cases.add("cast long long array index to unsigned",
\\#include <stdlib.h>
\\int main(int argc, char **argv) {
\\ long long a[10], i = 0;
\\ a[i] = 0;
\\ if (a[i] != 0) abort();
\\ return 0;
\\}
, "");
cases.add("case boolean expression converted to int",
\\#include <stdlib.h>
\\int main(int argc, char **argv) {
\\ int value = 1 + 2 * 3 + 4 * 5 + 6 << 7 | 8 == 9;
\\ if (value != 4224) abort();
\\ return 0;
\\}
, "");
cases.add("case boolean expression on left converted to int",
\\#include <stdlib.h>
\\int main(int argc, char **argv) {
\\ int value = 8 == 9 | 1 + 2 * 3 + 4 * 5 + 6 << 7;
\\ if (value != 4224) abort();
\\ return 0;
\\}
, "");
cases.add("case boolean and operator+ converts bool to int",
\\#include <stdlib.h>
\\int main(int argc, char **argv) {
\\ int value = (8 == 9) + 3;
\\ int value2 = 3 + (8 == 9);
\\ if (value != value2) abort();
\\ return 0;
\\}
, "");
cases.add("case boolean and operator<",
\\#include <stdlib.h>
\\int main(int argc, char **argv) {
\\ int value = (8 == 9) < 3;
\\ if (value == 0) abort();
\\ return 0;
\\}
, "");
cases.add("case boolean and operator*",
\\#include <stdlib.h>
\\int main(int argc, char **argv) {
\\ int value = (8 == 9) * 3;
\\ int value2 = 3 * (9 == 9);
\\ if (value != 0) abort();
\\ if (value2 == 0) abort();
\\ return 0;
\\}
, "");
cases.add("scoped typedef",
\\int main(int argc, char **argv) {
\\ typedef int Foo;
\\ typedef Foo Bar;
\\ typedef void (*func)(int);
\\ typedef int uint32_t;
\\ uint32_t a;
\\ Foo i;
\\ Bar j;
\\ return 0;
\\}
, "");
cases.add("scoped for loops with shadowing",
\\#include <stdlib.h>
\\int main() {
\\ int count = 0;
\\ for (int x = 0; x < 2; x++)
\\ for (int x = 0; x < 2; x++)
\\ count++;
\\
\\ if (count != 4) abort();
\\ return 0;
\\}
, "");
cases.add("array value type casts properly",
\\#include <stdlib.h>
\\unsigned int choose[53][10];
\\static int hash_binary(int k)
\\{
\\ choose[0][k] = 3;
\\ int sum = 0;
\\ sum += choose[0][k];
\\ return sum;
\\}
\\
\\int main() {
\\ int s = hash_binary(4);
\\ if (s != 3) abort();
\\ return 0;
\\}
, "");
cases.add("array value type casts properly use +=",
\\#include <stdlib.h>
\\static int hash_binary(int k)
\\{
\\ unsigned int choose[1][1] = {{3}};
\\ int sum = -1;
\\ int prev = 0;
\\ prev = sum += choose[0][0];
\\ if (sum != 2) abort();
\\ return sum + prev;
\\}
\\
\\int main() {
\\ int x = hash_binary(4);
\\ if (x != 4) abort();
\\ return 0;
\\}
, "");
cases.add("ensure array casts outisde +=",
\\#include <stdlib.h>
\\static int hash_binary(int k)
\\{
\\ unsigned int choose[3] = {1, 2, 3};
\\ int sum = -2;
\\ int prev = sum + choose[k];
\\ if (prev != 0) abort();
\\ return sum + prev;
\\}
\\
\\int main() {
\\ int x = hash_binary(1);
\\ if (x != -2) abort();
\\ return 0;
\\}
, "");
cases.add("array cast int to uint",
\\#include <stdlib.h>
\\static unsigned int hash_binary(int k)
\\{
\\ int choose[3] = {-1, -2, 3};
\\ unsigned int sum = 2;
\\ sum += choose[k];
\\ return sum;
\\}
\\
\\int main() {
\\ unsigned int x = hash_binary(1);
\\ if (x != 0) abort();
\\ 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;
\\}
, "");
}