zig/test/behavior/tuple.zig
mlugg d11bbde5f9
compiler: remove anonymous struct types, unify all tuples
This commit reworks how anonymous struct literals and tuples work.

Previously, an untyped anonymous struct literal
(e.g. `const x = .{ .a = 123 }`) was given an "anonymous struct type",
which is a special kind of struct which coerces using structural
equivalence. This mechanism was a holdover from before we used
RLS / result types as the primary mechanism of type inference. This
commit changes the language so that the type assigned here is a "normal"
struct type. It uses a form of equivalence based on the AST node and the
type's structure, much like a reified (`@Type`) type.

Additionally, tuples have been simplified. The distinction between
"simple" and "complex" tuple types is eliminated. All tuples, even those
explicitly declared using `struct { ... }` syntax, use structural
equivalence, and do not undergo staged type resolution. Tuples are very
restricted: they cannot have non-`auto` layouts, cannot have aligned
fields, and cannot have default values with the exception of `comptime`
fields. Tuples currently do not have optimized layout, but this can be
changed in the future.

This change simplifies the language, and fixes some problematic
coercions through pointers which led to unintuitive behavior.

Resolves: #16865
2024-10-31 20:42:53 +00:00

594 lines
18 KiB
Zig

const builtin = @import("builtin");
const std = @import("std");
const testing = std.testing;
const assert = std.debug.assert;
const expect = testing.expect;
const expectEqualStrings = std.testing.expectEqualStrings;
const expectEqual = std.testing.expectEqual;
test "tuple concatenation" {
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
const S = struct {
fn doTheTest() !void {
var a: i32 = 1;
var b: i32 = 2;
_ = .{ &a, &b };
const x = .{a};
const y = .{b};
const c = x ++ y;
try expect(@as(i32, 1) == c[0]);
try expect(@as(i32, 2) == c[1]);
}
};
try S.doTheTest();
try comptime S.doTheTest();
}
test "tuple multiplication" {
const S = struct {
fn doTheTest() !void {
{
const t = .{} ** 4;
try expect(@typeInfo(@TypeOf(t)).@"struct".fields.len == 0);
}
{
const t = .{'a'} ** 4;
try expect(@typeInfo(@TypeOf(t)).@"struct".fields.len == 4);
inline for (t) |x| try expect(x == 'a');
}
{
const t = .{ 1, 2, 3 } ** 4;
try expect(@typeInfo(@TypeOf(t)).@"struct".fields.len == 12);
inline for (t, 0..) |x, i| try expect(x == 1 + i % 3);
}
}
};
try S.doTheTest();
try comptime S.doTheTest();
}
test "more tuple concatenation" {
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
const T = struct {
fn consume_tuple(tuple: anytype, len: usize) !void {
try expect(tuple.len == len);
}
fn doTheTest() !void {
const t1 = .{};
var rt_var: u8 = 42;
const t2 = .{rt_var} ++ .{};
try expect(t2.len == 1);
try expect(t2.@"0" == rt_var);
try expect(t2.@"0" == 42);
try expect(&t2.@"0" != &rt_var);
try consume_tuple(t1 ++ t1, 0);
try consume_tuple(.{} ++ .{}, 0);
try consume_tuple(.{0} ++ .{}, 1);
try consume_tuple(.{0} ++ .{1}, 2);
try consume_tuple(.{ 0, 1, 2 } ++ .{ u8, 1, noreturn }, 6);
try consume_tuple(t2 ++ t1, 1);
try consume_tuple(t1 ++ t2, 1);
try consume_tuple(t2 ++ t2, 2);
try consume_tuple(.{rt_var} ++ .{}, 1);
try consume_tuple(.{rt_var} ++ t1, 1);
try consume_tuple(.{} ++ .{rt_var}, 1);
try consume_tuple(t2 ++ .{void}, 2);
try consume_tuple(t2 ++ .{0}, 2);
try consume_tuple(.{0} ++ t2, 2);
try consume_tuple(.{void} ++ t2, 2);
try consume_tuple(.{u8} ++ .{rt_var} ++ .{true}, 3);
}
};
try T.doTheTest();
try comptime T.doTheTest();
}
test "pass tuple to comptime var parameter" {
const S = struct {
fn Foo(comptime args: anytype) !void {
try expect(args[0] == 1);
}
fn doTheTest() !void {
try Foo(.{1});
}
};
try S.doTheTest();
try comptime S.doTheTest();
}
test "tuple initializer for var" {
const S = struct {
fn doTheTest() void {
const Bytes = struct {
id: usize,
};
var tmp = .{
.id = @as(usize, 2),
.name = Bytes{ .id = 20 },
};
_ = &tmp;
}
};
S.doTheTest();
comptime S.doTheTest();
}
test "array-like initializer for tuple types" {
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
const T = @Type(.{
.@"struct" = .{
.is_tuple = true,
.layout = .auto,
.decls = &.{},
.fields = &.{
.{
.name = "0",
.type = i32,
.default_value = null,
.is_comptime = false,
.alignment = @alignOf(i32),
},
.{
.name = "1",
.type = u8,
.default_value = null,
.is_comptime = false,
.alignment = @alignOf(u8),
},
},
},
});
const S = struct {
fn doTheTest() !void {
var obj: T = .{ -1234, 128 };
_ = &obj;
try expect(@as(i32, -1234) == obj[0]);
try expect(@as(u8, 128) == obj[1]);
}
};
try S.doTheTest();
try comptime S.doTheTest();
}
test "anon struct as the result from a labeled block" {
const S = struct {
fn doTheTest() !void {
const precomputed = comptime blk: {
var x: i32 = 1234;
_ = &x;
break :blk .{
.x = x,
};
};
try expect(precomputed.x == 1234);
}
};
try S.doTheTest();
try comptime S.doTheTest();
}
test "tuple as the result from a labeled block" {
const S = struct {
fn doTheTest() !void {
const precomputed = comptime blk: {
var x: i32 = 1234;
_ = &x;
break :blk .{x};
};
try expect(precomputed[0] == 1234);
}
};
try S.doTheTest();
try comptime S.doTheTest();
}
test "initializing tuple with explicit type" {
const T = @TypeOf(.{ @as(i32, 0), @as(u32, 0) });
var a = T{ 0, 0 };
_ = &a;
}
test "initializing anon struct with explicit type" {
const T = @TypeOf(.{ .foo = @as(i32, 1), .bar = @as(i32, 2) });
var a = T{ .foo = 1, .bar = 2 };
_ = &a;
}
test "fieldParentPtr of tuple" {
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
var x: u32 = 0;
_ = &x;
const tuple = .{ x, x };
try testing.expect(&tuple == @as(@TypeOf(&tuple), @fieldParentPtr("1", &tuple[1])));
}
test "fieldParentPtr of anon struct" {
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
var x: u32 = 0;
_ = &x;
const anon_st = .{ .foo = x, .bar = x };
try testing.expect(&anon_st == @as(@TypeOf(&anon_st), @fieldParentPtr("bar", &anon_st.bar)));
}
test "offsetOf tuple" {
var x: u32 = 0;
_ = &x;
const T = @TypeOf(.{ x, x });
try expect(@offsetOf(T, "1") == @sizeOf(u32));
}
test "offsetOf anon struct" {
var x: u32 = 0;
_ = &x;
const T = @TypeOf(.{ .foo = x, .bar = x });
try expect(@offsetOf(T, "bar") == @sizeOf(u32));
}
test "initializing tuple with mixed comptime-runtime fields" {
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
var x: u32 = 15;
_ = &x;
const T = @TypeOf(.{ @as(i32, -1234), @as(u32, 5678), x });
var a: T = .{ -1234, 5678, x + 1 };
_ = &a;
try expect(a[2] == 16);
}
test "initializing anon struct with mixed comptime-runtime fields" {
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
var x: u32 = 15;
_ = &x;
const T = @TypeOf(.{ .foo = @as(i32, -1234), .bar = x });
var a: T = .{ .foo = -1234, .bar = x + 1 };
_ = &a;
try expect(a.bar == 16);
}
test "tuple in tuple passed to generic function" {
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
const S = struct {
fn pair(x: f32, y: f32) std.meta.Tuple(&.{ f32, f32 }) {
return .{ x, y };
}
fn foo(x: anytype) !void {
try expect(x[0][0] == 1.5);
try expect(x[0][1] == 2.5);
}
};
const x = comptime S.pair(1.5, 2.5);
try S.foo(.{x});
}
test "coerce tuple to tuple" {
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
const T = std.meta.Tuple(&.{u8});
const S = struct {
fn foo(x: T) !void {
try expect(x[0] == 123);
}
};
try S.foo(.{123});
}
test "tuple type with void field" {
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
const T = std.meta.Tuple(&[_]type{void});
const x = T{{}};
try expect(@TypeOf(x[0]) == void);
}
test "zero sized struct in tuple handled correctly" {
const State = struct {
const Self = @This();
data: @Type(.{
.@"struct" = .{
.is_tuple = true,
.layout = .auto,
.decls = &.{},
.fields = &.{.{
.name = "0",
.type = struct {},
.default_value = null,
.is_comptime = false,
.alignment = 0,
}},
},
}),
pub fn do(this: Self) usize {
return @sizeOf(@TypeOf(this));
}
};
var s: State = undefined;
try expect(s.do() == 0);
}
test "tuple type with void field and a runtime field" {
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
const T = std.meta.Tuple(&[_]type{ usize, void });
var t: T = .{ 5, {} };
_ = &t;
try expect(t[0] == 5);
}
test "branching inside tuple literal" {
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
const S = struct {
fn foo(a: anytype) !void {
try expect(a[0] == 1234);
}
};
var a = false;
_ = &a;
try S.foo(.{if (a) @as(u32, 5678) else @as(u32, 1234)});
}
test "tuple initialized with a runtime known value" {
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
const E = union(enum) { e: []const u8 };
const W = union(enum) { w: E };
var e = E{ .e = "test" };
_ = &e;
const w = .{W{ .w = e }};
try expectEqualStrings(w[0].w.e, "test");
}
test "tuple of struct concatenation and coercion to array" {
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
const StructWithDefault = struct { value: f32 = 42 };
const SomeStruct = struct { array: [4]StructWithDefault };
const value1 = SomeStruct{ .array = .{StructWithDefault{}} ++ [_]StructWithDefault{.{}} ** 3 };
const value2 = SomeStruct{ .array = .{.{}} ++ [_]StructWithDefault{.{}} ** 3 };
try expectEqual(value1, value2);
}
test "nested runtime conditionals in tuple initializer" {
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
var data: u8 = 0;
_ = &data;
const x = .{
if (data != 0) "" else switch (@as(u1, @truncate(data))) {
0 => "up",
1 => "down",
},
};
try expectEqualStrings("up", x[0]);
}
test "sentinel slice in tuple with other fields" {
const S = struct {
a: u32,
b: u32,
};
const Submission = union(enum) {
open: struct { *S, [:0]const u8, u32 },
};
_ = Submission;
}
test "sentinel slice in tuple" {
const S = struct { [:0]const u8 };
_ = S;
}
test "tuple pointer is indexable" {
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
const S = struct { u32, bool };
const x: S = .{ 123, true };
comptime assert(@TypeOf(&(&x)[0]) == *const u32); // validate constness
try expectEqual(@as(u32, 123), (&x)[0]);
try expectEqual(true, (&x)[1]);
var y: S = .{ 123, true };
comptime assert(@TypeOf(&(&y)[0]) == *u32); // validate constness
try expectEqual(@as(u32, 123), (&y)[0]);
try expectEqual(true, (&y)[1]);
(&y)[0] = 100;
(&y)[1] = false;
try expectEqual(@as(u32, 100), (&y)[0]);
try expectEqual(false, (&y)[1]);
}
test "coerce anon tuple to tuple" {
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
var x: u8 = 1;
var y: u16 = 2;
_ = .{ &x, &y };
const t = .{ x, y };
const s: struct { u8, u16 } = t;
try expectEqual(x, s[0]);
try expectEqual(y, s[1]);
}
test "empty tuple type" {
const S = @Type(.{ .@"struct" = .{
.layout = .auto,
.fields = &.{},
.decls = &.{},
.is_tuple = true,
} });
const s: S = .{};
try expect(s.len == 0);
}
test "tuple with comptime fields with non empty initializer" {
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
const a: struct { comptime comptime_int = 0 } = .{0};
_ = a;
}
test "tuple with runtime value coerced into a slice with a sentinel" {
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
const S = struct {
fn f(a: [:null]const ?u8) !void {
try expect(a[0] == 42);
}
};
const c: u8 = 42;
try S.f(&[_:null]?u8{c});
try S.f(&.{c});
var v: u8 = 42;
_ = &v;
try S.f(&[_:null]?u8{v});
try S.f(&.{v});
}
test "tuple implicitly coerced to optional/error union struct/union" {
const SomeUnion = union(enum) {
variant: u8,
};
const SomeStruct = struct {
struct_field: u8,
};
const OptEnum = struct {
opt_union: ?SomeUnion,
};
const ErrEnum = struct {
err_union: anyerror!SomeUnion,
};
const OptStruct = struct {
opt_struct: ?SomeStruct,
};
const ErrStruct = struct {
err_struct: anyerror!SomeStruct,
};
try expect((OptEnum{
.opt_union = .{
.variant = 1,
},
}).opt_union.?.variant == 1);
try expect(((ErrEnum{
.err_union = .{
.variant = 1,
},
}).err_union catch unreachable).variant == 1);
try expect((OptStruct{
.opt_struct = .{
.struct_field = 1,
},
}).opt_struct.?.struct_field == 1);
try expect(((ErrStruct{
.err_struct = .{
.struct_field = 1,
},
}).err_struct catch unreachable).struct_field == 1);
}
test "comptime fields in tuple can be initialized" {
const T = @TypeOf(.{ @as(i32, 0), @as(u32, 0) });
var a: T = .{ 0, 0 };
_ = &a;
}
test "empty struct in tuple" {
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
const T = struct { struct {} };
const info = @typeInfo(T);
try std.testing.expectEqual(@as(usize, 1), info.@"struct".fields.len);
try std.testing.expectEqualStrings("0", info.@"struct".fields[0].name);
try std.testing.expect(@typeInfo(info.@"struct".fields[0].type) == .@"struct");
}
test "empty union in tuple" {
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
const T = struct { union {} };
const info = @typeInfo(T);
try std.testing.expectEqual(@as(usize, 1), info.@"struct".fields.len);
try std.testing.expectEqualStrings("0", info.@"struct".fields[0].name);
try std.testing.expect(@typeInfo(info.@"struct".fields[0].type) == .@"union");
}