diff --git a/doc/langref.html.in b/doc/langref.html.in index 4766e8d508d31b76b6258501ef1226f7d809c1e1..9be898fc0499bc091f9020bd053ad3f4a2a1e851 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -2271,6 +2271,7 @@ or This even works at {#link|comptime#}:

{#code|test_packed_structs.zig#} +

The backing integer can be inferred or explicitly provided. When inferred, it will be unsigned. When explicitly provided, its bit width @@ -2279,6 +2280,12 @@ or

{#code|test_missized_packed_struct.zig#} +

+ A {#syntax#}packed struct{#endsyntax#} can be converted to and from its backing + integer using {#link|@backingInt#} and {#link|@fromBackingInt#}: +

+ {#code|test_packed_struct_backing_int.zig#} +

Zig allows the address to be taken of a non-byte-aligned field:

@@ -2401,7 +2408,7 @@ or {#header_open|enum#} {#code|test_enums.zig#} - {#see_also|@typeInfo|@tagName|@sizeOf#} + {#see_also|@backingInt|@fromBackingInt|@typeInfo|@tagName|@sizeOf|noreturn#} {#header_open|extern enum#}

@@ -2431,9 +2438,7 @@ or The enum must specify a tag type and cannot consume every enumeration value.

- {#link|@enumFromInt#} on a non-exhaustive enum involves the safety semantics - of {#link|@intCast#} to the integer tag type, but beyond that always results in - a well-defined enum value. + {#link|@fromBackingInt#} on a non-exhaustive enum always results in a valid enum value.

A switch on a non-exhaustive enum can include a {#syntax#}_{#endsyntax#} prong as an alternative to an {#syntax#}else{#endsyntax#} prong. @@ -2461,7 +2466,8 @@ or {#code|test_simple_union.zig#}

- In order to use {#link|switch#} with a union, it must be a {#link|Tagged union#}. + In order to use {#link|switch#} with a union, it must be a {#link|tagged union|Tagged union#} + or a {#link|packed union#}.

To initialize a union when the tag is a {#link|comptime#}-known name, see {#link|@unionInit#}. @@ -2490,7 +2496,7 @@ or

Unions with inferred enum tag types can also assign ordinal values to their inferred tag. This requires the tag to specify an explicit integer type. - {#link|@intFromEnum#} can be used to access the ordinal value corresponding to the active field. + {#link|@backingInt#} can be used to access the ordinal value corresponding to the active field.

{#code|test_tagged_union_with_tag_values.zig#} @@ -2521,6 +2527,7 @@ or

{#code|test_packed_union_equality.zig#} + {#see_also|@backingInt|@fromBackingInt#} {#header_close#} {#header_open|Anonymous Union Literals#} @@ -3586,14 +3593,14 @@ void do_a_thing(struct Foo *foo) {

- Works at compile-time if {#syntax#}value{#endsyntax#} is known at compile time. It's a compile error to bitcast a value of undefined layout; this means that, besides the restriction from types which possess dedicated casting builtins (enums, pointers, error sets), bare structs, error unions, slices, optionals, and any other type without a well-defined memory layout, also cannot be used in this operation. + Works at compile-time if {#syntax#}value{#endsyntax#} is known at compile time. + It's a compile error to bitcast a value of undefined layout; this means that, + besides the restriction from types which possess dedicated casting builtins + (pointers, error sets), bare structs, error unions, slices, optionals, and any + other type without a well-defined memory layout, also cannot be used in this + operation.

+

+ Attempting to convert an integer with no corresponding tag value to an + {#syntax#}enum{#endsyntax#} invokes safety-checked {#link|Illegal Behavior#}. +

+ {#see_also|@ptrCast|@intFromPtr|@ptrFromInt|@errorCast|@intFromError|@errorFromInt|@backingInt|@fromBackingInt#} {#header_close#} {#header_open|@bitOffsetOf#} @@ -4798,6 +4827,9 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val {#header_open|@enumFromInt#}
{#syntax#}@enumFromInt(integer: anytype) anytype{#endsyntax#}

+ Deprecated. Use {#link|@fromBackingInt#} or {#link|@bitCast#} instead. +

+

Converts an integer into an {#link|enum#} value. The return type is the inferred result type.

@@ -4953,6 +4985,22 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val

{#header_close#} + {#header_open|@fromBackingInt#} +
{#syntax#}@fromBackingInt(backing_int: BackingInt(T)) T{#endsyntax#}
+

+ Converts an integer into a {#link|enum#}, a {#link|packed struct#} or a + {#link|packed union#} value. The return type is the inferred result type. +

+

+ Attempting to convert an integer with no corresponding tag value to an + {#syntax#}enum{#endsyntax#} invokes safety-checked {#link|Illegal Behavior#}. + Note that a {#link|non-exhaustive enum|Non-exhaustive enum#} has corresponding values for + all integers in the enum's integer tag type: the {#syntax#}_{#endsyntax#} value represents + all the remaining unnamed integers in the enum's tag type. +

+ {#see_also|@backingInt|@bitCast#} + {#header_close#} + {#header_open|@hasDecl#}
{#syntax#}@hasDecl(comptime Namespace: type, comptime name: []const u8) bool{#endsyntax#}

Returns whether or not a {#link|Namespace#} has a declaration matching {#syntax#}name{#endsyntax#}.

@@ -5035,13 +5083,12 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val {#header_open|@intFromEnum#}
{#syntax#}@intFromEnum(enum_or_tagged_union: anytype) anytype{#endsyntax#}

+ Deprecated. Use {#link|@backingInt#} or {#link|@bitCast#} instead. +

+

Converts an enumeration value into its integer tag type. When a tagged union is passed, the tag value is used as the enumeration value.

-

- If there is only one possible enum value, the result is a {#syntax#}comptime_int{#endsyntax#} - known at {#link|comptime#}. -

{#see_also|@enumFromInt#} {#header_close#} @@ -5077,7 +5124,7 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val Converts {#syntax#}value{#endsyntax#} to a {#syntax#}usize{#endsyntax#} which is the address of the pointer. {#syntax#}value{#endsyntax#} can be {#syntax#}*T{#endsyntax#} or {#syntax#}?*T{#endsyntax#}.

-

To convert the other way, use {#link|@ptrFromInt#}

+ {#see_also|@ptrFromInt#} {#header_close#} {#header_open|@max#} @@ -5281,6 +5328,7 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val If the destination pointer type does not allow address zero and {#syntax#}address{#endsyntax#} is zero, this invokes safety-checked {#link|Illegal Behavior#}.

+ {#see_also|@intFromPtr#} {#header_close#} {#header_open|@rem#} diff --git a/doc/langref/runtime_invalid_enum_cast.zig b/doc/langref/runtime_invalid_enum_cast.zig index f4a44645d55a38fe5d9e8dac4a7abdc2e9877b9e..6d3c48febf69800674da4aa237a365aa7af45406 100644 --- a/doc/langref/runtime_invalid_enum_cast.zig +++ b/doc/langref/runtime_invalid_enum_cast.zig @@ -1,16 +1,18 @@ const std = @import("std"); -const Foo = enum { +const Foo = enum(u2) { a, b, c, }; -pub fn main() void { - var a: u2 = 3; - _ = &a; - const b: Foo = @enumFromInt(a); +fn foo(a: u2) void { + const b: Foo = @fromBackingInt(a); std.debug.print("value: {s}\n", .{@tagName(b)}); } +pub fn main() void { + foo(3); +} + // exe=fail diff --git a/doc/langref/test_comptime_invalid_enum_cast.zig b/doc/langref/test_comptime_invalid_enum_cast.zig index 8371091fe1f26bddb2d90e699c65ae65a3f7aa9e..6e3471eed2aa30bca7e6fc3144c551a123cb575d 100644 --- a/doc/langref/test_comptime_invalid_enum_cast.zig +++ b/doc/langref/test_comptime_invalid_enum_cast.zig @@ -1,11 +1,11 @@ -const Foo = enum { +const Foo = enum(u2) { a, b, c, }; comptime { const a: u2 = 3; - const b: Foo = @enumFromInt(a); + const b: Foo = @fromBackingInt(a); _ = b; } diff --git a/doc/langref/test_enums.zig b/doc/langref/test_enums.zig index 9502e7ae74c05c9b786c69847c3cfd5aa08888c3..10c1a7264c9a0b859900ee782715188f34198714 100644 --- a/doc/langref/test_enums.zig +++ b/doc/langref/test_enums.zig @@ -22,9 +22,9 @@ const Value = enum(u2) { // Now you can cast between u2 and Value. // The ordinal value starts from 0, counting up by 1 from the previous member. test "enum ordinal value" { - try expectEqual(0, @intFromEnum(Value.zero)); - try expectEqual(1, @intFromEnum(Value.one)); - try expectEqual(2, @intFromEnum(Value.two)); + try expectEqual(0, @backingInt(Value.zero)); + try expectEqual(1, @backingInt(Value.one)); + try expectEqual(2, @backingInt(Value.two)); } // You can override the ordinal value for an enum. @@ -34,9 +34,9 @@ const Value2 = enum(u32) { million = 1000000, }; test "set enum ordinal value" { - try expectEqual(100, @intFromEnum(Value2.hundred)); - try expectEqual(1000, @intFromEnum(Value2.thousand)); - try expectEqual(1000000, @intFromEnum(Value2.million)); + try expectEqual(100, @backingInt(Value2.hundred)); + try expectEqual(1000, @backingInt(Value2.thousand)); + try expectEqual(1000000, @backingInt(Value2.million)); } // You can also override only some values. @@ -48,11 +48,11 @@ const Value3 = enum(u4) { e, }; test "enum implicit ordinal values and overridden values" { - try expectEqual(0, @intFromEnum(Value3.a)); - try expectEqual(8, @intFromEnum(Value3.b)); - try expectEqual(9, @intFromEnum(Value3.c)); - try expectEqual(4, @intFromEnum(Value3.d)); - try expectEqual(5, @intFromEnum(Value3.e)); + try expectEqual(0, @backingInt(Value3.a)); + try expectEqual(8, @backingInt(Value3.b)); + try expectEqual(9, @backingInt(Value3.c)); + try expectEqual(4, @backingInt(Value3.d)); + try expectEqual(5, @backingInt(Value3.e)); } // Enums can have methods, the same as structs and unions. @@ -111,4 +111,10 @@ test "@tagName" { try expectEqualStrings(@tagName(Small.three), "three"); } +// Empty enums are uninstantiable, their tag type is always noreturn. +const Empty = enum {}; +test "empty enum" { + try expectEqual(noreturn, @typeInfo(Empty).@"enum".tag_type); +} + // test diff --git a/doc/langref/test_inline_else.zig b/doc/langref/test_inline_else.zig index c500bd685d3dc192f9273bdd59091a38e0b08501..5bfdca6c426d807964657a04964468b96cb59fa7 100644 --- a/doc/langref/test_inline_else.zig +++ b/doc/langref/test_inline_else.zig @@ -9,7 +9,7 @@ const SliceTypeB = extern struct { ptr: [*]SliceTypeA, len: usize, }; -const AnySlice = union(enum) { +const AnySlice = union(enum(u8)) { a: SliceTypeA, b: SliceTypeB, c: []const u8, @@ -23,7 +23,7 @@ fn withFor(any: AnySlice) usize { // With `inline for` the function gets generated as // a series of `if` statements relying on the optimizer // to convert it to a switch. - if (field_value == @intFromEnum(any)) { + if (field_value == @backingInt(any)) { return @field(any, field_name).len; } } diff --git a/doc/langref/test_packed_struct_backing_int.zig b/doc/langref/test_packed_struct_backing_int.zig new file mode 100644 index 0000000000000000000000000000000000000000..434abb0c4ee78e8826294c8f485d0be19d5d30df --- /dev/null +++ b/doc/langref/test_packed_struct_backing_int.zig @@ -0,0 +1,21 @@ +const std = @import("std"); +const assert = std.debug.assert; +const expectEqual = std.testing.expectEqual; + +const PackedStruct = packed struct(u8) { + lo: u4, + hi: u4, +}; + +test "convert to and from backing integer" { + const original: PackedStruct = .{ .lo = 0b1100, .hi = 0b0101 }; + + const backing_int = @backingInt(original); + comptime assert(@TypeOf(backing_int) == u8); + try expectEqual(0b0101_1100, backing_int); + + const reconstructed: PackedStruct = @fromBackingInt(backing_int); + try expectEqual(original, reconstructed); +} + +// test diff --git a/doc/langref/test_tagged_union_with_tag_values.zig b/doc/langref/test_tagged_union_with_tag_values.zig index 229a31d6780b213a76cd9d895a6ed838de777f0d..b18db3e81ec0658b78cf23da310c8f3680b80891 100644 --- a/doc/langref/test_tagged_union_with_tag_values.zig +++ b/doc/langref/test_tagged_union_with_tag_values.zig @@ -8,10 +8,10 @@ const Tagged = union(enum(u32)) { test "tag values" { const int: Tagged = .{ .int = -40 }; - try expectEqual(123, @intFromEnum(int)); + try expectEqual(123, @backingInt(int)); const boolean: Tagged = .{ .boolean = false }; - try expectEqual(67, @intFromEnum(boolean)); + try expectEqual(67, @backingInt(boolean)); } // test diff --git a/lib/std/meta.zig b/lib/std/meta.zig index 14270ba9d86763f72a5a75eb5f846dee00759954..e891e4320d5bdc9faa01be96cd8fd1ba1d8b2165 100644 --- a/lib/std/meta.zig +++ b/lib/std/meta.zig @@ -408,7 +408,8 @@ pub fn FieldEnum(comptime T: type) type { else => {}, } - const IntTag = std.math.IntFittingRange(0, field_names.len -| 1); + if (field_names.len == 0) return enum {}; + const IntTag = std.math.IntFittingRange(0, field_names.len - 1); return @Enum(IntTag, .exhaustive, field_names, &std.simd.iota(IntTag, field_names.len)); } @@ -469,7 +470,8 @@ test FieldEnum { pub fn DeclEnum(comptime T: type) type { const decl_names = declarations(T); - const IntTag = std.math.IntFittingRange(0, decl_names.len -| 1); + if (decl_names.len == 0) return enum {}; + const IntTag = std.math.IntFittingRange(0, decl_names.len - 1); return @Enum(IntTag, .exhaustive, decl_names, &std.simd.iota(IntTag, decl_names.len)); } @@ -504,10 +506,40 @@ pub fn BareUnion(comptime T: type) type { .@"union" => |u| u, else => @compileError("expected union type, found '" ++ @typeName(T) ++ "'"), }; - return @Union(u.layout, null, u.field_names, u.field_types[0..], u.field_attrs[0..]); } +/// For enums, packed unions and packed structs, returns their backing integer type. +/// For tagged unions, returns the backing integer type of their enum tag type. +pub fn BackingInt(comptime T: type) type { + switch (@typeInfo(T)) { + .@"enum" => |info| return info.tag_type, + .@"struct" => |info| if (info.backing_integer) |Int| return Int, + .@"union" => |info| switch (info.layout) { + .@"packed" => return info.backing_integer.?, + .auto => if (info.tag_type) |EnumTag| + return @typeInfo(EnumTag).@"enum".tag_type, + .@"extern" => {}, + }, + else => {}, + } + @compileError("expected enum, tagged union, packed union or packed struct type, found '" ++ @typeName(T) ++ "'"); +} + +test BackingInt { + const E = enum(u8) { a, b, c }; + try testing.expect(BackingInt(E) == u8); + + const S = packed struct(u16) { x: u8, y: i8 }; + try testing.expect(BackingInt(S) == u16); + + const U = packed union(i32) { a: u32, b: enum(i32) { _ } }; + try testing.expect(BackingInt(U) == i32); + + const T = union(enum(i8)) { a, b, c }; + try testing.expect(BackingInt(T) == i8); +} + pub fn Tag(comptime T: type) type { return switch (@typeInfo(T)) { .@"enum" => |info| info.tag_type, diff --git a/lib/std/zig.zig b/lib/std/zig.zig index 3568ecf4d32886675b4b075112c285acbed84329..424effacc3e7f1b38a977ce9e685dc3ced6dd83f 100644 --- a/lib/std/zig.zig +++ b/lib/std/zig.zig @@ -906,7 +906,6 @@ pub const SimpleComptimeReason = enum(u32) { slice_single_item_ptr_bounds, stored_to_comptime_field, stored_to_comptime_var, - casted_to_comptime_enum, casted_to_comptime_int, casted_to_comptime_float, std_lang_decl, @@ -989,7 +988,6 @@ pub const SimpleComptimeReason = enum(u32) { .slice_single_item_ptr_bounds => "slice of single-item pointer must have comptime-known bounds", .stored_to_comptime_field => "value stored to a comptime field must be comptime-known", .stored_to_comptime_var => "value stored to a comptime variable must be comptime-known", - .casted_to_comptime_enum => "value casted to enum with 'comptime_int' tag type must be comptime-known", .casted_to_comptime_int => "value casted to 'comptime_int' must be comptime-known", .casted_to_comptime_float => "value casted to 'comptime_float' must be comptime-known", .std_lang_decl => "'std.lang' declaration values must be comptime-known", diff --git a/lib/std/zig/Ast/Render.zig b/lib/std/zig/Ast/Render.zig index 8f5b2e2c276f5b8680fb56173154bcbdbf8966e6..2c430ca716fcaace9e76ad67d80348ff3a88fb66 100644 --- a/lib/std/zig/Ast/Render.zig +++ b/lib/std/zig/Ast/Render.zig @@ -1652,7 +1652,17 @@ fn renderBuiltinCall( const tree = r.tree; const ais = r.ais; - try renderToken(r, builtin_token, .none); // @name + // remove before 0.18.0 is released + const builtin_token_slice = tree.tokenSlice(builtin_token); // @name + const lexeme: []const u8, const have_int_cast: bool = lexeme: { + if (mem.eql(u8, builtin_token_slice, "@intFromEnum")) + break :lexeme .{ "@backingInt", false }; + if (mem.eql(u8, builtin_token_slice, "@enumFromInt")) + break :lexeme .{ "@fromBackingInt(@intCast", true }; + break :lexeme .{ builtin_token_slice, false }; + }; + try ais.writeAll(lexeme); + try renderSpace(r, builtin_token, builtin_token_slice.len, .none); if (r.fixups.rebase_imported_paths) |prefix| { const slice = tree.tokenSlice(builtin_token); @@ -1675,7 +1685,14 @@ fn renderBuiltinCall( } } - return renderParamList(r, builtin_token + 1, params, space); + try renderParamList(r, builtin_token + 1, params, .skip); // space is rendered below + if (have_int_cast) try ais.writeAll(")"); + const rparen: Ast.TokenIndex = rparen: { + if (params.len == 0) break :rparen builtin_token + 1 + 1; + const after_last_param_tok = tree.lastToken(params[params.len - 1]) + 1; + break :rparen after_last_param_tok + @intFromBool(tree.tokenTag(after_last_param_tok) == .comma); + }; + return renderSpace(r, rparen, tokenSliceForRender(tree, rparen).len, space); } fn fnProtoRparen(tree: Ast, fn_proto: Ast.full.FnProto, maybe_bang: Ast.TokenIndex) Ast.TokenIndex { diff --git a/lib/std/zig/AstGen.zig b/lib/std/zig/AstGen.zig index 16e0616ede4ac141162b054a48a36694f12e222f..1b158188c832b05ee3027df3d31fde60268e14df 100644 --- a/lib/std/zig/AstGen.zig +++ b/lib/std/zig/AstGen.zig @@ -2703,6 +2703,7 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As .elem_type, .indexable_ptr_elem_type, .splat_op_result_ty, + .from_backing_int_arg_ty, .reify_int, .vector_type, .indexable_ptr_len, @@ -2803,6 +2804,8 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As .error_set_decl, .enum_from_int, .int_from_enum, + .backing_int, + .from_backing_int, .type_info, .size_of, .bit_size_of, @@ -9168,6 +9171,7 @@ fn builtinCall( .set_eval_branch_quota => return simpleUnOp(gz, scope, ri, node, .{ .rl = .{ .coerced_ty = .u32_type } }, params[0], .set_eval_branch_quota), .int_from_enum => return simpleUnOp(gz, scope, ri, node, .{ .rl = .none }, params[0], .int_from_enum), .int_from_bool => return simpleUnOp(gz, scope, ri, node, .{ .rl = .none }, params[0], .int_from_bool), + .backing_int => return simpleUnOp(gz, scope, ri, node, .{ .rl = .none }, params[0], .backing_int), .embed_file => return simpleUnOp(gz, scope, ri, node, .{ .rl = .{ .coerced_ty = .slice_const_u8_type } }, params[0], .embed_file), .error_name => return simpleUnOp(gz, scope, ri, node, .{ .rl = .{ .coerced_ty = .anyerror_type } }, params[0], .error_name), .set_runtime_safety => return simpleUnOp(gz, scope, ri, node, coerced_bool_ri, params[0], .set_runtime_safety), @@ -9199,6 +9203,20 @@ fn builtinCall( .truncate => return typeCast(gz, scope, ri, node, params[0], .truncate, builtin_name), // zig fmt: on + .from_backing_int => { + const cursor = maybeAdvanceSourceCursorToMainToken(gz, node); + const result_ty = try ri.rl.resultTypeForCast(gz, node, builtin_name); + const backing_int_ty = try gz.addUnNode(.from_backing_int_arg_ty, result_ty, node); + const operand = try expr(gz, scope, .{ .rl = .{ .coerced_ty = backing_int_ty } }, params[0]); + + try emitDbgStmt(gz, cursor); + const result = try gz.addPlNode(.from_backing_int, node, Zir.Inst.Bin{ + .lhs = result_ty, + .rhs = operand, + }); + return rvalue(gz, ri, result, node); + }, + .in_comptime => if (gz.is_comptime) { return astgen.failNode(node, "redundant '@inComptime' in comptime scope", .{}); } else { diff --git a/lib/std/zig/AstRlAnnotate.zig b/lib/std/zig/AstRlAnnotate.zig index c42528e3ee2cf8d91509e86fd4a444994027be81..4e010dee5a66e96daec28d6ad641970455ac16fa 100644 --- a/lib/std/zig/AstRlAnnotate.zig +++ b/lib/std/zig/AstRlAnnotate.zig @@ -889,6 +889,7 @@ fn builtinCall(astrl: *AstRlAnnotate, block: ?*Block, ri: ResultInfo, node: Ast. .int_from_bool, .int_from_error, .error_from_int, + .from_backing_int, .embed_file, .error_name, .set_runtime_safety, @@ -916,6 +917,7 @@ fn builtinCall(astrl: *AstRlAnnotate, block: ?*Block, ri: ResultInfo, node: Ast. .float_from_int, .ptr_from_int, .enum_from_int, + .backing_int, .float_cast, .int_cast, .truncate, diff --git a/lib/std/zig/BuiltinFn.zig b/lib/std/zig/BuiltinFn.zig index 7ff834487ce43f5700e96de0f58b5f12664343ac..99ac5ce5d6c7d58506895a2b66cc42871adac8cb 100644 --- a/lib/std/zig/BuiltinFn.zig +++ b/lib/std/zig/BuiltinFn.zig @@ -56,6 +56,8 @@ pub const Tag = enum { import, in_comptime, int_cast, + backing_int, + from_backing_int, enum_from_int, error_from_int, float_from_int, @@ -564,6 +566,20 @@ pub const list = list: { .param_count = 1, }, }, + .{ + "@backingInt", + .{ + .tag = .backing_int, + .param_count = 1, + }, + }, + .{ + "@fromBackingInt", + .{ + .tag = .from_backing_int, + .param_count = 1, + }, + }, .{ "@enumFromInt", .{ diff --git a/lib/std/zig/Zir.zig b/lib/std/zig/Zir.zig index 6e14e5ee9096b1e6ef9f190d6cb5dfd7e23de5a0..f118e24a216eed245c10faf6e36a16958937d83a 100644 --- a/lib/std/zig/Zir.zig +++ b/lib/std/zig/Zir.zig @@ -283,6 +283,14 @@ pub const Inst = struct { /// /// Uses the `un_node` field. splat_op_result_ty, + /// Given a type, strips away any error unions or optionals stacked + /// on top, validates it for usage with `@fromBackingInt` and returns + /// its backing integer type. + /// + /// `E!?enum(T) { _ }` -> `T` + /// + /// Uses the `un_node` field. + from_backing_int_arg_ty, /// Given a pointer to an indexable object, returns the len property. This is /// used by for loops. This instruction also emits a for-loop specific compile /// error if the indexable object is not indexable. @@ -872,6 +880,9 @@ pub const Inst = struct { /// Converts an enum value into an integer. Resulting type will be the tag type /// of the enum. Uses `un_node`. int_from_enum, + /// Implements the `@backingInt` builtin. + /// Uses `un_node`. + backing_int, /// Implement builtin `@alignOf`. Uses `un_node`. align_of, /// Implement builtin `@intFromBool`. Uses `un_node`. @@ -934,6 +945,9 @@ pub const Inst = struct { /// Converts an integer into an enum value. /// Uses `pl_node` with payload `Bin`. `lhs` is dest type, `rhs` is operand. enum_from_int, + /// Implements the `@fromBackingInt` builtin. + /// Uses `pl_node` with payload `Bin`. `lhs` is dest type, `rhs` is operand. + from_backing_int, /// Convert a larger float type to any other float type, possibly causing /// a loss of precision. /// Uses the `pl_node` field. AST is the `@floatCast` syntax. @@ -1115,6 +1129,7 @@ pub const Inst = struct { .elem_type, .indexable_ptr_elem_type, .splat_op_result_ty, + .from_backing_int_arg_ty, .indexable_ptr_len, .anyframe_type, .as_node, @@ -1229,6 +1244,8 @@ pub const Inst = struct { .field_type_ref, .enum_from_int, .int_from_enum, + .backing_int, + .from_backing_int, .type_info, .size_of, .bit_size_of, @@ -1409,6 +1426,7 @@ pub const Inst = struct { .elem_type, .indexable_ptr_elem_type, .splat_op_result_ty, + .from_backing_int_arg_ty, .indexable_ptr_len, .anyframe_type, .as_node, @@ -1511,6 +1529,8 @@ pub const Inst = struct { .field_type_ref, .enum_from_int, .int_from_enum, + .backing_int, + .from_backing_int, .type_info, .size_of, .bit_size_of, @@ -1645,6 +1665,7 @@ pub const Inst = struct { .elem_type = .un_node, .indexable_ptr_elem_type = .un_node, .splat_op_result_ty = .un_node, + .from_backing_int_arg_ty = .un_node, .indexable_ptr_len = .un_node, .anyframe_type = .un_node, .as_node = .pl_node, @@ -1774,6 +1795,7 @@ pub const Inst = struct { .compile_error = .un_node, .set_eval_branch_quota = .un_node, .int_from_enum = .un_node, + .backing_int = .un_node, .align_of = .un_node, .int_from_bool = .un_node, .embed_file = .un_node, @@ -1803,6 +1825,7 @@ pub const Inst = struct { .float_from_int = .pl_node, .ptr_from_int = .pl_node, .enum_from_int = .pl_node, + .from_backing_int = .pl_node, .float_cast = .pl_node, .int_cast = .pl_node, .ptr_cast = .pl_node, @@ -2160,8 +2183,8 @@ pub const Inst = struct { astgen_error, /// Given a type, strips away any error unions or optionals stacked /// on top and returns the base type. That base type must be a float. - /// For example: Provided with error{Foo}!?f64, returns f64. - /// `operand` is `operand: Air.Inst.Ref`. + /// For example: Provided with `error{Foo}!?f64`, returns `f64`. + /// `operand` is payload index to `UnNode`. float_op_result_ty, pub const InstData = struct { @@ -4141,6 +4164,7 @@ fn findTrackableInner( .elem_type, .indexable_ptr_elem_type, .splat_op_result_ty, + .from_backing_int_arg_ty, .indexable_ptr_len, .anyframe_type, .as_node, @@ -4296,6 +4320,8 @@ fn findTrackableInner( .float_from_int, .ptr_from_int, .enum_from_int, + .backing_int, + .from_backing_int, .float_cast, .int_cast, .ptr_cast, diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig index e46ac087648f95f8ac4b40c42c33ad248ecac645..7e149d14fdee9105ac1b17c9e70f46197c81f638 100644 --- a/lib/std/zig/parser_test.zig +++ b/lib/std/zig/parser_test.zig @@ -1120,7 +1120,7 @@ test "zig fmt: empty enum decls" { \\const A = enum {}; \\const B = enum(u32) {}; \\const C = extern enum(c_int) {}; - \\const D = packed enum(u8) {}; + \\const D = packed enum(noreturn) {}; \\ ); } @@ -6938,6 +6938,66 @@ test "zig fmt: inner over-indented if expressions becoming multiline" { ); } +test "zig fmt: canonicalize @intFromEnum(x) to @backingInt(x)" { + try testTransform( + \\const a = @intFromEnum(x); + \\ + \\const b = @intFromEnum( + \\ x, + \\); + \\ + \\const c = @intFromEnum(x); // comment preserved + \\ + \\const d = @intFromEnum( // comment 1 preserved + \\ x, // comment 2 preserved + \\); // comment 3 preserved + \\ + , + \\const a = @backingInt(x); + \\ + \\const b = @backingInt( + \\ x, + \\); + \\ + \\const c = @backingInt(x); // comment preserved + \\ + \\const d = @backingInt( // comment 1 preserved + \\ x, // comment 2 preserved + \\); // comment 3 preserved + \\ + ); +} + +test "zig fmt: canonicalize @enumFromInt(x) to @fromBackingInt(@intCast(x))" { + try testTransform( + \\const a: E = @enumFromInt(x); + \\ + \\const b: E = @enumFromInt( + \\ x, + \\); + \\ + \\const c: E = @enumFromInt(x); // comment preserved + \\ + \\const d: E = @enumFromInt( // comment 1 preserved + \\ x, // comment 2 preserved + \\); // comment 3 preserved + \\ + , + \\const a: E = @fromBackingInt(@intCast(x)); + \\ + \\const b: E = @fromBackingInt(@intCast( + \\ x, + \\)); + \\ + \\const c: E = @fromBackingInt(@intCast(x)); // comment preserved + \\ + \\const d: E = @fromBackingInt(@intCast( // comment 1 preserved + \\ x, // comment 2 preserved + \\)); // comment 3 preserved + \\ + ); +} + test "recovery: top level" { try testError( \\test "" {inline} diff --git a/src/Air.zig b/src/Air.zig index cdae23d2f5cf1b4ff470ee5f4bb0584b07e26a52..141ed8da170572cc6e27d7cd5bd6f5111c756b2e 100644 --- a/src/Air.zig +++ b/src/Air.zig @@ -288,6 +288,10 @@ pub const Inst = struct { /// /// Uses the `ty_op` field. bit_cast, + /// Like `bit_cast`, but triggers a safety panic if the destination type is an exhaustive + /// enum and the operand is not a valid value of this type; + /// i.e. equivalent to a safety check based on `.is_named_enum_value` + bit_cast_safe, /// Cast a pointer to a different pointer type. The result type is a slice iff the operand /// type is a slice (the length of the slice does not change). All other pointer attributes /// except for the address space may change. @@ -1717,6 +1721,7 @@ pub fn typeOfIndex(air: *const Air, inst: Air.Inst.Index, ip: *const InternPool) .not, .bit_cast, + .bit_cast_safe, .ptr_cast, .ptr_from_int, .int_from_ptr, @@ -1969,6 +1974,7 @@ pub fn mustLower(air: Air, inst: Air.Inst.Index, ip: *const InternPool) bool { .add_safe, .sub_safe, .mul_safe, + .bit_cast_safe, .int_cast_safe, .int_from_float_safe, .int_from_float_optimized_safe, diff --git a/src/Air/Legalize.zig b/src/Air/Legalize.zig index 81df10c169d0f358adc769d0681ce27bf45e6484..0feca2b747ac0203bb340ac1475e6c37d5afa9ec 100644 --- a/src/Air/Legalize.zig +++ b/src/Air/Legalize.zig @@ -156,6 +156,10 @@ pub const Feature = enum { /// Legalize splat to a one element vector to a bitcast. splat_one_elem_to_bit_cast, + /// Replace `bit_cast_safe` with an explicit safety check which `call`s the panic function on failure. + /// `scalarize_*` variants for `bit_cast_safe` do not exist since the safety check is only desired if the result + /// type is a scalar enum type, so the scalarizatins for regular `bit_cast` are exactly equivalent. + expand_bit_cast_safe, /// Replace `int_cast_safe` with an explicit safety check which `call`s the panic function on failure. /// Not compatible with `scalarize_int_cast_safe`. expand_int_cast_safe, @@ -600,9 +604,28 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { continue :inst l.replaceInst(inst, .block, payload); } }, + .bit_cast_safe => if (l.features.has(.expand_bit_cast_safe)) { + if (try l.safeBitcastBlockPayload(inst)) |payload| { + continue :inst l.replaceInst(inst, .block, payload); + } + const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op; + continue :inst l.replaceInst(inst, .bit_cast, .{ .ty_op = ty_op }); + } else if (l.features.hasAny(&.{ + .scalarize_bit_cast_array, + .scalarize_bit_cast_vector_non_elementwise, + .scalarize_bit_cast_padded_elems, + })) { + if (try l.scalarizeBitcastBlockPayload(inst)) |payload| { + continue :inst l.replaceInst(inst, .block, payload); + } + }, .int_cast_safe => if (l.features.has(.expand_int_cast_safe)) { assert(!l.features.has(.scalarize_int_cast_safe)); // it doesn't make sense to do both - continue :inst l.replaceInst(inst, .block, try l.safeIntcastBlockPayload(inst)); + if (try l.safeIntcastBlockPayload(inst)) |payload| { + continue :inst l.replaceInst(inst, .block, payload); + } + const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op; + continue :inst l.replaceInst(inst, .int_cast, .{ .ty_op = ty_op }); } else if (l.features.has(.scalarize_int_cast_safe)) { const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op; if (ty_op.ty.toType().isVector(zcu)) { @@ -1006,7 +1029,10 @@ fn scalarizeBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index, form: Scalariz if (result_is_array) { // This is only allowed when legalizing an elementwise bitcast. - assert(orig.tag == .bit_cast); + switch (orig.tag) { + .bit_cast, .bit_cast_safe => {}, + else => unreachable, + } assert(form == .ty_op); } @@ -1076,7 +1102,11 @@ fn scalarizeBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index, form: Scalariz orig_operand, index_val, ).toRef(); - break :elem loop.block.addTyOp(l, orig.tag, res_elem_ty, operand).toRef(); + const scalar_tag: Air.Inst.Tag = switch (orig.tag) { + .bit_cast_safe => .bit_cast, // safety check is not supposed to be elementwise + else => orig.tag, + }; + break :elem loop.block.addTyOp(l, scalar_tag, res_elem_ty, operand).toRef(); }, .bin_op => elem: { const orig_bin = orig.data.bin_op; @@ -1700,9 +1730,23 @@ fn scalarizeBitcastBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index) Error!? // Now convert `uint_ty` (`uN`) to `dest_ty`. + // We omit the safety check when casting to an array or a vector since it's + // not supposed to be elementwise. + if (dest_ty.zigTypeTag(zcu) == .@"enum") assert(int_to_dest_ok); + if (int_to_dest_ok) { _ = main_block.stealCapacity(17); - const result = main_block.addBitCast(l, dest_ty, uint_val); + const result = switch (l.air_instructions.items(.tag)[@intFromEnum(orig_inst)]) { + .bit_cast => main_block.addBitCast(l, dest_ty, uint_val), + .bit_cast_safe => main_block.add(l, .{ + .tag = .bit_cast_safe, + .data = .{ .ty_op = .{ + .ty = .fromType(dest_ty), + .operand = uint_val, + } }, + }).toRef(), + else => unreachable, + }; main_block.addBr(l, orig_inst, result); } else if (dest_ty.arrayLenIncludingSentinel(zcu) == 1) { _ = main_block.stealCapacity(16); @@ -2089,7 +2133,62 @@ fn scalarizeReduceBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index, optimize } }; } -fn safeIntcastBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index) Error!Air.Inst.Data { +fn safeBitcastBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index) Error!?Air.Inst.Data { + const pt = l.pt; + const zcu = pt.zcu; + const ty_op = l.air_instructions.items(.data)[@intFromEnum(orig_inst)].ty_op; + + const operand_ref = ty_op.operand; + const dest_ty = ty_op.ty.toType(); + + if (dest_ty.zigTypeTag(zcu) != .@"enum" or + dest_ty.isNonexhaustiveEnum(zcu) or + !zcu.backendSupportsFeature(.is_named_enum_value)) + { + return null; + } + + // We are building this: + // + // %x = block({ + // %1 = bit_cast(@res_ty, %y) + // %2 = is_named_enum_value(%1) + // %3 = cond_br(%2, { + // %4 = br(%x, %1) + // }, { + // %5 = call(@panic.invalidEnumValue, []) + // %6 = unreach() + // }) + // }) + + var inst_buf: [6]Air.Inst.Index = undefined; + try l.air_instructions.ensureUnusedCapacity(zcu.gpa, inst_buf.len); + + var block: Block = .init(&inst_buf); + + const cast_inst = block.addBitCast(l, dest_ty, operand_ref); + const is_named_inst = block.add(l, .{ + .tag = .is_named_enum_value, + .data = .{ .un_op = cast_inst }, + }); + + var condbr: CondBr = .init(l, is_named_inst.toRef(), &block, .{ .false = .cold }); + + condbr.then_block = .init(block.stealRemainingCapacity()); + condbr.then_block.addBr(l, orig_inst, cast_inst); + + condbr.else_block = .init(condbr.then_block.stealRemainingCapacity()); + try condbr.else_block.addPanic(l, .invalid_enum_value); + + try condbr.finish(l); + + return .{ .ty_pl = .{ + .ty = .fromType(dest_ty), + .payload = try l.addBlockBody(block.body()), + } }; +} + +fn safeIntcastBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index) Error!?Air.Inst.Data { const pt = l.pt; const zcu = pt.zcu; const ty_op = l.air_instructions.items(.data)[@intFromEnum(orig_inst)].ty_op; @@ -2108,6 +2207,9 @@ fn safeIntcastBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index) Error!Air.In .@"enum" => true, else => unreachable, }; + const have_enum_value_check = dest_is_enum and + !dest_ty.isNonexhaustiveEnum(zcu) and + zcu.backendSupportsFeature(.is_named_enum_value); const operand_info = operand_scalar_ty.intInfo(zcu); const dest_info = dest_scalar_ty.intInfo(zcu); @@ -2123,6 +2225,10 @@ fn safeIntcastBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index) Error!Air.In }; }; + if (!have_enum_value_check and !have_min_check and !have_max_check) { + return null; + } + // The worst-case scenario in terms of total instructions and total condbrs is the case where // the result type is an exhaustive enum whose tag type is smaller than the operand type: // @@ -2172,7 +2278,7 @@ fn safeIntcastBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index) Error!Air.In const panic_id: Zcu.SimplePanicId = if (dest_is_enum) .invalid_enum_value else .integer_out_of_bounds; if (have_min_check or have_max_check) { - const dest_int_ty = if (dest_is_enum) dest_ty.intTagType(zcu) else dest_ty; + const dest_int_ty = if (dest_is_enum) dest_ty.backingIntType(zcu) else dest_ty; const condbr = &condbr_buf[condbr_idx]; condbr_idx += 1; const below_min_inst: Air.Inst.Index = if (have_min_check) inst: { @@ -2218,7 +2324,7 @@ fn safeIntcastBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index) Error!Air.In } }, }); // For ints we're already done, but for exhaustive enums we must check this is a valid tag. - if (dest_is_enum and !dest_ty.isNonexhaustiveEnum(zcu) and zcu.backendSupportsFeature(.is_named_enum_value)) { + if (have_enum_value_check) { assert(!is_vector); // vectors of enums don't exist // We are building this: // %1 = is_named_enum_value(%cast_inst) @@ -2240,16 +2346,12 @@ fn safeIntcastBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index) Error!Air.In cur_block = &condbr.then_block; } // Finally, just `br` to our outer `block`. - _ = cur_block.add(l, .{ - .tag = .br, - .data = .{ .br = .{ - .block_inst = orig_inst, - .operand = cast_inst.toRef(), - } }, - }); + cur_block.addBr(l, orig_inst, cast_inst.toRef()); + // We might not have used all of the instructions; that's intentional. _ = cur_block.stealRemainingCapacity(); + assert(condbr_idx != 0); // should have already returned `null` for (condbr_buf[0..condbr_idx]) |*condbr| try condbr.finish(l); return .{ .ty_pl = .{ .ty = Air.internedToRef(dest_ty.toIntern()), diff --git a/src/Air/Liveness.zig b/src/Air/Liveness.zig index fbfd74a772c0ec3028b56b22201d33d8895d166b..fcdf0017cee39cbfe6836f3e6fe33d550ee77726 100644 --- a/src/Air/Liveness.zig +++ b/src/Air/Liveness.zig @@ -491,6 +491,7 @@ fn analyzeInst( .not, .bit_cast, + .bit_cast_safe, .ptr_cast, .ptr_from_int, .int_from_ptr, diff --git a/src/Air/Liveness/Verify.zig b/src/Air/Liveness/Verify.zig index 24737ebedf6e14446e5d699af845e7c8648f1833..dd7c189bdab31cd31f09969e8d9a697176be33a0 100644 --- a/src/Air/Liveness/Verify.zig +++ b/src/Air/Liveness/Verify.zig @@ -79,6 +79,7 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void { // unary .not, .bit_cast, + .bit_cast_safe, .ptr_cast, .ptr_from_int, .int_from_ptr, diff --git a/src/Air/Verify.zig b/src/Air/Verify.zig index b2812f8660d136ce40a4b088b5c30b3bb138e385..b3f9f002295e671b24c7e4f0558012e42384d933 100644 --- a/src/Air/Verify.zig +++ b/src/Air/Verify.zig @@ -118,7 +118,7 @@ fn body(verify: *Verify, body_insts: []const Air.Inst.Index) Error!void { if (ptr_ty.childType(zcu).toIntern() != verify.ret_ty.toIntern()) return verify.fail("bad return type"); }, - .bit_cast => { + .bit_cast, .bit_cast_safe => { const ty_op = data[@intFromEnum(inst)].ty_op; const operand_ty = air.typeOf(ty_op.operand, ip); const result_ty = ty_op.ty.toType(); diff --git a/src/Air/print.zig b/src/Air/print.zig index b263c20634f3ea5a0442e6fa14af24ef77b9b64e..eccc5619967b588ccc87f54cb35f7fe4e87f78e7 100644 --- a/src/Air/print.zig +++ b/src/Air/print.zig @@ -231,6 +231,7 @@ const Writer = struct { .not, .bit_cast, + .bit_cast_safe, .ptr_cast, .ptr_from_int, .int_from_ptr, diff --git a/src/InternPool.zig b/src/InternPool.zig index eae7f32c7f17782b694a21d06c52bfe456049dcf..bb68c6c5a07161773911af2c740b6ff68d5cfa72 100644 --- a/src/InternPool.zig +++ b/src/InternPool.zig @@ -5040,7 +5040,6 @@ pub const Tag = enum(u8) { /// The set of values that are encoded this way is: /// * An array or vector which has length 0. /// * A struct which has all fields comptime-known. - /// * An empty enum or union. TODO: this value's existence is strange, because such a type in reality has no values. See #15909 /// data is Index of the type, which is known to be zero bits at runtime. only_possible_value, /// data is extra index to Key.Union. @@ -7740,12 +7739,8 @@ pub fn get(ip: *InternPool, gpa: Allocator, io: Io, tid: Zcu.PerThread.Id, key: }), .enum_tag => |enum_tag| { - assert(ip.isEnumType(enum_tag.ty)); - switch (ip.indexToKey(enum_tag.ty)) { - .simple_type => assert(ip.isIntegerType(ip.typeOf(enum_tag.int))), - .enum_type => assert(ip.typeOf(enum_tag.int) == ip.loadEnumType(enum_tag.ty).int_tag_type), - else => unreachable, - } + const enum_obj = ip.loadEnumType(enum_tag.ty); + assert(ip.typeOf(enum_tag.int) == enum_obj.int_tag_type); items.appendAssumeCapacity(.{ .tag = .enum_tag, .data = try addExtra(extra, enum_tag), @@ -10197,6 +10192,7 @@ pub fn getCoerced( .enum_type => { const enum_type = ip.loadEnumType(new_ty); const index = enum_type.nameIndex(ip, enum_literal).?; + assert(enum_type.int_tag_type != .noreturn_type); return ip.get(gpa, io, tid, .{ .enum_tag = .{ .ty = new_ty, .int = if (enum_type.field_values.len != 0) diff --git a/src/Sema.zig b/src/Sema.zig index 4b29027056be47f0bccaa0250f7b30a3a8980667..e20dfdc99c595493ab66ebc23265bd75553cac2f 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -1191,11 +1191,14 @@ fn analyzeBodyInner( .elem_type => try sema.zirElemType(block, inst), .indexable_ptr_elem_type => try sema.zirIndexablePtrElemType(block, inst), .splat_op_result_ty => try sema.zirSplatOpResultType(block, inst), + .from_backing_int_arg_ty => try sema.zirFromBackingIntArgTy(block, inst), .enum_literal => try sema.zirEnumLiteral(block, inst), .decl_literal => try sema.zirDeclLiteral(block, inst, true), .decl_literal_no_coerce => try sema.zirDeclLiteral(block, inst, false), .int_from_enum => try sema.zirIntFromEnum(block, inst), .enum_from_int => try sema.zirEnumFromInt(block, inst), + .backing_int => try sema.zirBackingInt(block, inst), + .from_backing_int => try sema.zirFromBackingInt(block, inst), .err_union_code => try sema.zirErrUnionCode(block, inst), .err_union_code_ptr => try sema.zirErrUnionCodePtr(block, inst), .err_union_payload_unsafe => try sema.zirErrUnionPayload(block, inst), @@ -2428,6 +2431,28 @@ fn failWithInvalidSwitchTagCapture(sema: *Sema, block: *Block, tag_capture_src: }); } +fn failWithAmbiguousBackingIntType( + sema: *Sema, + block: *Block, + src: LazySrcLoc, + int_backed_ty: Type, + builtin_name: []const u8, +) CompileError { + const pt = sema.pt; + const zcu = pt.zcu; + return sema.failWithOwnedErrorMsg(block, msg: { + const msg = try sema.errMsg(src, "{s} is ambiguous for type '{f}'", .{ + builtin_name, int_backed_ty.fmt(pt), + }); + errdefer msg.destroy(sema.gpa); + try sema.errNote(int_backed_ty.srcLoc(zcu), msg, "backing integer type of {t} is inferred", .{ + int_backed_ty.zigTypeTag(zcu), + }); + try sema.errNote(int_backed_ty.srcLoc(zcu), msg, "consider explicitly specifying the backing integer type", .{}); + break :msg msg; + }); +} + fn failWithExpectedOptionalType(sema: *Sema, block: *Block, src: LazySrcLoc, non_optional_ty: Type) CompileError { const pt = sema.pt; const msg = msg: { @@ -4782,7 +4807,7 @@ fn failWithBadUnionFieldAccess( return sema.failWithOwnedErrorMsg(block, msg); } -pub fn addDeclaredHereNote(sema: *Sema, parent: *Zcu.ErrorMsg, decl_ty: Type) !void { +pub fn addDeclaredHereNote(sema: *Sema, parent: *Zcu.ErrorMsg, decl_ty: Type) Allocator.Error!void { const zcu = sema.pt.zcu; const src_loc = decl_ty.srcLocOrNull(zcu) orelse return; const category = switch (decl_ty.zigTypeTag(zcu)) { @@ -7853,19 +7878,12 @@ fn zirIntFromEnum(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError }, }; const enum_tag_ty = sema.typeOf(enum_tag); - const int_tag_ty = enum_tag_ty.intTagType(zcu); - - // TODO: use correct solution - // https://github.com/ziglang/zig/issues/15909 - if (enum_tag_ty.enumFieldCount(zcu) == 0 and !enum_tag_ty.isNonexhaustiveEnum(zcu)) { - return sema.fail(block, operand_src, "cannot use @intFromEnum on empty enum '{f}'", .{ - enum_tag_ty.fmt(pt), - }); - } + const int_tag_ty = enum_tag_ty.backingIntType(zcu); + assert(int_tag_ty.classify(zcu) != .no_possible_value); if (sema.resolveValue(enum_tag)) |enum_tag_val| { if (enum_tag_val.isUndef(zcu)) return pt.undefRef(int_tag_ty); - return .fromValue(enum_tag_val.intFromEnum(zcu)); + return .fromValue(enum_tag_val.backingInt(zcu)); } try sema.requireRuntimeBlock(block, src, operand_src); @@ -7891,7 +7909,7 @@ fn zirEnumFromInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError if (sema.resolveValue(operand)) |int_val| { if (dest_ty.isNonexhaustiveEnum(zcu)) { - const int_tag_ty = dest_ty.intTagType(zcu); + const int_tag_ty = dest_ty.backingIntType(zcu); if (int_val.intFitsInType(int_tag_ty, null, zcu)) { return Air.internedToRef((try pt.getCoerced(int_val, dest_ty)).toIntern()); } @@ -7910,15 +7928,11 @@ fn zirEnumFromInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError return Air.internedToRef((try pt.getCoerced(int_val, dest_ty)).toIntern()); } - if (dest_ty.intTagType(zcu).zigTypeTag(zcu) == .comptime_int) { - return sema.failWithNeededComptime(block, operand_src, .{ .simple = .casted_to_comptime_enum }); - } - if (try dest_ty.onePossibleValue(pt)) |opv| { if (block.wantSafety()) { // The operand is runtime-known but the result is comptime-known. In // this case we still need a safety check. - const expect_int = try pt.getCoerced(opv.intFromEnum(zcu), operand_ty); + const expect_int = try pt.getCoerced(opv.backingInt(zcu), operand_ty); const ok = try block.addBinOp(.cmp_eq, operand, .fromValue(expect_int)); try sema.addSafetyCheck(block, src, ok, .invalid_enum_value); } @@ -9372,7 +9386,6 @@ fn zirBitcast(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air .pointer => try sema.errNote(src, msg, "use @ptrCast to cast from '{f}'", .{operand_ty.fmt(pt)}), else => {}, } - break :msg msg; }), .array => switch (dest_ty.arrayBase(zcu)[0].zigTypeTag(zcu)) { @@ -9407,7 +9420,183 @@ fn zirBitcast(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air return sema.fail(block, operand_src, "cannot @bitCast from '{f}'", .{operand_ty.fmt(pt)}); } - return sema.bitCast(block, dest_ty, operand, block.nodeOffset(inst_data.src_node)); + operand_ty.assertHasLayout(zcu); + try sema.ensureLayoutResolved(dest_ty, src, .init); + + const operand_bits = operand_ty.bitSize(zcu); + const dest_bits = dest_ty.bitSize(zcu); + if (operand_bits != dest_bits) { + return sema.fail(block, src, "@bitCast size mismatch: destination type '{f}' has {d} bits but source type '{f}' has {d} bits", .{ + dest_ty.fmt(pt), + dest_bits, + operand_ty.fmt(pt), + operand_bits, + }); + } + + if (sema.resolveValue(operand)) |operand_val| { + const dest_is_exhaustive_enum = dest_ty.zigTypeTag(zcu) == .@"enum" and + !dest_ty.isNonexhaustiveEnum(zcu); + if (dest_is_exhaustive_enum and operand_val.isUndef(zcu)) { + return sema.failWithUseOfUndef(block, operand_src, null); + } + + const result_val = try sema.bitCastVal(operand_val, dest_ty); + + if (dest_is_exhaustive_enum and + dest_ty.enumTagFieldIndex(result_val, zcu) == null) + { + return sema.fail(block, src, "enum '{f}' has no tag with value '{f}'", .{ + dest_ty.fmt(pt), result_val.backingInt(zcu).fmtValueSema(pt, sema), + }); + } + + return .fromValue(result_val); + } + + try sema.validateRuntimeValue(block, src, operand); + + if (block.wantSafety()) { + try sema.preparePanicId(src, .invalid_enum_value); + return block.addTyOp(.bit_cast_safe, dest_ty, operand); + } + return block.addTyOp(.bit_cast, dest_ty, operand); +} + +fn zirBackingInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { + const pt = sema.pt; + const zcu = pt.zcu; + const gpa = zcu.comp.gpa; + const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node; + const operand_src = block.builtinCallArgSrc(inst_data.src_node, 0); + + const operand = sema.resolveInst(inst_data.operand); + const operand_ty = sema.typeOf(operand); + operand_ty.assertHasLayout(zcu); + const int_backed_ref: Air.Inst.Ref = ref: switch (operand_ty.zigTypeTag(zcu)) { + .@"enum" => operand, + .@"union" => { + const union_obj = zcu.intern_pool.loadUnionType(operand_ty.toIntern()); + if (union_obj.tag_usage == .tagged) break :ref try sema.unionToTag(block, operand); + if (union_obj.layout == .@"packed") break :ref operand; + return sema.failWithOwnedErrorMsg(block, msg: { + const msg = try sema.errMsg(operand_src, "non-packed union '{f}' does not have a backing integer", .{operand_ty.fmt(pt)}); + errdefer msg.deinit(gpa); + try sema.errNote(operand_src, msg, "untagged union '{f}' does not have an enum tag with a backing integer", .{operand_ty.fmt(pt)}); + try sema.addDeclaredHereNote(msg, operand_ty); + break :msg msg; + }); + }, + .@"struct" => { + if (operand_ty.containerLayout(zcu) != .@"packed") { + return sema.fail(block, operand_src, "non-packed struct '{f}' does not have a backing integer", .{ + operand_ty.fmt(pt), + }); + } + break :ref operand; + }, + else => { + return sema.fail(block, operand_src, "expected enum, tagged union, packed union or packed struct, found '{f}'", .{ + operand_ty.fmt(pt), + }); + }, + }; + const int_backed_ty = sema.typeOf(int_backed_ref); + if (int_backed_ty.backingIntMode(zcu) != .explicit and int_backed_ty.zigTypeTag(zcu) != .@"enum") + return sema.failWithAmbiguousBackingIntType(block, operand_src, int_backed_ty, "@backingInt"); + const backing_int_ty = int_backed_ty.backingIntType(zcu); + + if (sema.resolveValue(int_backed_ref)) |int_backed_val| { + if (int_backed_val.isUndef(zcu)) return pt.undefRef(backing_int_ty); + return .fromValue(int_backed_val.backingInt(zcu)); + } + + switch (backing_int_ty.classify(zcu)) { + .partially_comptime, .fully_comptime => unreachable, // does not apply to integers + .no_possible_value => unreachable, // enum also NPV, cannot instantiate NPV types + .one_possible_value => unreachable, // enum or bitpack also OPV, should have been resolve above + .runtime => {}, + } + + return block.addTyOp(.bit_cast, backing_int_ty, int_backed_ref); +} + +fn zirFromBackingIntArgTy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { + const pt = sema.pt; + const zcu = pt.zcu; + const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node; + const src = block.nodeOffset(inst_data.src_node); + + const dest_ty = try sema.resolveDestType(block, src, inst_data.operand, .remove_eu_opt, "@fromBackingInt"); + try sema.ensureLayoutResolved(dest_ty, src, .init); + switch (dest_ty.zigTypeTag(zcu)) { + .@"enum" => {}, + .@"struct", .@"union" => |type_tag| if (dest_ty.containerLayout(zcu) != .@"packed") { + return sema.fail(block, src, "non-packed {t} '{f}' does not have a backing integer", .{ + type_tag, dest_ty.fmt(pt), + }); + }, + else => { + return sema.fail(block, src, "expected enum, packed union or packed struct, found '{f}'", .{ + dest_ty.fmt(pt), + }); + }, + } + if (dest_ty.backingIntMode(zcu) != .explicit and dest_ty.zigTypeTag(zcu) != .@"enum") + return sema.failWithAmbiguousBackingIntType(block, src, dest_ty, "@fromBackingInt"); + return .fromType(dest_ty.backingIntType(zcu)); +} + +fn zirFromBackingInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { + const pt = sema.pt; + const zcu = pt.zcu; + const ip = &zcu.intern_pool; + + const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node; + const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data; + const src = block.nodeOffset(inst_data.src_node); + const operand_src = block.builtinCallArgSrc(inst_data.src_node, 0); + + // Type has already been validated and layout-resolved by `zirFromBackingIntArgTy`. + const dest_ty = try sema.resolveDestType(block, .unneeded, extra.lhs, .remove_eu_opt, undefined); + dest_ty.assertHasLayout(zcu); + + const operand = sema.resolveInst(extra.rhs); + const backing_int_ref = try sema.coerce(block, dest_ty.backingIntType(zcu), operand, operand_src); + + if (sema.resolveValue(backing_int_ref)) |backing_int_val| { + if (dest_ty.zigTypeTag(zcu) != .@"enum") { + // we don't do any safety checks for bitpacks + if (backing_int_val.isUndef(zcu)) return pt.undefRef(dest_ty); + return .fromValue(try pt.bitpackValue(dest_ty, backing_int_val)); + } + const enum_obj = ip.loadEnumType(dest_ty.toIntern()); + if (backing_int_val.isUndef(zcu)) { + if (enum_obj.nonexhaustive) return pt.undefRef(dest_ty); + return sema.failWithUseOfUndef(block, operand_src, null); + } + if (!enum_obj.nonexhaustive and + enum_obj.tagValueIndex(ip, backing_int_val.toIntern()) == null) + { + return sema.fail(block, src, "enum '{f}' has no tag with value '{f}'", .{ + dest_ty.fmt(pt), backing_int_val.fmtValueSema(pt, sema), + }); + } + return .fromValue(try pt.enumValue(dest_ty, backing_int_val)); + } + + switch (dest_ty.classify(zcu)) { + .partially_comptime, .fully_comptime => unreachable, // does not apply to enums or bitpacks + .no_possible_value => unreachable, // backing int also NPV, cannot coerce to NPV type + .one_possible_value => unreachable, // backing int also OPV, should have been resolve above + .runtime => {}, + } + + if (block.wantSafety()) { + try sema.preparePanicId(src, .invalid_enum_value); + return block.addTyOp(.bit_cast_safe, dest_ty, backing_int_ref); + } + return block.addTyOp(.bit_cast, dest_ty, backing_int_ref); } fn zirFloatCast(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -9940,12 +10129,8 @@ fn analyzeSwitchBlock( operand_ty.containerLayout(zcu) != .@"packed"; const err_set = operand_ty.zigTypeTag(zcu) == .error_set; - if (item_ty.zigTypeTag(zcu) == .@"enum" and - validated_switch.seen_enum_fields.len == 0 and - !operand_ty.isNonexhaustiveEnum(zcu)) - { - return .void_value; // switch on empty enum/union - } + // TODO audit the `err_set` special case (https://github.com/ziglang/zig/issues/15909) + if (!err_set) assert(validated_switch.case_vals.len != 0 or has_else or zir_switch.has_under); // NPV types cannot be instantiated const cond_ref = switch (operand) { .simple => |s| s.cond, @@ -10451,26 +10636,11 @@ fn finishSwitchBr( } var prev_result_overflowed = false; - while (item.compareScalar(.lte, item_last, operand_ty, zcu)) : ({ - const int_val: Value, const int_ty: Type = switch (operand_ty.zigTypeTag(zcu)) { - .int => .{ item, operand_ty }, - .@"enum" => b: { - const int_val: Value = .fromInterned(ip.indexToKey(item.toIntern()).enum_tag.int); - break :b .{ int_val, int_val.typeOf(zcu) }; - }, - else => unreachable, - }; + while (item.compareScalar(.lte, item_last, item_ty, zcu)) : ({ assert(!prev_result_overflowed); - const result = try arith.incrementDefinedInt(sema, int_ty, int_val); + const result = try arith.incrementDefinedInt(sema, item_ty, item); prev_result_overflowed = result.overflow; - item = switch (operand_ty.zigTypeTag(zcu)) { - .int => result.val, - .@"enum" => .fromInterned(try pt.intern(.{ .enum_tag = .{ - .ty = operand_ty.toIntern(), - .int = result.val.toIntern(), - } })), - else => unreachable, - }; + item = result.val; }) { cases_len += 1; case_block.instructions.clearRetainingCapacity(); @@ -10603,7 +10773,7 @@ fn finishSwitchBr( break :check_enumerable .{ undefined, min_int }; }, .@"union", .@"struct" => { - const backing_int_ty = item_ty.bitpackBackingInt(zcu); + const backing_int_ty = item_ty.backingIntType(zcu); const min_backing_int = try backing_int_ty.minInt(pt, backing_int_ty); break :check_enumerable .{ undefined, min_backing_int }; }, @@ -10904,7 +11074,7 @@ const ValidatedSwitchBlock = struct { var cur_val = it.next_val orelse return null; const int_ty = switch (type_tag) { .int => item_ty, - .@"union", .@"struct" => item_ty.bitpackBackingInt(zcu), + .@"union", .@"struct" => item_ty.backingIntType(zcu), else => unreachable, }; while (it.next_idx < it.seen_ranges.len and @@ -11334,7 +11504,7 @@ fn validateSwitchBlock( check_range: { const int_ty = switch (type_tag) { .int => item_ty, - .@"union", .@"struct" => item_ty.bitpackBackingInt(zcu), + .@"union", .@"struct" => item_ty.backingIntType(zcu), else => unreachable, }; const min_int = try int_ty.minInt(pt, int_ty); @@ -11956,14 +12126,7 @@ fn analyzeSwitchCaptures( try sema.analyzeUnreachable(case_block, operand_src, false); break :payload_ref .unreachable_value; }; - if (sema.resolveValue(loaded_operand)) |err_val| { - break :payload_ref .fromIntern(try pt.intern(.{ .err = .{ - .ty = capture_err_ty.toIntern(), - .name = zcu.intern_pool.indexToKey(err_val.toIntern()).err.name, - } })); - } else { - break :payload_ref try case_block.addTyOp(.error_cast, capture_err_ty, loaded_operand); - } + break :payload_ref try sema.errorCastUnchecked(case_block, capture_err_ty, loaded_operand); }, .item_refs => |item_refs| { var names: InferredErrorSet.NameMap = .{}; @@ -11973,14 +12136,7 @@ fn analyzeSwitchCaptures( names.putAssumeCapacityNoClobber(item_val.getErrorName(zcu).unwrap().?, {}); } const capture_err_ty = try pt.errorSetFromUnsortedNames(names.keys()); - if (sema.resolveValue(loaded_operand)) |err_val| { - break :payload_ref .fromIntern(try pt.intern(.{ .err = .{ - .ty = capture_err_ty.toIntern(), - .name = zcu.intern_pool.indexToKey(err_val.toIntern()).err.name, - } })); - } else { - break :payload_ref try case_block.addTyOp(.error_cast, capture_err_ty, loaded_operand); - } + break :payload_ref try sema.errorCastUnchecked(case_block, capture_err_ty, loaded_operand); }, } } @@ -12461,7 +12617,7 @@ fn validateSwitchItemOrRange( .first = .fromInterned(backing_int_val), .last = .fromInterned(backing_int_val), .src = item_src, - }, item_ty.bitpackBackingInt(zcu), zcu); + }, item_ty.backingIntType(zcu), zcu); }, .enum_literal, .@"fn", .pointer, .type => { break :maybe_prev_src if (seen_sparse_values.fetchPutAssumeCapacity(item_val.toIntern(), item_src)) |prev| @@ -18418,7 +18574,7 @@ fn zirUnionInit(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A const payload = try sema.coerce(block, field_ty, sema.resolveInst(extra.init), payload_src); if (union_ty.containerLayout(zcu) == .@"packed") { - return sema.bitCast(block, union_ty, payload, block.nodeOffset(inst_data.src_node)); + return sema.bitCastUnchecked(block, union_ty, payload); } if (sema.resolveValue(payload)) |payload_val| { @@ -18555,7 +18711,7 @@ fn zirStructInit( const init_inst = try sema.coerce(block, field_ty, uncoerced_init_inst, field_src); if (resolved_ty.containerLayout(zcu) == .@"packed") { - const union_val = try sema.bitCast(block, resolved_ty, init_inst, src); + const union_val = try sema.bitCastUnchecked(block, resolved_ty, init_inst); const result_val = try sema.coerce(block, result_ty, union_val, src); if (is_ref) { return sema.analyzeRef(block, src, result_val, .none); @@ -19567,14 +19723,6 @@ fn zirTagName(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air operand_ty.fmt(pt), }), }; - if (enum_ty.enumFieldCount(zcu) == 0) { - // TODO I don't think this is the correct way to handle this but - // it prevents a crash. - // https://github.com/ziglang/zig/issues/15909 - return sema.fail(block, operand_src, "cannot get @tagName of empty enum '{f}'", .{ - enum_ty.fmt(pt), - }); - } const casted_operand = try sema.coerce(block, enum_ty, operand, operand_src); if (try sema.resolveDefinedValue(block, operand_src, casted_operand)) |val| { const field_index = enum_ty.enumTagFieldIndex(val, zcu) orelse { @@ -29742,41 +29890,29 @@ fn storePtrVal( } } -fn bitCast( +/// Asserts that the layout of `dest_ty` is already resolved. +fn bitCastUnchecked( sema: *Sema, block: *Block, dest_ty: Type, inst: Air.Inst.Ref, - inst_src: LazySrcLoc, ) CompileError!Air.Inst.Ref { - const pt = sema.pt; - const zcu = pt.zcu; + const zcu = sema.pt.zcu; const old_ty = sema.typeOf(inst); old_ty.assertHasLayout(zcu); - try sema.ensureLayoutResolved(dest_ty, inst_src, .init); + dest_ty.assertHasLayout(zcu); assert(old_ty.hasBitRepresentation(zcu)); assert(dest_ty.hasBitRepresentation(zcu)); assert(old_ty.scalarType(zcu).zigTypeTag(zcu) != .pointer); assert(dest_ty.scalarType(zcu).zigTypeTag(zcu) != .pointer); - - const dest_bits = dest_ty.bitSize(zcu); - const old_bits = old_ty.bitSize(zcu); - - if (old_bits != dest_bits) { - return sema.fail(block, inst_src, "@bitCast size mismatch: destination type '{f}' has {d} bits but source type '{f}' has {d} bits", .{ - dest_ty.fmt(pt), - dest_bits, - old_ty.fmt(pt), - old_bits, - }); - } + assert(old_ty.bitSize(zcu) == dest_ty.bitSize(zcu)); if (sema.resolveValue(inst)) |val| { return .fromValue(try sema.bitCastVal(val, dest_ty)); } - try sema.validateRuntimeValue(block, inst_src, inst); + return block.addTyOp(.bit_cast, dest_ty, inst); } @@ -29800,6 +29936,26 @@ pub fn bitCastVal( } } +fn errorCastUnchecked( + sema: *Sema, + block: *Block, + dest_ty: Type, + inst: Air.Inst.Ref, +) CompileError!Air.Inst.Ref { + const pt = sema.pt; + const zcu = pt.zcu; + assert(dest_ty.zigTypeTag(zcu) == .error_set); + assert(sema.typeOf(inst).zigTypeTag(zcu) == .error_set); + if (sema.resolveValue(inst)) |val| { + if (val.isUndef(zcu)) return pt.undefRef(dest_ty); + return .fromIntern(try pt.intern(.{ .err = .{ + .ty = dest_ty.toIntern(), + .name = zcu.intern_pool.indexToKey(val.toIntern()).err.name, + } })); + } + return block.addTyOp(.error_cast, dest_ty, inst); +} + fn checkSpirvSliceAllowed( sema: *Sema, block: *Block, @@ -33968,14 +34124,6 @@ fn intFromFloatScalar( return pt.getCoerced(cti_result, int_ty); } -fn intInRange(sema: *Sema, tag_ty: Type, int_val: Value, end: usize) !bool { - const pt = sema.pt; - if (!int_val.compareAllWithZero(.gte, pt.zcu)) return false; - const end_val = try pt.intValue(tag_ty, end); - if (!(try sema.compareAll(int_val, .lt, end_val, tag_ty))) return false; - return true; -} - /// Asserts the type is an exhaustive enum. fn enumHasInt(sema: *Sema, ty: Type, int: Value) CompileError!bool { const pt = sema.pt; @@ -33985,6 +34133,7 @@ fn enumHasInt(sema: *Sema, ty: Type, int: Value) CompileError!bool { // The `tagValueIndex` function call below relies on the type being the integer tag type. // `getCoerced` assumes the value will fit the new type. const int_tag_ty: Type = .fromInterned(enum_type.int_tag_type); + if (int_tag_ty.classify(zcu) == .no_possible_value) return false; if (!int.intFitsInType(int_tag_ty, null, zcu)) return false; const int_coerced = try pt.getCoerced(int, int_tag_ty); return enum_type.tagValueIndex(&zcu.intern_pool, int_coerced.toIntern()) != null; diff --git a/src/Sema/reinterpret.zig b/src/Sema/reinterpret.zig index 21a5bad1abd72afc1a6c8982e9caadd1825ea824..59e2626221c8f22c4be7bfb1e4c5086b9bf20aae 100644 --- a/src/Sema/reinterpret.zig +++ b/src/Sema/reinterpret.zig @@ -353,7 +353,7 @@ const PackValueBytes = struct { return pt.aggregateValue(ty, elems); }, .@"packed" => { - const backing_int_val = try pack.primitive(ty.bitpackBackingInt(zcu)); + const backing_int_val = try pack.primitive(ty.backingIntType(zcu)); if (backing_int_val.isUndef(zcu)) return pt.undefValue(ty); return pt.bitpackValue(ty, backing_int_val); }, @@ -424,15 +424,15 @@ const PackValueBytes = struct { } }, .@"packed" => { - const backing_int_val = try pack.primitive(ty.bitpackBackingInt(zcu)); + const backing_int_val = try pack.primitive(ty.backingIntType(zcu)); if (backing_int_val.isUndef(zcu)) return pt.undefValue(ty); return pt.bitpackValue(ty, backing_int_val); }, }, .@"enum" => { - const tag_int_val = try pack.primitive(ty.intTagType(zcu)); + const tag_int_val = try pack.primitive(ty.backingIntType(zcu)); if (tag_int_val.isUndef(zcu)) return pt.undefValue(ty); - return pt.enumValue(ty, tag_int_val.toIntern()); + return pt.enumValue(ty, tag_int_val); }, else => return pack.primitive(ty), } diff --git a/src/Sema/type_resolution.zig b/src/Sema/type_resolution.zig index 116ee444f05dc2c575f6b2481378c8f11c8d9fd0..6cdd65667aae567a7939c7a1a7b95831cc39672e 100644 --- a/src/Sema/type_resolution.zig +++ b/src/Sema/type_resolution.zig @@ -63,7 +63,7 @@ pub const LayoutResolveReason = enum { .@"export" => "for export here", .@"extern" => "for extern declaration here", .asm_out_type => "for inline assembly output type declared here", - .std_lang_type => "from 'std.lang'", + .std_lang_type => "from 'std.lang'", // zig fmt: on }; } @@ -1325,15 +1325,31 @@ pub fn resolveEnumLayout(sema: *Sema, enum_ty: Type) CompileError!void { const type_ref = try sema.resolveInlineBody(&block, tag_type_body, zir_index); break :ty try sema.analyzeAsType(&block, tag_type_src, .enum_int_tag_type, type_ref); }; + const empty_exhaustive = enum_obj.field_names.len == 0 and !enum_obj.nonexhaustive; const int_tag_ty: Type = if (explicit_int_tag_ty) |int_tag_ty| ty: { - if (int_tag_ty.zigTypeTag(zcu) != .int) return sema.fail( - &block, - block.src(.container_arg), - "expected integer tag type, found '{f}'", - .{int_tag_ty.fmt(pt)}, - ); + switch (int_tag_ty.zigTypeTag(zcu)) { + .int => if (empty_exhaustive) return sema.fail( + &block, + block.src(.container_arg), + "empty exhaustive enums must be backed by 'noreturn'", + .{}, + ), + .noreturn => if (!empty_exhaustive) return sema.fail( + &block, + block.src(.container_arg), + "non-empty enums cannot be backed by 'noreturn'", + .{}, + ), + else => return sema.fail( + &block, + block.src(.container_arg), + "expected integer tag type, found '{f}'", + .{int_tag_ty.fmt(pt)}, + ), + } break :ty int_tag_ty; } else ty: { + if (empty_exhaustive) break :ty .noreturn; // Infer the int tag type from the field count const bits = Type.smallestUnsignedBits(enum_obj.field_names.len -| 1); break :ty try pt.intType(.unsigned, bits); diff --git a/src/Type.zig b/src/Type.zig index 6372c1e157172f6a7b4ee97f3d441af76e9a500b..37476ceb060a007d1b21d0038307ddff842bbe87 100644 --- a/src/Type.zig +++ b/src/Type.zig @@ -1580,15 +1580,28 @@ pub fn containerLayout(ty: Type, zcu: *const Zcu) std.lang.Type.ContainerLayout }; } -pub fn bitpackBackingInt(ty: Type, zcu: *const Zcu) Type { +/// Asserts that the type is either an enum or a bitpack. +pub fn backingIntType(ty: Type, zcu: *const Zcu) Type { const ip = &zcu.intern_pool; return switch (ip.indexToKey(ty.toIntern())) { + .enum_type => .fromInterned(ip.loadEnumType(ty.toIntern()).int_tag_type), .struct_type => .fromInterned(ip.loadStructType(ty.toIntern()).packed_backing_int_type), .union_type => .fromInterned(ip.loadUnionType(ty.toIntern()).packed_backing_int_type), else => unreachable, }; } +/// For unions, returns the *backing int* mode, not the *enum tag* mode. +pub fn backingIntMode(ty: Type, zcu: *const Zcu) InternPool.BackingTypeMode { + const ip = &zcu.intern_pool; + return switch (ip.indexToKey(ty.toIntern())) { + .enum_type => ip.loadEnumType(ty.toIntern()).int_tag_mode, + .struct_type => ip.loadStructType(ty.toIntern()).packed_backing_mode, + .union_type => ip.loadUnionType(ty.toIntern()).packed_backing_mode, + else => unreachable, + }; +} + /// Asserts that the type is an error union. pub fn errorUnionPayload(ty: Type, zcu: *const Zcu) Type { return Type.fromInterned(zcu.intern_pool.indexToKey(ty.toIntern()).error_union_type.payload_type); @@ -2093,11 +2106,8 @@ pub fn onePossibleValue(ty: Type, pt: Zcu.PerThread) !?Value { return try pt.unionValue(ty, tag_val, payload_val); } else unreachable; }, - .enum_type => if (try ty.intTagType(zcu).onePossibleValue(pt)) |int_tag_opv| { - return .fromInterned(try pt.intern(.{ .enum_tag = .{ - .ty = ty.toIntern(), - .int = int_tag_opv.toIntern(), - } })); + .enum_type => if (try ty.backingIntType(zcu).onePossibleValue(pt)) |int_tag_opv| { + return try pt.enumValue(ty, int_tag_opv); } else null, // values, not types @@ -2274,17 +2284,6 @@ pub fn maxIntScalar(ty: Type, pt: Zcu.PerThread, dest_ty: Type) !Value { return pt.intValue_big(dest_ty, res.toConst()); } -/// Asserts the type is an enum or a union. -pub fn intTagType(ty: Type, zcu: *const Zcu) Type { - const ip = &zcu.intern_pool; - const enum_ty: Type = switch (ip.indexToKey(ty.toIntern())) { - .union_type => .fromInterned(ip.loadUnionType(ty.toIntern()).enum_tag_type), - .enum_type => ty, - else => unreachable, - }; - return .fromInterned(ip.loadEnumType(enum_ty.toIntern()).int_tag_type); -} - pub fn isNonexhaustiveEnum(ty: Type, zcu: *const Zcu) bool { const ip = &zcu.intern_pool; return switch (ip.indexToKey(ty.toIntern())) { @@ -3058,8 +3057,11 @@ pub fn unpackable(ty: Type, zcu: *const Zcu) ?UnpackableReason { .one, .many, .c => .pointer, }, - .@"enum" => switch (zcu.intern_pool.loadEnumType(ty.toIntern()).int_tag_mode) { - .explicit => null, + .@"enum" => switch (ty.backingIntMode(zcu)) { + .explicit => switch (ty.backingIntType(zcu).toIntern()) { + else => null, + .noreturn_type => .other, + }, .auto => .{ .enum_inferred_int_tag = ty }, }, @@ -3225,7 +3227,11 @@ pub fn hasBitRepresentation(ty: Type, zcu: *const Zcu) bool { .float, => true, - .@"enum" => zcu.intern_pool.loadEnumType(ty.toIntern()).int_tag_mode == .explicit, + .@"enum" => { + const enum_obj = zcu.intern_pool.loadEnumType(ty.toIntern()); + return enum_obj.int_tag_mode == .explicit and + enum_obj.int_tag_type != .noreturn_type; + }, .pointer, .optional => ty.isPtrAtRuntime(zcu), .@"struct", .@"union" => ty.containerLayout(zcu) == .@"packed", diff --git a/src/Value.zig b/src/Value.zig index 7db732c60e38d5a10353a230a1552530f58e7592..adcd7abfce33e2a3c40e5d405e3dc49c67e0769d 100644 --- a/src/Value.zig +++ b/src/Value.zig @@ -146,8 +146,13 @@ pub fn toType(self: Value) Type { return Type.fromInterned(self.toIntern()); } -pub fn intFromEnum(val: Value, zcu: *const Zcu) Value { - return .fromInterned(zcu.intern_pool.indexToKey(val.toIntern()).enum_tag.int); +/// Asserts that value is defined and of enum or bitpack type. +pub fn backingInt(val: Value, zcu: *const Zcu) Value { + return switch (zcu.intern_pool.indexToKey(val.toIntern())) { + .enum_tag => |enum_tag| .fromInterned(enum_tag.int), + .bitpack => |bitpack| .fromInterned(bitpack.backing_int_val), + else => unreachable, + }; } /// Asserts that `val` is an integer. @@ -415,7 +420,7 @@ pub fn writeToPackedMemory( } }, .@"enum" => { - const int_val = val.intFromEnum(zcu); + const int_val = val.backingInt(zcu); int_val.writeToPackedMemory(zcu, buffer, bit_offset); }, .int => { @@ -564,7 +569,7 @@ pub fn readFromPackedMemory( return pt.intValue_big(ty, bigint.toConst()); }, .@"enum" => { - const int_ty = ty.intTagType(zcu); + const int_ty = ty.backingIntType(zcu); const int_val: Value = try .readFromPackedMemory(int_ty, pt, buffer, bit_offset); return pt.getCoerced(int_val, ty); }, @@ -581,7 +586,7 @@ pub fn readFromPackedMemory( } })), .@"struct", .@"union" => { assert(ty.containerLayout(zcu) == .@"packed"); - const int_val: Value = try .readFromPackedMemory(ty.bitpackBackingInt(zcu), pt, buffer, bit_offset); + const int_val: Value = try .readFromPackedMemory(ty.backingIntType(zcu), pt, buffer, bit_offset); return pt.bitpackValue(ty, int_val); }, .array, .vector => { @@ -2368,7 +2373,7 @@ pub fn uninterpret(val: anytype, ty: Type, pt: Zcu.PerThread) error{ OutOfMemory try pt.nullValue(ty), .@"enum" => switch (interpret_mode) { - .direct => try pt.enumValue(ty, (try uninterpret(@intFromEnum(val), ty.intTagType(zcu), pt)).toIntern()), + .direct => try pt.enumValue(ty, try uninterpret(@intFromEnum(val), ty.backingIntType(zcu), pt)), .by_name => { const field_name_ip = try ip.getOrPutString(zcu.gpa, io, pt.tid, @tagName(val), .no_embedded_nulls); const field_idx = ty.enumFieldIndex(field_name_ip, zcu) orelse return error.TypeMismatch; diff --git a/src/Zcu/PerThread.zig b/src/Zcu/PerThread.zig index 62f11a83f671cc7b2ea3b20c7b6e0e94f342d354..a839c03685ae6b64389e7b72751ed5e109a87399 100644 --- a/src/Zcu/PerThread.zig +++ b/src/Zcu/PerThread.zig @@ -3956,7 +3956,7 @@ pub fn getCoerced(pt: Zcu.PerThread, val: Value, new_ty: Type) Allocator.Error!V } pub fn intType(pt: Zcu.PerThread, signedness: std.lang.Signedness, bits: u16) Allocator.Error!Type { - return Type.fromInterned(try pt.intern(.{ .int_type = .{ + return .fromInterned(try pt.intern(.{ .int_type = .{ .signedness = signedness, .bits = bits, } })); @@ -3967,15 +3967,15 @@ pub fn errorIntType(pt: Zcu.PerThread) std.mem.Allocator.Error!Type { } pub fn arrayType(pt: Zcu.PerThread, info: InternPool.Key.ArrayType) Allocator.Error!Type { - return Type.fromInterned(try pt.intern(.{ .array_type = info })); + return .fromInterned(try pt.intern(.{ .array_type = info })); } pub fn vectorType(pt: Zcu.PerThread, info: InternPool.Key.VectorType) Allocator.Error!Type { - return Type.fromInterned(try pt.intern(.{ .vector_type = info })); + return .fromInterned(try pt.intern(.{ .vector_type = info })); } pub fn optionalType(pt: Zcu.PerThread, child_type: InternPool.Index) Allocator.Error!Type { - return Type.fromInterned(try pt.intern(.{ .opt_type = child_type })); + return .fromInterned(try pt.intern(.{ .opt_type = child_type })); } pub fn ptrType(pt: Zcu.PerThread, info: InternPool.Key.PtrType) Allocator.Error!Type { @@ -3997,7 +3997,7 @@ pub fn ptrType(pt: Zcu.PerThread, info: InternPool.Key.PtrType) Allocator.Error! _ => assert(@intFromEnum(info.flags.vector_index) < info.packed_offset.host_size), } - return Type.fromInterned(try pt.intern(.{ .ptr_type = canon_info })); + return .fromInterned(try pt.intern(.{ .ptr_type = canon_info })); } pub fn singleMutPtrType(pt: Zcu.PerThread, child_type: Type) Allocator.Error!Type { @@ -4037,11 +4037,11 @@ pub fn funcType(pt: Zcu.PerThread, key: InternPool.GetFuncTypeKey) Allocator.Err /// Use this for `anyframe->T` only. /// For `anyframe`, use the `InternPool.Index.anyframe` tag directly. pub fn anyframeType(pt: Zcu.PerThread, payload_ty: Type) Allocator.Error!Type { - return Type.fromInterned(try pt.intern(.{ .anyframe_type = payload_ty.toIntern() })); + return .fromInterned(try pt.intern(.{ .anyframe_type = payload_ty.toIntern() })); } pub fn errorUnionType(pt: Zcu.PerThread, error_set_ty: Type, payload_ty: Type) Allocator.Error!Type { - return Type.fromInterned(try pt.intern(.{ .error_union_type = .{ + return .fromInterned(try pt.intern(.{ .error_union_type = .{ .error_set_type = error_set_ty.toIntern(), .payload_type = payload_ty.toIntern(), } })); @@ -4050,7 +4050,7 @@ pub fn errorUnionType(pt: Zcu.PerThread, error_set_ty: Type, payload_ty: Type) A pub fn singleErrorSetType(pt: Zcu.PerThread, name: InternPool.NullTerminatedString) Allocator.Error!Type { const names: *const [1]InternPool.NullTerminatedString = &name; const comp = pt.zcu.comp; - return Type.fromInterned(try pt.zcu.intern_pool.getErrorSetType(comp.gpa, comp.io, pt.tid, names)); + return .fromInterned(try pt.zcu.intern_pool.getErrorSetType(comp.gpa, comp.io, pt.tid, names)); } /// Sorts `names` in place. @@ -4066,7 +4066,7 @@ pub fn errorSetFromUnsortedNames( ); const comp = pt.zcu.comp; const new_ty = try pt.zcu.intern_pool.getErrorSetType(comp.gpa, comp.io, pt.tid, names); - return Type.fromInterned(new_ty); + return .fromInterned(new_ty); } /// Supports only pointers, not pointer-like optionals. @@ -4074,7 +4074,7 @@ pub fn ptrIntValue(pt: Zcu.PerThread, ty: Type, x: u64) Allocator.Error!Value { const zcu = pt.zcu; assert(ty.zigTypeTag(zcu) == .pointer and !ty.isSlice(zcu)); assert(x != 0 or ty.isAllowzeroPtr(zcu)); - return Value.fromInterned(try pt.intern(.{ .ptr = .{ + return .fromInterned(try pt.intern(.{ .ptr = .{ .ty = ty.toIntern(), .base_addr = .int, .byte_offset = x, @@ -4082,14 +4082,11 @@ pub fn ptrIntValue(pt: Zcu.PerThread, ty: Type, x: u64) Allocator.Error!Value { } /// Creates an enum tag value based on the integer tag value. -pub fn enumValue(pt: Zcu.PerThread, ty: Type, tag_int: InternPool.Index) Allocator.Error!Value { - if (std.debug.runtime_safety) { - const tag = ty.zigTypeTag(pt.zcu); - assert(tag == .@"enum"); - } - return Value.fromInterned(try pt.intern(.{ .enum_tag = .{ +pub fn enumValue(pt: Zcu.PerThread, ty: Type, tag_int: Value) Allocator.Error!Value { + if (std.debug.runtime_safety) assert(ty.zigTypeTag(pt.zcu) == .@"enum"); + return .fromInterned(try pt.intern(.{ .enum_tag = .{ .ty = ty.toIntern(), - .int = tag_int, + .int = tag_int.toIntern(), } })); } @@ -4103,7 +4100,7 @@ pub fn enumValueFieldIndex(pt: Zcu.PerThread, ty: Type, field_index: u32) Alloca if (enum_type.field_values.len == 0) { // Auto-numbered fields. - return Value.fromInterned(try pt.intern(.{ .enum_tag = .{ + return .fromInterned(try pt.intern(.{ .enum_tag = .{ .ty = ty.toIntern(), .int = try pt.intern(.{ .int = .{ .ty = enum_type.int_tag_type, @@ -4262,7 +4259,7 @@ pub fn floatValue(pt: Zcu.PerThread, ty: Type, x: anytype) Allocator.Error!Value /// Create a value whose type is a `packed struct` or `packed union`, from the backing integer value. pub fn bitpackValue(pt: Zcu.PerThread, ty: Type, backing_int_val: Value) Allocator.Error!Value { - assert(backing_int_val.typeOf(pt.zcu).toIntern() == ty.bitpackBackingInt(pt.zcu).toIntern()); + assert(backing_int_val.typeOf(pt.zcu).toIntern() == ty.backingIntType(pt.zcu).toIntern()); return .fromInterned(try pt.intern(.{ .bitpack = .{ .ty = ty.toIntern(), .backing_int_val = backing_int_val.toIntern(), diff --git a/src/codegen.zig b/src/codegen.zig index 55d73b476a7a9a9a89a18fc4c8be9b8e139ac6c3..6872fbe84c4d0f1d3889bab30e3d65b6c9308968 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -415,7 +415,7 @@ pub fn generateSymbol( } }, .enum_tag => |enum_tag| { - const int_tag_ty = ty.intTagType(zcu); + const int_tag_ty = ty.backingIntType(zcu); try generateSymbol(bin_file, pt, try pt.getCoerced(Value.fromInterned(enum_tag.int), int_tag_ty), w, reloc_parent); }, .float => |float| storage: switch (float.storage) { diff --git a/src/codegen/aarch64.zig b/src/codegen/aarch64.zig index 14a718b871b5a48b00a81f92a235ea3eebd303f1..690c9a49747c292c8a25df1983fc1074b91f8b39 100644 --- a/src/codegen/aarch64.zig +++ b/src/codegen/aarch64.zig @@ -5,8 +5,10 @@ pub const encoding = @import("aarch64/encoding.zig"); pub const Mir = @import("aarch64/Mir.zig"); pub const Select = @import("aarch64/Select.zig"); -pub fn legalizeFeatures(_: *const std.Target) ?*Air.Legalize.Features { - return null; +pub fn legalizeFeatures(_: *const std.Target) *const Air.Legalize.Features { + return comptime &.initMany(&.{ + .expand_bit_cast_safe, + }); } pub fn generate( diff --git a/src/codegen/aarch64/Select.zig b/src/codegen/aarch64/Select.zig index b74c7fe9ec580071a887498cd8ab0ef04e287257..9a832f2c858698f1f790c82f138cd583ecf91a95 100644 --- a/src/codegen/aarch64/Select.zig +++ b/src/codegen/aarch64/Select.zig @@ -362,6 +362,7 @@ pub fn analyze(isel: *Select, air_body: []const Air.Inst.Index) !void { air_inst_index = air_body[air_body_index]; continue :air_tag air_tags[@intFromEnum(air_inst_index)]; }, + .bit_cast_safe => unreachable, // legalized inline .block, .dbg_inline_block => |air_tag| { const air_body_block = switch (air_tag) { else => comptime unreachable, @@ -3201,6 +3202,7 @@ pub fn body(isel: *Select, air_body: []const Air.Inst.Index) error{ OutOfMemory, if (air.next()) |next_air_tag| continue :air_tag next_air_tag; }, .bit_cast, + .bit_cast_safe, // TODO safety check .ptr_cast, .ptr_from_int, .int_from_ptr, diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 3cf7d9c2fbc2921c3f7745c8f020520f464e380e..bf1d0ab00fb16a94f4144f3d6d6dd5f7a54fac67 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -27,6 +27,7 @@ pub fn legalizeFeatures(_: *const std.Target) ?*const Air.Legalize.Features { return comptime switch (dev.env.supports(.legalize)) { inline false, true => |supports_legalize| &.init(.{ // we don't currently ask zig1 to use safe optimization modes + .expand_bit_cast_safe = supports_legalize, .expand_int_cast_safe = supports_legalize, .expand_int_from_float_safe = supports_legalize, .expand_int_from_float_optimized_safe = supports_legalize, @@ -1460,7 +1461,7 @@ pub const DeclGen = struct { } return w.writeByte('}'); }, - .@"packed" => return dg.renderUndefValue(w, ty.bitpackBackingInt(zcu), location), + .@"packed" => return dg.renderUndefValue(w, ty.backingIntType(zcu), location), } }, .tuple_type => |tuple_info| { @@ -1520,7 +1521,7 @@ pub const DeclGen = struct { if (loaded_union.has_runtime_tag) try w.writeByte(' '); if (loaded_union.layout == .auto) try w.writeByte('}'); }, - .@"packed" => return dg.renderUndefValue(w, ty.bitpackBackingInt(zcu), location), + .@"packed" => return dg.renderUndefValue(w, ty.backingIntType(zcu), location), } }, .error_union_type => |error_union| { @@ -2876,6 +2877,7 @@ fn genBodyInner(f: *Function, body: []const Air.Inst.Index) Error!void { .add_safe, .sub_safe, .mul_safe, + .bit_cast_safe, .int_cast_safe, .int_from_float_safe, .int_from_float_optimized_safe, diff --git a/src/codegen/c/type.zig b/src/codegen/c/type.zig index 2944d1092e2c119cfd91eeb2e7221b67437cd398..8588c1dd030849f586a2c5ad719087ab6e4412e7 100644 --- a/src/codegen/c/type.zig +++ b/src/codegen/c/type.zig @@ -484,8 +484,7 @@ pub const CType = union(enum) { pub fn classifyInt(ty: Type, zcu: *const Zcu) IntClass { const int_ty: Type = switch (ty.zigTypeTag(zcu)) { .error_set => return classifyBitInt(.unsigned, zcu.errorSetBits(), zcu), - .@"enum" => ty.intTagType(zcu), - .@"struct", .@"union" => ty.bitpackBackingInt(zcu), + .@"enum", .@"struct", .@"union" => ty.backingIntType(zcu), .int => ty, else => unreachable, }; diff --git a/src/codegen/c/type/render_defs.zig b/src/codegen/c/type/render_defs.zig index c8607d748ccf0d3c1f0860c1a3ce3ea38f0479f4..03aae41ee0ec47cca9b560bd358263a5b0cb2352 100644 --- a/src/codegen/c/type/render_defs.zig +++ b/src/codegen/c/type/render_defs.zig @@ -212,7 +212,7 @@ pub fn defineComplete( }, .@"enum" => { const name_cty: CType = .{ .@"enum" = ty }; - const cty: CType = try .lower(ty.intTagType(zcu), deps, arena, zcu); + const cty: CType = try .lower(ty.backingIntType(zcu), deps, arena, zcu); try w.print("typedef {f}{f}{f}; /* {f} */\n", .{ cty.fmtDeclaratorPrefix(zcu), name_cty.fmtTypeName(zcu), @@ -343,7 +343,7 @@ fn defineBitpack( ) (Allocator.Error || Writer.Error)!void { const zcu = pt.zcu; const name_cty: CType = .{ .bitpack = ty }; - const cty: CType = try .lower(ty.bitpackBackingInt(zcu), deps, arena, zcu); + const cty: CType = try .lower(ty.backingIntType(zcu), deps, arena, zcu); try w.print("typedef {f}{f}{f}; /* {f} */\n", .{ cty.fmtDeclaratorPrefix(zcu), name_cty.fmtTypeName(zcu), diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index a93ac0853f5d579e053207a1bd535a178a6a6a88..59a27d8b408f62cd189bbad84ca30e68bd1b5235 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -3252,7 +3252,7 @@ pub const Object = struct { return ty; }, .opaque_type, .spirv_type => unreachable, // no runtime bits - .enum_type => try o.lowerType(t.intTagType(zcu), repr), + .enum_type => try o.lowerType(t.backingIntType(zcu), repr), .func_type => |func_type| try o.lowerFnType(t, func_type), .error_set_type, .inferred_error_set_type => try o.errorIntType(repr), // values, not types diff --git a/src/codegen/llvm/FuncGen.zig b/src/codegen/llvm/FuncGen.zig index 602277a91188d72994ac83b025ecf5c580f5ce2b..6e737fa482f6eb121062e9516b7028f65aefd576 100644 --- a/src/codegen/llvm/FuncGen.zig +++ b/src/codegen/llvm/FuncGen.zig @@ -465,7 +465,8 @@ fn genBody(self: *FuncGen, body: []const Air.Inst.Index, coverage_point: Air.Cov .alloc => try self.airAlloc(inst), .ret_ptr => try self.airRetPtr(inst), .arg => try self.airArg(inst), - .bit_cast => try self.airBitCast(inst), + .bit_cast => try self.airBitCast(inst, false), + .bit_cast_safe => try self.airBitCast(inst, true), .ptr_cast => try self.airNopCast(inst), .ptr_from_int => try self.airPtrFromInt(inst), .int_from_ptr => try self.airIntFromPtr(inst), @@ -1254,7 +1255,6 @@ fn cmp( const zcu = o.zcu; const scalar_ty = operand_ty.scalarType(zcu); const int_ty = switch (scalar_ty.zigTypeTag(zcu)) { - .@"enum" => scalar_ty.intTagType(zcu), .int, .bool, .pointer, .error_set => scalar_ty, .optional => blk: { const payload_ty = operand_ty.optionalChild(zcu); @@ -1328,7 +1328,7 @@ fn cmp( return phi.toValue(); }, .float => return self.buildFloatCmp(fast, op, operand_ty, .{ lhs, rhs }), - .@"struct", .@"union" => scalar_ty.bitpackBackingInt(zcu), + .@"enum", .@"struct", .@"union" => scalar_ty.backingIntType(zcu), else => unreachable, }; const is_signed = int_ty.isSignedInt(zcu); @@ -4539,7 +4539,7 @@ fn airFpext(self: *FuncGen, inst: Air.Inst.Index) Allocator.Error!Builder.Value } } -fn airBitCast(fg: *FuncGen, inst: Air.Inst.Index) Allocator.Error!Builder.Value { +fn airBitCast(fg: *FuncGen, inst: Air.Inst.Index, safety: bool) Allocator.Error!Builder.Value { const o = fg.object; const zcu = o.zcu; @@ -4564,7 +4564,26 @@ fn airBitCast(fg: *FuncGen, inst: Air.Inst.Index) Allocator.Error!Builder.Value assert(!isByRef(dest_ty, zcu)); const llvm_dest_ty = try o.lowerType(dest_ty, .by_value); - return fg.wip.cast(.bitcast, operand, llvm_dest_ty, ""); + const result = try fg.wip.cast(.bitcast, operand, llvm_dest_ty, ""); + if (safety and dest_ty.zigTypeTag(zcu) == .@"enum" and !dest_ty.isNonexhaustiveEnum(zcu)) { + const llvm_fn = try o.getIsNamedEnumValueFunction(dest_ty); + const is_valid_enum_val = try fg.wip.call( + .normal, + .fastcc, + .none, + llvm_fn.typeOf(&o.builder), + llvm_fn.toValue(&o.builder), + &.{result}, + "", + ); + const fail_block = try fg.wip.block(1, "ValidEnumFail"); + const ok_block = try fg.wip.block(1, "ValidEnumOk"); + _ = try fg.wip.brCond(is_valid_enum_val, ok_block, fail_block, .none); + fg.wip.cursor = .{ .block = fail_block }; + try fg.buildSimplePanic(.invalid_enum_value); + fg.wip.cursor = .{ .block = ok_block }; + } + return result; } fn airNopCast(fg: *FuncGen, inst: Air.Inst.Index) Allocator.Error!Builder.Value { diff --git a/src/codegen/riscv64/CodeGen.zig b/src/codegen/riscv64/CodeGen.zig index b11b898556c0de6479e0aeaf2fe25d47d31e8445..4e5aac5f4d33cc85a30893b0973ab5cbb01488a3 100644 --- a/src/codegen/riscv64/CodeGen.zig +++ b/src/codegen/riscv64/CodeGen.zig @@ -51,6 +51,7 @@ const InnerError = codegen.Error || error{OutOfRegisters}; pub fn legalizeFeatures(_: *const std.Target) *const Air.Legalize.Features { return comptime &.initMany(&.{ + .expand_bit_cast_safe, .expand_int_cast_safe, .expand_int_from_float_safe, .expand_int_from_float_optimized_safe, @@ -1454,6 +1455,7 @@ fn genBody(func: *Func, body: []const Air.Inst.Index) InnerError!void { .add_safe, .sub_safe, .mul_safe, + .bit_cast_safe, .int_cast_safe, .int_from_float_safe, .int_from_float_optimized_safe, @@ -5128,7 +5130,6 @@ fn airCmp(func: *Func, inst: Air.Inst.Index, tag: Air.Inst.Tag) !void { .@"struct", => { const int_ty: Type = switch (lhs_ty.zigTypeTag(zcu)) { - .@"enum" => lhs_ty.intTagType(zcu), .int => lhs_ty, .bool => .u1, .pointer => .u64, @@ -5143,7 +5144,7 @@ fn airCmp(func: *Func, inst: Air.Inst.Index, tag: Air.Inst.Tag) !void { return func.fail("TODO riscv cmp non-pointer optionals", .{}); } }, - .@"struct", .@"union" => lhs_ty.bitpackBackingInt(zcu), + .@"enum", .@"struct", .@"union" => lhs_ty.backingIntType(zcu), else => unreachable, }; diff --git a/src/codegen/sparc64/CodeGen.zig b/src/codegen/sparc64/CodeGen.zig index 2d67e7cc54576394bd88d2dcbacab8330b94b21f..23a69bab00cb5c7c83a0cdfc4aad4c4b63a60fd7 100644 --- a/src/codegen/sparc64/CodeGen.zig +++ b/src/codegen/sparc64/CodeGen.zig @@ -697,6 +697,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .add_safe, .sub_safe, .mul_safe, + .bit_cast_safe, .int_cast_safe, .int_from_float_safe, .int_from_float_optimized_safe, @@ -1374,7 +1375,7 @@ fn airCmp(self: *Self, inst: Air.Inst.Index, op: math.CompareOperator) !void { const int_ty: Type = switch (lhs_ty.zigTypeTag(zcu)) { .vector => unreachable, // Handled by cmp_vector. - .@"enum" => lhs_ty.intTagType(zcu), + .@"enum" => lhs_ty.backingIntType(zcu), .int => lhs_ty, .bool => .u1, .pointer => .usize, diff --git a/src/codegen/spirv/CodeGen.zig b/src/codegen/spirv/CodeGen.zig index a12d07072983ef74bf4e45e510803c7b4da1e0a1..d6b82ea773dc8ccf41dd9aa38794197cd255a573 100644 --- a/src/codegen/spirv/CodeGen.zig +++ b/src/codegen/spirv/CodeGen.zig @@ -121,6 +121,7 @@ const StructType = struct { pub fn legalizeFeatures(_: *const std.Target) *const Air.Legalize.Features { return comptime &.initMany(&.{ + .expand_bit_cast_safe, .expand_int_cast_safe, .expand_int_from_float_safe, .expand_int_from_float_optimized_safe, @@ -1353,7 +1354,7 @@ fn arithmeticTypeInfo(cg: *CodeGen, ty: Type) ArithmeticTypeInfo { const target = cg.zcu.getTarget(); var scalar_ty = ty.scalarType(zcu); if (scalar_ty.zigTypeTag(zcu) == .@"enum") { - scalar_ty = scalar_ty.intTagType(zcu); + scalar_ty = scalar_ty.backingIntType(zcu); } const vector_len = if (ty.isVector(zcu)) ty.vectorLen(zcu) else null; return switch (scalar_ty.zigTypeTag(zcu)) { @@ -1732,8 +1733,8 @@ fn constant(cg: *CodeGen, ty: Type, val: Value, repr: Repr) Error!Id { return try cg.constructComposite(comp_ty_id, &constituents); }, .enum_tag => { - const int_val = val.intFromEnum(zcu); - const int_ty = ty.intTagType(zcu); + const int_val = val.backingInt(zcu); + const int_ty = ty.backingIntType(zcu); break :cache try cg.constant(int_ty, int_val, repr); }, .ptr => return cg.constantPtr(val), @@ -2213,7 +2214,7 @@ fn resolveType(cg: *CodeGen, ty: Type, repr: Repr) Error!Id { const int_info = ty.intInfo(zcu); return try cg.intType(int_info.signedness, int_info.bits); }, - .@"enum" => return try cg.resolveType(ty.intTagType(zcu), repr), + .@"enum" => return try cg.resolveType(ty.backingIntType(zcu), repr), .float => { const bits = ty.floatBits(target); const supported = switch (bits) { @@ -5807,7 +5808,7 @@ fn cmp( .int, .bool, .float => {}, .@"enum" => { assert(!is_vector); - const ty = lhs.ty.intTagType(zcu); + const ty = lhs.ty.backingIntType(zcu); return try cg.cmp(op, lhs.pun(ty), rhs.pun(ty)); }, .@"struct" => { @@ -6887,7 +6888,7 @@ fn unionInit( const tag_int = if (layout.tag_size != 0) blk: { const tag_val = try pt.enumValueFieldIndex(tag_ty, active_field); - const tag_int_val = tag_val.intFromEnum(zcu); + const tag_int_val = tag_val.backingInt(zcu); break :blk tag_int_val.toUnsignedInt(zcu); } else 0; @@ -8164,7 +8165,7 @@ fn airSwitchBr(cg: *CodeGen, inst: Air.Inst.Index) !void { break :blk if (backing_bits <= 32) 1 else 2; }, .@"enum" => blk: { - const int_ty = cond_ty.intTagType(zcu); + const int_ty = cond_ty.backingIntType(zcu); const int_info = int_ty.intInfo(zcu); const backing_bits, const big_int = cg.backingIntBits(int_info.bits); if (big_int) return cg.todo("implement composite int switch", .{}); @@ -8224,7 +8225,7 @@ fn airSwitchBr(cg: *CodeGen, inst: Air.Inst.Index) !void { const value: Value = .fromInterned(item.toInterned().?); const int_val: u64 = switch (cond_ty.zigTypeTag(zcu)) { .bool, .int => if (cond_ty.isSignedInt(zcu)) @bitCast(value.toSignedInt(zcu)) else value.toUnsignedInt(zcu), - .@"enum" => value.intFromEnum(zcu).toUnsignedInt(zcu), + .@"enum" => value.backingInt(zcu).toUnsignedInt(zcu), .error_set => value.getErrorInt(zcu), .pointer => value.toUnsignedInt(zcu), else => unreachable, @@ -8378,7 +8379,7 @@ fn airLoopSwitchBr(cg: *CodeGen, inst: Air.Inst.Index) !void { break :blk if (backing_bits <= 32) 1 else 2; }, .@"enum" => blk: { - const int_ty = cond_ty.intTagType(zcu); + const int_ty = cond_ty.backingIntType(zcu); const int_info = int_ty.intInfo(zcu); const backing_bits, const big_int = cg.backingIntBits(int_info.bits); if (big_int) return cg.todo("implement composite int loop switch", .{}); @@ -8464,7 +8465,7 @@ fn airLoopSwitchBr(cg: *CodeGen, inst: Air.Inst.Index) !void { const value: Value = .fromInterned(item.toInterned().?); const int_val: u64 = switch (cond_ty.zigTypeTag(zcu)) { .bool, .int => if (cond_ty.isSignedInt(zcu)) @bitCast(value.toSignedInt(zcu)) else value.toUnsignedInt(zcu), - .@"enum" => value.intFromEnum(zcu).toUnsignedInt(zcu), + .@"enum" => value.backingInt(zcu).toUnsignedInt(zcu), .error_set => value.getErrorInt(zcu), .pointer => value.toUnsignedInt(zcu), else => unreachable, diff --git a/src/codegen/wasm/CodeGen.zig b/src/codegen/wasm/CodeGen.zig index 05adc1eaae49731a64913219a50cc77ee00aafb9..644beb2b7192f24f87b19c7dbc04f9216363fec4 100644 --- a/src/codegen/wasm/CodeGen.zig +++ b/src/codegen/wasm/CodeGen.zig @@ -32,6 +32,7 @@ const compilerRtIntAbbrev = target_util.compilerRtIntAbbrev; pub fn legalizeFeatures(_: *const std.Target) *const Air.Legalize.Features { return comptime &.initMany(&.{ + .expand_bit_cast_safe, .expand_int_cast_safe, .expand_int_from_float_safe, .expand_int_from_float_optimized_safe, @@ -615,7 +616,7 @@ pub fn typeToValtype(ty: Type, zcu: *const Zcu, target: *const std.Target) std.w .unrolled => .i32, }, .@"union", .@"struct" => switch (ty.containerLayout(zcu)) { - .@"packed" => typeToValtype(ty.bitpackBackingInt(zcu), zcu, target), + .@"packed" => typeToValtype(ty.backingIntType(zcu), zcu, target), .auto, .@"extern" => .i32, }, else => .i32, // all represented as reference/immediate @@ -1226,7 +1227,7 @@ fn isByRef(ty: Type, zcu: *const Zcu, target: *const std.Target) bool { .frame, => return ty.hasRuntimeBits(zcu), .@"struct", .@"union" => switch (ty.containerLayout(zcu)) { - .@"packed" => return isByRef(ty.bitpackBackingInt(zcu), zcu, target), + .@"packed" => return isByRef(ty.backingIntType(zcu), zcu, target), .@"extern", .auto => return ty.hasRuntimeBits(zcu), }, .vector => return determineSimdStoreStrategy(ty, zcu, target) == .unrolled, @@ -1905,6 +1906,7 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { .add_safe, .sub_safe, .mul_safe, + .bit_cast_safe, .int_cast_safe, .int_from_float_safe, .int_from_float_optimized_safe, @@ -2525,18 +2527,10 @@ const IntType = struct { .f16, .f32, .f64, .f80, .f128, .c_longdouble => unreachable, .anyopaque, .void, .type, .comptime_int, .comptime_float, .noreturn, .null, .undefined, .enum_literal, .generic_poison => unreachable, }, - .struct_type => { - const loaded_struct = ip.loadStructType(ty_index); - switch (loaded_struct.layout) { - .auto, .@"extern" => unreachable, - .@"packed" => ty_index = loaded_struct.packed_backing_int_type, - } - }, - .union_type => return switch (ip.loadUnionType(ty_index).layout) { - .auto, .@"extern" => unreachable, - .@"packed" => .{ .is_signed = false, .bits = @intCast(ty.bitSize(zcu)) }, - }, - .enum_type => ty_index = ip.loadEnumType(ty_index).int_tag_type, + .enum_type, + .struct_type, + .union_type, + => ty_index = Type.fromInterned(ty_index).backingIntType(zcu).toIntern(), .error_set_type, .inferred_error_set_type => return .{ .is_signed = false, .bits = zcu.errorSetBits() }, else => unreachable, }; @@ -5134,7 +5128,7 @@ fn emitUndefined(cg: *CodeGen, ty: Type) InnerError!WValue { return .{ .imm32 = 0xaaaaaaaa }; }, .@"struct", .@"union" => { - const backing_int_ty = ty.bitpackBackingInt(zcu); + const backing_int_ty = ty.backingIntType(zcu); return cg.emitUndefined(backing_int_ty); }, else => return cg.fail("Wasm TODO: emitUndefined for type: {t}\n", .{ty.zigTypeTag(zcu)}), diff --git a/src/codegen/x86_64/CodeGen.zig b/src/codegen/x86_64/CodeGen.zig index dbb942eaed207109e57a2f5a39bf7d5d4f21c0d7..10adf9b858ee9ab1f977218652a1dfa0d37b89ff 100644 --- a/src/codegen/x86_64/CodeGen.zig +++ b/src/codegen/x86_64/CodeGen.zig @@ -63,6 +63,7 @@ pub fn legalizeFeatures(_: *const std.Target) *const Air.Legalize.Features { .reduce_one_elem_to_bit_cast, .splat_one_elem_to_bit_cast, + .expand_bit_cast_safe, .expand_int_cast_safe, .expand_int_from_float_safe, .expand_int_from_float_optimized_safe, @@ -67446,6 +67447,7 @@ fn genBody(cg: *CodeGen, body: []const Air.Inst.Index) InnerError!void { .int_from_error, .union_from_enum, => try cg.airBitCast(inst), + .bit_cast_safe => unreachable, .block => { const block = cg.air.unwrapBlock(inst); if (!cg.mod.strip) try cg.asmPseudo(.pseudo_dbg_enter_block_none); diff --git a/src/link/Dwarf.zig b/src/link/Dwarf.zig index b6ff6c754bc97b0efc78522b30dcf302d9ad072d..baa5be93e937a72c22193b3e159bd4df3b960ac1 100644 --- a/src/link/Dwarf.zig +++ b/src/link/Dwarf.zig @@ -5098,6 +5098,7 @@ fn DeclValEnum(comptime T: type) type { if (min_value == null or min_value.? > value) min_value = value; if (max_value == null or max_value.? < value) max_value = value; } + if (fields_len == 0) return enum {}; const TagInt = std.math.IntFittingRange(min_value orelse 0, max_value orelse 0); var field_vals: [fields_len]TagInt = undefined; for (field_names[0..fields_len], &field_vals) |name, *val| val.* = @field(T, name); diff --git a/src/link/Wasm/Flush.zig b/src/link/Wasm/Flush.zig index 959814d1bcb5e1248fc6b7c840277e195be41081..a19fd7a57f38f88b727ba883595bc4f434fdc6ea 100644 --- a/src/link/Wasm/Flush.zig +++ b/src/link/Wasm/Flush.zig @@ -151,7 +151,7 @@ pub fn finish(f: *Flush, wasm: *Wasm) !void { assert(ip.indexToKey(data.ip_index) == .enum_type); const gop = try wasm.zcu_funcs.getOrPut(gpa, data.ip_index); if (!gop.found_existing) { - const int_tag_ty = Zcu.Type.fromInterned(data.ip_index).intTagType(zcu); + const int_tag_ty = Zcu.Type.fromInterned(data.ip_index).backingIntType(zcu); gop.value_ptr.* = .{ .tag_name = .{ .symbol_name = try wasm.internStringFmt("__zig_tag_index_{d}", .{data.ip_index}), .type_index = try wasm.internFunctionType(.auto, &.{int_tag_ty.ip_index}, .u32, false, target), diff --git a/src/print_value.zig b/src/print_value.zig index 54d81d50d26abba08d8bde2d60820c8b652f02f9..b659b53df4031797d4b8cf4212c7365ad0ca5f4f 100644 --- a/src/print_value.zig +++ b/src/print_value.zig @@ -94,15 +94,14 @@ pub fn print( enum_literal.fmt(ip), }), .enum_tag => |enum_tag| { - const enum_type = ip.loadEnumType(val.typeOf(zcu).toIntern()); - if (enum_type.tagValueIndex(ip, enum_tag.int)) |tag_index| { - return writer.print(".{f}", .{enum_type.field_names.get(ip)[tag_index].fmt(ip)}); + const ty: Type = .fromInterned(enum_tag.ty); + const enum_obj = ip.loadEnumType(ty.toIntern()); + if (enum_obj.tagValueIndex(ip, enum_tag.int)) |tag_index| { + return writer.print(".{f}", .{enum_obj.field_names.get(ip)[tag_index].fmt(ip)}); } - if (level == 0) { - return writer.writeAll("@enumFromInt(...)"); - } - try writer.writeAll("@enumFromInt("); - try print(Value.fromInterned(enum_tag.int), writer, level - 1, pt, opt_sema); + try writer.writeAll("@fromBackingInt("); + if (level == 0) return writer.writeAll("...)"); + try print(.fromInterned(enum_tag.int), writer, level - 1, pt, opt_sema); try writer.writeAll(")"); }, .float => |float| switch (float.storage) { @@ -190,10 +189,17 @@ pub fn print( try writer.writeAll(" }"); return; }, - .@"union" => { - try writer.print("@bitCast(@as({f}, ", .{ty.bitpackBackingInt(zcu).fmt(pt)}); - try print(.fromInterned(bitpack.backing_int_val), writer, level - 1, pt, opt_sema); - try writer.writeAll("))"); + .@"union" => switch (ty.backingIntMode(zcu)) { + .auto => { + try writer.print("@bitCast(@as({f}, ", .{ty.backingIntType(zcu).fmt(pt)}); + try print(.fromInterned(bitpack.backing_int_val), writer, level - 1, pt, opt_sema); + try writer.writeAll("))"); + }, + .explicit => { + try writer.writeAll("@fromBackingInt("); + try print(.fromInterned(bitpack.backing_int_val), writer, level - 1, pt, opt_sema); + try writer.writeAll(")"); + }, }, else => unreachable, } diff --git a/src/print_zir.zig b/src/print_zir.zig index 74aaf0bda3e6406fefa28610ed092dce5bc583c8..375210445b0a3efcc9615c180d5e4c721b8b3b09 100644 --- a/src/print_zir.zig +++ b/src/print_zir.zig @@ -193,6 +193,7 @@ const Writer = struct { .elem_type, .indexable_ptr_elem_type, .splat_op_result_ty, + .from_backing_int_arg_ty, .indexable_ptr_len, .anyframe_type, .bit_not, @@ -232,6 +233,7 @@ const Writer = struct { .compile_error, .set_eval_branch_quota, .int_from_enum, + .backing_int, .align_of, .int_from_bool, .embed_file, @@ -418,6 +420,8 @@ const Writer = struct { .for_len => try self.writePlNodeMultiOp(stream, inst), + .from_backing_int => try self.writePlNodeBin(stream, inst), + .elem_val_imm => try self.writeElemValImm(stream, inst), .@"export" => try self.writePlNodeExport(stream, inst), @@ -985,6 +989,16 @@ const Writer = struct { try self.writeSrcNode(stream, inst_data.src_node); } + fn writeFromBackingInt(self: *Writer, stream: *std.Io.Writer, inst: Zir.Inst.Index) !void { + const inst_data = self.code.instructions.items(.data)[@intFromEnum(inst)].pl_node; + const extra = self.code.extraData(Zir.Inst.FromBackingInt, inst_data.payload_index); + try self.writeInstRef(stream, extra.data.result_type); + try stream.writeAll(", "); + try self.writeBracedBody(stream, self.code.bodySlice(extra.end, extra.data.body_len)); + try stream.writeAll(") "); + try self.writeSrcNode(stream, inst_data.src_node); + } + fn writeBuiltinCall(self: *Writer, stream: *std.Io.Writer, inst: Zir.Inst.Index) !void { const inst_data = self.code.instructions.items(.data)[@intFromEnum(inst)].pl_node; const extra = self.code.extraData(Zir.Inst.BuiltinCall, inst_data.payload_index).data; diff --git a/stage1/zig1.wasm b/stage1/zig1.wasm index 6a222c0c6138663c1a36630a48e164f351fd17b7..1664bd4095df86103a6e4bacfc682054a0cb8fc1 100644 Binary files a/stage1/zig1.wasm and b/stage1/zig1.wasm differ diff --git a/test/behavior.zig b/test/behavior.zig index 25c39e172eb6d27c12defc046b5513bb32cff7ac..b64c883d9a6489eeba791ad50254555e4a27214c 100644 --- a/test/behavior.zig +++ b/test/behavior.zig @@ -6,6 +6,7 @@ test { _ = @import("behavior/alignof.zig"); _ = @import("behavior/array.zig"); _ = @import("behavior/atomics.zig"); + _ = @import("behavior/backing_int.zig"); _ = @import("behavior/basic.zig"); _ = @import("behavior/bit_shifting.zig"); _ = @import("behavior/bitcast.zig"); diff --git a/test/behavior/backing_int.zig b/test/behavior/backing_int.zig new file mode 100644 index 0000000000000000000000000000000000000000..1ab8b5970e1d93eaf7b7b1196cee5cf51ab5622f --- /dev/null +++ b/test/behavior/backing_int.zig @@ -0,0 +1,384 @@ +const builtin = @import("builtin"); +const std = @import("std"); +const expect = std.testing.expect; +const assert = std.debug.assert; + +const E1 = enum(u8) { + a, + b, + c, + d, + const expected = .{ + .val = @as(E1, .b), + .int = @as(@typeInfo(E1).@"enum".tag_type, 1), + }; +}; +const E2 = enum(i20) { + x, + y, + z = -5, + const expected = .{ + .val = @as(E2, .z), + .int = @as(@typeInfo(E2).@"enum".tag_type, -5), + }; +}; +const E3 = enum(i32) { + _, + const zero: E3 = @bitCast(@as(i32, 0)); + const expected = .{ + .val = @as(E3, .zero), + .int = @as(@typeInfo(E3).@"enum".tag_type, 0), + }; +}; +const E4 = enum(i200) { + min = -(1 << 199), + const expected = .{ + .val = @as(E4, .min), + .int = @as(@typeInfo(E4).@"enum".tag_type, -(1 << 199)), + }; +}; +const E5 = enum(u0) { + a, + const expected = .{ + .val = @as(E5, .a), + .int = @as(@typeInfo(E5).@"enum".tag_type, 0), + }; +}; + +test "@backingInt with enums" { + const static = struct { + fn doTheTest(v1: E1, v2: E2, v3: E3, v4: E4, v5: E5) !void { + const b1 = @backingInt(v1); + comptime assert(@TypeOf(b1) == @typeInfo(E1).@"enum".tag_type); + try expect(b1 == E1.expected.int); + + const b2 = @backingInt(v2); + comptime assert(@TypeOf(b2) == @typeInfo(E2).@"enum".tag_type); + try expect(b2 == E2.expected.int); + + const b3 = @backingInt(v3); + comptime assert(@TypeOf(b3) == @typeInfo(E3).@"enum".tag_type); + try expect(b3 == E3.expected.int); + + const b4 = @backingInt(v4); + comptime assert(@TypeOf(b4) == @typeInfo(E4).@"enum".tag_type); + try expect(b4 == E4.expected.int); + + const b5 = @backingInt(v5); + comptime assert(@TypeOf(b5) == @typeInfo(E5).@"enum".tag_type); + try expect(b5 == E5.expected.int); + } + }; + try static.doTheTest(E1.expected.val, E2.expected.val, E3.expected.val, E4.expected.val, E5.expected.val); + try comptime static.doTheTest(E1.expected.val, E2.expected.val, E3.expected.val, E4.expected.val, E5.expected.val); +} + +test "@fromBackingInt with enums" { + const static = struct { + fn doTheTest( + b1: @typeInfo(E1).@"enum".tag_type, + b2: @typeInfo(E2).@"enum".tag_type, + b3: @typeInfo(E3).@"enum".tag_type, + b4: @typeInfo(E4).@"enum".tag_type, + b5: @typeInfo(E5).@"enum".tag_type, + ) !void { + const v1: E1 = @fromBackingInt(b1); + try expect(v1 == E1.expected.val); + + const v2: E2 = @fromBackingInt(b2); + try expect(v2 == E2.expected.val); + + const v3: E3 = @fromBackingInt(b3); + try expect(v3 == E3.expected.val); + + const v4: E4 = @fromBackingInt(b4); + try expect(v4 == E4.expected.val); + + const v5: E5 = @fromBackingInt(b5); + try expect(v5 == E5.expected.val); + } + }; + try static.doTheTest(E1.expected.int, E2.expected.int, E3.expected.int, E4.expected.int, E5.expected.int); + try comptime static.doTheTest(E1.expected.int, E2.expected.int, E3.expected.int, E4.expected.int, E5.expected.int); +} + +const T1 = union(E1) { + a: u8, + b: []const u16, + c: []const u8, + d: i32, + const expected = .{ + .val = @unionInit(T1, @tagName(E1.expected.val), &.{ 1, 2, 3 }), + .int = E1.expected.int, + }; +}; +const T2 = union(E2) { + x, + y: i32, + z, + const expected = .{ + .val = @unionInit(T2, @tagName(E2.expected.val), {}), + .int = E2.expected.int, + }; +}; +const T4 = union(E4) { + min: f32, + const expected = .{ + .val = @unionInit(T4, @tagName(E4.expected.val), 0.123), + .int = E4.expected.int, + }; +}; +const T5 = union(E5) { + a: u0, + const expected = .{ + .val = @unionInit(T5, @tagName(E5.expected.val), 0), + .int = E5.expected.int, + }; +}; + +test "@backingInt with tagged unions" { + const static = struct { + fn doTheTest(v1: T1, v2: T2, v4: T4, v5: T5) !void { + const b1 = @backingInt(v1); + comptime assert(@TypeOf(b1) == @typeInfo(@typeInfo(T1).@"union".tag_type.?).@"enum".tag_type); + try expect(b1 == E1.expected.int); + + const b2 = @backingInt(v2); + comptime assert(@TypeOf(b2) == @typeInfo(@typeInfo(T2).@"union".tag_type.?).@"enum".tag_type); + try expect(b2 == E2.expected.int); + + const b4 = @backingInt(v4); + comptime assert(@TypeOf(b4) == @typeInfo(@typeInfo(T4).@"union".tag_type.?).@"enum".tag_type); + try expect(b4 == E4.expected.int); + + const b5 = @backingInt(v5); + comptime assert(@TypeOf(b5) == @typeInfo(@typeInfo(T5).@"union".tag_type.?).@"enum".tag_type); + try expect(b5 == E5.expected.int); + } + }; + try static.doTheTest(T1.expected.val, T2.expected.val, T4.expected.val, T5.expected.val); + try comptime static.doTheTest(T1.expected.val, T2.expected.val, T4.expected.val, T5.expected.val); +} + +const S1 = packed struct(u8) { + a: u4, + b: i4, + const expected = .{ + .val = @as(S1, .{ .a = 0b1000, .b = 0b0010 }), + .int = @as(@typeInfo(S1).@"struct".backing_integer.?, 0b0010_1000), + }; +}; +const S2 = packed struct(i20) { + a: u10, + b: enum(i10) { x, y, z }, + const expected = .{ + .val = @as(S2, .{ .a = 0b0011001100, .b = .z }), + .int = @as(@typeInfo(S2).@"struct".backing_integer.?, 0b0000000010_0011001100), + }; +}; +const S3 = packed struct(i32) { + a: packed struct(u12) { + x: u8, + y: i4, + }, + b: packed union(i20) { + x: i20, + y: enum(u20) { u, v }, + }, + const expected = .{ + .val = @as(S3, .{ .a = .{ .x = 0b10010001, .y = 0b0110 }, .b = .{ .y = .v } }), + .int = @as(@typeInfo(S3).@"struct".backing_integer.?, 0b00000000000000000001_0110_10010001), + }; +}; +const S4 = packed struct(i200) { + a: u200, + const expected = .{ + .val = @as(S4, .{ .a = (1 << 199) + 10 }), + .int = @as(@typeInfo(S4).@"struct".backing_integer.?, @bitCast(@as(u200, (1 << 199) + 10))), + }; +}; +const S5 = packed struct(u0) { + a: u0, + const expected = .{ + .val = @as(S5, .{ .a = 0 }), + .int = @as(@typeInfo(S5).@"struct".backing_integer.?, 0), + }; +}; + +test "@backingInt with packed structs" { + const static = struct { + fn doTheTest(v1: S1, v2: S2, v3: S3, v4: S4, v5: S5) !void { + const b1 = @backingInt(v1); + comptime assert(@TypeOf(b1) == @typeInfo(S1).@"struct".backing_integer.?); + try expect(b1 == S1.expected.int); + + const b2 = @backingInt(v2); + comptime assert(@TypeOf(b2) == @typeInfo(S2).@"struct".backing_integer.?); + try expect(b2 == S2.expected.int); + + const b3 = @backingInt(v3); + comptime assert(@TypeOf(b3) == @typeInfo(S3).@"struct".backing_integer.?); + try expect(b3 == S3.expected.int); + + const b4 = @backingInt(v4); + comptime assert(@TypeOf(b4) == @typeInfo(S4).@"struct".backing_integer.?); + try expect(b4 == S4.expected.int); + + const b5 = @backingInt(v5); + comptime assert(@TypeOf(b5) == @typeInfo(S5).@"struct".backing_integer.?); + try expect(b5 == S5.expected.int); + } + }; + try static.doTheTest(S1.expected.val, S2.expected.val, S3.expected.val, S4.expected.val, S5.expected.val); + try comptime static.doTheTest(S1.expected.val, S2.expected.val, S3.expected.val, S4.expected.val, S5.expected.val); +} + +test "@fromBackingInt with packed structs" { + const static = struct { + fn doTheTest( + b1: @typeInfo(S1).@"struct".backing_integer.?, + b2: @typeInfo(S2).@"struct".backing_integer.?, + b3: @typeInfo(S3).@"struct".backing_integer.?, + b4: @typeInfo(S4).@"struct".backing_integer.?, + b5: @typeInfo(S5).@"struct".backing_integer.?, + ) !void { + const v1: S1 = @fromBackingInt(b1); + try expect(v1 == S1.expected.val); + + const v2: S2 = @fromBackingInt(b2); + try expect(v2 == S2.expected.val); + + const v3: S3 = @fromBackingInt(b3); + try expect(v3 == S3.expected.val); + + // https://codeberg.org/ziglang/zig/issues/35982 + if (builtin.zig_backend != .stage2_x86_64) { + const v4: S4 = @fromBackingInt(b4); + try expect(v4 == S4.expected.val); + } + + const v5: S5 = @fromBackingInt(b5); + try expect(v5 == S5.expected.val); + } + }; + try static.doTheTest(S1.expected.int, S2.expected.int, S3.expected.int, S4.expected.int, S5.expected.int); + try comptime static.doTheTest(S1.expected.int, S2.expected.int, S3.expected.int, S4.expected.int, S5.expected.int); +} + +const U1 = packed union(u8) { + a: u8, + b: i8, + const expected = .{ + .val = @as(U1, .{ .b = -123 }), + .int = @as(@typeInfo(U1).@"union".backing_integer.?, @bitCast(@as(i8, -123))), + }; +}; +const U2 = packed union(i20) { + a: u20, + b: enum(i20) { x, y, z }, + const expected = .{ + .val = @as(U2, .{ .b = .z }), + .int = @as(@typeInfo(U2).@"union".backing_integer.?, 0b00000000000000000000000000000010), + }; +}; +const U3 = packed union(i32) { + a: packed struct(u32) { + x: u18, + y: i14, + }, + b: packed union(u32) { + x: i32, + y: enum(u32) { u, v }, + }, + const expected = .{ + .val = @as(U3, .{ .b = .{ .y = .v } }), + .int = @as(@typeInfo(U3).@"union".backing_integer.?, 0b00000000000000000000000000000001), + }; +}; +const U4 = packed union(i200) { + a: u200, + const expected = .{ + .val = @as(U4, .{ .a = (1 << 199) + 10 }), + .int = @as(@typeInfo(U4).@"union".backing_integer.?, @bitCast(@as(u200, (1 << 199) + 10))), + }; +}; +const U5 = packed union(u0) { + a: u0, + const expected = .{ + .val = @as(U5, .{ .a = 0 }), + .int = @as(@typeInfo(U5).@"union".backing_integer.?, 0), + }; +}; + +test "@backingInt with packed unions" { + const static = struct { + fn doTheTest(v1: U1, v2: U2, v3: U3, v4: U4, v5: U5) !void { + const b1 = @backingInt(v1); + comptime assert(@TypeOf(b1) == @typeInfo(U1).@"union".backing_integer.?); + try expect(b1 == U1.expected.int); + + const b2 = @backingInt(v2); + comptime assert(@TypeOf(b2) == @typeInfo(U2).@"union".backing_integer.?); + try expect(b2 == U2.expected.int); + + const b3 = @backingInt(v3); + comptime assert(@TypeOf(b3) == @typeInfo(U3).@"union".backing_integer.?); + try expect(b3 == U3.expected.int); + + const b4 = @backingInt(v4); + comptime assert(@TypeOf(b4) == @typeInfo(U4).@"union".backing_integer.?); + try expect(b4 == U4.expected.int); + + const b5 = @backingInt(v5); + comptime assert(@TypeOf(b5) == @typeInfo(U5).@"union".backing_integer.?); + try expect(b5 == U5.expected.int); + } + }; + try static.doTheTest(U1.expected.val, U2.expected.val, U3.expected.val, U4.expected.val, U5.expected.val); + try comptime static.doTheTest(U1.expected.val, U2.expected.val, U3.expected.val, U4.expected.val, U5.expected.val); +} + +test "@fromBackingInt with packed unions" { + const static = struct { + fn doTheTest( + b1: @typeInfo(U1).@"union".backing_integer.?, + b2: @typeInfo(U2).@"union".backing_integer.?, + b3: @typeInfo(U3).@"union".backing_integer.?, + b4: @typeInfo(U4).@"union".backing_integer.?, + b5: @typeInfo(U5).@"union".backing_integer.?, + ) !void { + const v1: U1 = @fromBackingInt(b1); + try expect(v1 == U1.expected.val); + + const v2: U2 = @fromBackingInt(b2); + try expect(v2 == U2.expected.val); + + const v3: U3 = @fromBackingInt(b3); + try expect(v3 == U3.expected.val); + + // https://codeberg.org/ziglang/zig/issues/35982 + if (builtin.zig_backend != .stage2_x86_64) { + const v4: U4 = @fromBackingInt(b4); + try expect(v4 == U4.expected.val); + } + + const v5: U5 = @fromBackingInt(b5); + try expect(v5 == U5.expected.val); + } + }; + try static.doTheTest(U1.expected.int, U2.expected.int, U3.expected.int, U4.expected.int, U5.expected.int); + try comptime static.doTheTest(U1.expected.int, U2.expected.int, U3.expected.int, U4.expected.int, U5.expected.int); +} + +test "@fromBackingInt provides result type to its argument" { + const E = enum(u32) { a, b, c }; + const static = struct { + fn doTheTest(x: u8) !void { + const e: E = @fromBackingInt(x); + try expect(e == .b); + try expect(@backingInt(e) == x); + } + }; + try static.doTheTest(1); + try comptime static.doTheTest(1); +} diff --git a/test/behavior/enum.zig b/test/behavior/enum.zig index ca253a102ff5351d730e5ad6f004755e0c225c5f..93365bb889555b722b4ff83f4ad30d24110a293d 100644 --- a/test/behavior/enum.zig +++ b/test/behavior/enum.zig @@ -1451,29 +1451,6 @@ test "comptime @enumFromInt with signed arithmetic" { comptime assert(@intFromEnum(x) == 0); } -test "switch on empty enum" { - const E = enum {}; - var e: E = undefined; - _ = &e; - switch (e) {} -} - -test "switch on empty enum with a specified tag type" { - const E = enum(u8) {}; - var e: E = undefined; - _ = &e; - switch (e) {} -} - -test "empty enum passed as argument" { - const E = enum { - fn f(e: @This()) void { - switch (e) {} - } - }; - E.f(@as(E, undefined)); -} - test "enum int tag type uses declaration inside the enum" { const static = struct { const E = enum(E.IntTag) { @@ -1488,3 +1465,18 @@ test "enum int tag type uses declaration inside the enum" { try expect(val == .b); try expect(@intFromEnum(val) == 1); } + +test "convert from/to backing int" { + const E = enum(u33) { + a, + b, + c, + fn doTheTest(s: @This()) !void { + const backing_int = @backingInt(s); + const reconstructed: @This() = @fromBackingInt(backing_int); + try expect(reconstructed == s); + } + }; + try E.doTheTest(.b); + try comptime E.doTheTest(.b); +} diff --git a/test/behavior/packed-struct.zig b/test/behavior/packed-struct.zig index 103d6a21775fdb3590e7ed873ccb07b5210907bd..96585d056cdf4c13b1aff3610ad08088fc0bf61c 100644 --- a/test/behavior/packed-struct.zig +++ b/test/behavior/packed-struct.zig @@ -122,7 +122,7 @@ test "correct sizeOf and offsets in packed structs" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO - const PStruct = packed struct { + const PStruct = packed struct(u32) { bool_a: bool, bool_b: bool, bool_c: bool, @@ -162,7 +162,7 @@ test "correct sizeOf and offsets in packed structs" { try expectEqual(22, @bitOffsetOf(PStruct, "u10_b")); try expectEqual(4, @sizeOf(PStruct)); - const s1 = @as(PStruct, @bitCast(@as(u32, 0x12345678))); + const s1: PStruct = @fromBackingInt(0x12345678); try expectEqual(false, s1.bool_a); try expectEqual(false, s1.bool_b); try expectEqual(false, s1.bool_c); @@ -176,7 +176,7 @@ test "correct sizeOf and offsets in packed structs" { try expectEqual(0b1101000101, s1.u10_a); try expectEqual(0b0001001000, s1.u10_b); - const s2 = @as(packed struct { x: u1, y: u7, z: u24 }, @bitCast(@as(u32, 0xd5c71ff4))); + const s2: packed struct(u32) { x: u1, y: u7, z: u24 } = @fromBackingInt(0xd5c71ff4); try expectEqual(0, s2.x); try expectEqual(0b1111010, s2.y); try expectEqual(0xd5c71f, s2.z); @@ -191,7 +191,7 @@ test "nested packed structs" { const S2 = packed struct { d: u8, e: u8, f: u8 }; const S3 = packed struct { x: S1, y: S2 }; - const S3Padded = packed struct { s3: S3, pad: u16 }; + const S3Padded = packed struct(u64) { s3: S3, pad: u16 }; try expectEqual(48, @bitSizeOf(S3)); try expectEqual(@sizeOf(u48), @sizeOf(S3)); @@ -199,7 +199,7 @@ test "nested packed structs" { try expectEqual(3, @offsetOf(S3, "y")); try expectEqual(24, @bitOffsetOf(S3, "y")); - const s3 = @as(S3Padded, @bitCast(@as(u64, 0xe952d5c71ff4))).s3; + const s3 = @as(S3Padded, @fromBackingInt(0xe952d5c71ff4)).s3; try expectEqual(0xf4, s3.x.a); try expectEqual(0x1f, s3.x.b); try expectEqual(0xc7, s3.x.c); @@ -558,7 +558,7 @@ test "packed struct fields modification" { // Originally reported at https://github.com/ziglang/zig/issues/16615 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; - const Small = packed struct { + const Small = packed struct(u16) { val: u8 = 0, lo: u4 = 0, hi: u4 = 0, @@ -570,12 +570,12 @@ test "packed struct fields modification" { .lo = 3, .hi = 4, }; - try expect(@as(u16, @bitCast(Small.p)) == 0x4312); + try expect(@backingInt(Small.p) == 0x4312); Small.p.val -= Small.p.lo; Small.p.val += Small.p.hi; Small.p.hi -= Small.p.lo; - try expect(@as(u16, @bitCast(Small.p)) == 0x1313); + try expect(@backingInt(Small.p) == 0x1313); } test "nested packed struct field access test" { @@ -1246,3 +1246,31 @@ test "initialize packed struct field to undefined at comptime" { const val: S = .{ .x = undefined }; _ = val; } + +test "convert from/to backing int" { + const S = packed struct(u33) { + a: u7, + b: enum(u10) { x, y, z }, + c: f16, + fn doTheTest(s: @This()) !void { + const backing_int = @backingInt(s); + const reconstructed: @This() = @fromBackingInt(backing_int); + try expect(reconstructed == s); + } + }; + try S.doTheTest(.{ .a = 123, .b = .y, .c = 0.23 }); + try comptime S.doTheTest(.{ .a = 123, .b = .y, .c = 0.23 }); +} + +test "equality with wide backing integer" { + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // https://codeberg.org/ziglang/zig/issues/35982 + + const S = packed struct(i200) { + x: u200, + fn doTheTest(s: @This(), int: i200) !void { + try expect(s == @as(@This(), @bitCast(int))); + } + }; + try S.doTheTest(.{ .x = (1 << 200) - 1 }, -1); + try comptime S.doTheTest(.{ .x = (1 << 200) - 1 }, -1); +} diff --git a/test/behavior/packed-union.zig b/test/behavior/packed-union.zig index 20732eab98a312ba077ffde4494e8deec179225d..4fd25526ce4a37deef7233b5b71120742604ce80 100644 --- a/test/behavior/packed-union.zig +++ b/test/behavior/packed-union.zig @@ -227,3 +227,30 @@ test "initialize packed union field to undefined at comptime" { const val: U = .{ .x = undefined }; _ = val; } + +test "convert from/to backing int" { + const U = packed union(u10) { + a: i10, + b: enum(u10) { x, y, z }, + fn doTheTest(u: @This()) !void { + const backing_int = @backingInt(u); + const reconstructed: @This() = @fromBackingInt(backing_int); + try expect(reconstructed == u); + } + }; + try U.doTheTest(.{ .a = 123 }); + try comptime U.doTheTest(.{ .a = 123 }); +} + +test "equality with wide backing integer" { + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // https://codeberg.org/ziglang/zig/issues/35982 + + const U = packed union(i200) { + x: u200, + fn doTheTest(s: @This(), int: i200) !void { + try expect(s == @as(@This(), @bitCast(int))); + } + }; + try U.doTheTest(.{ .x = (1 << 200) - 1 }, -1); + try comptime U.doTheTest(.{ .x = (1 << 200) - 1 }, -1); +} diff --git a/test/behavior/switch.zig b/test/behavior/switch.zig index 555eda7f30ef354061a1c30038532641be591b7e..c7323ed1d623ff502da131454bcd55ade52eafb8 100644 --- a/test/behavior/switch.zig +++ b/test/behavior/switch.zig @@ -1132,13 +1132,13 @@ test "decl literals as switch cases" { try comptime E.doTheTest(.foo); } -// TODO audit after #15909 and/or #19855 are decided/implemented. +// TODO audit after https://github.com/ziglang/zig/issues/15909 is fully decided. // When we do that, consider adding an 'error{}' case if possible. test "switch with uninstantiable union fields" { const U = union(enum) { ok: void, a: noreturn, - b: noreturn, + b: enum {}, fn doTheTest(u: @This()) void { switch (u) { diff --git a/test/behavior/type.zig b/test/behavior/type.zig index c2d1fc2f4fbf36674bdf3eab2b506fd4acd1fdb4..d4b8a60aa697f80d32aceadfaaeec9f172b9d409 100644 --- a/test/behavior/type.zig +++ b/test/behavior/type.zig @@ -266,13 +266,21 @@ test "Type.Union from regular enum" { test "Type.Union from empty regular enum" { const E = enum {}; const U = @Union(.auto, E, &.{}, &.{}, &.{}); - try testing.expectEqual(@typeInfo(U).@"union".field_names.len, 0); + + const info = @typeInfo(U).@"union"; + try testing.expect(info.field_names.len == 0); + try testing.expect(info.tag_type != null); + try testing.expect(@typeInfo(info.tag_type.?).@"enum".tag_type == noreturn); } test "Type.Union from empty Type.Enum" { - const E = @Enum(u0, .exhaustive, &.{}, &.{}); + const E = @Enum(noreturn, .exhaustive, &.{}, &.{}); const U = @Union(.auto, E, &.{}, &.{}, &.{}); - try testing.expectEqual(@typeInfo(U).@"union".field_names.len, 0); + + const info = @typeInfo(U).@"union"; + try testing.expect(info.field_names.len == 0); + try testing.expect(info.tag_type != null); + try testing.expect(@typeInfo(info.tag_type.?).@"enum".tag_type == noreturn); } test "Type.Fn" { diff --git a/test/cases/compile_errors/backing_int_invalid_arg_type.zig b/test/cases/compile_errors/backing_int_invalid_arg_type.zig new file mode 100644 index 0000000000000000000000000000000000000000..8bc9de52797b13a648165962c38912ec6d965597 --- /dev/null +++ b/test/cases/compile_errors/backing_int_invalid_arg_type.zig @@ -0,0 +1,38 @@ +const S1 = extern struct { x: u32 }; +export fn entry1(x: S1) u32 { + return @backingInt(x); +} + +const U1 = extern union { x: u32 }; +export fn entry2(x: U1) u32 { + return @backingInt(x); +} + +export fn entry3(x: u32) u32 { + return @backingInt(x); +} + +const S2 = packed struct { x: u32 }; +export fn entry4(x: u32) u32 { + return @backingInt(@as(S2, .{ .x = x })); +} + +const U2 = packed union { x: u32 }; +export fn entry5(x: u32) u32 { + return @backingInt(@as(U2, .{ .x = x })); +} + +// error +// +// :3:24: error: non-packed struct 'tmp.S1' does not have a backing integer +// :1:19: note: struct declared here +// :8:24: error: non-packed union 'tmp.U1' does not have a backing integer +// :8:24: note: untagged union 'tmp.U1' does not have an enum tag with a backing integer +// :6:19: note: union declared here +// :12:24: error: expected enum, tagged union, packed union or packed struct, found 'u32' +// :17:24: error: @backingInt is ambiguous for type 'tmp.S2' +// :15:19: note: backing integer type of struct is inferred +// :15:19: note: consider explicitly specifying the backing integer type +// :22:24: error: @backingInt is ambiguous for type 'tmp.U2' +// :20:19: note: backing integer type of union is inferred +// :20:19: note: consider explicitly specifying the backing integer type diff --git a/test/cases/compile_errors/bitCast_with_invalid_array_element_type.zig b/test/cases/compile_errors/bitCast_with_invalid_array_element_type.zig deleted file mode 100644 index bc04258d26921f4543a7b3b962ee57cab82c9aaa..0000000000000000000000000000000000000000 --- a/test/cases/compile_errors/bitCast_with_invalid_array_element_type.zig +++ /dev/null @@ -1,23 +0,0 @@ -export fn foo() void { - const S = struct { - f: u8, - }; - _ = @as([@sizeOf(S)]u8, @bitCast([1]S{undefined})); -} - -export fn bar() void { - const S = struct { - f: u8, - }; - _ = @as([1]S, @bitCast(@as([@sizeOf(S)]u8, undefined))); -} - -export fn baz() void { - _ = @as([1]u32, @bitCast([1]comptime_int{0})); -} - -// error -// -// :5:42: error: cannot @bitCast from '[1]tmp.foo.S' -// :12:19: error: cannot @bitCast to '[1]tmp.bar.S' -// :16:45: error: cannot @bitCast from '[1]comptime_int' diff --git a/test/cases/compile_errors/bitcast_invalid_types.zig b/test/cases/compile_errors/bitcast_invalid_types.zig new file mode 100644 index 0000000000000000000000000000000000000000..fdf25aade9326f1cffb22c8be76956c00511663b --- /dev/null +++ b/test/cases/compile_errors/bitcast_invalid_types.zig @@ -0,0 +1,205 @@ +const y: u32 = 0; + +export fn entry1() void { + _ = @as(comptime_float, @bitCast(y)); +} +export fn entry2() void { + const x: comptime_float = undefined; + _ = @as(u32, @bitCast(x)); +} + +export fn entry3() void { + _ = @as(comptime_int, @bitCast(y)); +} +export fn entry4() void { + const x: comptime_int = undefined; + _ = @as(u32, @bitCast(x)); +} + +export fn entry5() void { + _ = @as(@EnumLiteral(), @bitCast(y)); +} +export fn entry6() void { + const x: @EnumLiteral() = undefined; + _ = @as(u32, @bitCast(x)); +} + +export fn entry7() void { + _ = @as(error{}, @bitCast(y)); +} +export fn entry8() void { + const x: error{} = undefined; + _ = @as(u32, @bitCast(x)); +} + +export fn entry9() void { + _ = @as(?(anyerror!u32), @bitCast(y)); +} +export fn entry10() void { + const x: anyerror!u32 = undefined; + _ = @as(u32, @bitCast(x)); +} + +export fn entry11() void { + _ = @as(fn () void, @bitCast(y)); +} +export fn entry12() void { + const x: fn () void = undefined; + _ = @as(u32, @bitCast(x)); +} + +export fn entry13() void { + _ = @as(noreturn, @bitCast(y)); +} + +export fn entry14() void { + _ = @as(@TypeOf(null), @bitCast(y)); +} +export fn entry15() void { + const x: @TypeOf(null) = undefined; + _ = @as(u32, @bitCast(x)); +} + +export fn entry16() void { + const O = opaque {}; + _ = @as(O, @bitCast(y)); +} + +export fn entry17() void { + _ = @as(??u32, @bitCast(y)); +} +export fn entry18() void { + const x: ?u32 = undefined; + _ = @as(u32, @bitCast(x)); +} + +export fn entry19() void { + _ = @as(type, @bitCast(y)); +} +export fn entry20() void { + const x: type = undefined; + _ = @as(u32, @bitCast(x)); +} + +export fn entry21() void { + _ = @as(@TypeOf(undefined), @bitCast(y)); +} +export fn entry22() void { + const x: @TypeOf(undefined) = undefined; + _ = @as(u32, @bitCast(x)); +} + +export fn entry23() void { + _ = @as(void, @bitCast(y)); +} +export fn entry24() void { + const x: void = undefined; + _ = @as(u32, @bitCast(x)); +} + +export fn entry25() void { + _ = @as(*u8, @bitCast(y)); +} +export fn entry26() void { + const x: *u8 = undefined; + _ = @as(u32, @bitCast(x)); +} + +export fn entry27() void { + _ = @as(*u8, @bitCast(@as(*u32, @ptrFromInt(y)))); +} +export fn entry28() void { + const x: *u8 = undefined; + _ = @as(*u32, @bitCast(x)); +} + +export fn entry29() void { + const S = struct { x: u32 }; + _ = @as(S, @bitCast(y)); +} +export fn entry30() void { + const S = struct { x: u32 }; + const x: S = undefined; + _ = @as(u32, @bitCast(x)); +} + +export fn entry31() void { + const U = union { x: u32 }; + _ = @as(U, @bitCast(y)); +} +export fn entry32() void { + const U = union { x: u32 }; + const x: U = undefined; + _ = @as(u32, @bitCast(x)); +} + +export fn entry33() void { + const S = struct { x: u32 }; + _ = @as([10]S, @bitCast(y)); +} +export fn entry34() void { + const S = struct { x: u32 }; + const x: [10]S = undefined; + _ = @as(u32, @bitCast(x)); +} + +export fn entry35() void { + const E = enum {}; + _ = @as(E, @bitCast(y)); +} + +export fn entry36() void { + const E = enum { a, b, c }; + _ = @as(E, @bitCast(y)); +} + +// error +// +// :4:29: error: cannot @bitCast to 'comptime_float' +// :8:27: error: cannot @bitCast from 'comptime_float' +// :12:27: error: cannot @bitCast to 'comptime_int' +// :16:27: error: cannot @bitCast from 'comptime_int' +// :20:29: error: cannot @bitCast to '@EnumLiteral()' +// :24:27: error: cannot @bitCast from '@EnumLiteral()' +// :28:22: error: cannot @bitCast to 'error{}' +// :32:27: error: cannot @bitCast from 'error{}' +// :36:30: error: cannot @bitCast to 'anyerror!u32' +// :40:27: error: cannot @bitCast from 'anyerror!u32' +// :44:25: error: cannot @bitCast to 'fn () void' +// :48:27: error: cannot @bitCast from 'fn () void' +// :52:23: error: cannot @bitCast to 'noreturn' +// :56:28: error: cannot @bitCast to '@TypeOf(null)' +// :60:27: error: cannot @bitCast from '@TypeOf(null)' +// :65:16: error: cannot @bitCast to 'tmp.entry16.O' +// :64:15: note: opaque declared here +// :69:20: error: cannot @bitCast to '?u32' +// :69:20: note: use @ptrFromInt to cast from 'u32' +// :73:27: error: cannot @bitCast from '?u32' +// :73:27: note: use @intFromPtr to cast to 'u32' +// :77:19: error: cannot @bitCast to 'type' +// :81:27: error: cannot @bitCast from 'type' +// :85:33: error: cannot @bitCast to '@TypeOf(undefined)' +// :89:27: error: cannot @bitCast from '@TypeOf(undefined)' +// :93:19: error: @bitCast size mismatch: destination type 'void' has 0 bits but source type 'u32' has 32 bits +// :97:18: error: @bitCast size mismatch: destination type 'u32' has 32 bits but source type 'void' has 0 bits +// :101:18: error: cannot @bitCast to '*u8' +// :101:18: note: use @ptrFromInt to cast from 'u32' +// :105:27: error: cannot @bitCast from '*u8' +// :105:27: note: use @intFromPtr to cast to 'u32' +// :109:49: error: pointer type '*u32' does not allow address zero +// :113:19: error: cannot @bitCast to '*u32' +// :113:19: note: use @ptrCast to cast from '*u8' +// :118:16: error: cannot @bitCast to 'tmp.entry29.S' +// :117:15: note: struct declared here +// :123:27: error: cannot @bitCast from 'tmp.entry30.S' +// :121:15: note: struct declared here +// :128:16: error: cannot @bitCast to 'tmp.entry31.U' +// :127:15: note: union declared here +// :133:27: error: cannot @bitCast from 'tmp.entry32.U' +// :131:15: note: union declared here +// :138:20: error: cannot @bitCast to '[10]tmp.entry33.S' +// :143:27: error: cannot @bitCast from '[10]tmp.entry34.S' +// :148:16: error: cannot @bitCast to 'tmp.entry35.E' +// :147:15: note: enum declared here +// :153:16: error: cannot @bitCast to 'tmp.entry36.E' +// :152:15: note: enum declared here diff --git a/test/cases/compile_errors/bitcast_to_enum_invalid_tag_value.zig b/test/cases/compile_errors/bitcast_to_enum_invalid_tag_value.zig new file mode 100644 index 0000000000000000000000000000000000000000..456cb226c1861860424c41483ef42297496d9023 --- /dev/null +++ b/test/cases/compile_errors/bitcast_to_enum_invalid_tag_value.zig @@ -0,0 +1,16 @@ +const E = enum(u8) { a, b, c }; +export fn entry1() void { + const x: E = @bitCast(@as(u8, 3)); + _ = x; +} + +export fn entry2() void { + const x: E = @bitCast(@as(u8, undefined)); + _ = x; +} + +// error +// +// :3:18: error: enum 'tmp.E' has no tag with value '3' +// :1:11: note: enum declared here +// :8:27: error: use of undefined value here causes illegal behavior diff --git a/test/cases/compile_errors/empty_enum_from_backing_int.zig b/test/cases/compile_errors/empty_enum_from_backing_int.zig new file mode 100644 index 0000000000000000000000000000000000000000..939ed64765c32791258e2e19b2a1d4b3bfbe5f35 --- /dev/null +++ b/test/cases/compile_errors/empty_enum_from_backing_int.zig @@ -0,0 +1,18 @@ +const E = enum(noreturn) {}; + +export fn entry1() void { + const e: E = @fromBackingInt(undefined); + _ = e; +} + +export fn entry2() void { + const e: E = @fromBackingInt(0); + _ = e; +} + +// error +// +// :4:34: error: expected type 'noreturn', found '@TypeOf(undefined)' +// :4:34: note: cannot coerce to uninstantiable type 'noreturn' +// :9:34: error: expected type 'noreturn', found 'comptime_int' +// :9:34: note: cannot coerce to uninstantiable type 'noreturn' diff --git a/test/cases/compile_errors/enum_noreturn_backing_type.zig b/test/cases/compile_errors/enum_noreturn_backing_type.zig new file mode 100644 index 0000000000000000000000000000000000000000..d2ca3252ecb427ddf1d52ac76648967438e8ae1c --- /dev/null +++ b/test/cases/compile_errors/enum_noreturn_backing_type.zig @@ -0,0 +1,16 @@ +const E1 = enum(u32) {}; +export fn entry1() void { + const e: E1 = undefined; + _ = e; +} + +const E2 = enum(noreturn) { a, b, c }; +export fn entry2() void { + const e: E2 = undefined; + _ = e; +} + +// error +// +// :1:17: error: empty exhaustive enums must be backed by 'noreturn' +// :7:17: error: non-empty enums cannot be backed by 'noreturn' diff --git a/test/cases/compile_errors/extern_struct_with_non-extern-compatible_integer_tag_type.zig b/test/cases/compile_errors/extern_struct_with_non-extern-compatible_integer_tag_type.zig index fdf0f9aadd08021eb5a34367e80b93ade4ea1b3b..1dd3dd5b517ae85f4a077c90dbf65697c3b72bf4 100644 --- a/test/cases/compile_errors/extern_struct_with_non-extern-compatible_integer_tag_type.zig +++ b/test/cases/compile_errors/extern_struct_with_non-extern-compatible_integer_tag_type.zig @@ -1,15 +1,23 @@ -pub const E = enum(u31) { A, B, C }; -pub const S = extern struct { - e: E, -}; -export fn entry() void { - const s: S = undefined; - _ = s; +export fn entry1() void { + const E = enum(u31) { A, B, C }; + _ = @sizeOf(extern struct { + x: E, + }); +} +export fn entry2() void { + const E = enum(noreturn) {}; + _ = @sizeOf(extern struct { + x: E, + }); } // error // -// :3:8: error: extern structs cannot contain fields of type 'tmp.E' -// :1:15: note: enum tag type 'u31' is not extern compatible -// :1:15: note: only integers with 0 or power of two bits are extern compatible -// :1:15: note: enum declared here +// :4:12: error: extern structs cannot contain fields of type 'tmp.entry1.E' +// :2:15: note: enum tag type 'u31' is not extern compatible +// :2:15: note: only integers with 0 or power of two bits are extern compatible +// :2:15: note: enum declared here +// :10:12: error: extern structs cannot contain fields of type 'tmp.entry2.E' +// :8:15: note: enum tag type 'noreturn' is not extern compatible +// :8:15: note: 'noreturn' is only allowed as a return type +// :8:15: note: enum declared here diff --git a/test/cases/compile_errors/from_backing_int_invalid_dest_type.zig b/test/cases/compile_errors/from_backing_int_invalid_dest_type.zig new file mode 100644 index 0000000000000000000000000000000000000000..e24ef72f6e4fa7afe8c21f6c85982fe0b8c73340 --- /dev/null +++ b/test/cases/compile_errors/from_backing_int_invalid_dest_type.zig @@ -0,0 +1,37 @@ +const S1 = extern struct { x: u32 }; +export fn entry1(x: u32) void { + _ = @as(S1, @fromBackingInt(x)); +} + +const U1 = extern union { x: u32 }; +export fn entry2(x: u32) void { + _ = @as(U1, @fromBackingInt(x)); +} + +export fn entry3(x: u32) void { + _ = @as(u32, @fromBackingInt(x)); +} + +const S2 = packed struct { x: u32 }; +export fn entry4(x: u32) void { + _ = @as(S2, @fromBackingInt(x)); +} + +const U2 = packed union { x: u32 }; +export fn entry5(x: u32) u32 { + _ = @as(U2, @fromBackingInt(x)); +} + +// error +// +// :3:17: error: non-packed struct 'tmp.S1' does not have a backing integer +// :1:19: note: struct declared here +// :8:17: error: non-packed union 'tmp.U1' does not have a backing integer +// :6:19: note: union declared here +// :12:18: error: expected enum, packed union or packed struct, found 'u32' +// :17:17: error: @fromBackingInt is ambiguous for type 'tmp.S2' +// :15:19: note: backing integer type of struct is inferred +// :15:19: note: consider explicitly specifying the backing integer type +// :22:17: error: @fromBackingInt is ambiguous for type 'tmp.U2' +// :20:19: note: backing integer type of union is inferred +// :20:19: note: consider explicitly specifying the backing integer type diff --git a/test/cases/compile_errors/from_backing_int_type_mismatch.zig b/test/cases/compile_errors/from_backing_int_type_mismatch.zig new file mode 100644 index 0000000000000000000000000000000000000000..945d483da0adaae103732bdbf46752324de6f986 --- /dev/null +++ b/test/cases/compile_errors/from_backing_int_type_mismatch.zig @@ -0,0 +1,23 @@ +const E = enum(u32) { a, b, c }; +export fn entry1(x: u64) E { + return @fromBackingInt(x); +} + +const S = packed struct(u32) { x: u32 }; +export fn entry2(x: u64) S { + return @fromBackingInt(x); +} + +const U = packed union(u32) { x: u32 }; +export fn entry3(x: u64) U { + return @fromBackingInt(x); +} + +// error +// +// :3:28: error: expected type 'u32', found 'u64' +// :3:28: note: unsigned 32-bit int cannot represent all possible unsigned 64-bit values +// :8:28: error: expected type 'u32', found 'u64' +// :8:28: note: unsigned 32-bit int cannot represent all possible unsigned 64-bit values +// :13:28: error: expected type 'u32', found 'u64' +// :13:28: note: unsigned 32-bit int cannot represent all possible unsigned 64-bit values diff --git a/test/cases/compile_errors/from_backing_int_undef.zig b/test/cases/compile_errors/from_backing_int_undef.zig new file mode 100644 index 0000000000000000000000000000000000000000..a45e2a7f9ec3bfaafbbdf2fd3594dee7d8c52057 --- /dev/null +++ b/test/cases/compile_errors/from_backing_int_undef.zig @@ -0,0 +1,22 @@ +const E = enum(u32) { x }; +export fn entry1() void { + @compileLog(@as(E, @fromBackingInt(undefined))); +} + +const S = packed struct(u32) { x: u32 }; +export fn entry2() void { + @compileLog(@as(S, @fromBackingInt(undefined))); +} + +const U = packed union(u32) { x: u32 }; +export fn entry3() void { + @compileLog(@as(U, @fromBackingInt(undefined))); +} + +// error +// +// :3:40: error: use of undefined value here causes illegal behavior +// +// Compile Log Output: +// @as(tmp.S, undefined) +// @as(tmp.U, undefined) diff --git a/test/cases/compile_errors/initialize_empty_union.zig b/test/cases/compile_errors/initialize_empty_union.zig index 2847446b2516a1eb4f968a8bb31b24abc7bd132d..a7945a105b6d973411f6d2e31810ae6e5f7e595e 100644 --- a/test/cases/compile_errors/initialize_empty_union.zig +++ b/test/cases/compile_errors/initialize_empty_union.zig @@ -1,10 +1,10 @@ const EnumInferred = enum {}; -const EnumExplicit = enum(u8) {}; +const EnumExplicit = enum(noreturn) {}; const EnumNonexhaustive = enum(u8) { _ }; const U0 = union {}; const U1 = union(enum) {}; -const U2 = union(enum(u8)) {}; +const U2 = union(enum(noreturn)) {}; const U3 = union(EnumInferred) {}; const U4 = union(EnumExplicit) {}; const U5 = union(EnumNonexhaustive) {}; diff --git a/test/cases/compile_errors/instantiate_empty_enum.zig b/test/cases/compile_errors/instantiate_empty_enum.zig new file mode 100644 index 0000000000000000000000000000000000000000..bc469024c841db6a5cc95dc33785046fe9dc317c --- /dev/null +++ b/test/cases/compile_errors/instantiate_empty_enum.zig @@ -0,0 +1,32 @@ +const E = enum {}; + +export fn entry1() void { + const e: E = undefined; + _ = e; +} + +export fn entry2() void { + const e: E = @enumFromInt(@as(u8, undefined)); + _ = e; +} + +export fn entry3() void { + const e: E = .a; + _ = e; +} + +export fn entry4() void { + const e: E = @enumFromInt(0); + _ = e; +} + +// error +// +// :4:18: error: expected type 'tmp.E', found '@TypeOf(undefined)' +// :4:18: note: cannot coerce to uninstantiable type 'tmp.E' +// :1:11: note: enum declared here +// :9:31: error: use of undefined value here causes illegal behavior +// :14:19: error: enum 'tmp.E' has no member named 'a' +// :1:11: note: enum declared here +// :19:18: error: enum 'tmp.E' has no tag with value '0' +// :1:11: note: enum declared here diff --git a/test/cases/compile_errors/int_from_enum_undefined.zig b/test/cases/compile_errors/int_from_enum_undefined.zig deleted file mode 100644 index 96aaa48c280064a7f886fd583ad97a0359b86477..0000000000000000000000000000000000000000 --- a/test/cases/compile_errors/int_from_enum_undefined.zig +++ /dev/null @@ -1,11 +0,0 @@ -export fn a() void { - const E = enum {}; - var e: E = undefined; - _ = &e; - _ = @intFromEnum(e); -} - -// error -// -// :5:22: error: cannot use @intFromEnum on empty enum 'tmp.a.E' -// :2:15: note: enum declared here diff --git a/test/cases/compile_errors/invalid_non-exhaustive_enum_to_union.zig b/test/cases/compile_errors/invalid_non-exhaustive_enum_to_union.zig index a6f10668b628fab7b71ee06ce9ca107960ff4686..439b80e13de47f3bcb9f8722ad7576ed5e342edc 100644 --- a/test/cases/compile_errors/invalid_non-exhaustive_enum_to_union.zig +++ b/test/cases/compile_errors/invalid_non-exhaustive_enum_to_union.zig @@ -22,5 +22,5 @@ export fn bar() void { // // :12:16: error: runtime coercion to union 'tmp.U' from non-exhaustive enum // :1:11: note: enum declared here -// :17:16: error: union 'tmp.U' has no tag with value '@enumFromInt(15)' +// :17:16: error: union 'tmp.U' has no tag with value '@fromBackingInt(15)' // :6:11: note: union declared here diff --git a/test/cases/compile_errors/packed_struct_with_fields_of_not_allowed_types.zig b/test/cases/compile_errors/packed_struct_with_fields_of_not_allowed_types.zig index d7a5d355c9d26a565b179d3610255a55bafaaab7..efe1f9157f0c9e6b5552e71cc17fed01b8fe1df6 100644 --- a/test/cases/compile_errors/packed_struct_with_fields_of_not_allowed_types.zig +++ b/test/cases/compile_errors/packed_struct_with_fields_of_not_allowed_types.zig @@ -81,6 +81,12 @@ export fn entry15() void { x: *const u32, }); } +export fn entry16() void { + const E = enum(noreturn) {}; + _ = @sizeOf(packed struct { + x: E, + }); +} // error // @@ -114,3 +120,5 @@ export fn entry15() void { // :81:12: error: packed structs cannot contain fields of type '*const u32' // :81:12: note: pointers cannot be directly bitpacked // :81:12: note: consider using 'usize' and '@intFromPtr' +// :87:12: error: packed structs cannot contain fields of type 'tmp.entry16.E' +// :87:12: note: type does not have a bit-packed representation diff --git a/test/cases/compile_errors/packed_union_with_fields_of_not_allowed_types.zig b/test/cases/compile_errors/packed_union_with_fields_of_not_allowed_types.zig index d0e09742b5080e165af9fc105ff62fad177ff993..a479141e5a4ab67e6809cad5c460459bc8babac4 100644 --- a/test/cases/compile_errors/packed_union_with_fields_of_not_allowed_types.zig +++ b/test/cases/compile_errors/packed_union_with_fields_of_not_allowed_types.zig @@ -10,6 +10,12 @@ export fn entry1() void { x: *const u32, }); } +export fn entry2() void { + const E = enum(noreturn) {}; + _ = @sizeOf(packed union { + x: E, + }); +} // error // @@ -19,3 +25,5 @@ export fn entry1() void { // :10:12: error: packed unions cannot contain fields of type '*const u32' // :10:12: note: pointers cannot be directly bitpacked // :10:12: note: consider using 'usize' and '@intFromPtr' +// :16:12: error: packed unions cannot contain fields of type 'tmp.entry2.E' +// :16:12: note: type does not have a bit-packed representation diff --git a/test/cases/compile_errors/reify_type_for_tagged_union_with_no_enum_fields.zig b/test/cases/compile_errors/reify_type_for_tagged_union_with_no_enum_fields.zig index e9c27b7eeafa92db4b461173e196c3ce91eb26a2..d204d18f195dddca616550c57b10465f38c06218 100644 --- a/test/cases/compile_errors/reify_type_for_tagged_union_with_no_enum_fields.zig +++ b/test/cases/compile_errors/reify_type_for_tagged_union_with_no_enum_fields.zig @@ -1,4 +1,4 @@ -const Tag = @Enum(u0, .exhaustive, &.{}, &.{}); +const Tag = @Enum(noreturn, .exhaustive, &.{}, &.{}); const Tagged = @Union(.auto, Tag, &.{ "signed", "unsigned" }, &.{ i32, u32 }, &@splat(.{})); export fn entry() void { const tagged: Tagged = undefined; diff --git a/test/cases/compile_errors/sizeof_alignof_empty_union.zig b/test/cases/compile_errors/sizeof_alignof_empty_union.zig index 58e6aa635088c30cab0c1d7306f088406fcb34d1..1144670907582660a5e8b03224d5aceb61f6de3e 100644 --- a/test/cases/compile_errors/sizeof_alignof_empty_union.zig +++ b/test/cases/compile_errors/sizeof_alignof_empty_union.zig @@ -1,10 +1,10 @@ const EnumInferred = enum {}; -const EnumExplicit = enum(u8) {}; +const EnumExplicit = enum(noreturn) {}; const EnumNonexhaustive = enum(u8) { _ }; const U0 = union {}; const U1 = union(enum) {}; -const U2 = union(enum(u8)) {}; +const U2 = union(enum(noreturn)) {}; const U3 = union(EnumInferred) {}; const U4 = union(EnumExplicit) {}; const U5 = union(EnumNonexhaustive) {}; diff --git a/test/cases/compile_errors/tagName_on_invalid_value_of_non-exhaustive_enum.zig b/test/cases/compile_errors/tagName_on_invalid_value_of_non-exhaustive_enum.zig index f35817bda75d92bb710da6703b0cdbc5ee81a763..73af4fde6cacddf5ca1a6685c4a0d2601d6b47df 100644 --- a/test/cases/compile_errors/tagName_on_invalid_value_of_non-exhaustive_enum.zig +++ b/test/cases/compile_errors/tagName_on_invalid_value_of_non-exhaustive_enum.zig @@ -6,5 +6,5 @@ test "enum" { // error // is_test=true // -// :3:9: error: no field with value '@enumFromInt(5)' in enum 'tmp.test.enum.E' +// :3:9: error: no field with value '@fromBackingInt(5)' in enum 'tmp.test.enum.E' // :2:15: note: declared here diff --git a/test/cases/safety/backing_int_no_matching_tag_value.zig b/test/cases/safety/backing_int_no_matching_tag_value.zig new file mode 100644 index 0000000000000000000000000000000000000000..fa359c489242e7e59baa0f3e0de47040ee1eb497 --- /dev/null +++ b/test/cases/safety/backing_int_no_matching_tag_value.zig @@ -0,0 +1,25 @@ +const std = @import("std"); + +pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn { + _ = stack_trace; + if (std.mem.eql(u8, message, "invalid enum value")) { + std.process.exit(0); + } + std.process.exit(1); +} +const Foo = enum(u8) { + a, + b, + c, +}; +pub fn main() !void { + _ = bar(3); + return error.TestFailed; +} +fn bar(a: u8) Foo { + return @fromBackingInt(a); +} + +// run +// backend=selfhosted,llvm +// target=x86_64-linux,aarch64-linux diff --git a/test/cases/safety/bitcast_to_enum_no_matching_tag_value.zig b/test/cases/safety/bitcast_to_enum_no_matching_tag_value.zig new file mode 100644 index 0000000000000000000000000000000000000000..d1393575db10d1a285245ffae003d5e9635a2bf2 --- /dev/null +++ b/test/cases/safety/bitcast_to_enum_no_matching_tag_value.zig @@ -0,0 +1,25 @@ +const std = @import("std"); + +pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn { + _ = stack_trace; + if (std.mem.eql(u8, message, "invalid enum value")) { + std.process.exit(0); + } + std.process.exit(1); +} +const Foo = enum(u8) { + a, + b, + c, +}; +pub fn main() !void { + _ = bar(3); + return error.TestFailed; +} +fn bar(a: u8) Foo { + return @bitCast(a); +} + +// run +// backend=selfhosted,llvm +// target=x86_64-linux,aarch64-linux