Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update std.testing.expectEqual and friends to use peer type resolution #17431

Merged
merged 4 commits into from
Jan 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions lib/std/debug.zig
Original file line number Diff line number Diff line change
Expand Up @@ -853,8 +853,8 @@ test "machoSearchSymbols" {
.{ .addr = 300, .strx = undefined, .size = undefined, .ofile = undefined },
};

try testing.expectEqual(@as(?*const MachoSymbol, null), machoSearchSymbols(&symbols, 0));
try testing.expectEqual(@as(?*const MachoSymbol, null), machoSearchSymbols(&symbols, 99));
try testing.expectEqual(null, machoSearchSymbols(&symbols, 0));
try testing.expectEqual(null, machoSearchSymbols(&symbols, 99));
try testing.expectEqual(&symbols[0], machoSearchSymbols(&symbols, 100).?);
try testing.expectEqual(&symbols[0], machoSearchSymbols(&symbols, 150).?);
try testing.expectEqual(&symbols[0], machoSearchSymbols(&symbols, 199).?);
Expand Down
4 changes: 2 additions & 2 deletions lib/std/http.zig
Original file line number Diff line number Diff line change
Expand Up @@ -283,8 +283,8 @@ pub const Status = enum(u10) {
}

test {
try std.testing.expectEqual(@as(?Status.Class, Status.Class.success), Status.ok.class());
try std.testing.expectEqual(@as(?Status.Class, Status.Class.client_error), Status.not_found.class());
try std.testing.expectEqual(Status.Class.success, Status.ok.class());
try std.testing.expectEqual(Status.Class.client_error, Status.not_found.class());
}
};

Expand Down
26 changes: 13 additions & 13 deletions lib/std/json/static_test.zig
Original file line number Diff line number Diff line change
Expand Up @@ -373,19 +373,19 @@ test "test all types" {
test "parse" {
try testing.expectEqual(false, try parseFromSliceLeaky(bool, testing.allocator, "false", .{}));
try testing.expectEqual(true, try parseFromSliceLeaky(bool, testing.allocator, "true", .{}));
try testing.expectEqual(@as(u1, 1), try parseFromSliceLeaky(u1, testing.allocator, "1", .{}));
try testing.expectEqual(1, try parseFromSliceLeaky(u1, testing.allocator, "1", .{}));
try testing.expectError(error.Overflow, parseFromSliceLeaky(u1, testing.allocator, "50", .{}));
try testing.expectEqual(@as(u64, 42), try parseFromSliceLeaky(u64, testing.allocator, "42", .{}));
try testing.expectEqual(@as(f64, 42), try parseFromSliceLeaky(f64, testing.allocator, "42.0", .{}));
try testing.expectEqual(@as(?bool, null), try parseFromSliceLeaky(?bool, testing.allocator, "null", .{}));
try testing.expectEqual(@as(?bool, true), try parseFromSliceLeaky(?bool, testing.allocator, "true", .{}));
try testing.expectEqual(42, try parseFromSliceLeaky(u64, testing.allocator, "42", .{}));
try testing.expectEqual(42, try parseFromSliceLeaky(f64, testing.allocator, "42.0", .{}));
try testing.expectEqual(null, try parseFromSliceLeaky(?bool, testing.allocator, "null", .{}));
try testing.expectEqual(true, try parseFromSliceLeaky(?bool, testing.allocator, "true", .{}));

try testing.expectEqual(@as([3]u8, "foo".*), try parseFromSliceLeaky([3]u8, testing.allocator, "\"foo\"", .{}));
try testing.expectEqual(@as([3]u8, "foo".*), try parseFromSliceLeaky([3]u8, testing.allocator, "[102, 111, 111]", .{}));
try testing.expectEqual(@as([0]u8, undefined), try parseFromSliceLeaky([0]u8, testing.allocator, "[]", .{}));
try testing.expectEqual("foo".*, try parseFromSliceLeaky([3]u8, testing.allocator, "\"foo\"", .{}));
try testing.expectEqual("foo".*, try parseFromSliceLeaky([3]u8, testing.allocator, "[102, 111, 111]", .{}));
try testing.expectEqual(undefined, try parseFromSliceLeaky([0]u8, testing.allocator, "[]", .{}));

try testing.expectEqual(@as(u64, 12345678901234567890), try parseFromSliceLeaky(u64, testing.allocator, "\"12345678901234567890\"", .{}));
try testing.expectEqual(@as(f64, 123.456), try parseFromSliceLeaky(f64, testing.allocator, "\"123.456\"", .{}));
try testing.expectEqual(12345678901234567890, try parseFromSliceLeaky(u64, testing.allocator, "\"12345678901234567890\"", .{}));
try testing.expectEqual(123.456, try parseFromSliceLeaky(f64, testing.allocator, "\"123.456\"", .{}));
}

test "parse into enum" {
Expand All @@ -394,9 +394,9 @@ test "parse into enum" {
Bar,
@"with\\escape",
};
try testing.expectEqual(@as(T, .Foo), try parseFromSliceLeaky(T, testing.allocator, "\"Foo\"", .{}));
try testing.expectEqual(@as(T, .Foo), try parseFromSliceLeaky(T, testing.allocator, "42", .{}));
try testing.expectEqual(@as(T, .@"with\\escape"), try parseFromSliceLeaky(T, testing.allocator, "\"with\\\\escape\"", .{}));
try testing.expectEqual(.Foo, try parseFromSliceLeaky(T, testing.allocator, "\"Foo\"", .{}));
try testing.expectEqual(.Foo, try parseFromSliceLeaky(T, testing.allocator, "42", .{}));
try testing.expectEqual(.@"with\\escape", try parseFromSliceLeaky(T, testing.allocator, "\"with\\\\escape\"", .{}));
try testing.expectError(error.InvalidEnumTag, parseFromSliceLeaky(T, testing.allocator, "5", .{}));
try testing.expectError(error.InvalidEnumTag, parseFromSliceLeaky(T, testing.allocator, "\"Qux\"", .{}));
}
Expand Down
30 changes: 15 additions & 15 deletions lib/std/math/float.zig
Original file line number Diff line number Diff line change
Expand Up @@ -138,11 +138,11 @@ test "math.inf" {
const inf_u64: u64 = 0x7FF0000000000000;
const inf_u80: u80 = 0x7FFF8000000000000000;
const inf_u128: u128 = 0x7FFF0000000000000000000000000000;
try expectEqual(inf_u16, @bitCast(inf(f16)));
try expectEqual(inf_u32, @bitCast(inf(f32)));
try expectEqual(inf_u64, @bitCast(inf(f64)));
try expectEqual(inf_u80, @bitCast(inf(f80)));
try expectEqual(inf_u128, @bitCast(inf(f128)));
try expectEqual(inf_u16, @as(u16, @bitCast(inf(f16))));
try expectEqual(inf_u32, @as(u32, @bitCast(inf(f32))));
try expectEqual(inf_u64, @as(u64, @bitCast(inf(f64))));
try expectEqual(inf_u80, @as(u80, @bitCast(inf(f80))));
try expectEqual(inf_u128, @as(u128, @bitCast(inf(f128))));
}

test "math.nan" {
Expand All @@ -151,11 +151,11 @@ test "math.nan" {
const qnan_u64: u64 = 0x7FF8000000000000;
const qnan_u80: u80 = 0x7FFFC000000000000000;
const qnan_u128: u128 = 0x7FFF8000000000000000000000000000;
try expectEqual(qnan_u16, @bitCast(nan(f16)));
try expectEqual(qnan_u32, @bitCast(nan(f32)));
try expectEqual(qnan_u64, @bitCast(nan(f64)));
try expectEqual(qnan_u80, @bitCast(nan(f80)));
try expectEqual(qnan_u128, @bitCast(nan(f128)));
try expectEqual(qnan_u16, @as(u16, @bitCast(nan(f16))));
try expectEqual(qnan_u32, @as(u32, @bitCast(nan(f32))));
try expectEqual(qnan_u64, @as(u64, @bitCast(nan(f64))));
try expectEqual(qnan_u80, @as(u80, @bitCast(nan(f80))));
try expectEqual(qnan_u128, @as(u128, @bitCast(nan(f128))));
}

test "math.snan" {
Expand All @@ -167,9 +167,9 @@ test "math.snan" {
const snan_u64: u64 = 0x7FF4000000000000;
const snan_u80: u80 = 0x7FFFA000000000000000;
const snan_u128: u128 = 0x7FFF4000000000000000000000000000;
try expectEqual(snan_u16, @bitCast(snan(f16)));
try expectEqual(snan_u32, @bitCast(snan(f32)));
try expectEqual(snan_u64, @bitCast(snan(f64)));
try expectEqual(snan_u80, @bitCast(snan(f80)));
try expectEqual(snan_u128, @bitCast(snan(f128)));
try expectEqual(snan_u16, @as(u16, @bitCast(snan(f16))));
try expectEqual(snan_u32, @as(u32, @bitCast(snan(f32))));
try expectEqual(snan_u64, @as(u64, @bitCast(snan(f64))));
try expectEqual(snan_u80, @as(u80, @bitCast(snan(f80))));
try expectEqual(snan_u128, @as(u128, @bitCast(snan(f128))));
}
10 changes: 6 additions & 4 deletions lib/std/mem.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3262,7 +3262,7 @@ test "indexOfMax" {
/// Finds the indices of the smallest and largest number in a slice. O(n).
/// Returns an anonymous struct with the fields `index_min` and `index_max`.
/// `slice` must not be empty.
pub fn indexOfMinMax(comptime T: type, slice: []const T) struct { index_min: usize, index_max: usize } {
pub fn indexOfMinMax(comptime T: type, slice: []const T) IndexOfMinMaxResult {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems suspiciously like a regression. Does .{ .index_min = 0, .index_max = 6 } not work for the test cases?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't work for the same reason as test/behavior/cast.zig. You have to use reflection and get the type of the actual value and explicitly coerce to it, which is very inconvenient.

pub fn indexOfMinMax(comptime T: type, slice: []const T) struct { index_min: usize, index_max: usize } {
    // ...
}

test "indexOfMinMax" {
    const actual = indexOfMinMax(u8, "abcdefg");
    try testing.expectEqual(@as(@TypeOf(actual), .{ .index_min = 0, .index_max = 6 }), actual);
}

assert(slice.len > 0);
var minVal = slice[0];
var maxVal = slice[0];
Expand All @@ -3281,10 +3281,12 @@ pub fn indexOfMinMax(comptime T: type, slice: []const T) struct { index_min: usi
return .{ .index_min = minIdx, .index_max = maxIdx };
}

pub const IndexOfMinMaxResult = struct { index_min: usize, index_max: usize };

test "indexOfMinMax" {
try testing.expectEqual(indexOfMinMax(u8, "abcdefg"), .{ .index_min = 0, .index_max = 6 });
try testing.expectEqual(indexOfMinMax(u8, "gabcdef"), .{ .index_min = 1, .index_max = 0 });
try testing.expectEqual(indexOfMinMax(u8, "a"), .{ .index_min = 0, .index_max = 0 });
try testing.expectEqual(IndexOfMinMaxResult{ .index_min = 0, .index_max = 6 }, indexOfMinMax(u8, "abcdefg"));
try testing.expectEqual(IndexOfMinMaxResult{ .index_min = 1, .index_max = 0 }, indexOfMinMax(u8, "gabcdef"));
try testing.expectEqual(IndexOfMinMaxResult{ .index_min = 0, .index_max = 0 }, indexOfMinMax(u8, "a"));
}

pub fn swap(comptime T: type, a: *T, b: *T) void {
Expand Down
24 changes: 12 additions & 12 deletions lib/std/multi_array_list.zig
Original file line number Diff line number Diff line change
Expand Up @@ -842,24 +842,24 @@ test "union" {
&.{ .a, .b, .b, .a, .a, .a, .a, .a, .a },
list.items(.tags),
);
try testing.expectEqual(list.get(0), .{ .a = 1 });
try testing.expectEqual(list.get(1), .{ .b = "zigzag" });
try testing.expectEqual(list.get(2), .{ .b = "foobar" });
try testing.expectEqual(list.get(3), .{ .a = 4 });
try testing.expectEqual(list.get(4), .{ .a = 5 });
try testing.expectEqual(list.get(5), .{ .a = 6 });
try testing.expectEqual(list.get(6), .{ .a = 7 });
try testing.expectEqual(list.get(7), .{ .a = 8 });
try testing.expectEqual(list.get(8), .{ .a = 9 });
try testing.expectEqual(Foo{ .a = 1 }, list.get(0));
try testing.expectEqual(Foo{ .b = "zigzag" }, list.get(1));
try testing.expectEqual(Foo{ .b = "foobar" }, list.get(2));
try testing.expectEqual(Foo{ .a = 4 }, list.get(3));
try testing.expectEqual(Foo{ .a = 5 }, list.get(4));
try testing.expectEqual(Foo{ .a = 6 }, list.get(5));
try testing.expectEqual(Foo{ .a = 7 }, list.get(6));
try testing.expectEqual(Foo{ .a = 8 }, list.get(7));
try testing.expectEqual(Foo{ .a = 9 }, list.get(8));

list.shrinkAndFree(ally, 3);

try testing.expectEqual(@as(usize, 3), list.items(.tags).len);
try testing.expectEqualSlices(meta.Tag(Foo), list.items(.tags), &.{ .a, .b, .b });

try testing.expectEqual(list.get(0), .{ .a = 1 });
try testing.expectEqual(list.get(1), .{ .b = "zigzag" });
try testing.expectEqual(list.get(2), .{ .b = "foobar" });
try testing.expectEqual(Foo{ .a = 1 }, list.get(0));
try testing.expectEqual(Foo{ .b = "zigzag" }, list.get(1));
try testing.expectEqual(Foo{ .b = "foobar" }, list.get(2));
}

test "sorting a span" {
Expand Down
36 changes: 27 additions & 9 deletions lib/std/testing.zig
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,13 @@ pub fn expectError(expected_error: anyerror, actual_error_union: anytype) !void
/// This function is intended to be used only in tests. When the two values are not
/// equal, prints diagnostics to stderr to show exactly how they are not equal,
/// then returns a test failure error.
/// `actual` is casted to the type of `expected`.
pub fn expectEqual(expected: anytype, actual: @TypeOf(expected)) !void {
/// `actual` and `expected` are coerced to a common type using peer type resolution.
pub inline fn expectEqual(expected: anytype, actual: anytype) !void {
const T = @TypeOf(expected, actual);
return expectEqualInner(T, expected, actual);
}

fn expectEqualInner(comptime T: type, expected: T, actual: T) !void {
switch (@typeInfo(@TypeOf(actual))) {
.NoReturn,
.Opaque,
Expand Down Expand Up @@ -224,9 +229,13 @@ pub fn expectFmt(expected: []const u8, comptime template: []const u8, args: anyt
/// to show exactly how they are not equal, then returns a test failure error.
/// See `math.approxEqAbs` for more information on the tolerance parameter.
/// The types must be floating-point.
pub fn expectApproxEqAbs(expected: anytype, actual: @TypeOf(expected), tolerance: @TypeOf(expected)) !void {
const T = @TypeOf(expected);
/// `actual` and `expected` are coerced to a common type using peer type resolution.
pub inline fn expectApproxEqAbs(expected: anytype, actual: anytype, tolerance: anytype) !void {
const T = @TypeOf(expected, actual, tolerance);
return expectApproxEqAbsInner(T, expected, actual, tolerance);
}

fn expectApproxEqAbsInner(comptime T: type, expected: T, actual: T, tolerance: T) !void {
switch (@typeInfo(T)) {
.Float => if (!math.approxEqAbs(T, expected, actual, tolerance)) {
print("actual {}, not within absolute tolerance {} of expected {}\n", .{ actual, tolerance, expected });
Expand Down Expand Up @@ -256,9 +265,13 @@ test "expectApproxEqAbs" {
/// to show exactly how they are not equal, then returns a test failure error.
/// See `math.approxEqRel` for more information on the tolerance parameter.
/// The types must be floating-point.
pub fn expectApproxEqRel(expected: anytype, actual: @TypeOf(expected), tolerance: @TypeOf(expected)) !void {
const T = @TypeOf(expected);
/// `actual` and `expected` are coerced to a common type using peer type resolution.
pub inline fn expectApproxEqRel(expected: anytype, actual: anytype, tolerance: anytype) !void {
const T = @TypeOf(expected, actual, tolerance);
return expectApproxEqRelInner(T, expected, actual, tolerance);
}

fn expectApproxEqRelInner(comptime T: type, expected: T, actual: T, tolerance: T) !void {
switch (@typeInfo(T)) {
.Float => if (!math.approxEqRel(T, expected, actual, tolerance)) {
print("actual {}, not within relative tolerance {} of expected {}\n", .{ actual, tolerance, expected });
Expand Down Expand Up @@ -653,17 +666,22 @@ pub fn expectStringEndsWith(actual: []const u8, expected_ends_with: []const u8)
/// This function is intended to be used only in tests. When the two values are not
/// deeply equal, prints diagnostics to stderr to show exactly how they are not equal,
/// then returns a test failure error.
/// `actual` is casted to the type of `expected`.
/// `actual` and `expected` are coerced to a common type using peer type resolution.
///
/// Deeply equal is defined as follows:
/// Primitive types are deeply equal if they are equal using `==` operator.
/// Primitive types are deeply equal if they are equal using `==` operator.
/// Struct values are deeply equal if their corresponding fields are deeply equal.
/// Container types(like Array/Slice/Vector) deeply equal when their corresponding elements are deeply equal.
/// Pointer values are deeply equal if values they point to are deeply equal.
///
/// Note: Self-referential structs are supported (e.g. things like std.SinglyLinkedList)
/// but may cause infinite recursion or stack overflow when a container has a pointer to itself.
pub fn expectEqualDeep(expected: anytype, actual: @TypeOf(expected)) error{TestExpectedEqual}!void {
pub inline fn expectEqualDeep(expected: anytype, actual: anytype) error{TestExpectedEqual}!void {
const T = @TypeOf(expected, actual);
return expectEqualDeepInner(T, expected, actual);
}

fn expectEqualDeepInner(comptime T: type, expected: T, actual: T) error{TestExpectedEqual}!void {
switch (@typeInfo(@TypeOf(actual))) {
.NoReturn,
.Opaque,
Expand Down
2 changes: 1 addition & 1 deletion test/behavior/cast.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1156,7 +1156,7 @@ test "cast function with an opaque parameter" {
.func = @ptrCast(&Foo.funcImpl),
};
c.func(c.ctx);
try std.testing.expectEqual(foo, .{ .x = 101, .y = 201 });
try std.testing.expectEqual(Foo{ .x = 101, .y = 201 }, foo);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this work?

Suggested change
try std.testing.expectEqual(Foo{ .x = 101, .y = 201 }, foo);
try std.testing.expectEqual(.{ .x = 101, .y = 201 }, foo);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fails with

lib\std\testing.zig:57:15: error: incompatible types: 'struct{comptime x: comptime_int = 101, comptime y: comptime_int = 201}' and 'cast.test.cast function with an opaque parameter.Foo'
    const T = @TypeOf(expected, actual);
              ^~~~~~~~~~~~~~~~~~~~~~~~~
lib\std\testing.zig:57:23: note: type 'struct{comptime x: comptime_int = 101, comptime y: comptime_int = 201}' here
    const T = @TypeOf(expected, actual);
                      ^~~~~~~~
lib\std\testing.zig:57:33: note: type 'cast.test.cast function with an opaque parameter.Foo' here
    const T = @TypeOf(expected, actual);
                                ^~~~~~
test\behavior\cast.zig:1159:32: note: called from here
    try std.testing.expectEqual(.{ .x = 101, .y = 201 }, foo);
        ~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's pretty surprising. I wonder if peer type resolution is supposed to work for this, and this is a limitation of the language that is planned to be fixed someday. 🤔

Since the status quo uses the anonymous struct construction, I assume that expectEqualInner(@as(@TypeOf(actual), expected), actual) or something like that would work, where the type of the actual parameter is the authoritative one.

Copy link
Contributor Author

@castholm castholm Jan 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Related accepted proposal? #16865

I don't know much about the compiler internals but I suspect that the literal is being demoted from an "anonymous struct type" to a "tuple with comptime fields" once it enters the function, which is why the coercion no longer can happen.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one is actually currently supposed to work - this is a PTR bug/limitation. However, it wouldn't work once anonymous struct types are removed from Zig, which is an accepted proposal.

}

test "implicit ptr to *anyopaque" {
Expand Down
50 changes: 25 additions & 25 deletions test/behavior/packed-struct.zig
Original file line number Diff line number Diff line change
Expand Up @@ -173,17 +173,17 @@ test "correct sizeOf and offsets in packed structs" {
try expectEqual(true, s1.bool_d);
try expectEqual(true, s1.bool_e);
try expectEqual(true, s1.bool_f);
try expectEqual(@as(u1, 1), s1.u1_a);
try expectEqual(1, s1.u1_a);
try expectEqual(false, s1.bool_g);
try expectEqual(@as(u1, 0), s1.u1_b);
try expectEqual(@as(u3, 3), s1.u3_a);
try expectEqual(@as(u10, 0b1101000101), s1.u10_a);
try expectEqual(@as(u10, 0b0001001000), s1.u10_b);
try expectEqual(0, s1.u1_b);
try expectEqual(3, s1.u3_a);
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)));
try expectEqual(@as(u1, 0), s2.x);
try expectEqual(@as(u7, 0b1111010), s2.y);
try expectEqual(@as(u24, 0xd5c71f), s2.z);
try expectEqual(0, s2.x);
try expectEqual(0b1111010, s2.y);
try expectEqual(0xd5c71f, s2.z);
}
}

Expand All @@ -208,12 +208,12 @@ test "nested packed structs" {

if (native_endian == .little) {
const s3 = @as(S3Padded, @bitCast(@as(u64, 0xe952d5c71ff4))).s3;
try expectEqual(@as(u8, 0xf4), s3.x.a);
try expectEqual(@as(u8, 0x1f), s3.x.b);
try expectEqual(@as(u8, 0xc7), s3.x.c);
try expectEqual(@as(u8, 0xd5), s3.y.d);
try expectEqual(@as(u8, 0x52), s3.y.e);
try expectEqual(@as(u8, 0xe9), s3.y.f);
try expectEqual(0xf4, s3.x.a);
try expectEqual(0x1f, s3.x.b);
try expectEqual(0xc7, s3.x.c);
try expectEqual(0xd5, s3.y.d);
try expectEqual(0x52, s3.y.e);
try expectEqual(0xe9, s3.y.f);
}

const S4 = packed struct { a: i32, b: i8 };
Expand Down Expand Up @@ -249,8 +249,8 @@ test "regular in irregular packed struct" {
foo.bar.a = 235;
foo.bar.b = 42;

try expectEqual(@as(u16, 235), foo.bar.a);
try expectEqual(@as(u8, 42), foo.bar.b);
try expectEqual(235, foo.bar.a);
try expectEqual(42, foo.bar.b);
}

test "nested packed struct unaligned" {
Expand Down Expand Up @@ -456,12 +456,12 @@ test "nested packed struct field pointers" {
const ptr_p0_c = &S2.s.p0.c;
const ptr_p1_a = &S2.s.p1.a;
const ptr_p1_b = &S2.s.p1.b;
try expectEqual(@as(u8, 1), ptr_base.*);
try expectEqual(@as(u4, 2), ptr_p0_a.*);
try expectEqual(@as(u4, 3), ptr_p0_b.*);
try expectEqual(@as(u8, 4), ptr_p0_c.*);
try expectEqual(@as(u7, 5), ptr_p1_a.*);
try expectEqual(@as(u8, 6), ptr_p1_b.*);
try expectEqual(1, ptr_base.*);
try expectEqual(2, ptr_p0_a.*);
try expectEqual(3, ptr_p0_b.*);
try expectEqual(4, ptr_p0_c.*);
try expectEqual(5, ptr_p1_a.*);
try expectEqual(6, ptr_p1_b.*);
}

test "load pointer from packed struct" {
Expand Down Expand Up @@ -1033,12 +1033,12 @@ test "modify nested packed struct aligned field" {

var opts = Options{};
opts.pretty_print.indent += 1;
try std.testing.expectEqual(@as(u17, 0b00000000100100000), @bitCast(opts));
try std.testing.expectEqual(0b00000000100100000, @as(u17, @bitCast(opts)));
try std.testing.expect(!opts.foo);
try std.testing.expect(!opts.bar);
try std.testing.expect(!opts.pretty_print.enabled);
try std.testing.expectEqual(@as(u4, 4), opts.pretty_print.num_spaces);
try std.testing.expectEqual(@as(u8, 1), opts.pretty_print.indent);
try std.testing.expectEqual(4, opts.pretty_print.num_spaces);
try std.testing.expectEqual(1, opts.pretty_print.indent);
try std.testing.expect(!opts.baz);
}

Expand Down
4 changes: 2 additions & 2 deletions test/behavior/type.zig
Original file line number Diff line number Diff line change
Expand Up @@ -438,9 +438,9 @@ test "Type.Union" {
},
});
var tagged = Tagged{ .signed = -1 };
try testing.expectEqual(Tag.signed, tagged);
try testing.expectEqual(Tag.signed, @as(Tag, tagged));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a surprising change. Does peer type resolution not work for this? I would have thought this implicit case would be unnecessary if peer type resolution would work.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fails with

lib\std\testing.zig:58:32: error: coercion from enum 'type.test.Type.Union.Tag' to union 'type.test.Type.Union.Tagged' must initialize 'i32' field 'signed'
    return expectEqualInner(T, expected, actual);
                               ^~~~~~~~
test\behavior\type.zig:429:20: note: field 'signed' declared here
    const Tagged = @Type(.{
                   ^~~~~
test\behavior\type.zig:429:20: note: union declared here
    const Tagged = @Type(.{
                   ^~~~~
test\behavior\type.zig:441:28: note: called from here
    try testing.expectEqual(Tag.signed, tagged);
        ~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whilst unions are able to coerce to their tag type, it is not desirable for PTR to be able to trigger such a coercion. Consider this code:

const U = union(enum) {
    a: u32,
    b: u32,
};
const x = if (foo) U{ .a = 1 } else .b;

It would be very unexpected if the type of x here was the tag type of U, silently dropping the payload!

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whilst unions are able to coerce to their tag type, it is not desirable for PTR to be able to trigger such a coercion. Consider this code:

const U = union(enum) {
    a: u32,
    b: u32,
};
const x = if (foo) U{ .a = 1 } else .b;

It would be very unexpected if the type of x here was the tag type of U, silently dropping the payload!

I would actually expect it to drop the payload: since the else branch doesn't have one, there is no way the final result can contain the payload. Alternatively I could also understand if PTR yields an error in this case (although I'm not immediately convinced this is a good idea: intuitively if a==b performs a comparison in terms of type T, I'd expect this type to be the result of @TypeOf(a,b)). But not yielding a union, I don't see how this makes sense.

tagged = .{ .unsigned = 1 };
try testing.expectEqual(Tag.unsigned, tagged);
try testing.expectEqual(Tag.unsigned, @as(Tag, tagged));
}

test "Type.Union from Type.Enum" {
Expand Down
Loading