authorgravatar for justus@klausecker.deJustus Klausecker <justus@klausecker.de> 2026-03-11 16:30:12+01:00
committergravatar for justus@klausecker.deJustus Klausecker <justus@klausecker.de> 2026-03-11 16:44:08+01:00
logbe9b42d7074077eadf18e8d934c6f22051397a72
tree10f8788abd89af5dedcd9c3a371ab9f32b30f5e7
parentb4ffb402c082605c4b324e88120306fc8fb3cf32

compiler: allow equality comparisons for packed unions

This was already possible in practice by just wrapping a packed union into a packed struct. Now it's also possible without doing that.

5 files changed, 43 insertions(+), 3 deletions(-)

doc/langref.html.in+7
...@@ -2513,6 +2513,13 @@ or...@@ -2513,6 +2513,13 @@ or
2513 <p>A {#syntax#}packed union{#endsyntax#} has well-defined in-memory layout and is eligible2513 <p>A {#syntax#}packed union{#endsyntax#} has well-defined in-memory layout and is eligible
2514 to be in a {#link|packed struct#}.</p>2514 to be in a {#link|packed struct#}.</p>
2515 <p>All fields in a packed union must have the same {#link|@bitSizeOf#}.</p>2515 <p>All fields in a packed union must have the same {#link|@bitSizeOf#}.</p>
2516
2517 <p>
2518 Equating packed unions results in a comparison of the backing integer,
2519 and only works for the {#syntax#}=={#endsyntax#} and {#syntax#}!={#endsyntax#} {#link|Operators#}.
2520 </p>
2521 {#code|test_packed_union_equality.zig#}
2522
2516 {#header_close#}2523 {#header_close#}
25172524
2518 {#header_open|Anonymous Union Literals#}2525 {#header_open|Anonymous Union Literals#}
doc/langref/test_packed_union_equality.zig created+14
...@@ -0,0 +1,14 @@
1const std = @import("std");
2const expectEqual = std.testing.expectEqual;
3
4test "packed union equality" {
5 const U = packed union {
6 a: u4,
7 b: i4,
8 };
9 const x: U = .{ .a = 3 };
10 const y: U = .{ .b = 3 };
11 try expectEqual(x, y);
12}
13
14// test
src/Type.zig+1-2
...@@ -334,11 +334,10 @@ pub fn isSelfComparable(ty: Type, zcu: *const Zcu, is_equality_cmp: bool) bool {...@@ -334,11 +334,10 @@ pub fn isSelfComparable(ty: Type, zcu: *const Zcu, is_equality_cmp: bool) bool {
334 .undefined,334 .undefined,
335 .null,335 .null,
336 .error_union,336 .error_union,
337 .@"union",
338 .frame,337 .frame,
339 => false,338 => false,
340339
341 .@"struct" => is_equality_cmp and ty.containerLayout(zcu) == .@"packed",340 .@"struct", .@"union" => is_equality_cmp and ty.containerLayout(zcu) == .@"packed",
342 .pointer => !ty.isSlice(zcu) and (is_equality_cmp or ty.isCPtr(zcu)),341 .pointer => !ty.isSlice(zcu) and (is_equality_cmp or ty.isCPtr(zcu)),
343 .optional => {342 .optional => {
344 if (!is_equality_cmp) return false;343 if (!is_equality_cmp) return false;
src/codegen/llvm.zig+1-1
...@@ -5750,7 +5750,7 @@ pub const FuncGen = struct {...@@ -5750,7 +5750,7 @@ pub const FuncGen = struct {
5750 return phi.toValue();5750 return phi.toValue();
5751 },5751 },
5752 .float => return self.buildFloatCmp(fast, op, operand_ty, .{ lhs, rhs }),5752 .float => return self.buildFloatCmp(fast, op, operand_ty, .{ lhs, rhs }),
5753 .@"struct" => scalar_ty.bitpackBackingInt(zcu),5753 .@"struct", .@"union" => scalar_ty.bitpackBackingInt(zcu),
5754 else => unreachable,5754 else => unreachable,
5755 };5755 };
5756 const is_signed = int_ty.isSignedInt(zcu);5756 const is_signed = int_ty.isSignedInt(zcu);
test/behavior/packed-union.zig+20
...@@ -199,3 +199,23 @@ test "packed union with explicit backing integer" {...@@ -199,3 +199,23 @@ test "packed union with explicit backing integer" {
199 try U.check(.{ .raw = -2 });199 try U.check(.{ .raw = -2 });
200 try comptime U.check(.{ .raw = -2 });200 try comptime U.check(.{ .raw = -2 });
201}201}
202
203test "packed union equality" {
204 const Foo = packed union {
205 a: u4,
206 b: i4,
207 };
208
209 const S = struct {
210 fn doTest(x: Foo, y: Foo) !void {
211 try expect(x == y);
212 try expect(!(x != y));
213 }
214 };
215
216 const x: Foo = .{ .a = 3 };
217 const y: Foo = .{ .b = 3 };
218
219 try S.doTest(x, y);
220 comptime try S.doTest(x, y);
221}