diff --git a/lib/std/meta.zig b/lib/std/meta.zig index 7ec29dcd0e..fd3e03bdbd 100644 --- a/lib/std/meta.zig +++ b/lib/std/meta.zig @@ -1094,6 +1094,58 @@ test "sizeof" { testing.expect(sizeof(c_void) == 1); } +pub const CIntLiteralRadix = enum { decimal, octal, hexadecimal }; + +fn PromoteIntLiteralReturnType(comptime SuffixType: type, comptime number: comptime_int, comptime radix: CIntLiteralRadix) type { + const signed_decimal = [_]type{ c_int, c_long, c_longlong }; + const signed_oct_hex = [_]type{ c_int, c_uint, c_long, c_ulong, c_longlong, c_ulonglong }; + const unsigned = [_]type{ c_uint, c_ulong, c_ulonglong }; + + const list: []const type = if (@typeInfo(SuffixType).Int.signedness == .unsigned) + &unsigned + else if (radix == .decimal) + &signed_decimal + else + &signed_oct_hex; + + var pos = mem.indexOfScalar(type, list, SuffixType).?; + + while (pos < list.len) : (pos += 1) { + if (number >= math.minInt(list[pos]) and number <= math.maxInt(list[pos])) { + return list[pos]; + } + } + @compileError("Integer literal is too large"); +} + +/// Promote the type of an integer literal until it fits as C would. +/// This is for translate-c and is not intended for general use. +pub fn promoteIntLiteral( + comptime SuffixType: type, + comptime number: comptime_int, + comptime radix: CIntLiteralRadix, +) PromoteIntLiteralReturnType(SuffixType, number, radix) { + return number; +} + +test "promoteIntLiteral" { + const signed_hex = promoteIntLiteral(c_int, math.maxInt(c_int) + 1, .hexadecimal); + testing.expectEqual(c_uint, @TypeOf(signed_hex)); + + if (math.maxInt(c_longlong) == math.maxInt(c_int)) return; + + const signed_decimal = promoteIntLiteral(c_int, math.maxInt(c_int) + 1, .decimal); + const unsigned = promoteIntLiteral(c_uint, math.maxInt(c_uint) + 1, .hexadecimal); + + if (math.maxInt(c_long) > math.maxInt(c_int)) { + testing.expectEqual(c_long, @TypeOf(signed_decimal)); + testing.expectEqual(c_ulong, @TypeOf(unsigned)); + } else { + testing.expectEqual(c_longlong, @TypeOf(signed_decimal)); + testing.expectEqual(c_ulonglong, @TypeOf(unsigned)); + } +} + /// For a given function type, returns a tuple type which fields will /// correspond to the argument types. /// diff --git a/src/translate_c.zig b/src/translate_c.zig index f0fd54eceb..c5ff56f32a 100644 --- a/src/translate_c.zig +++ b/src/translate_c.zig @@ -4435,40 +4435,68 @@ fn parseCNumLit(c: *Context, m: *MacroCtx) ParseError!Node { switch (m.list[m.i].id) { .IntegerLiteral => |suffix| { + var radix: []const u8 = "decimal"; if (lit_bytes.len > 2 and lit_bytes[0] == '0') { switch (lit_bytes[1]) { '0'...'7' => { // Octal - lit_bytes = try std.fmt.allocPrint(c.arena, "0o{s}", .{lit_bytes}); + lit_bytes = try std.fmt.allocPrint(c.arena, "0o{s}", .{lit_bytes[1..]}); + radix = "octal"; }, 'X' => { // Hexadecimal with capital X, valid in C but not in Zig lit_bytes = try std.fmt.allocPrint(c.arena, "0x{s}", .{lit_bytes[2..]}); + radix = "hexadecimal"; + }, + 'x' => { + radix = "hexadecimal"; }, else => {}, } } - if (suffix == .none) { - return transCreateNodeNumber(c, lit_bytes, .int); - } - const type_node = try Tag.type.create(c.arena, switch (suffix) { + .none => "c_int", .u => "c_uint", .l => "c_long", .lu => "c_ulong", .ll => "c_longlong", .llu => "c_ulonglong", - else => unreachable, + .f => unreachable, }); lit_bytes = lit_bytes[0 .. lit_bytes.len - switch (suffix) { - .u, .l => @as(u8, 1), + .none => @as(u8, 0), + .u, .l => 1, .lu, .ll => 2, .llu => 3, - else => unreachable, + .f => unreachable, }]; - const rhs = try transCreateNodeNumber(c, lit_bytes, .int); - return Tag.as.create(c.arena, .{ .lhs = type_node, .rhs = rhs }); + + const value = std.fmt.parseInt(i128, lit_bytes, 0) catch math.maxInt(i128); + + // make the output less noisy by skipping promoteIntLiteral where + // it's guaranteed to not be required because of C standard type constraints + const guaranteed_to_fit = switch (suffix) { + .none => if (math.cast(i16, value)) |_| true else |_| false, + .u => if (math.cast(u16, value)) |_| true else |_| false, + .l => if (math.cast(i32, value)) |_| true else |_| false, + .lu => if (math.cast(u32, value)) |_| true else |_| false, + .ll => if (math.cast(i64, value)) |_| true else |_| false, + .llu => if (math.cast(u64, value)) |_| true else |_| false, + .f => unreachable, + }; + + const literal_node = try transCreateNodeNumber(c, lit_bytes, .int); + + if (guaranteed_to_fit) { + return Tag.as.create(c.arena, .{ .lhs = type_node, .rhs = literal_node }); + } else { + return Tag.std_meta_promoteIntLiteral.create(c.arena, .{ + .type = type_node, + .value = literal_node, + .radix = try Tag.enum_literal.create(c.arena, radix), + }); + } }, .FloatLiteral => |suffix| { if (lit_bytes[0] == '.') diff --git a/src/translate_c/ast.zig b/src/translate_c/ast.zig index e984274c75..e5f76cc1de 100644 --- a/src/translate_c/ast.zig +++ b/src/translate_c/ast.zig @@ -39,6 +39,7 @@ pub const Node = extern union { float_literal, string_literal, char_literal, + enum_literal, identifier, @"if", /// if (!operand) break; @@ -117,6 +118,7 @@ pub const Node = extern union { /// @intCast(lhs, rhs) int_cast, /// @rem(lhs, rhs) + std_meta_promoteIntLiteral, rem, /// @divTrunc(lhs, rhs) div_trunc, @@ -312,6 +314,7 @@ pub const Node = extern union { .float_literal, .string_literal, .char_literal, + .enum_literal, .identifier, .warning, .type, @@ -328,6 +331,7 @@ pub const Node = extern union { .tuple => Payload.TupleInit, .container_init => Payload.ContainerInit, .std_meta_cast => Payload.Infix, + .std_meta_promoteIntLiteral => Payload.PromoteIntLiteral, .block => Payload.Block, .c_pointer, .single_pointer => Payload.Pointer, .array_type => Payload.Array, @@ -651,6 +655,15 @@ pub const Payload = struct { field_name: []const u8, }, }; + + pub const PromoteIntLiteral = struct { + base: Payload, + data: struct { + value: Node, + type: Node, + radix: Node, + }, + }; }; /// Converts the nodes into a Zig ast. @@ -821,6 +834,11 @@ fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex { const import_node = try renderStdImport(c, "meta", "cast"); return renderCall(c, import_node, &.{ payload.lhs, payload.rhs }); }, + .std_meta_promoteIntLiteral => { + const payload = node.castTag(.std_meta_promoteIntLiteral).?.data; + const import_node = try renderStdImport(c, "meta", "promoteIntLiteral"); + return renderCall(c, import_node, &.{ payload.type, payload.value, payload.radix }); + }, .std_meta_sizeof => { const payload = node.castTag(.std_meta_sizeof).?.data; const import_node = try renderStdImport(c, "meta", "sizeof"); @@ -988,6 +1006,15 @@ fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex { .data = undefined, }); }, + .enum_literal => { + const payload = node.castTag(.enum_literal).?.data; + _ = try c.addToken(.period, "."); + return c.addNode(.{ + .tag = .enum_literal, + .main_token = try c.addToken(.identifier, payload), + .data = undefined, + }); + }, .fail_decl => { const payload = node.castTag(.fail_decl).?.data; // pub const name = @compileError(msg); @@ -1982,11 +2009,13 @@ fn renderNodeGrouped(c: *Context, node: Node) !NodeIndex { .typeof, .std_meta_sizeof, .std_meta_cast, + .std_meta_promoteIntLiteral, .std_mem_zeroinit, .integer_literal, .float_literal, .string_literal, .char_literal, + .enum_literal, .identifier, .field_access, .ptr_cast, diff --git a/test/translate_c.zig b/test/translate_c.zig index e74af41f80..47d7c5d9eb 100644 --- a/test/translate_c.zig +++ b/test/translate_c.zig @@ -232,12 +232,12 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\ | (*((unsigned char *)(p) + 1) << 8) \ \\ | (*((unsigned char *)(p) + 2) << 16)) , &[_][]const u8{ - \\pub const FOO = (foo + 2).*; + \\pub const FOO = (foo + @as(c_int, 2)).*; , - \\pub const VALUE = ((((1 + (2 * 3)) + (4 * 5)) + 6) << 7) | @boolToInt(8 == 9); + \\pub const VALUE = ((((@as(c_int, 1) + (@as(c_int, 2) * @as(c_int, 3))) + (@as(c_int, 4) * @as(c_int, 5))) + @as(c_int, 6)) << @as(c_int, 7)) | @boolToInt(@as(c_int, 8) == @as(c_int, 9)); , - \\pub fn _AL_READ3BYTES(p: anytype) callconv(.Inline) @TypeOf((@import("std").meta.cast([*c]u8, p).* | ((@import("std").meta.cast([*c]u8, p) + 1).* << 8)) | ((@import("std").meta.cast([*c]u8, p) + 2).* << 16)) { - \\ return (@import("std").meta.cast([*c]u8, p).* | ((@import("std").meta.cast([*c]u8, p) + 1).* << 8)) | ((@import("std").meta.cast([*c]u8, p) + 2).* << 16); + \\pub fn _AL_READ3BYTES(p: anytype) callconv(.Inline) @TypeOf((@import("std").meta.cast([*c]u8, p).* | ((@import("std").meta.cast([*c]u8, p) + @as(c_int, 1)).* << @as(c_int, 8))) | ((@import("std").meta.cast([*c]u8, p) + @as(c_int, 2)).* << @as(c_int, 16))) { + \\ return (@import("std").meta.cast([*c]u8, p).* | ((@import("std").meta.cast([*c]u8, p) + @as(c_int, 1)).* << @as(c_int, 8))) | ((@import("std").meta.cast([*c]u8, p) + @as(c_int, 2)).* << @as(c_int, 16)); \\} }); @@ -312,14 +312,14 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\ return type_1; \\} , - \\pub const LIGHTGRAY = @import("std").mem.zeroInit(CLITERAL(Color), .{ 200, 200, 200, 255 }); + \\pub const LIGHTGRAY = @import("std").mem.zeroInit(CLITERAL(Color), .{ @as(c_int, 200), @as(c_int, 200), @as(c_int, 200), @as(c_int, 255) }); , \\pub const struct_boom_t = extern struct { \\ i1: c_int, \\}; \\pub const boom_t = struct_boom_t; , - \\pub const FOO = @import("std").mem.zeroInit(boom_t, .{1}); + \\pub const FOO = @import("std").mem.zeroInit(boom_t, .{@as(c_int, 1)}); }); cases.add("complex switch", @@ -343,8 +343,8 @@ pub fn addCases(cases: *tests.TranslateCContext) void { cases.add("correct semicolon after infixop", \\#define __ferror_unlocked_body(_fp) (((_fp)->_flags & _IO_ERR_SEEN) != 0) , &[_][]const u8{ - \\pub fn __ferror_unlocked_body(_fp: anytype) callconv(.Inline) @TypeOf((_fp.*._flags & _IO_ERR_SEEN) != 0) { - \\ return (_fp.*._flags & _IO_ERR_SEEN) != 0; + \\pub fn __ferror_unlocked_body(_fp: anytype) callconv(.Inline) @TypeOf((_fp.*._flags & _IO_ERR_SEEN) != @as(c_int, 0)) { + \\ return (_fp.*._flags & _IO_ERR_SEEN) != @as(c_int, 0); \\} }); @@ -352,11 +352,11 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\#define FOO(x) ((x >= 0) + (x >= 0)) \\#define BAR 1 && 2 > 4 , &[_][]const u8{ - \\pub fn FOO(x: anytype) callconv(.Inline) @TypeOf(@boolToInt(x >= 0) + @boolToInt(x >= 0)) { - \\ return @boolToInt(x >= 0) + @boolToInt(x >= 0); + \\pub fn FOO(x: anytype) callconv(.Inline) @TypeOf(@boolToInt(x >= @as(c_int, 0)) + @boolToInt(x >= @as(c_int, 0))) { + \\ return @boolToInt(x >= @as(c_int, 0)) + @boolToInt(x >= @as(c_int, 0)); \\} , - \\pub const BAR = (1 != 0) and (2 > 4); + \\pub const BAR = (@as(c_int, 1) != 0) and (@as(c_int, 2) > @as(c_int, 4)); }); cases.add("struct with aligned fields", @@ -401,15 +401,15 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\ break :blk bar; \\}; , - \\pub fn bar(x: anytype) callconv(.Inline) @TypeOf(baz(1, 2)) { + \\pub fn bar(x: anytype) callconv(.Inline) @TypeOf(baz(@as(c_int, 1), @as(c_int, 2))) { \\ return blk: { \\ _ = &x; - \\ _ = 3; - \\ _ = 4 == 4; - \\ _ = 5 * 6; - \\ _ = baz(1, 2); - \\ _ = 2 % 2; - \\ break :blk baz(1, 2); + \\ _ = @as(c_int, 3); + \\ _ = @as(c_int, 4) == @as(c_int, 4); + \\ _ = @as(c_int, 5) * @as(c_int, 6); + \\ _ = baz(@as(c_int, 1), @as(c_int, 2)); + \\ _ = @as(c_int, 2) % @as(c_int, 2); + \\ break :blk baz(@as(c_int, 1), @as(c_int, 2)); \\ }; \\} }); @@ -418,9 +418,9 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\#define foo 1 \\#define inline 2 , &[_][]const u8{ - \\pub const foo = 1; + \\pub const foo = @as(c_int, 1); , - \\pub const @"inline" = 2; + \\pub const @"inline" = @as(c_int, 2); }); cases.add("macro line continuation", @@ -507,7 +507,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { cases.add("#define hex literal with capital X", \\#define VAL 0XF00D , &[_][]const u8{ - \\pub const VAL = 0xF00D; + \\pub const VAL = @import("std").meta.promoteIntLiteral(c_int, 0xF00D, .hexadecimal); }); cases.add("anonymous struct & unions", @@ -878,7 +878,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { cases.add("macro with left shift", \\#define REDISMODULE_READ (1<<0) , &[_][]const u8{ - \\pub const REDISMODULE_READ = 1 << 0; + \\pub const REDISMODULE_READ = @as(c_int, 1) << @as(c_int, 0); }); cases.add("macro with right shift", @@ -887,7 +887,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { , &[_][]const u8{ \\pub const FLASH_SIZE = @as(c_ulong, 0x200000); , - \\pub const FLASH_BANK_SIZE = FLASH_SIZE >> 1; + \\pub const FLASH_BANK_SIZE = FLASH_SIZE >> @as(c_int, 1); }); cases.add("double define struct", @@ -955,14 +955,14 @@ pub fn addCases(cases: *tests.TranslateCContext) void { cases.add("#define an unsigned integer literal", \\#define CHANNEL_COUNT 24 , &[_][]const u8{ - \\pub const CHANNEL_COUNT = 24; + \\pub const CHANNEL_COUNT = @as(c_int, 24); }); cases.add("#define referencing another #define", \\#define THING2 THING1 \\#define THING1 1234 , &[_][]const u8{ - \\pub const THING1 = 1234; + \\pub const THING1 = @as(c_int, 1234); , \\pub const THING2 = THING1; }); @@ -1008,7 +1008,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { cases.add("macro with parens around negative number", \\#define LUA_GLOBALSINDEX (-10002) , &[_][]const u8{ - \\pub const LUA_GLOBALSINDEX = -10002; + \\pub const LUA_GLOBALSINDEX = -@as(c_int, 10002); }); cases.add( @@ -1091,8 +1091,8 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\#define foo 1 //foo \\#define bar /* bar */ 2 , &[_][]const u8{ - "pub const foo = 1;", - "pub const bar = 2;", + "pub const foo = @as(c_int, 1);", + "pub const bar = @as(c_int, 2);", }); cases.add("string prefix", @@ -1722,7 +1722,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { cases.add("comment after integer literal", \\#define SDL_INIT_VIDEO 0x00000020 /**< SDL_INIT_VIDEO implies SDL_INIT_EVENTS */ , &[_][]const u8{ - \\pub const SDL_INIT_VIDEO = 0x00000020; + \\pub const SDL_INIT_VIDEO = @as(c_int, 0x00000020); }); cases.add("u integer suffix after hex literal", @@ -1836,8 +1836,8 @@ pub fn addCases(cases: *tests.TranslateCContext) void { , &[_][]const u8{ \\pub extern var c: c_int; , - \\pub fn BASIC(c_1: anytype) callconv(.Inline) @TypeOf(c_1 * 2) { - \\ return c_1 * 2; + \\pub fn BASIC(c_1: anytype) callconv(.Inline) @TypeOf(c_1 * @as(c_int, 2)) { + \\ return c_1 * @as(c_int, 2); \\} , \\pub fn FOO(L: anytype, b: anytype) callconv(.Inline) @TypeOf(L + b) { @@ -2481,7 +2481,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\ return array[@intCast(c_uint, index)]; \\} , - \\pub const ACCESS = array[2]; + \\pub const ACCESS = array[@as(c_int, 2)]; }); cases.add("cast signed array index to unsigned", @@ -3097,7 +3097,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { , \\pub const BAR = @import("std").meta.cast(?*c_void, a); , - \\pub const BAZ = @import("std").meta.cast(u32, 2); + \\pub const BAZ = @import("std").meta.cast(u32, @as(c_int, 2)); }); cases.add("macro with cast to unsigned short, long, and long long", @@ -3105,9 +3105,9 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\#define CURLAUTH_BASIC ((unsigned long) 1) \\#define CURLAUTH_BASIC_BUT_ULONGLONG ((unsigned long long) 1) , &[_][]const u8{ - \\pub const CURLAUTH_BASIC_BUT_USHORT = @import("std").meta.cast(c_ushort, 1); - \\pub const CURLAUTH_BASIC = @import("std").meta.cast(c_ulong, 1); - \\pub const CURLAUTH_BASIC_BUT_ULONGLONG = @import("std").meta.cast(c_ulonglong, 1); + \\pub const CURLAUTH_BASIC_BUT_USHORT = @import("std").meta.cast(c_ushort, @as(c_int, 1)); + \\pub const CURLAUTH_BASIC = @import("std").meta.cast(c_ulong, @as(c_int, 1)); + \\pub const CURLAUTH_BASIC_BUT_ULONGLONG = @import("std").meta.cast(c_ulonglong, @as(c_int, 1)); }); cases.add("macro conditional operator", @@ -3202,7 +3202,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\ bar_1 = 2; \\} , - \\pub const bar = 4; + \\pub const bar = @as(c_int, 4); }); cases.add("don't export inline functions", @@ -3331,9 +3331,9 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\#define NULL ((void*)0) \\#define FOO ((int)0x8000) , &[_][]const u8{ - \\pub const NULL = @import("std").meta.cast(?*c_void, 0); + \\pub const NULL = @import("std").meta.cast(?*c_void, @as(c_int, 0)); , - \\pub const FOO = @import("std").meta.cast(c_int, 0x8000); + \\pub const FOO = @import("std").meta.cast(c_int, @import("std").meta.promoteIntLiteral(c_int, 0x8000, .hexadecimal)); }); if (std.Target.current.abi == .msvc) { @@ -3398,4 +3398,24 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\ unnamed_0: struct_unnamed_2, \\}; }); + + cases.add("integer literal promotion", + \\#define GUARANTEED_TO_FIT_1 1024 + \\#define GUARANTEED_TO_FIT_2 10241024L + \\#define GUARANTEED_TO_FIT_3 20482048LU + \\#define MAY_NEED_PROMOTION_1 10241024 + \\#define MAY_NEED_PROMOTION_2 307230723072L + \\#define MAY_NEED_PROMOTION_3 819281928192LU + \\#define MAY_NEED_PROMOTION_HEX 0x80000000 + \\#define MAY_NEED_PROMOTION_OCT 020000000000 + , &[_][]const u8{ + \\pub const GUARANTEED_TO_FIT_1 = @as(c_int, 1024); + \\pub const GUARANTEED_TO_FIT_2 = @as(c_long, 10241024); + \\pub const GUARANTEED_TO_FIT_3 = @as(c_ulong, 20482048); + \\pub const MAY_NEED_PROMOTION_1 = @import("std").meta.promoteIntLiteral(c_int, 10241024, .decimal); + \\pub const MAY_NEED_PROMOTION_2 = @import("std").meta.promoteIntLiteral(c_long, 307230723072, .decimal); + \\pub const MAY_NEED_PROMOTION_3 = @import("std").meta.promoteIntLiteral(c_ulong, 819281928192, .decimal); + \\pub const MAY_NEED_PROMOTION_HEX = @import("std").meta.promoteIntLiteral(c_int, 0x80000000, .hexadecimal); + \\pub const MAY_NEED_PROMOTION_OCT = @import("std").meta.promoteIntLiteral(c_int, 0o20000000000, .octal); + }); }