Commit dc28526c6c

Andrew Kelley <andrew@ziglang.org>
2020-01-02 00:10:43
translate-c: improve support of integer casting
Widening and truncating integer casting to different signedness works better now. For example `(unsigned long)-1` is now translated to zig code that compiles correctly.
1 parent 5575e2a
Changed files (3)
src-self-hosted/clang.zig
@@ -804,8 +804,8 @@ pub extern fn ZigClangType_getAsArrayTypeUnsafe(self: *const ZigClangType) *cons
 pub extern fn ZigClangStmt_getBeginLoc(self: *const struct_ZigClangStmt) struct_ZigClangSourceLocation;
 pub extern fn ZigClangStmt_getStmtClass(self: ?*const struct_ZigClangStmt) ZigClangStmtClass;
 pub extern fn ZigClangStmt_classof_Expr(self: ?*const struct_ZigClangStmt) bool;
-pub extern fn ZigClangExpr_getStmtClass(self: ?*const struct_ZigClangExpr) ZigClangStmtClass;
-pub extern fn ZigClangExpr_getType(self: ?*const struct_ZigClangExpr) struct_ZigClangQualType;
+pub extern fn ZigClangExpr_getStmtClass(self: *const struct_ZigClangExpr) ZigClangStmtClass;
+pub extern fn ZigClangExpr_getType(self: *const struct_ZigClangExpr) struct_ZigClangQualType;
 pub extern fn ZigClangExpr_getBeginLoc(self: *const struct_ZigClangExpr) struct_ZigClangSourceLocation;
 pub extern fn ZigClangInitListExpr_getInit(self: ?*const struct_ZigClangInitListExpr, i: c_uint) *const ZigClangExpr;
 pub extern fn ZigClangInitListExpr_getArrayFiller(self: ?*const struct_ZigClangInitListExpr) *const ZigClangExpr;
src-self-hosted/translate_c.zig
@@ -9,6 +9,7 @@ usingnamespace @import("clang.zig");
 const ctok = @import("c_tokenizer.zig");
 const CToken = ctok.CToken;
 const mem = std.mem;
+const math = std.math;
 
 const CallingConvention = std.builtin.TypeInfo.CallingConvention;
 
@@ -498,10 +499,13 @@ fn visitVarDecl(c: *Context, var_decl: *const ZigClangVarDecl) Error!void {
     var eq_tok: ast.TokenIndex = undefined;
     var init_node: ?*ast.Node = null;
 
+    // If the initialization expression is not present, initialize with undefined.
+    // If it is an integer literal, we can skip the @as since it will be redundant
+    // with the variable type.
     if (ZigClangVarDecl_hasInit(var_decl)) {
         eq_tok = try appendToken(c, .Equal, "=");
         init_node = if (ZigClangVarDecl_getInit(var_decl)) |expr|
-            transExpr(rp, &c.global_scope.base, expr, .used, .r_value) catch |err| switch (err) {
+            transExprCoercing(rp, &c.global_scope.base, expr, .used, .r_value) catch |err| switch (err) {
                 error.UnsupportedTranslation,
                 error.UnsupportedType,
                 => {
@@ -857,7 +861,7 @@ fn transStmt(
         .DeclStmtClass => return transDeclStmt(rp, scope, @ptrCast(*const ZigClangDeclStmt, stmt)),
         .DeclRefExprClass => return transDeclRefExpr(rp, scope, @ptrCast(*const ZigClangDeclRefExpr, stmt), lrvalue),
         .ImplicitCastExprClass => return transImplicitCastExpr(rp, scope, @ptrCast(*const ZigClangImplicitCastExpr, stmt), result_used),
-        .IntegerLiteralClass => return transIntegerLiteral(rp, scope, @ptrCast(*const ZigClangIntegerLiteral, stmt), result_used),
+        .IntegerLiteralClass => return transIntegerLiteral(rp, scope, @ptrCast(*const ZigClangIntegerLiteral, stmt), result_used, .with_as),
         .ReturnStmtClass => return transReturnStmt(rp, scope, @ptrCast(*const ZigClangReturnStmt, stmt)),
         .StringLiteralClass => return transStringLiteral(rp, scope, @ptrCast(*const ZigClangStringLiteral, stmt), result_used),
         .ParenExprClass => {
@@ -891,7 +895,7 @@ fn transStmt(
         .DefaultStmtClass => return transDefault(rp, scope, @ptrCast(*const ZigClangDefaultStmt, stmt)),
         .ConstantExprClass => return transConstantExpr(rp, scope, @ptrCast(*const ZigClangExpr, stmt), result_used),
         .PredefinedExprClass => return transPredefinedExpr(rp, scope, @ptrCast(*const ZigClangPredefinedExpr, stmt), result_used),
-        .CharacterLiteralClass => return transCharLiteral(rp, scope, @ptrCast(*const ZigClangCharacterLiteral, stmt), result_used),
+        .CharacterLiteralClass => return transCharLiteral(rp, scope, @ptrCast(*const ZigClangCharacterLiteral, stmt), result_used, .with_as),
         .StmtExprClass => return transStmtExpr(rp, scope, @ptrCast(*const ZigClangStmtExpr, stmt), result_used),
         .MemberExprClass => return transMemberExpr(rp, scope, @ptrCast(*const ZigClangMemberExpr, stmt), result_used),
         .ArraySubscriptExprClass => return transArrayAccess(rp, scope, @ptrCast(*const ZigClangArraySubscriptExpr, stmt), result_used),
@@ -1160,7 +1164,7 @@ fn transDeclStmt(rp: RestorePoint, scope: *Scope, stmt: *const ZigClangDeclStmt)
 
                 node.eq_token = try appendToken(c, .Equal, "=");
                 var init_node = if (ZigClangVarDecl_getInit(var_decl)) |expr|
-                    try transExpr(rp, scope, expr, .used, .r_value)
+                    try transExprCoercing(rp, scope, expr, .used, .r_value)
                 else
                     try transCreateNodeUndefinedLiteral(c);
                 if (isBoolRes(init_node)) {
@@ -1391,19 +1395,50 @@ fn finishBoolExpr(
     return revertAndWarn(rp, error.UnsupportedType, loc, "unsupported bool expression type", .{});
 }
 
+const SuppressCast = enum {
+    with_as,
+    no_as,
+};
 fn transIntegerLiteral(
     rp: RestorePoint,
     scope: *Scope,
     expr: *const ZigClangIntegerLiteral,
     result_used: ResultUsed,
+    suppress_as: SuppressCast,
 ) TransError!*ast.Node {
     var eval_result: ZigClangExprEvalResult = undefined;
     if (!ZigClangIntegerLiteral_EvaluateAsInt(expr, &eval_result, rp.c.clang_context)) {
         const loc = ZigClangIntegerLiteral_getBeginLoc(expr);
         return revertAndWarn(rp, error.UnsupportedTranslation, loc, "invalid integer literal", .{});
     }
-    const node = try transCreateNodeAPInt(rp.c, ZigClangAPValue_getInt(&eval_result.Val));
-    return maybeSuppressResult(rp, scope, result_used, node);
+
+    if (suppress_as == .no_as) {
+        const int_lit_node = try transCreateNodeAPInt(rp.c, ZigClangAPValue_getInt(&eval_result.Val));
+        return maybeSuppressResult(rp, scope, result_used, int_lit_node);
+    }
+
+    // Integer literals in C have types, and this can matter for several reasons.
+    // For example, this is valid C:
+    //     unsigned char y = 256;
+    // How this gets evaluated is the 256 is an integer, which gets truncated to signed char, then bit-casted
+    // to unsigned char, resulting in 0. In order for this to work, we have to emit this zig code:
+    //     var y = @bitCast(u8, @truncate(i8, @as(c_int, 256)));
+    // Ideally in translate-c we could flatten this out to simply:
+    //     var y: u8 = 0;
+    // But the first step is to be correct, and the next step is to make the output more elegant.
+
+    // @as(T, x)
+    const expr_base = @ptrCast(*const ZigClangExpr, expr);
+    const as_node = try transCreateNodeBuiltinFnCall(rp.c, "@as");
+    const ty_node = try transQualType(rp, ZigClangExpr_getType(expr_base), ZigClangExpr_getBeginLoc(expr_base));
+    try as_node.params.push(ty_node);
+    _ = try appendToken(rp.c, .Comma, ",");
+
+    const int_lit_node = try transCreateNodeAPInt(rp.c, ZigClangAPValue_getInt(&eval_result.Val));
+    try as_node.params.push(int_lit_node);
+
+    as_node.rparen_token = try appendToken(rp.c, .RParen, ")");
+    return maybeSuppressResult(rp, scope, result_used, &as_node.base);
 }
 
 fn transReturnStmt(
@@ -1413,7 +1448,7 @@ fn transReturnStmt(
 ) TransError!*ast.Node {
     const node = try transCreateNodeReturnExpr(rp.c);
     if (ZigClangReturnStmt_getRetValue(expr)) |val_expr| {
-        node.rhs = try transExpr(rp, scope, val_expr, .used, .r_value);
+        node.rhs = try transExprCoercing(rp, scope, val_expr, .used, .r_value);
     }
     _ = try appendToken(rp.c, .Semicolon, ";");
     return &node.base;
@@ -1502,11 +1537,41 @@ fn transCCast(
     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)) {
-        // @intCast(dest_type, val)
-        const cast_node = try transCreateNodeBuiltinFnCall(rp.c, "@intCast");
+        // 1. Extend or truncate without changing signed-ness.
+        // 2. Bit-cast to correct signed-ness
+
+        // @bitCast(dest_type, intermediate_value)
+        const cast_node = try transCreateNodeBuiltinFnCall(rp.c, "@bitCast");
         try cast_node.params.push(try transQualType(rp, dst_type, loc));
         _ = try appendToken(rp.c, .Comma, ",");
-        try cast_node.params.push(expr);
+
+        switch (cIntTypeCmp(dst_type, src_type)) {
+            .lt => {
+                // @truncate(SameSignSmallerInt, src_type)
+                const trunc_node = try transCreateNodeBuiltinFnCall(rp.c, "@truncate");
+                const ty_node = try transQualTypeIntWidthOf(rp.c, dst_type, cIsSignedInteger(src_type));
+                try trunc_node.params.push(ty_node);
+                _ = try appendToken(rp.c, .Comma, ",");
+                try trunc_node.params.push(expr);
+                trunc_node.rparen_token = try appendToken(rp.c, .RParen, ")");
+
+                try cast_node.params.push(&trunc_node.base);
+            },
+            .gt => {
+                // @as(SameSignBiggerInt, src_type)
+                const as_node = try transCreateNodeBuiltinFnCall(rp.c, "@as");
+                const ty_node = try transQualTypeIntWidthOf(rp.c, dst_type, cIsSignedInteger(src_type));
+                try as_node.params.push(ty_node);
+                _ = try appendToken(rp.c, .Comma, ",");
+                try as_node.params.push(expr);
+                as_node.rparen_token = try appendToken(rp.c, .RParen, ")");
+
+                try cast_node.params.push(&as_node.base);
+            },
+            .eq => {
+                try cast_node.params.push(expr);
+            },
+        }
         cast_node.rparen_token = try appendToken(rp.c, .RParen, ")");
         return &cast_node.base;
     }
@@ -1579,9 +1644,6 @@ fn transCCast(
         builtin_node.rparen_token = try appendToken(rp.c, .RParen, ")");
         return &builtin_node.base;
     }
-    // TODO: maybe widen to increase size
-    // TODO: maybe bitcast to change sign
-    // TODO: maybe truncate to reduce size
     const cast_node = try transCreateNodeBuiltinFnCall(rp.c, "@as");
     try cast_node.params.push(try transQualType(rp, dst_type, loc));
     _ = try appendToken(rp.c, .Comma, ",");
@@ -1600,6 +1662,33 @@ fn transExpr(
     return transStmt(rp, scope, @ptrCast(*const ZigClangStmt, expr), used, lrvalue);
 }
 
+/// Same as `transExpr` but with the knowledge that the operand will be type coerced, and therefore
+/// an `@as` would be redundant. This is used to prevent redundant `@as` in integer literals.
+fn transExprCoercing(
+    rp: RestorePoint,
+    scope: *Scope,
+    expr: *const ZigClangExpr,
+    used: ResultUsed,
+    lrvalue: LRValue,
+) TransError!*ast.Node {
+    switch (ZigClangStmt_getStmtClass(@ptrCast(*const ZigClangStmt, expr))) {
+        .IntegerLiteralClass => {
+            return transIntegerLiteral(rp, scope, @ptrCast(*const ZigClangIntegerLiteral, expr), .used, .no_as);
+        },
+        .CharacterLiteralClass => {
+            return transCharLiteral(rp, scope, @ptrCast(*const ZigClangCharacterLiteral, expr), .used, .no_as);
+        },
+        .UnaryOperatorClass => {
+            const un_expr = @ptrCast(*const ZigClangUnaryOperator, expr);
+            if (ZigClangUnaryOperator_getOpcode(un_expr) == .Extension) {
+                return transExprCoercing(rp, scope, ZigClangUnaryOperator_getSubExpr(un_expr), used, lrvalue);
+            }
+        },
+        else => {},
+    }
+    return transExpr(rp, scope, expr, .used, .r_value);
+}
+
 fn transInitListExpr(
     rp: RestorePoint,
     scope: *Scope,
@@ -1625,7 +1714,7 @@ fn transInitListExpr(
     const init_count = ZigClangInitListExpr_getNumInits(expr);
     const const_arr_ty = @ptrCast(*const ZigClangConstantArrayType, qual_type);
     const size_ap_int = ZigClangConstantArrayType_getSize(const_arr_ty);
-    const all_count = ZigClangAPInt_getLimitedValue(size_ap_int, std.math.maxInt(usize));
+    const all_count = ZigClangAPInt_getLimitedValue(size_ap_int, math.maxInt(usize));
     const leftover_count = all_count - init_count;
 
     var init_node: *ast.Node.SuffixOp = undefined;
@@ -2033,16 +2122,17 @@ fn transCharLiteral(
     scope: *Scope,
     stmt: *const ZigClangCharacterLiteral,
     result_used: ResultUsed,
+    suppress_as: SuppressCast,
 ) TransError!*ast.Node {
     const kind = ZigClangCharacterLiteral_getKind(stmt);
-    switch (kind) {
-        .Ascii, .UTF8 => {
+    const int_lit_node = switch (kind) {
+        .Ascii, .UTF8 => blk: {
             const val = ZigClangCharacterLiteral_getValue(stmt);
             if (kind == .Ascii) {
                 // C has a somewhat obscure feature called multi-character character
                 // constant
                 if (val > 255)
-                    return transCreateNodeInt(rp.c, val);
+                    break :blk try transCreateNodeInt(rp.c, val);
             }
             var char_buf: [4]u8 = undefined;
             const token = try appendTokenFmt(rp.c, .CharLiteral, "'{}'", .{escapeChar(@intCast(u8, val), &char_buf)});
@@ -2050,7 +2140,7 @@ fn transCharLiteral(
             node.* = .{
                 .token = token,
             };
-            return maybeSuppressResult(rp, scope, result_used, &node.base);
+            break :blk &node.base;
         },
         .UTF16, .UTF32, .Wide => return revertAndWarn(
             rp,
@@ -2060,7 +2150,22 @@ fn transCharLiteral(
             .{kind},
         ),
         else => unreachable,
+    };
+    if (suppress_as == .no_as) {
+        return maybeSuppressResult(rp, scope, result_used, int_lit_node);
     }
+    // See comment in `transIntegerLiteral` for why this code is here.
+    // @as(T, x)
+    const expr_base = @ptrCast(*const ZigClangExpr, stmt);
+    const as_node = try transCreateNodeBuiltinFnCall(rp.c, "@as");
+    const ty_node = try transQualType(rp, ZigClangExpr_getType(expr_base), ZigClangExpr_getBeginLoc(expr_base));
+    try as_node.params.push(ty_node);
+    _ = try appendToken(rp.c, .Comma, ",");
+
+    try as_node.params.push(int_lit_node);
+
+    as_node.rparen_token = try appendToken(rp.c, .RParen, ")");
+    return maybeSuppressResult(rp, scope, result_used, &as_node.base);
 }
 
 fn transStmtExpr(rp: RestorePoint, scope: *Scope, stmt: *const ZigClangStmtExpr, used: ResultUsed) TransError!*ast.Node {
@@ -2480,7 +2585,10 @@ fn transCreateCompoundAssign(
         // zig: lhs += rhs
         const lhs_node = try transExpr(rp, scope, lhs, .used, .l_value);
         const eq_token = try appendToken(rp.c, assign_tok_id, assign_bytes);
-        var rhs_node = try transExpr(rp, scope, rhs, .used, .r_value);
+        var rhs_node = if (is_shift)
+            try transExprCoercing(rp, scope, rhs, .used, .r_value)
+        else
+            try transExpr(rp, scope, rhs, .used, .r_value);
 
         if (is_shift) {
             const as_node = try transCreateNodeBuiltinFnCall(rp.c, "@as");
@@ -2681,6 +2789,30 @@ fn transQualType(rp: RestorePoint, qt: ZigClangQualType, source_loc: ZigClangSou
     return transType(rp, ZigClangQualType_getTypePtr(qt), source_loc);
 }
 
+/// Produces a Zig AST node by translating a Clang QualType, respecting the width, but modifying the signed-ness.
+/// Asserts the type is an integer.
+fn transQualTypeIntWidthOf(c: *Context, ty: ZigClangQualType, is_signed: bool) TypeError!*ast.Node {
+    return transTypeIntWidthOf(c, qualTypeCanon(ty), is_signed);
+}
+
+/// Produces a Zig AST node by translating a Clang Type, respecting the width, but modifying the signed-ness.
+/// Asserts the type is an integer.
+fn transTypeIntWidthOf(c: *Context, ty: *const ZigClangType, is_signed: bool) TypeError!*ast.Node {
+    assert(ZigClangType_getTypeClass(ty) == .Builtin);
+    const builtin_ty = @ptrCast(*const ZigClangBuiltinType, ty);
+    return transCreateNodeIdentifier(c, switch (ZigClangBuiltinType_getKind(builtin_ty)) {
+        .Char_U, .Char_S, .UChar, .SChar, .Char8 => if (is_signed) "i8" else "u8",
+        .UShort, .Short => if (is_signed) "c_short" else "c_ushort",
+        .UInt, .Int => if (is_signed) "c_int" else "c_uint",
+        .ULong, .Long => if (is_signed) "c_long" else "c_ulong",
+        .ULongLong, .LongLong => if (is_signed) "c_longlong" else "c_ulonglong",
+        .UInt128, .Int128 => if (is_signed) "i128" else "u128",
+        .Char16 => if (is_signed) "i16" else "u16",
+        .Char32 => if (is_signed) "i32" else "u32",
+        else => unreachable, // only call this function when it has already been determined the type is int
+    });
+}
+
 fn isCBuiltinType(qt: ZigClangQualType, kind: ZigClangBuiltinTypeKind) bool {
     const c_type = qualTypeCanon(qt);
     if (ZigClangType_getTypeClass(c_type) != .Builtin)
@@ -2742,7 +2874,7 @@ fn qualTypeToLog2IntRef(rp: RestorePoint, qt: ZigClangQualType, source_loc: ZigC
 
     if (int_bit_width != 0) {
         // we can perform the log2 now.
-        const cast_bit_width = std.math.log2_int(u64, int_bit_width);
+        const cast_bit_width = math.log2_int(u64, int_bit_width);
         const node = try rp.c.a().create(ast.Node.IntegerLiteral);
         node.* = ast.Node.IntegerLiteral{
             .token = try appendTokenFmt(rp.c, .Identifier, "u{}", .{cast_bit_width}),
@@ -2902,6 +3034,28 @@ fn cIsUnsignedInteger(qt: ZigClangQualType) bool {
     };
 }
 
+fn cIntTypeToIndex(qt: ZigClangQualType) u8 {
+    const c_type = qualTypeCanon(qt);
+    assert(ZigClangType_getTypeClass(c_type) == .Builtin);
+    const builtin_ty = @ptrCast(*const ZigClangBuiltinType, c_type);
+    return switch (ZigClangBuiltinType_getKind(builtin_ty)) {
+        .Bool, .Char_U, .Char_S, .UChar, .SChar, .Char8 => 1,
+        .WChar_U, .WChar_S => 2,
+        .UShort, .Short, .Char16 => 3,
+        .UInt, .Int, .Char32 => 4,
+        .ULong, .Long => 5,
+        .ULongLong, .LongLong => 6,
+        .UInt128, .Int128 => 7,
+        else => unreachable,
+    };
+}
+
+fn cIntTypeCmp(a: ZigClangQualType, b: ZigClangQualType) math.Order {
+    const a_index = cIntTypeToIndex(a);
+    const b_index = cIntTypeToIndex(b);
+    return math.order(a_index, b_index);
+}
+
 fn cIsSignedInteger(qt: ZigClangQualType) bool {
     const c_type = qualTypeCanon(qt);
     if (ZigClangType_getTypeClass(c_type) != .Builtin) return false;
@@ -2946,7 +3100,7 @@ fn transCreateNodeAssign(
     if (result_used == .unused) {
         const lhs_node = try transExpr(rp, scope, lhs, .used, .l_value);
         const eq_token = try appendToken(rp.c, .Equal, "=");
-        var rhs_node = try transExpr(rp, scope, rhs, .used, .r_value);
+        var rhs_node = try transExprCoercing(rp, scope, rhs, .used, .r_value);
         if (isBoolRes(rhs_node)) {
             const builtin_node = try transCreateNodeBuiltinFnCall(rp.c, "@boolToInt");
             try builtin_node.params.push(rhs_node);
@@ -3158,7 +3312,7 @@ fn transCreateNodeAPInt(c: *Context, int: *const ZigClangAPSInt) !*ast.Node {
     const is_negative = ZigClangAPSInt_isSigned(int) and ZigClangAPSInt_isNegative(int);
     if (is_negative)
         aps_int = ZigClangAPSInt_negate(aps_int);
-    var big = try std.math.big.Int.initCapacity(c.a(), num_limbs);
+    var big = try math.big.Int.initCapacity(c.a(), num_limbs);
     if (is_negative)
         big.negate();
     defer big.deinit();
@@ -3522,7 +3676,7 @@ fn transCreateNodeShiftOp(
     const rhs_type = try qualTypeToLog2IntRef(rp, ZigClangBinaryOperator_getType(stmt), rhs_location);
     try as_node.params.push(rhs_type);
     _ = try appendToken(rp.c, .Comma, ",");
-    const rhs = try transExpr(rp, scope, rhs_expr, .used, .r_value);
+    const rhs = try transExprCoercing(rp, scope, rhs_expr, .used, .r_value);
     try as_node.params.push(rhs);
     as_node.rparen_token = try appendToken(rp.c, .RParen, ")");
 
@@ -3652,7 +3806,7 @@ fn transType(rp: RestorePoint, ty: *const ZigClangType, source_loc: ZigClangSour
             const const_arr_ty = @ptrCast(*const ZigClangConstantArrayType, ty);
 
             const size_ap_int = ZigClangConstantArrayType_getSize(const_arr_ty);
-            const size = ZigClangAPInt_getLimitedValue(size_ap_int, std.math.maxInt(usize));
+            const size = ZigClangAPInt_getLimitedValue(size_ap_int, math.maxInt(usize));
             var node = try transCreateNodePrefixOp(
                 rp.c,
                 .{
test/translate_c.zig
@@ -17,13 +17,17 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
         \\    char b = 123;
         \\    const int c;
         \\    const unsigned d = 440;
+        \\    int e = 10;
+        \\    unsigned int f = 10u;
         \\}
     , &[_][]const u8{
         \\pub export fn foo() void {
         \\    var a: c_int = undefined;
-        \\    var b: u8 = @intCast(u8, 123);
+        \\    var b: u8 = @bitCast(u8, @truncate(i8, @as(c_int, 123)));
         \\    const c: c_int = undefined;
-        \\    const d: c_uint = @intCast(c_uint, 440);
+        \\    const d: c_uint = @bitCast(c_uint, @as(c_int, 440));
+        \\    var e: c_int = 10;
+        \\    var f: c_uint = 10;
         \\}
     });
 
@@ -39,14 +43,24 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
     , &[_][]const u8{
         \\pub export fn foo() void {
         \\    var a: c_int = undefined;
-        \\    _ = 1;
+        \\    _ = @as(c_int, 1);
         \\    _ = "hey";
-        \\    _ = (1 + 1);
-        \\    _ = (1 - 1);
+        \\    _ = (@as(c_int, 1) + @as(c_int, 1));
+        \\    _ = (@as(c_int, 1) - @as(c_int, 1));
         \\    a = 1;
         \\}
     });
 
+    cases.add("function with no prototype",
+        \\int foo() {
+        \\    return 5;
+        \\}
+    , &[_][]const u8{
+        \\pub export fn foo() c_int {
+        \\    return 5;
+        \\}
+    });
+
     cases.add("variables",
         \\extern int extern_var;
         \\static const int int_var = 13;
@@ -474,26 +488,6 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
         \\}
     });
 
-    cases.add("ignore result, no function arguments",
-        \\void foo() {
-        \\    int a;
-        \\    1;
-        \\    "hey";
-        \\    1 + 1;
-        \\    1 - 1;
-        \\    a = 1;
-        \\}
-    , &[_][]const u8{
-        \\pub export fn foo() void {
-        \\    var a: c_int = undefined;
-        \\    _ = 1;
-        \\    _ = "hey";
-        \\    _ = (1 + 1);
-        \\    _ = (1 - 1);
-        \\    a = 1;
-        \\}
-    });
-
     cases.add("constant size array",
         \\void func(int array[20]);
     , &[_][]const u8{
@@ -582,7 +576,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
         \\pub export fn foo() void {
         \\    {
         \\        var i: c_int = 0;
-        \\        while (i != 0) : (i = (i + 1)) {}
+        \\        while (i != 0) : (i = (i + @as(c_int, 1))) {}
         \\    }
         \\}
     });
@@ -721,7 +715,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
         \\}
     , &[_][]const u8{
         \\pub export fn foo() c_int {
-        \\    return (1 << @as(@import("std").math.Log2Int(c_int), 2)) >> @as(@import("std").math.Log2Int(c_int), 1);
+        \\    return (@as(c_int, 1) << @as(@import("std").math.Log2Int(c_int), 2)) >> @as(@import("std").math.Log2Int(c_int), 1);
         \\}
     });
 
@@ -789,7 +783,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
         \\    var a: c_int = undefined;
         \\    var b: f32 = undefined;
         \\    var c: ?*c_void = undefined;
-        \\    return !(a == 0);
+        \\    return !(a == @as(c_int, 0));
         \\    return !(a != 0);
         \\    return !(b != 0);
         \\    return !(c != null);
@@ -864,7 +858,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
     , &[_][]const u8{
         \\pub fn foo() void {
         \\    var arr: [10]u8 = .{
-        \\        @intCast(u8, 1),
+        \\        @bitCast(u8, @truncate(i8, @as(c_int, 1))),
         \\    } ++ .{0} ** 9;
         \\    var arr1: [10][*c]u8 = .{
         \\        null,
@@ -1103,18 +1097,18 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
         \\    unsigned d = 440;
         \\}
     , &[_][]const u8{
-        \\pub var a: c_long = @intCast(c_long, 2);
-        \\pub var b: c_long = @intCast(c_long, 2);
+        \\pub var a: c_long = @bitCast(c_long, @as(c_long, @as(c_int, 2)));
+        \\pub var b: c_long = @bitCast(c_long, @as(c_long, @as(c_int, 2)));
         \\pub var c: c_int = 4;
         \\pub export fn foo(arg_c_1: u8) void {
         \\    var c_1 = arg_c_1;
         \\    var a_2: c_int = undefined;
-        \\    var b_3: u8 = @intCast(u8, 123);
-        \\    b_3 = @intCast(u8, a_2);
+        \\    var b_3: u8 = @bitCast(u8, @truncate(i8, @as(c_int, 123)));
+        \\    b_3 = @bitCast(u8, @truncate(i8, a_2));
         \\    {
         \\        var d: c_int = 5;
         \\    }
-        \\    var d: c_uint = @intCast(c_uint, 440);
+        \\    var d: c_uint = @bitCast(c_uint, @as(c_int, 440));
         \\}
     });
 
@@ -1125,11 +1119,11 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
         \\}
     , &[_][]const u8{
         \\pub export fn foo() c_int {
-        \\    _ = 2;
-        \\    _ = 4;
-        \\    _ = 2;
-        \\    _ = 4;
-        \\    return 6;
+        \\    _ = @as(c_int, 2);
+        \\    _ = @as(c_int, 4);
+        \\    _ = @as(c_int, 2);
+        \\    _ = @as(c_int, 4);
+        \\    return @as(c_int, 6);
         \\}
     });
 
@@ -1144,36 +1138,13 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
         \\    var a: c_int = undefined;
         \\    var b: c_int = undefined;
         \\    a = blk: {
-        \\        const tmp = 2;
+        \\        const tmp = @as(c_int, 2);
         \\        b = tmp;
         \\        break :blk tmp;
         \\    };
         \\}
     });
 
-    cases.add("if statements",
-        \\int foo() {
-        \\    if (2) {
-        \\        int a = 2;
-        \\    }
-        \\    if (2, 5) {
-        \\        int a = 2;
-        \\    }
-        \\}
-    , &[_][]const u8{
-        \\pub export fn foo() c_int {
-        \\    if (2 != 0) {
-        \\        var a: c_int = 2;
-        \\    }
-        \\    if ((blk: {
-        \\        _ = 2;
-        \\        break :blk 5;
-        \\    }) != 0) {
-        \\        var a: c_int = 2;
-        \\    }
-        \\}
-    });
-
     cases.add("while loops",
         \\int foo() {
         \\    int a = 5;
@@ -1195,21 +1166,21 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
     , &[_][]const u8{
         \\pub export fn foo() c_int {
         \\    var a: c_int = 5;
-        \\    while (2 != 0) a = 2;
-        \\    while (4 != 0) {
+        \\    while (@as(c_int, 2) != 0) a = 2;
+        \\    while (@as(c_int, 4) != 0) {
         \\        var a_1: c_int = 4;
         \\        a_1 = 9;
-        \\        _ = 6;
+        \\        _ = @as(c_int, 6);
         \\        return a_1;
         \\    }
         \\    while (true) {
         \\        var a_1: c_int = 2;
         \\        a_1 = 12;
-        \\        if (!(4 != 0)) break;
+        \\        if (!(@as(c_int, 4) != 0)) break;
         \\    }
         \\    while (true) {
         \\        a = 7;
-        \\        if (!(4 != 0)) break;
+        \\        if (!(@as(c_int, 4) != 0)) break;
         \\    }
         \\}
     });
@@ -1227,21 +1198,21 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
         \\    {
         \\        var i: c_int = 2;
         \\        var b: c_int = 4;
-        \\        while ((i + 2) != 0) : (i = 2) {
+        \\        while ((i + @as(c_int, 2)) != 0) : (i = 2) {
         \\            var a: c_int = 2;
         \\            a = 6;
-        \\            _ = 5;
-        \\            _ = 7;
+        \\            _ = @as(c_int, 5);
+        \\            _ = @as(c_int, 7);
         \\        }
         \\    }
-        \\    var i: u8 = @intCast(u8, 2);
+        \\    var i: u8 = @bitCast(u8, @truncate(i8, @as(c_int, 2)));
         \\}
     });
 
     cases.add("shadowing primitive types",
         \\unsigned anyerror = 2;
     , &[_][]const u8{
-        \\pub export var _anyerror: c_uint = @intCast(c_uint, 2);
+        \\pub export var _anyerror: c_uint = @bitCast(c_uint, @as(c_int, 2));
     });
 
     cases.add("floats",
@@ -1253,7 +1224,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
         \\pub export var a: f32 = @floatCast(f32, 3.1415);
         \\pub export var b: f64 = 3.1415;
         \\pub export var c: c_int = @floatToInt(c_int, 3.1415);
-        \\pub export var d: f64 = @intToFloat(f64, 3);
+        \\pub export var d: f64 = @intToFloat(f64, @as(c_int, 3));
     });
 
     cases.add("conditional operator",
@@ -1263,8 +1234,8 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
         \\}
     , &[_][]const u8{
         \\pub export fn bar() c_int {
-        \\    if ((if (2 != 0) 5 else (if (5 != 0) 4 else 6)) != 0) _ = 2;
-        \\    return if (2 != 0) 5 else if (5 != 0) 4 else 6;
+        \\    if ((if (@as(c_int, 2) != 0) @as(c_int, 5) else (if (@as(c_int, 5) != 0) @as(c_int, 4) else @as(c_int, 6))) != 0) _ = @as(c_int, 2);
+        \\    return if (@as(c_int, 2) != 0) @as(c_int, 5) else if (@as(c_int, 5) != 0) @as(c_int, 4) else @as(c_int, 6);
         \\}
     });
 
@@ -1303,7 +1274,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
         \\                }
         \\                res = 2;
         \\            }
-        \\            res = (3 * i);
+        \\            res = (@as(c_int, 3) * i);
         \\            break :__switch;
         \\        }
         \\        res = 5;
@@ -1397,6 +1368,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
         \\}
     });
 
+    // TODO translate-c should in theory be able to figure out to drop all these casts
     cases.add("escape sequences",
         \\const char *escapes() {
         \\char a = '\'',
@@ -1415,17 +1387,17 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
         \\
     , &[_][]const u8{
         \\pub export fn escapes() [*c]const u8 {
-        \\    var a: u8 = @intCast(u8, '\'');
-        \\    var b: u8 = @intCast(u8, '\\');
-        \\    var c: u8 = @intCast(u8, '\x07');
-        \\    var d: u8 = @intCast(u8, '\x08');
-        \\    var e: u8 = @intCast(u8, '\x0c');
-        \\    var f: u8 = @intCast(u8, '\n');
-        \\    var g: u8 = @intCast(u8, '\r');
-        \\    var h: u8 = @intCast(u8, '\t');
-        \\    var i: u8 = @intCast(u8, '\x0b');
-        \\    var j: u8 = @intCast(u8, '\x00');
-        \\    var k: u8 = @intCast(u8, '\"');
+        \\    var a: u8 = @bitCast(u8, @truncate(i8, @as(c_int, '\'')));
+        \\    var b: u8 = @bitCast(u8, @truncate(i8, @as(c_int, '\\')));
+        \\    var c: u8 = @bitCast(u8, @truncate(i8, @as(c_int, '\x07')));
+        \\    var d: u8 = @bitCast(u8, @truncate(i8, @as(c_int, '\x08')));
+        \\    var e: u8 = @bitCast(u8, @truncate(i8, @as(c_int, '\x0c')));
+        \\    var f: u8 = @bitCast(u8, @truncate(i8, @as(c_int, '\n')));
+        \\    var g: u8 = @bitCast(u8, @truncate(i8, @as(c_int, '\r')));
+        \\    var h: u8 = @bitCast(u8, @truncate(i8, @as(c_int, '\t')));
+        \\    var i: u8 = @bitCast(u8, @truncate(i8, @as(c_int, '\x0b')));
+        \\    var j: u8 = @bitCast(u8, @truncate(i8, @as(c_int, '\x00')));
+        \\    var k: u8 = @bitCast(u8, @truncate(i8, @as(c_int, '\"')));
         \\    return "\'\\\x07\x08\x0c\n\r\t\x0b\x00\"";
         \\}
     });
@@ -1446,12 +1418,12 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
         \\pub export fn foo() void {
         \\    var a: c_int = 2;
         \\    while (true) {
-        \\        a = (a - 1);
+        \\        a = (a - @as(c_int, 1));
         \\        if (!(a != 0)) break;
         \\    }
         \\    var b: c_int = 2;
         \\    while (true) {
-        \\        b = (b - 1);
+        \\        b = (b - @as(c_int, 1));
         \\        if (!(b != 0)) break;
         \\    }
         \\}
@@ -1602,7 +1574,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
         \\pub const yes = [*c]u8;
         \\pub export fn foo() void {
         \\    var a: yes = undefined;
-        \\    if (a != null) _ = 2;
+        \\    if (a != null) _ = @as(c_int, 2);
         \\}
     });
 
@@ -1695,7 +1667,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
         \\}
     });
 
-    cases.add("if statement",
+    cases.add("simple if statement",
         \\int max(int a, int b) {
         \\    if (a < b)
         \\        return b;
@@ -1717,6 +1689,29 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
         \\}
     });
 
+    cases.add("if statements",
+        \\int foo() {
+        \\    if (2) {
+        \\        int a = 2;
+        \\    }
+        \\    if (2, 5) {
+        \\        int a = 2;
+        \\    }
+        \\}
+    , &[_][]const u8{
+        \\pub export fn foo() c_int {
+        \\    if (@as(c_int, 2) != 0) {
+        \\        var a: c_int = 2;
+        \\    }
+        \\    if ((blk: {
+        \\        _ = @as(c_int, 2);
+        \\        break :blk @as(c_int, 5);
+        \\    }) != 0) {
+        \\        var a: c_int = 2;
+        \\    }
+        \\}
+    });
+
     cases.add("if on non-bool",
         \\enum SomeEnum { A, B, C };
         \\int if_none_bool(int a, float b, void *c, enum SomeEnum d) {
@@ -1764,7 +1759,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
     , &[_][]const u8{
         \\pub export fn abs(arg_a: c_int) c_int {
         \\    var a = arg_a;
-        \\    return if (a < 0) -a else a;
+        \\    return if (a < @as(c_int, 0)) -a else a;
         \\}
     });
 
@@ -1845,7 +1840,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
     , &[_][]const u8{
         \\pub export fn foo() void {
         \\    var i: c_int = 0;
-        \\    var u: c_uint = @intCast(c_uint, 0);
+        \\    var u: c_uint = @bitCast(c_uint, @as(c_int, 0));
         \\    i += 1;
         \\    i -= 1;
         \\    u +%= 1;
@@ -1885,7 +1880,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
         \\pub export fn log2(arg_a: c_uint) c_int {
         \\    var a = arg_a;
         \\    var i: c_int = 0;
-        \\    while (a > @intCast(c_uint, 0)) {
+        \\    while (a > @bitCast(c_uint, @as(c_int, 0))) {
         \\        a >>= @as(@import("std").math.Log2Int(c_int), 1);
         \\    }
         \\    return i;
@@ -1905,7 +1900,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
         \\pub export fn log2(arg_a: u32) c_int {
         \\    var a = arg_a;
         \\    var i: c_int = 0;
-        \\    while (a > @intCast(c_uint, 0)) {
+        \\    while (a > @bitCast(c_uint, @as(c_int, 0))) {
         \\        a >>= @as(@import("std").math.Log2Int(c_int), 1);
         \\    }
         \\    return i;
@@ -1929,42 +1924,42 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
         \\    var a: c_int = 0;
         \\    a += (blk: {
         \\        const ref = &a;
-        \\        ref.* = ref.* + 1;
+        \\        ref.* = ref.* + @as(c_int, 1);
         \\        break :blk ref.*;
         \\    });
         \\    a -= (blk: {
         \\        const ref = &a;
-        \\        ref.* = ref.* - 1;
+        \\        ref.* = ref.* - @as(c_int, 1);
         \\        break :blk ref.*;
         \\    });
         \\    a *= (blk: {
         \\        const ref = &a;
-        \\        ref.* = ref.* * 1;
+        \\        ref.* = ref.* * @as(c_int, 1);
         \\        break :blk ref.*;
         \\    });
         \\    a &= (blk: {
         \\        const ref = &a;
-        \\        ref.* = ref.* & 1;
+        \\        ref.* = ref.* & @as(c_int, 1);
         \\        break :blk ref.*;
         \\    });
         \\    a |= (blk: {
         \\        const ref = &a;
-        \\        ref.* = ref.* | 1;
+        \\        ref.* = ref.* | @as(c_int, 1);
         \\        break :blk ref.*;
         \\    });
         \\    a ^= (blk: {
         \\        const ref = &a;
-        \\        ref.* = ref.* ^ 1;
+        \\        ref.* = ref.* ^ @as(c_int, 1);
         \\        break :blk ref.*;
         \\    });
         \\    a >>= @as(@import("std").math.Log2Int(c_int), (blk: {
         \\        const ref = &a;
-        \\        ref.* = ref.* >> @as(@import("std").math.Log2Int(c_int), 1);
+        \\        ref.* = ref.* >> @as(@import("std").math.Log2Int(c_int), @as(c_int, 1));
         \\        break :blk ref.*;
         \\    }));
         \\    a <<= @as(@import("std").math.Log2Int(c_int), (blk: {
         \\        const ref = &a;
-        \\        ref.* = ref.* << @as(@import("std").math.Log2Int(c_int), 1);
+        \\        ref.* = ref.* << @as(@import("std").math.Log2Int(c_int), @as(c_int, 1));
         \\        break :blk ref.*;
         \\    }));
         \\}
@@ -1984,45 +1979,45 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
         \\}
     , &[_][]const u8{
         \\pub export fn foo() void {
-        \\    var a: c_uint = @intCast(c_uint, 0);
+        \\    var a: c_uint = @bitCast(c_uint, @as(c_int, 0));
         \\    a +%= (blk: {
         \\        const ref = &a;
-        \\        ref.* = ref.* +% @intCast(c_uint, 1);
+        \\        ref.* = ref.* +% @bitCast(c_uint, @as(c_int, 1));
         \\        break :blk ref.*;
         \\    });
         \\    a -%= (blk: {
         \\        const ref = &a;
-        \\        ref.* = ref.* -% @intCast(c_uint, 1);
+        \\        ref.* = ref.* -% @bitCast(c_uint, @as(c_int, 1));
         \\        break :blk ref.*;
         \\    });
         \\    a *%= (blk: {
         \\        const ref = &a;
-        \\        ref.* = ref.* *% @intCast(c_uint, 1);
+        \\        ref.* = ref.* *% @bitCast(c_uint, @as(c_int, 1));
         \\        break :blk ref.*;
         \\    });
         \\    a &= (blk: {
         \\        const ref = &a;
-        \\        ref.* = ref.* & @intCast(c_uint, 1);
+        \\        ref.* = ref.* & @bitCast(c_uint, @as(c_int, 1));
         \\        break :blk ref.*;
         \\    });
         \\    a |= (blk: {
         \\        const ref = &a;
-        \\        ref.* = ref.* | @intCast(c_uint, 1);
+        \\        ref.* = ref.* | @bitCast(c_uint, @as(c_int, 1));
         \\        break :blk ref.*;
         \\    });
         \\    a ^= (blk: {
         \\        const ref = &a;
-        \\        ref.* = ref.* ^ @intCast(c_uint, 1);
+        \\        ref.* = ref.* ^ @bitCast(c_uint, @as(c_int, 1));
         \\        break :blk ref.*;
         \\    });
         \\    a >>= @as(@import("std").math.Log2Int(c_uint), (blk: {
         \\        const ref = &a;
-        \\        ref.* = ref.* >> @as(@import("std").math.Log2Int(c_int), 1);
+        \\        ref.* = ref.* >> @as(@import("std").math.Log2Int(c_int), @as(c_int, 1));
         \\        break :blk ref.*;
         \\    }));
         \\    a <<= @as(@import("std").math.Log2Int(c_uint), (blk: {
         \\        const ref = &a;
-        \\        ref.* = ref.* << @as(@import("std").math.Log2Int(c_int), 1);
+        \\        ref.* = ref.* << @as(@import("std").math.Log2Int(c_int), @as(c_int, 1));
         \\        break :blk ref.*;
         \\    }));
         \\}
@@ -2044,7 +2039,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
     , &[_][]const u8{
         \\pub export fn foo() void {
         \\    var i: c_int = 0;
-        \\    var u: c_uint = @intCast(c_uint, 0);
+        \\    var u: c_uint = @bitCast(c_uint, @as(c_int, 0));
         \\    i += 1;
         \\    i -= 1;
         \\    u +%= 1;
@@ -2115,19 +2110,19 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
         \\    fn_int(@floatToInt(c_int, 3));
         \\    fn_int(@floatToInt(c_int, 3));
         \\    fn_int(@floatToInt(c_int, 3));
-        \\    fn_int(1094861636);
-        \\    fn_f32(@intToFloat(f32, 3));
-        \\    fn_f64(@intToFloat(f64, 3));
-        \\    fn_char(@intCast(u8, '3'));
-        \\    fn_char(@intCast(u8, '\x01'));
-        \\    fn_char(@intCast(u8, 0));
+        \\    fn_int(@as(c_int, 1094861636));
+        \\    fn_f32(@intToFloat(f32, @as(c_int, 3)));
+        \\    fn_f64(@intToFloat(f64, @as(c_int, 3)));
+        \\    fn_char(@bitCast(u8, @truncate(i8, @as(c_int, '3'))));
+        \\    fn_char(@bitCast(u8, @truncate(i8, @as(c_int, '\x01'))));
+        \\    fn_char(@bitCast(u8, @truncate(i8, @as(c_int, 0))));
         \\    fn_f32(3);
         \\    fn_f64(3);
-        \\    fn_bool(123 != 0);
-        \\    fn_bool(0 != 0);
+        \\    fn_bool(@as(c_int, 123) != 0);
+        \\    fn_bool(@as(c_int, 0) != 0);
         \\    fn_bool(@ptrToInt(&fn_int) != 0);
         \\    fn_int(@intCast(c_int, @ptrToInt(&fn_int)));
-        \\    fn_ptr(@intToPtr(?*c_void, 42));
+        \\    fn_ptr(@intToPtr(?*c_void, @as(c_int, 42)));
         \\}
     });
 
@@ -2223,8 +2218,8 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
         \\}
     , &[_][]const u8{
         \\pub fn foo() void {
-        \\    if (1 != 0) while (true) {
-        \\        if (!(0 != 0)) break;
+        \\    if (@as(c_int, 1) != 0) while (true) {
+        \\        if (!(@as(c_int, 0) != 0)) break;
         \\    };
         \\}
     });
@@ -2263,4 +2258,21 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
         \\    };
         \\}
     });
+
+    cases.add("widening and truncating integer casting to different signedness",
+        \\unsigned long foo(void) {
+        \\    return -1;
+        \\}
+        \\unsigned short bar(long x) {
+        \\    return x;
+        \\}
+    , &[_][]const u8{
+        \\pub export fn foo() c_ulong {
+        \\    return @bitCast(c_ulong, @as(c_long, -@as(c_int, 1)));
+        \\}
+        \\pub export fn bar(arg_x: c_long) c_ushort {
+        \\    var x = arg_x;
+        \\    return @bitCast(c_ushort, @truncate(c_short, x));
+        \\}
+    });
 }