diff options
| author | mlugg <mlugg@mlugg.co.uk> | 2024-10-26 23:13:58 +0100 |
|---|---|---|
| committer | mlugg <mlugg@mlugg.co.uk> | 2024-10-31 20:42:53 +0000 |
| commit | d11bbde5f9c64ef58405604601d55f88bb5d5f3a (patch) | |
| tree | 4e05b679cd791e90db68f3f2198294667a6fc57c /lib/std | |
| parent | a916bc7fdd3975a9e2ef13c44f814c71ce017193 (diff) | |
| download | zig-d11bbde5f9c64ef58405604601d55f88bb5d5f3a.tar.gz zig-d11bbde5f9c64ef58405604601d55f88bb5d5f3a.zip | |
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
Diffstat (limited to 'lib/std')
| -rw-r--r-- | lib/std/SemanticVersion.zig | 2 | ||||
| -rw-r--r-- | lib/std/Target.zig | 2 | ||||
| -rw-r--r-- | lib/std/array_list.zig | 2 | ||||
| -rw-r--r-- | lib/std/crypto/phc_encoding.zig | 3 | ||||
| -rw-r--r-- | lib/std/meta.zig | 2 | ||||
| -rw-r--r-- | lib/std/zig/AstGen.zig | 189 | ||||
| -rw-r--r-- | lib/std/zig/BuiltinFn.zig | 7 | ||||
| -rw-r--r-- | lib/std/zig/Zir.zig | 79 | ||||
| -rw-r--r-- | lib/std/zig/system/darwin/macos.zig | 6 |
9 files changed, 210 insertions, 82 deletions
diff --git a/lib/std/SemanticVersion.zig b/lib/std/SemanticVersion.zig index bde3e906d8..7cb3888e54 100644 --- a/lib/std/SemanticVersion.zig +++ b/lib/std/SemanticVersion.zig @@ -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; diff --git a/lib/std/Target.zig b/lib/std/Target.zig index def304bc6c..8b021b5429 100644 --- a/lib/std/Target.zig +++ b/lib/std/Target.zig @@ -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 diff --git a/lib/std/array_list.zig b/lib/std/array_list.zig index ac1b144690..197b8c7fba 100644 --- a/lib/std/array_list.zig +++ b/lib/std/array_list.zig @@ -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; } diff --git a/lib/std/crypto/phc_encoding.zig b/lib/std/crypto/phc_encoding.zig index 3442073632..ba48a9954f 100644 --- a/lib/std/crypto/phc_encoding.zig +++ b/lib/std/crypto/phc_encoding.zig @@ -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" { diff --git a/lib/std/meta.zig b/lib/std/meta.zig index ea81c87648..0ea83bb11e 100644 --- a/lib/std/meta.zig +++ b/lib/std/meta.zig @@ -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, }; } diff --git a/lib/std/zig/AstGen.zig b/lib/std/zig/AstGen.zig index dc39ce62f5..807d634886 100644 --- a/lib/std/zig/AstGen.zig +++ b/lib/std/zig/AstGen.zig @@ -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", .{}, &.{ @@ -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; @@ -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; @@ -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) { @@ -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, @@ -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, @@ -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(.{}); @@ -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)); @@ -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, @@ -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, @@ -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), @@ -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, @@ -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, diff --git a/lib/std/zig/BuiltinFn.zig b/lib/std/zig/BuiltinFn.zig index ad9176b0ab..7ad5bb1a87 100644 --- a/lib/std/zig/BuiltinFn.zig +++ b/lib/std/zig/BuiltinFn.zig @@ -1,5 +1,3 @@ -const std = @import("std"); - pub const Tag = enum { add_with_overflow, addrspace_cast, @@ -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", .{ @@ -1011,3 +1009,6 @@ pub const list = list: { }, }); }; + +const std = @import("std"); +const BuiltinFn = @This(); diff --git a/lib/std/zig/Zir.zig b/lib/std/zig/Zir.zig index ab6bcf1b0e..00a48e21f7 100644 --- a/lib/std/zig/Zir.zig +++ b/lib/std/zig/Zir.zig @@ -1887,6 +1887,10 @@ pub const Inst = struct { /// `operand` is payload index to `OpaqueDecl`. /// `small` is `OpaqueDecl.Small`. opaque_decl, + /// A tuple type. Note that tuples are not namespace/container types. + /// `operand` is payload index to `TupleDecl`. + /// `small` is `fields_len: u16`. + tuple_decl, /// Implements the `@This` builtin. /// `operand` is `src_node: i32`. this, @@ -2187,7 +2191,7 @@ pub const Inst = struct { anyerror_void_error_union_type, adhoc_inferred_error_set_type, generic_poison_type, - empty_struct_type, + empty_tuple_type, undef, zero, zero_usize, @@ -2202,7 +2206,7 @@ pub const Inst = struct { null_value, bool_true, bool_false, - empty_struct, + empty_tuple, generic_poison, /// This Ref does not correspond to any ZIR instruction or constant @@ -3041,7 +3045,7 @@ pub const Inst = struct { /// 0b0X00: whether corresponding field is comptime /// 0bX000: whether corresponding field has a type expression /// 9. fields: { // for every fields_len - /// field_name: u32, // if !is_tuple + /// field_name: u32, /// doc_comment: NullTerminatedString, // .empty if no doc comment /// field_type: Ref, // if corresponding bit is not set. none means anytype. /// field_type_body_len: u32, // if corresponding bit is set @@ -3071,13 +3075,12 @@ pub const Inst = struct { has_backing_int: bool, known_non_opv: bool, known_comptime_only: bool, - is_tuple: bool, name_strategy: NameStrategy, layout: std.builtin.Type.ContainerLayout, any_default_inits: bool, any_comptime_fields: bool, any_aligned_fields: bool, - _: u2 = undefined, + _: u3 = undefined, }; }; @@ -3303,6 +3306,15 @@ pub const Inst = struct { }; /// Trailing: + /// 1. fields: { // for every `fields_len` (stored in `extended.small`) + /// type: Inst.Ref, + /// init: Inst.Ref, // `.none` for non-`comptime` fields + /// } + pub const TupleDecl = struct { + src_node: i32, // relative + }; + + /// Trailing: /// { // for every fields_len /// field_name: NullTerminatedString // null terminated string index /// doc_comment: NullTerminatedString // null terminated string index @@ -3329,6 +3341,11 @@ pub const Inst = struct { /// Trailing is an item per field. pub const StructInit = struct { + /// If this is an anonymous initialization (the operand is poison), this instruction becomes the owner of a type. + /// To resolve source locations, we need an absolute source node. + abs_node: Ast.Node.Index, + /// Likewise, we need an absolute line number. + abs_line: u32, fields_len: u32, pub const Item = struct { @@ -3344,6 +3361,11 @@ pub const Inst = struct { /// TODO make this instead array of inits followed by array of names because /// it will be simpler Sema code and better for CPU cache. pub const StructInitAnon = struct { + /// This is an anonymous initialization, meaning this instruction becomes the owner of a type. + /// To resolve source locations, we need an absolute source node. + abs_node: Ast.Node.Index, + /// Likewise, we need an absolute line number. + abs_line: u32, fields_len: u32, pub const Item = struct { @@ -3741,6 +3763,8 @@ fn findDeclsInner( defers: *std.AutoHashMapUnmanaged(u32, void), inst: Inst.Index, ) Allocator.Error!void { + comptime assert(Zir.inst_tracking_version == 0); + const tags = zir.instructions.items(.tag); const datas = zir.instructions.items(.data); @@ -3884,9 +3908,6 @@ fn findDeclsInner( .struct_init_empty, .struct_init_empty_result, .struct_init_empty_ref_result, - .struct_init_anon, - .struct_init, - .struct_init_ref, .validate_struct_init_ty, .validate_struct_init_result_ty, .validate_ptr_struct_init, @@ -3978,6 +3999,12 @@ fn findDeclsInner( .restore_err_ret_index_fn_entry, => return, + // Struct initializations need tracking, as they may create anonymous struct types. + .struct_init, + .struct_init_ref, + .struct_init_anon, + => return list.append(gpa, inst), + .extended => { const extended = datas[@intFromEnum(inst)].extended; switch (extended.opcode) { @@ -4034,6 +4061,7 @@ fn findDeclsInner( .builtin_value, .branch_hint, .inplace_arith_result_ty, + .tuple_decl, => return, // `@TypeOf` has a body. @@ -4110,8 +4138,7 @@ fn findDeclsInner( const has_type_body = @as(u1, @truncate(cur_bit_bag)) != 0; cur_bit_bag >>= 1; - fields_extra_index += @intFromBool(!small.is_tuple); // field_name - fields_extra_index += 1; // doc_comment + fields_extra_index += 2; // field_name, doc_comment if (has_type_body) { const field_type_body_len = zir.extra[fields_extra_index]; @@ -4736,3 +4763,35 @@ pub fn getAssociatedSrcHash(zir: Zir, inst: Zir.Inst.Index) ?std.zig.SrcHash { else => return null, } } + +/// When the ZIR update tracking logic must be modified to consider new instructions, +/// change this constant to trigger compile errors at all relevant locations. +pub const inst_tracking_version = 0; + +/// Asserts that a ZIR instruction is tracked across incremental updates, and +/// thus may be given an `InternPool.TrackedInst`. +pub fn assertTrackable(zir: Zir, inst_idx: Zir.Inst.Index) void { + comptime assert(Zir.inst_tracking_version == 0); + const inst = zir.instructions.get(@intFromEnum(inst_idx)); + switch (inst.tag) { + .struct_init, + .struct_init_ref, + .struct_init_anon, + => {}, // tracked in order, as the owner instructions of anonymous struct types + .func, + .func_inferred, + .func_fancy, + => {}, // tracked in order, as the owner instructions of function bodies + .declaration => {}, // tracked by correlating names in the namespace of the parent container + .extended => switch (inst.data.extended.opcode) { + .struct_decl, + .union_decl, + .enum_decl, + .opaque_decl, + .reify, + => {}, // tracked in order, as the owner instructions of explicit container types + else => unreachable, // assertion failure; not trackable + }, + else => unreachable, // assertion failure; not trackable + } +} diff --git a/lib/std/zig/system/darwin/macos.zig b/lib/std/zig/system/darwin/macos.zig index 3201f102b6..f5f413cb4e 100644 --- a/lib/std/zig/system/darwin/macos.zig +++ b/lib/std/zig/system/darwin/macos.zig @@ -277,7 +277,7 @@ const SystemVersionTokenizer = struct { }; test "detect" { - const cases = .{ + const cases: [5]struct { []const u8, std.SemanticVersion } = .{ .{ \\<?xml version="1.0" encoding="UTF-8"?> \\<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> @@ -388,8 +388,8 @@ test "detect" { inline for (cases) |case| { const ver0 = try parseSystemVersion(case[0]); - const ver1: std.SemanticVersion = case[1]; - try testing.expectEqual(@as(std.math.Order, .eq), ver0.order(ver1)); + const ver1 = case[1]; + try testing.expectEqual(std.math.Order.eq, ver0.order(ver1)); } } |
