Skip to content

Commit

Permalink
compiler: remove anonymous struct types, unify all tuples
Browse files Browse the repository at this point in the history
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: ziglang#16865
  • Loading branch information
mlugg committed Oct 26, 2024
1 parent 6a364b4 commit 6c45989
Show file tree
Hide file tree
Showing 39 changed files with 961 additions and 1,210 deletions.
2 changes: 1 addition & 1 deletion lib/compiler/test_runner.zig
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const io = std.io;
const testing = std.testing;
const assert = std.debug.assert;

pub const std_options = .{
pub const std_options: std.Options = .{
.logFn = log,
};

Expand Down
2 changes: 1 addition & 1 deletion lib/std/SemanticVersion.zig
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ test "precedence" {

test "zig_version" {
// An approximate Zig build that predates this test.
const older_version = .{ .major = 0, .minor = 8, .patch = 0, .pre = "dev.874" };
const older_version: Version = .{ .major = 0, .minor = 8, .patch = 0, .pre = "dev.874" };

// Simulated compatibility check using Zig version.
const compatible = comptime @import("builtin").zig_version.order(older_version) == .gt;
Expand Down
2 changes: 1 addition & 1 deletion lib/std/Target.zig
Original file line number Diff line number Diff line change
Expand Up @@ -509,7 +509,7 @@ pub const Os = struct {
.max = .{ .major = 6, .minor = 10, .patch = 3 },
},
.glibc = blk: {
const default_min = .{ .major = 2, .minor = 28, .patch = 0 };
const default_min: std.SemanticVersion = .{ .major = 2, .minor = 28, .patch = 0 };

for (std.zig.target.available_libcs) |libc| {
// We don't know the ABI here. We can get away with not checking it
Expand Down
2 changes: 1 addition & 1 deletion lib/std/array_list.zig
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
/// of this ArrayList. Empties this ArrayList.
pub fn moveToUnmanaged(self: *Self) ArrayListAlignedUnmanaged(T, alignment) {
const allocator = self.allocator;
const result = .{ .items = self.items, .capacity = self.capacity };
const result: ArrayListAlignedUnmanaged(T, alignment) = .{ .items = self.items, .capacity = self.capacity };
self.* = init(allocator);
return result;
}
Expand Down
3 changes: 1 addition & 2 deletions lib/std/crypto/phc_encoding.zig
Original file line number Diff line number Diff line change
Expand Up @@ -258,8 +258,7 @@ fn kvSplit(str: []const u8) !struct { key: []const u8, value: []const u8 } {
var it = mem.splitScalar(u8, str, kv_delimiter_scalar);
const key = it.first();
const value = it.next() orelse return Error.InvalidEncoding;
const ret = .{ .key = key, .value = value };
return ret;
return .{ .key = key, .value = value };
}

test "phc format - encoding/decoding" {
Expand Down
2 changes: 1 addition & 1 deletion lib/std/meta.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1018,7 +1018,7 @@ fn CreateUniqueTuple(comptime N: comptime_int, comptime types: [N]type) type {
.type = T,
.default_value = null,
.is_comptime = false,
.alignment = if (@sizeOf(T) > 0) @alignOf(T) else 0,
.alignment = 0,
};
}

Expand Down
185 changes: 125 additions & 60 deletions lib/std/zig/AstGen.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1711,7 +1711,7 @@ fn structInitExpr(
return rvalue(gz, ri, val, node);
},
.none, .ref, .inferred_ptr => {
return rvalue(gz, ri, .empty_struct, node);
return rvalue(gz, ri, .empty_tuple, node);
},
.destructure => |destructure| {
return astgen.failNodeNotes(node, "empty initializer cannot be destructured", .{}, &.{
Expand Down Expand Up @@ -5007,6 +5007,25 @@ fn structDeclInner(
layout: std.builtin.Type.ContainerLayout,
backing_int_node: Ast.Node.Index,
) InnerError!Zir.Inst.Ref {
const astgen = gz.astgen;
const gpa = astgen.gpa;
const tree = astgen.tree;

{
const is_tuple = for (container_decl.ast.members) |member_node| {
const container_field = tree.fullContainerField(member_node) orelse continue;
if (container_field.ast.tuple_like) break true;
} else false;

if (is_tuple) {
if (node == 0) {
return astgen.failTok(0, "file cannot be a tuple", .{});
} else {
return tupleDecl(gz, scope, node, container_decl, layout, backing_int_node);
}
}
}

const decl_inst = try gz.reserveInstructionIndex();

if (container_decl.ast.members.len == 0 and backing_int_node == 0) {
Expand All @@ -5019,7 +5038,6 @@ fn structDeclInner(
.has_backing_int = false,
.known_non_opv = false,
.known_comptime_only = false,
.is_tuple = false,
.any_comptime_fields = false,
.any_default_inits = false,
.any_aligned_fields = false,
Expand All @@ -5028,10 +5046,6 @@ fn structDeclInner(
return decl_inst.toRef();
}

const astgen = gz.astgen;
const gpa = astgen.gpa;
const tree = astgen.tree;

var namespace: Scope.Namespace = .{
.parent = scope,
.node = node,
Expand Down Expand Up @@ -5106,46 +5120,6 @@ fn structDeclInner(
// No defer needed here because it is handled by `wip_members.deinit()` above.
const bodies_start = astgen.scratch.items.len;

const node_tags = tree.nodes.items(.tag);
const is_tuple = for (container_decl.ast.members) |member_node| {
const container_field = tree.fullContainerField(member_node) orelse continue;
if (container_field.ast.tuple_like) break true;
} else false;

if (is_tuple) switch (layout) {
.auto => {},
.@"extern" => return astgen.failNode(node, "extern tuples are not supported", .{}),
.@"packed" => return astgen.failNode(node, "packed tuples are not supported", .{}),
};

if (is_tuple) for (container_decl.ast.members) |member_node| {
switch (node_tags[member_node]) {
.container_field_init,
.container_field_align,
.container_field,
.@"comptime",
.test_decl,
=> continue,
else => {
const tuple_member = for (container_decl.ast.members) |maybe_tuple| switch (node_tags[maybe_tuple]) {
.container_field_init,
.container_field_align,
.container_field,
=> break maybe_tuple,
else => {},
} else unreachable;
return astgen.failNodeNotes(
member_node,
"tuple declarations cannot contain declarations",
.{},
&[_]u32{
try astgen.errNoteNode(tuple_member, "tuple field here", .{}),
},
);
},
}
};

const old_hasher = astgen.src_hasher;
defer astgen.src_hasher = old_hasher;
astgen.src_hasher = std.zig.SrcHasher.init(.{});
Expand All @@ -5167,16 +5141,10 @@ fn structDeclInner(

astgen.src_hasher.update(tree.getNodeSource(member_node));

if (!is_tuple) {
const field_name = try astgen.identAsString(member.ast.main_token);

member.convertToNonTupleLike(astgen.tree.nodes);
assert(!member.ast.tuple_like);

wip_members.appendToField(@intFromEnum(field_name));
} else if (!member.ast.tuple_like) {
return astgen.failTok(member.ast.main_token, "tuple field has a name", .{});
}
const field_name = try astgen.identAsString(member.ast.main_token);
member.convertToNonTupleLike(astgen.tree.nodes);
assert(!member.ast.tuple_like);
wip_members.appendToField(@intFromEnum(field_name));

const doc_comment_index = try astgen.docCommentAsString(member.firstToken());
wip_members.appendToField(@intFromEnum(doc_comment_index));
Expand Down Expand Up @@ -5270,7 +5238,6 @@ fn structDeclInner(
.has_backing_int = backing_int_ref != .none,
.known_non_opv = known_non_opv,
.known_comptime_only = known_comptime_only,
.is_tuple = is_tuple,
.any_comptime_fields = any_comptime_fields,
.any_default_inits = any_default_inits,
.any_aligned_fields = any_aligned_fields,
Expand Down Expand Up @@ -5300,6 +5267,106 @@ fn structDeclInner(
return decl_inst.toRef();
}

fn tupleDecl(
gz: *GenZir,
scope: *Scope,
node: Ast.Node.Index,
container_decl: Ast.full.ContainerDecl,
layout: std.builtin.Type.ContainerLayout,
backing_int_node: Ast.Node.Index,
) InnerError!Zir.Inst.Ref {
const astgen = gz.astgen;
const gpa = astgen.gpa;
const tree = astgen.tree;

const node_tags = tree.nodes.items(.tag);

switch (layout) {
.auto => {},
.@"extern" => return astgen.failNode(node, "extern tuples are not supported", .{}),
.@"packed" => return astgen.failNode(node, "packed tuples are not supported", .{}),
}

if (backing_int_node != 0) {
return astgen.failNode(backing_int_node, "tuple does not support backing integer type", .{});
}

// We will use the scratch buffer, starting here, for the field data:
// 1. fields: { // for every `fields_len` (stored in `extended.small`)
// type: Inst.Ref,
// init: Inst.Ref, // `.none` for non-`comptime` fields
// }
const fields_start = astgen.scratch.items.len;
defer astgen.scratch.items.len = fields_start;

try astgen.scratch.ensureUnusedCapacity(gpa, container_decl.ast.members.len * 2);

for (container_decl.ast.members) |member_node| {
const field = tree.fullContainerField(member_node) orelse {
const tuple_member = for (container_decl.ast.members) |maybe_tuple| switch (node_tags[maybe_tuple]) {
.container_field_init,
.container_field_align,
.container_field,
=> break maybe_tuple,
else => {},
} else unreachable;
return astgen.failNodeNotes(
member_node,
"tuple declarations cannot contain declarations",
.{},
&.{try astgen.errNoteNode(tuple_member, "tuple field here", .{})},
);
};

if (!field.ast.tuple_like) {
return astgen.failTok(field.ast.main_token, "tuple field has a name", .{});
}

if (field.ast.align_expr != 0) {
return astgen.failTok(field.ast.main_token, "tuple field has alignment", .{});
}

if (field.ast.value_expr != 0 and field.comptime_token == null) {
return astgen.failTok(field.ast.main_token, "non-comptime tuple field has default initialization value", .{});
}

if (field.ast.value_expr == 0 and field.comptime_token != null) {
return astgen.failTok(field.comptime_token.?, "comptime field without default initialization value", .{});
}

const field_type_ref = try typeExpr(gz, scope, field.ast.type_expr);
astgen.scratch.appendAssumeCapacity(@intFromEnum(field_type_ref));

if (field.ast.value_expr != 0) {
const field_init_ref = try comptimeExpr(gz, scope, .{ .rl = .{ .coerced_ty = field_type_ref } }, field.ast.value_expr);
astgen.scratch.appendAssumeCapacity(@intFromEnum(field_init_ref));
} else {
astgen.scratch.appendAssumeCapacity(@intFromEnum(Zir.Inst.Ref.none));
}
}

const fields_len = std.math.cast(u16, container_decl.ast.members.len) orelse {
return astgen.failNode(node, "this compiler implementation only supports 65535 tuple fields", .{});
};

const extra_trail = astgen.scratch.items[fields_start..];
assert(extra_trail.len == fields_len * 2);
try astgen.extra.ensureUnusedCapacity(gpa, @typeInfo(Zir.Inst.TupleDecl).@"struct".fields.len + extra_trail.len);
const payload_index = astgen.addExtraAssumeCapacity(Zir.Inst.TupleDecl{
.src_node = gz.nodeIndexToRelative(node),
});
astgen.extra.appendSliceAssumeCapacity(extra_trail);

return gz.add(.{
.tag = .extended,
.data = .{ .extended = .{
.opcode = .tuple_decl,
.small = fields_len,
.operand = payload_index,
} },
});
}

fn unionDeclInner(
gz: *GenZir,
scope: *Scope,
Expand Down Expand Up @@ -11172,7 +11239,7 @@ fn rvalueInner(
as_ty | @intFromEnum(Zir.Inst.Ref.slice_const_u8_sentinel_0_type),
as_ty | @intFromEnum(Zir.Inst.Ref.anyerror_void_error_union_type),
as_ty | @intFromEnum(Zir.Inst.Ref.generic_poison_type),
as_ty | @intFromEnum(Zir.Inst.Ref.empty_struct_type),
as_ty | @intFromEnum(Zir.Inst.Ref.empty_tuple_type),
as_comptime_int | @intFromEnum(Zir.Inst.Ref.zero),
as_comptime_int | @intFromEnum(Zir.Inst.Ref.one),
as_comptime_int | @intFromEnum(Zir.Inst.Ref.negative_one),
Expand Down Expand Up @@ -13173,7 +13240,6 @@ const GenZir = struct {
layout: std.builtin.Type.ContainerLayout,
known_non_opv: bool,
known_comptime_only: bool,
is_tuple: bool,
any_comptime_fields: bool,
any_default_inits: bool,
any_aligned_fields: bool,
Expand Down Expand Up @@ -13217,7 +13283,6 @@ const GenZir = struct {
.has_backing_int = args.has_backing_int,
.known_non_opv = args.known_non_opv,
.known_comptime_only = args.known_comptime_only,
.is_tuple = args.is_tuple,
.name_strategy = gz.anon_name_strategy,
.layout = args.layout,
.any_comptime_fields = args.any_comptime_fields,
Expand Down
Loading

0 comments on commit 6c45989

Please sign in to comment.