From cb4c344e19a269ac227489a96e2ef53dd077ece0 Mon Sep 17 00:00:00 2001
From: Justus Klausecker
Date: Wed, 3 Jun 2026 15:27:46 +0200
Subject: [PATCH] compiler: add `@backingInt` and `@fromBackingInt`
This commit implements the `@backingInt` and `@fromBackingInt` builtins.
These builtins are meant to replace the now-deprecated `@intFromEnum` and
`@enumFromInt` builtins.
`@backingInt` works with all enums and with bitpacks with *explicit* backing
integer types only. It also works with tagged unions, returning the backing
integer of the active tag value.
An `undefined` enum or bitpack yields an `undefined` backing integer.
The builtin is lowered to `bit_cast`.
`@fromBackingInt` infers its result type, which may be any enum or a bitpack
with an *explicit* backing integer type. It takes a parameter of exactly
that backing integer type.
For enums, passing a backing integer that is either `undefined` or would
yield an invalid tag value results in safety-checked Illegal Behavior.
For bitpacks, passing an `undefined` backing integer yields an `undefined`
bitpack.
The builtin is lowered to `bit_cast` or `bit_cast_safe` (more details below).
`@bitCast` now also performs a safety check for invalid tag values if its
destination type is an `enum`.
This commit also introduces a new AIR instruction, `bit_cast_safe`.
Similarly to `int_cast_safe`, it includes a safety check for invalid enum
tag values. When `@enumFromInt` is eventually removed, `int_cast_safe` won't
need to perform this safety check anymore.
Legalize only implements `expand_bit_cast_safe`, and no additional insts
to scalarize `bit_cast_safe` with vector/array operands. That is because
it can just reuse the existing scalarizations for regular `bit_cast`.
The safety check mandated by `bit_cast_safe` is only desired if the result
type is a scalar enum tag value, so the scalarization process is exactly
equivalent. The only difference is that we have to perform a safety check
on the result of the scalarization.
This means that there's no reason for a backend to want scalarization of
regular `bit_cast`, but not of `bit_cast_safe` (or the other way round).
If a backend is having trouble with the `bit_cast_safe` safety check it
should just use `expand_bit_cast_safe` instead.
Also adds a `std.meta.BackingInt()` function to conveniently get the result
type of `@backingInt` and for use in the langref.
---
doc/langref.html.in | 76 +++-
doc/langref/runtime_invalid_enum_cast.zig | 12 +-
.../test_comptime_invalid_enum_cast.zig | 4 +-
doc/langref/test_enums.zig | 22 +-
doc/langref/test_inline_else.zig | 4 +-
.../test_packed_struct_backing_int.zig | 21 +
.../test_tagged_union_with_tag_values.zig | 4 +-
lib/std/meta.zig | 32 +-
lib/std/zig/AstGen.zig | 18 +
lib/std/zig/AstRlAnnotate.zig | 2 +
lib/std/zig/BuiltinFn.zig | 16 +
lib/std/zig/Zir.zig | 30 +-
src/Air.zig | 6 +
src/Air/Legalize.zig | 114 +++++-
src/Air/Liveness.zig | 1 +
src/Air/Liveness/Verify.zig | 1 +
src/Air/Verify.zig | 2 +-
src/Air/print.zig | 1 +
src/InternPool.zig | 1 +
src/Sema.zig | 320 +++++++++++----
src/Sema/reinterpret.zig | 8 +-
src/Sema/type_resolution.zig | 2 +-
src/Type.zig | 54 +--
src/Value.zig | 17 +-
src/Zcu/PerThread.zig | 35 +-
src/codegen.zig | 2 +-
src/codegen/aarch64.zig | 6 +-
src/codegen/aarch64/Select.zig | 2 +
src/codegen/c.zig | 6 +-
src/codegen/c/type.zig | 3 +-
src/codegen/c/type/render_defs.zig | 4 +-
src/codegen/llvm.zig | 2 +-
src/codegen/llvm/FuncGen.zig | 29 +-
src/codegen/riscv64/CodeGen.zig | 5 +-
src/codegen/sparc64/CodeGen.zig | 3 +-
src/codegen/spirv/CodeGen.zig | 21 +-
src/codegen/wasm/CodeGen.zig | 8 +-
src/codegen/x86_64/CodeGen.zig | 2 +
src/link/Wasm/Flush.zig | 2 +-
src/print_value.zig | 30 +-
src/print_zir.zig | 14 +
test/behavior.zig | 1 +
test/behavior/backing_int.zig | 378 ++++++++++++++++++
test/behavior/enum.zig | 15 +
test/behavior/packed-struct.zig | 31 +-
test/behavior/packed-union.zig | 14 +
test/behavior/switch.zig | 4 +-
test/behavior/type.zig | 12 +-
.../backing_int_invalid_arg_type.zig | 38 ++
...itCast_with_invalid_array_element_type.zig | 23 --
.../compile_errors/bitcast_invalid_types.zig | 205 ++++++++++
.../bitcast_to_enum_invalid_tag_value.zig | 16 +
.../empty_enum_from_backing_int.zig | 18 +
.../from_backing_int_invalid_dest_type.zig | 37 ++
.../from_backing_int_type_mismatch.zig | 23 ++
.../compile_errors/from_backing_int_undef.zig | 22 +
.../invalid_non-exhaustive_enum_to_union.zig | 2 +-
...n_invalid_value_of_non-exhaustive_enum.zig | 2 +-
.../backing_int_no_matching_tag_value.zig | 25 ++
.../bitcast_to_enum_no_matching_tag_value.zig | 25 ++
60 files changed, 1578 insertions(+), 255 deletions(-)
create mode 100644 doc/langref/test_packed_struct_backing_int.zig
create mode 100644 test/behavior/backing_int.zig
create mode 100644 test/cases/compile_errors/backing_int_invalid_arg_type.zig
delete mode 100644 test/cases/compile_errors/bitCast_with_invalid_array_element_type.zig
create mode 100644 test/cases/compile_errors/bitcast_invalid_types.zig
create mode 100644 test/cases/compile_errors/bitcast_to_enum_invalid_tag_value.zig
create mode 100644 test/cases/compile_errors/empty_enum_from_backing_int.zig
create mode 100644 test/cases/compile_errors/from_backing_int_invalid_dest_type.zig
create mode 100644 test/cases/compile_errors/from_backing_int_type_mismatch.zig
create mode 100644 test/cases/compile_errors/from_backing_int_undef.zig
create mode 100644 test/cases/safety/backing_int_no_matching_tag_value.zig
create mode 100644 test/cases/safety/bitcast_to_enum_no_matching_tag_value.zig
diff --git a/doc/langref.html.in b/doc/langref.html.in
index 7930468882d7eeae945dfeb77f62ac74dcf0e16a..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|noreturn#}
+ {#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) {
- {#link|@bitCast#} - change type but maintain bit representation
- {#link|@alignCast#} - make a pointer have more alignment
- - {#link|@enumFromInt#} - obtain an enum value based on its integer tag value
+ - {#link|@fromBackingInt#} - obtain an enum or a packed struct/union value based on its backing integer
- {#link|@errorFromInt#} - obtain an error code based on its integer value
- {#link|@errorCast#} - convert to a smaller error set
- {#link|@floatCast#} - convert a larger float to a smaller float
- {#link|@floatFromInt#} - convert an integer to a float value
- {#link|@intCast#} - convert between integer types
- {#link|@intFromBool#} - convert true to 1 and false to 0
- - {#link|@intFromEnum#} - obtain the integer tag value of an enum or tagged union
+ - {#link|@backingInt#} - obtain the backing integer value of an enum or a packed struct/union
- {#link|@intFromError#} - obtain the integer value of an error code
- {#link|@round#}, {#link|@floor#}, {#link|@ceil#}, {#link|@trunc#} - float to integer conversion
- {#link|@intFromPtr#} - obtain the address of a pointer
@@ -4432,6 +4439,18 @@ comptime {
{#see_also|@atomicLoad|@atomicRmw|@cmpxchgWeak|@cmpxchgStrong#}
{#header_close#}
+ {#header_open|@backingInt#}
+
{#syntax#}@backingInt(enum_or_bitpack: T) BackingInt(T){#endsyntax#}
+
+ Converts an {#link|enum#}, a {#link|packed struct#} or a {#link|packed union#} value
+ to its backing integer.
+
+
+ Also works with {#link|tagged unions|Tagged union#}, acting on the active enum tag value.
+
+ {#see_also|@fromBackingInt|@bitCast#}
+ {#header_close#}
+
{#header_open|@bitCast#}
{#syntax#}@bitCast(value: anytype) anytype{#endsyntax#}
@@ -4452,8 +4471,18 @@ comptime {
Convert {#syntax#}i32{#endsyntax#} to {#syntax#}u32{#endsyntax#} preserving twos complement
- 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 9da7c160edcabd10a2e8fc07a6e7e480edaffb2c..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.
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 78e26d6c8f4f99dd04dcf0dcf3e79e8ae14e20d2..e891e4320d5bdc9faa01be96cd8fd1ba1d8b2165 100644
--- a/lib/std/meta.zig
+++ b/lib/std/meta.zig
@@ -506,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/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/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..bd4f88158161b993768e5af3fb8369d5d49e422f 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,6 +604,17 @@ 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)) {
+ continue :inst l.replaceInst(inst, .block, try l.safeBitcastBlockPayload(inst));
+ } 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));
@@ -1006,7 +1021,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 +1094,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 +1722,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,6 +2125,76 @@ fn scalarizeReduceBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index, optimize
} };
}
+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();
+
+ // The worst case is a bitcast to an exhaustive enum and looks like 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 opt_condbr: ?CondBr = null;
+
+ var main_block: Block = .init(&inst_buf);
+ var cur_block: *Block = &main_block;
+
+ const cast_inst = cur_block.addBitCast(l, dest_ty, operand_ref);
+
+ if (dest_ty.zigTypeTag(zcu) == .@"enum" and
+ !dest_ty.isNonexhaustiveEnum(zcu) and
+ zcu.backendSupportsFeature(.is_named_enum_value))
+ {
+ // We are building this:
+ // %1 = is_named_enum_value(%cast_inst)
+ // %2 = cond_br(%1, {
+ //
+ // }, {
+ //
+ // })
+ const is_named_inst = cur_block.add(l, .{
+ .tag = .is_named_enum_value,
+ .data = .{ .un_op = cast_inst },
+ });
+ opt_condbr = .init(l, is_named_inst.toRef(), cur_block, .{ .false = .cold });
+ const condbr = &(opt_condbr.?);
+ condbr.else_block = .init(cur_block.stealRemainingCapacity());
+ try condbr.else_block.addPanic(l, .invalid_enum_value);
+ condbr.then_block = .init(condbr.else_block.stealRemainingCapacity());
+ 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,
+ } },
+ });
+ // We might not have used all of the instructions; that's intentional.
+ _ = cur_block.stealRemainingCapacity();
+
+ if (opt_condbr) |condbr| try condbr.finish(l);
+ return .{ .ty_pl = .{
+ .ty = .fromType(dest_ty),
+ .payload = try l.addBlockBody(main_block.body()),
+ } };
+}
+
fn safeIntcastBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index) Error!Air.Inst.Data {
const pt = l.pt;
const zcu = pt.zcu;
@@ -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: {
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 632bd75ca0f5ec2b1198f4bd8fac0bdc76fdbe0b..bb68c6c5a07161773911af2c740b6ff68d5cfa72 100644
--- a/src/InternPool.zig
+++ b/src/InternPool.zig
@@ -10192,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 12fc7cb5fc145b79ab9da6f9da193ed78aa1d338..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,12 +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);
+ 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);
@@ -7884,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());
}
@@ -7907,7 +7932,7 @@ fn zirEnumFromInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
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);
}
@@ -9361,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)) {
@@ -9396,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 {
@@ -9929,6 +10129,9 @@ fn analyzeSwitchBlock(
operand_ty.containerLayout(zcu) != .@"packed";
const err_set = operand_ty.zigTypeTag(zcu) == .error_set;
+ // 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,
.loop => |l| l.init_cond,
@@ -10433,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();
@@ -10585,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 };
},
@@ -10886,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
@@ -11316,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);
@@ -11938,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 = .{};
@@ -11955,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);
},
}
}
@@ -12443,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|
@@ -18400,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| {
@@ -18537,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);
@@ -29716,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);
}
@@ -29774,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,
@@ -33942,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;
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 5c4211d22980d2d5daedb08b80e1f151990bddcc..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
};
}
diff --git a/src/Type.zig b/src/Type.zig
index b56a4868bc587ca5473001779e107fd221068975..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,15 +3057,12 @@ pub fn unpackable(ty: Type, zcu: *const Zcu) ?UnpackableReason {
.one, .many, .c => .pointer,
},
- .@"enum" => {
- const enum_obj = zcu.intern_pool.loadEnumType(ty.toIntern());
- return switch (enum_obj.int_tag_mode) {
- .explicit => if (enum_obj.int_tag_type != .noreturn_type)
- null
- else
- .other,
- .auto => .{ .enum_inferred_int_tag = ty },
- };
+ .@"enum" => switch (ty.backingIntMode(zcu)) {
+ .explicit => switch (ty.backingIntType(zcu).toIntern()) {
+ else => null,
+ .noreturn_type => .other,
+ },
+ .auto => .{ .enum_inferred_int_tag = ty },
},
.@"struct" => switch (ty.containerLayout(zcu)) {
@@ -3231,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 6d42d8d1ff69c9f5270106b8c9c311de3f309586..8d203139b8854c4913902087f033d26af520c60d 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,
@@ -5134,7 +5136,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/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/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..a11976c72063f5f662ecab540e8b3e1ff08e2c47
--- /dev/null
+++ b/test/behavior/backing_int.zig
@@ -0,0 +1,378 @@
+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);
+
+ 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);
+
+ 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 aa664a46ff2a5f96f7b9b8175394676df235b379..93365bb889555b722b4ff83f4ad30d24110a293d 100644
--- a/test/behavior/enum.zig
+++ b/test/behavior/enum.zig
@@ -1465,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..986c15415486bf9c23b7b879a9d305e1a3cfaed1 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,18 @@ 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 });
+}
diff --git a/test/behavior/packed-union.zig b/test/behavior/packed-union.zig
index 20732eab98a312ba077ffde4494e8deec179225d..44090943d487e3b52beae89a32baf5033d034a3d 100644
--- a/test/behavior/packed-union.zig
+++ b/test/behavior/packed-union.zig
@@ -227,3 +227,17 @@ 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 });
+}
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 bf012e0061f86384fa985e74353fe4e16c12492b..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(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/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/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/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
--
2.54.0