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 31, 2024
1 parent 17a87d7 commit a0cc724
Show file tree
Hide file tree
Showing 49 changed files with 1,030 additions and 1,266 deletions.
2 changes: 1 addition & 1 deletion lib/compiler/aro/aro/Builtins.zig
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ fn createType(desc: TypeDescription, it: *TypeDescription.TypeIterator, comp: *c
.len = element_count,
.elem = child_ty,
};
const vector_ty = .{ .specifier = .vector, .data = .{ .array = arr_ty } };
const vector_ty: Type = .{ .specifier = .vector, .data = .{ .array = arr_ty } };
builder.specifier = Type.Builder.fromType(vector_ty);
},
.q => {
Expand Down
2 changes: 1 addition & 1 deletion lib/compiler/aro/aro/Parser.zig
Original file line number Diff line number Diff line change
Expand Up @@ -8095,7 +8095,7 @@ fn primaryExpr(p: *Parser) Error!Result {

fn makePredefinedIdentifier(p: *Parser, strings_top: usize) !Result {
const end: u32 = @intCast(p.strings.items.len);
const elem_ty = .{ .specifier = .char, .qual = .{ .@"const" = true } };
const elem_ty: Type = .{ .specifier = .char, .qual = .{ .@"const" = true } };
const arr_ty = try p.arena.create(Type.Array);
arr_ty.* = .{ .elem = elem_ty, .len = end - strings_top };
const ty: Type = .{ .specifier = .array, .data = .{ .array = arr_ty } };
Expand Down
2 changes: 1 addition & 1 deletion lib/compiler/aro/aro/text_literal.zig
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ pub const Parser = struct {
pub fn err(self: *Parser, tag: Diagnostics.Tag, extra: Diagnostics.Message.Extra) void {
if (self.errored) return;
self.errored = true;
const diagnostic = .{ .tag = tag, .extra = extra };
const diagnostic: CharDiagnostic = .{ .tag = tag, .extra = extra };
if (self.errors_len == self.errors_buffer.len) {
self.errors_buffer[self.errors_buffer.len - 1] = diagnostic;
} else {
Expand Down
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
189 changes: 129 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 @@ -1888,6 +1888,8 @@ fn structInitExprAnon(
const tree = astgen.tree;

const payload_index = try addExtra(astgen, Zir.Inst.StructInitAnon{
.abs_node = node,
.abs_line = astgen.source_line,
.fields_len = @intCast(struct_init.ast.fields.len),
});
const field_size = @typeInfo(Zir.Inst.StructInitAnon.Item).@"struct".fields.len;
Expand Down Expand Up @@ -1919,6 +1921,8 @@ fn structInitExprTyped(
const tree = astgen.tree;

const payload_index = try addExtra(astgen, Zir.Inst.StructInit{
.abs_node = node,
.abs_line = astgen.source_line,
.fields_len = @intCast(struct_init.ast.fields.len),
});
const field_size = @typeInfo(Zir.Inst.StructInit.Item).@"struct".fields.len;
Expand Down Expand Up @@ -5007,6 +5011,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 +5042,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 +5050,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 +5124,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 +5145,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 +5242,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 +5271,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 +11243,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 +13244,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 +13287,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
7 changes: 4 additions & 3 deletions lib/std/zig/BuiltinFn.zig
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
const std = @import("std");

pub const Tag = enum {
add_with_overflow,
addrspace_cast,
Expand Down Expand Up @@ -147,7 +145,7 @@ param_count: ?u8,

pub const list = list: {
@setEvalBranchQuota(3000);
break :list std.StaticStringMap(@This()).initComptime(.{
break :list std.StaticStringMap(BuiltinFn).initComptime([_]struct { []const u8, BuiltinFn }{
.{
"@addWithOverflow",
.{
Expand Down Expand Up @@ -1011,3 +1009,6 @@ pub const list = list: {
},
});
};

const std = @import("std");
const BuiltinFn = @This();
Loading

0 comments on commit a0cc724

Please sign in to comment.