diff --git a/doc/langref.html.in b/doc/langref.html.in index cea86e895541..c3abbeeaf6c9 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -2188,8 +2188,7 @@ or
  • {#syntax#}bool{#endsyntax#} fields use exactly 1 bit.
  • An {#link|enum#} field uses exactly the bit width of its integer tag type.
  • -
  • A {#link|packed union#} field uses exactly the bit width of the union field with - the largest bit width.
  • +
  • A {#link|packed union#} field uses exactly the bit width of its backing integer type.
  • This means that a {#syntax#}packed struct{#endsyntax#} can participate @@ -2405,8 +2404,11 @@ or {#header_close#} {#header_open|packed union#} -

    A {#syntax#}packed union{#endsyntax#} has well-defined in-memory layout and is eligible - to be in a {#link|packed struct#}.

    +

    + A {#syntax#}packed union{#endsyntax#} has well-defined in-memory layout and is eligible + to be in a {#link|packed struct#}. It's required to have a backing integer type and + all fields must have the same bit width as the backing integer type. +

    {#header_close#} {#header_open|Anonymous Union Literals#} diff --git a/lib/std/macho.zig b/lib/std/macho.zig index 75aa91e5361d..beab91f5e4a7 100644 --- a/lib/std/macho.zig +++ b/lib/std/macho.zig @@ -2110,8 +2110,8 @@ pub const UNWIND_ARM64_FRAMELESS_STACK_SIZE_MASK: u32 = 0x00FFF000; pub const UNWIND_ARM64_DWARF_SECTION_OFFSET: u32 = 0x00FFFFFF; pub const CompactUnwindEncoding = packed struct(u32) { - value: packed union { - x86_64: packed union { + value: packed union(u24) { + x86_64: packed union(u24) { frame: packed struct(u24) { reg4: u3, reg3: u3, @@ -2124,7 +2124,7 @@ pub const CompactUnwindEncoding = packed struct(u32) { frameless: packed struct(u24) { stack_reg_permutation: u10, stack_reg_count: u3, - stack: packed union { + stack: packed union(u11) { direct: packed struct(u11) { _: u3, stack_size: u8, @@ -2137,7 +2137,7 @@ pub const CompactUnwindEncoding = packed struct(u32) { }, dwarf: u24, }, - arm64: packed union { + arm64: packed union(u24) { frame: packed struct(u24) { x_reg_pairs: packed struct(u5) { x19_x20: u1, @@ -2161,7 +2161,7 @@ pub const CompactUnwindEncoding = packed struct(u32) { dwarf: u24, }, }, - mode: packed union { + mode: packed union(u4) { x86_64: UNWIND_X86_64_MODE, arm64: UNWIND_ARM64_MODE, }, diff --git a/lib/std/meta.zig b/lib/std/meta.zig index e7ea5b5f0efd..14ac02623123 100644 --- a/lib/std/meta.zig +++ b/lib/std/meta.zig @@ -257,7 +257,7 @@ test containerLayout { const U1 = union { a: u8, }; - const U2 = packed union { + const U2 = packed union(u8) { a: u8, }; const U3 = extern union { @@ -1190,19 +1190,14 @@ test hasMethod { pub inline fn hasUniqueRepresentation(comptime T: type) bool { return switch (@typeInfo(T)) { else => false, // TODO can we know if it's true for some of these types ? - .@"anyframe", .@"enum", .error_set, .@"fn", => true, - .bool => false, - .int => |info| @sizeOf(T) * 8 == info.bits, - .pointer => |info| info.size != .Slice, - .optional => |info| switch (@typeInfo(info.child)) { .pointer => |ptr| !ptr.is_allowzero and switch (ptr.size) { .Slice, .C => false, @@ -1210,9 +1205,14 @@ pub inline fn hasUniqueRepresentation(comptime T: type) bool { }, else => false, }, - + .@"union" => |info| { + if (info.layout == .@"packed") { + const tag_type = info.tag_type.?; // packed unions require a integer tag type + return @sizeOf(tag_type) * 8 == @bitSizeOf(tag_type); + } + return false; + }, .array => |info| hasUniqueRepresentation(info.child), - .@"struct" => |info| { if (info.layout == .@"packed") return @sizeOf(T) * 8 == @bitSizeOf(T); @@ -1225,7 +1225,6 @@ pub inline fn hasUniqueRepresentation(comptime T: type) bool { return @sizeOf(T) == sum_size; }, - .vector => |info| hasUniqueRepresentation(info.child) and @sizeOf(T) == @sizeOf(info.child) * info.len, }; @@ -1274,34 +1273,40 @@ test hasUniqueRepresentation { try testing.expect(hasUniqueRepresentation(TestStruct6)); - const TestUnion1 = packed union { + const TestUnion1 = packed union(u32) { a: u32, - b: u16, + b: i32, }; - try testing.expect(!hasUniqueRepresentation(TestUnion1)); + try testing.expect(hasUniqueRepresentation(TestUnion1)); - const TestUnion2 = extern union { - a: u32, - b: u16, + const TestUnion2 = packed union(u20) { + a: u20, + b: i20, }; - try testing.expect(!hasUniqueRepresentation(TestUnion2)); - const TestUnion3 = union { + const TestUnion3 = extern union { a: u32, b: u16, }; try testing.expect(!hasUniqueRepresentation(TestUnion3)); - const TestUnion4 = union(enum) { + const TestUnion4 = union { a: u32, b: u16, }; try testing.expect(!hasUniqueRepresentation(TestUnion4)); + const TestUnion5 = union(enum) { + a: u32, + b: u16, + }; + + try testing.expect(!hasUniqueRepresentation(TestUnion5)); + inline for ([_]type{ i0, u8, i16, u32, i64 }) |T| { try testing.expect(hasUniqueRepresentation(T)); } diff --git a/lib/std/zig/AstGen.zig b/lib/std/zig/AstGen.zig index 6a1cdb910aa4..d94826edbd38 100644 --- a/lib/std/zig/AstGen.zig +++ b/lib/std/zig/AstGen.zig @@ -5290,12 +5290,11 @@ fn unionDeclInner( const decl_count = try astgen.scanContainer(&namespace, members, .@"union"); const field_count: u32 = @intCast(members.len - decl_count); - if (layout != .auto and (auto_enum_tok != null or arg_node != 0)) { - if (arg_node != 0) { - return astgen.failNode(arg_node, "{s} union does not support enum tag type", .{@tagName(layout)}); - } else { - return astgen.failTok(auto_enum_tok.?, "{s} union does not support enum tag type", .{@tagName(layout)}); - } + if (layout != .auto and auto_enum_tok != null) { + return astgen.failTok(auto_enum_tok.?, "{s} union does not support enum tag type", .{@tagName(layout)}); + } + if (layout == .@"packed" and arg_node == 0) { + return astgen.failNode(node, "packed union must have a backing integer", .{}); } const arg_inst: Zir.Inst.Ref = if (arg_node != 0) @@ -5425,6 +5424,236 @@ fn unionDeclInner( return decl_inst.toRef(); } +fn enumDeclInner( + gz: *GenZir, + scope: *Scope, + node: Ast.Node.Index, + members: []const Ast.Node.Index, + arg_node: Ast.Node.Index, +) InnerError!Zir.Inst.Ref { + const astgen = gz.astgen; + const gpa = astgen.gpa; + const tree = astgen.tree; + + // Count total fields as well as how many have explicitly provided tag values. + const counts = blk: { + var values: usize = 0; + var total_fields: usize = 0; + var decls: usize = 0; + var nonexhaustive_node: Ast.Node.Index = 0; + var nonfinal_nonexhaustive = false; + for (members) |member_node| { + var member = tree.fullContainerField(member_node) orelse { + decls += 1; + continue; + }; + member.convertToNonTupleLike(astgen.tree.nodes); + if (member.ast.tuple_like) { + return astgen.failTok(member.ast.main_token, "enum field missing name", .{}); + } + if (member.comptime_token) |comptime_token| { + return astgen.failTok(comptime_token, "enum fields cannot be marked comptime", .{}); + } + if (member.ast.type_expr != 0) { + return astgen.failNodeNotes( + member.ast.type_expr, + "enum fields do not have types", + .{}, + &[_]u32{ + try astgen.errNoteNode( + node, + "consider 'union(enum)' here to make it a tagged union", + .{}, + ), + }, + ); + } + if (member.ast.align_expr != 0) { + return astgen.failNode(member.ast.align_expr, "enum fields cannot be aligned", .{}); + } + + const name_token = member.ast.main_token; + if (mem.eql(u8, tree.tokenSlice(name_token), "_")) { + if (nonexhaustive_node != 0) { + return astgen.failNodeNotes( + member_node, + "redundant non-exhaustive enum mark", + .{}, + &[_]u32{ + try astgen.errNoteNode( + nonexhaustive_node, + "other mark here", + .{}, + ), + }, + ); + } + nonexhaustive_node = member_node; + if (member.ast.value_expr != 0) { + return astgen.failNode(member.ast.value_expr, "'_' is used to mark an enum as non-exhaustive and cannot be assigned a value", .{}); + } + continue; + } else if (nonexhaustive_node != 0) { + nonfinal_nonexhaustive = true; + } + total_fields += 1; + if (member.ast.value_expr != 0) { + if (arg_node == 0) { + return astgen.failNode(member.ast.value_expr, "value assigned to enum tag with inferred tag type", .{}); + } + values += 1; + } + } + if (nonfinal_nonexhaustive) { + return astgen.failNode(nonexhaustive_node, "'_' field of non-exhaustive enum must be last", .{}); + } + break :blk .{ + .total_fields = total_fields, + .values = values, + .decls = decls, + .nonexhaustive_node = nonexhaustive_node, + }; + }; + if (counts.nonexhaustive_node != 0 and arg_node == 0) { + try astgen.appendErrorNodeNotes( + node, + "non-exhaustive enum missing integer tag type", + .{}, + &[_]u32{ + try astgen.errNoteNode( + counts.nonexhaustive_node, + "marked non-exhaustive here", + .{}, + ), + }, + ); + } + // In this case we must generate ZIR code for the tag values, similar to + // how structs are handled above. + const nonexhaustive = counts.nonexhaustive_node != 0; + + const decl_inst = try gz.reserveInstructionIndex(); + + var namespace: Scope.Namespace = .{ + .parent = scope, + .node = node, + .inst = decl_inst, + .declaring_gz = gz, + .maybe_generic = astgen.within_fn, + }; + defer namespace.deinit(gpa); + + // The enum_decl instruction introduces a scope in which the decls of the enum + // are in scope, so that tag values can refer to decls within the enum itself. + astgen.advanceSourceCursorToNode(node); + var block_scope: GenZir = .{ + .parent = &namespace.base, + .decl_node_index = node, + .decl_line = gz.decl_line, + .astgen = astgen, + .is_comptime = true, + .instructions = gz.instructions, + .instructions_top = gz.instructions.items.len, + }; + defer block_scope.unstack(); + + _ = try astgen.scanContainer(&namespace, members, .@"enum"); + namespace.base.tag = .namespace; + + const arg_inst: Zir.Inst.Ref = if (arg_node != 0) + try comptimeExpr(&block_scope, &namespace.base, coerced_type_ri, arg_node) + else + .none; + + const bits_per_field = 1; + const max_field_size = 3; + var wip_members = try WipMembers.init(gpa, &astgen.scratch, @intCast(counts.decls), @intCast(counts.total_fields), bits_per_field, max_field_size); + defer wip_members.deinit(); + + const old_hasher = astgen.src_hasher; + defer astgen.src_hasher = old_hasher; + astgen.src_hasher = std.zig.SrcHasher.init(.{}); + if (arg_node != 0) { + astgen.src_hasher.update(tree.getNodeSource(arg_node)); + } + astgen.src_hasher.update(&.{@intFromBool(nonexhaustive)}); + + for (members) |member_node| { + if (member_node == counts.nonexhaustive_node) + continue; + astgen.src_hasher.update(tree.getNodeSource(member_node)); + var member = switch (try containerMember(&block_scope, &namespace.base, &wip_members, member_node)) { + .decl => continue, + .field => |field| field, + }; + member.convertToNonTupleLike(astgen.tree.nodes); + assert(member.comptime_token == null); + assert(member.ast.type_expr == 0); + assert(member.ast.align_expr == 0); + + const field_name = try astgen.identAsString(member.ast.main_token); + wip_members.appendToField(@intFromEnum(field_name)); + + const doc_comment_index = try astgen.docCommentAsString(member.firstToken()); + wip_members.appendToField(@intFromEnum(doc_comment_index)); + + const have_value = member.ast.value_expr != 0; + wip_members.nextField(bits_per_field, .{have_value}); + + if (have_value) { + if (arg_inst == .none) { + return astgen.failNodeNotes( + node, + "explicitly valued enum missing integer tag type", + .{}, + &[_]u32{ + try astgen.errNoteNode( + member.ast.value_expr, + "tag value specified here", + .{}, + ), + }, + ); + } + const tag_value_inst = try expr(&block_scope, &namespace.base, .{ .rl = .{ .ty = arg_inst } }, member.ast.value_expr); + wip_members.appendToField(@intFromEnum(tag_value_inst)); + } + } + + if (!block_scope.isEmpty()) { + _ = try block_scope.addBreak(.break_inline, decl_inst, .void_value); + } + + var fields_hash: std.zig.SrcHash = undefined; + astgen.src_hasher.final(&fields_hash); + + const body = block_scope.instructionsSlice(); + const body_len = astgen.countBodyLenAfterFixups(body); + + try gz.setEnum(decl_inst, .{ + .src_node = node, + .nonexhaustive = nonexhaustive, + .tag_type = arg_inst, + .captures_len = @intCast(namespace.captures.count()), + .body_len = body_len, + .fields_len = @intCast(counts.total_fields), + .decls_len = @intCast(counts.decls), + .fields_hash = fields_hash, + }); + + wip_members.finishBits(bits_per_field); + const decls_slice = wip_members.declsSlice(); + const fields_slice = wip_members.fieldsSlice(); + try astgen.extra.ensureUnusedCapacity(gpa, namespace.captures.count() + decls_slice.len + body_len + fields_slice.len); + astgen.extra.appendSliceAssumeCapacity(@ptrCast(namespace.captures.keys())); + astgen.extra.appendSliceAssumeCapacity(decls_slice); + astgen.appendBodyWithFixups(body); + astgen.extra.appendSliceAssumeCapacity(fields_slice); + + block_scope.unstack(); + return decl_inst.toRef(); +} + fn containerDecl( gz: *GenZir, scope: *Scope, @@ -5469,223 +5698,8 @@ fn containerDecl( if (container_decl.layout_token) |t| { return astgen.failTok(t, "enums do not support 'packed' or 'extern'; instead provide an explicit integer tag type", .{}); } - // Count total fields as well as how many have explicitly provided tag values. - const counts = blk: { - var values: usize = 0; - var total_fields: usize = 0; - var decls: usize = 0; - var nonexhaustive_node: Ast.Node.Index = 0; - var nonfinal_nonexhaustive = false; - for (container_decl.ast.members) |member_node| { - var member = tree.fullContainerField(member_node) orelse { - decls += 1; - continue; - }; - member.convertToNonTupleLike(astgen.tree.nodes); - if (member.ast.tuple_like) { - return astgen.failTok(member.ast.main_token, "enum field missing name", .{}); - } - if (member.comptime_token) |comptime_token| { - return astgen.failTok(comptime_token, "enum fields cannot be marked comptime", .{}); - } - if (member.ast.type_expr != 0) { - return astgen.failNodeNotes( - member.ast.type_expr, - "enum fields do not have types", - .{}, - &[_]u32{ - try astgen.errNoteNode( - node, - "consider 'union(enum)' here to make it a tagged union", - .{}, - ), - }, - ); - } - if (member.ast.align_expr != 0) { - return astgen.failNode(member.ast.align_expr, "enum fields cannot be aligned", .{}); - } - - const name_token = member.ast.main_token; - if (mem.eql(u8, tree.tokenSlice(name_token), "_")) { - if (nonexhaustive_node != 0) { - return astgen.failNodeNotes( - member_node, - "redundant non-exhaustive enum mark", - .{}, - &[_]u32{ - try astgen.errNoteNode( - nonexhaustive_node, - "other mark here", - .{}, - ), - }, - ); - } - nonexhaustive_node = member_node; - if (member.ast.value_expr != 0) { - return astgen.failNode(member.ast.value_expr, "'_' is used to mark an enum as non-exhaustive and cannot be assigned a value", .{}); - } - continue; - } else if (nonexhaustive_node != 0) { - nonfinal_nonexhaustive = true; - } - total_fields += 1; - if (member.ast.value_expr != 0) { - if (container_decl.ast.arg == 0) { - return astgen.failNode(member.ast.value_expr, "value assigned to enum tag with inferred tag type", .{}); - } - values += 1; - } - } - if (nonfinal_nonexhaustive) { - return astgen.failNode(nonexhaustive_node, "'_' field of non-exhaustive enum must be last", .{}); - } - break :blk .{ - .total_fields = total_fields, - .values = values, - .decls = decls, - .nonexhaustive_node = nonexhaustive_node, - }; - }; - if (counts.nonexhaustive_node != 0 and container_decl.ast.arg == 0) { - try astgen.appendErrorNodeNotes( - node, - "non-exhaustive enum missing integer tag type", - .{}, - &[_]u32{ - try astgen.errNoteNode( - counts.nonexhaustive_node, - "marked non-exhaustive here", - .{}, - ), - }, - ); - } - // In this case we must generate ZIR code for the tag values, similar to - // how structs are handled above. - const nonexhaustive = counts.nonexhaustive_node != 0; - - const decl_inst = try gz.reserveInstructionIndex(); - - var namespace: Scope.Namespace = .{ - .parent = scope, - .node = node, - .inst = decl_inst, - .declaring_gz = gz, - .maybe_generic = astgen.within_fn, - }; - defer namespace.deinit(gpa); - - // The enum_decl instruction introduces a scope in which the decls of the enum - // are in scope, so that tag values can refer to decls within the enum itself. - astgen.advanceSourceCursorToNode(node); - var block_scope: GenZir = .{ - .parent = &namespace.base, - .decl_node_index = node, - .decl_line = gz.decl_line, - .astgen = astgen, - .is_comptime = true, - .instructions = gz.instructions, - .instructions_top = gz.instructions.items.len, - }; - defer block_scope.unstack(); - - _ = try astgen.scanContainer(&namespace, container_decl.ast.members, .@"enum"); - namespace.base.tag = .namespace; - - const arg_inst: Zir.Inst.Ref = if (container_decl.ast.arg != 0) - try comptimeExpr(&block_scope, &namespace.base, coerced_type_ri, container_decl.ast.arg) - else - .none; - - const bits_per_field = 1; - const max_field_size = 3; - var wip_members = try WipMembers.init(gpa, &astgen.scratch, @intCast(counts.decls), @intCast(counts.total_fields), bits_per_field, max_field_size); - defer wip_members.deinit(); - - const old_hasher = astgen.src_hasher; - defer astgen.src_hasher = old_hasher; - astgen.src_hasher = std.zig.SrcHasher.init(.{}); - if (container_decl.ast.arg != 0) { - astgen.src_hasher.update(tree.getNodeSource(container_decl.ast.arg)); - } - astgen.src_hasher.update(&.{@intFromBool(nonexhaustive)}); - - for (container_decl.ast.members) |member_node| { - if (member_node == counts.nonexhaustive_node) - continue; - astgen.src_hasher.update(tree.getNodeSource(member_node)); - var member = switch (try containerMember(&block_scope, &namespace.base, &wip_members, member_node)) { - .decl => continue, - .field => |field| field, - }; - member.convertToNonTupleLike(astgen.tree.nodes); - assert(member.comptime_token == null); - assert(member.ast.type_expr == 0); - assert(member.ast.align_expr == 0); - - const field_name = try astgen.identAsString(member.ast.main_token); - wip_members.appendToField(@intFromEnum(field_name)); - - const doc_comment_index = try astgen.docCommentAsString(member.firstToken()); - wip_members.appendToField(@intFromEnum(doc_comment_index)); - - const have_value = member.ast.value_expr != 0; - wip_members.nextField(bits_per_field, .{have_value}); - - if (have_value) { - if (arg_inst == .none) { - return astgen.failNodeNotes( - node, - "explicitly valued enum missing integer tag type", - .{}, - &[_]u32{ - try astgen.errNoteNode( - member.ast.value_expr, - "tag value specified here", - .{}, - ), - }, - ); - } - const tag_value_inst = try expr(&block_scope, &namespace.base, .{ .rl = .{ .ty = arg_inst } }, member.ast.value_expr); - wip_members.appendToField(@intFromEnum(tag_value_inst)); - } - } - - if (!block_scope.isEmpty()) { - _ = try block_scope.addBreak(.break_inline, decl_inst, .void_value); - } - - var fields_hash: std.zig.SrcHash = undefined; - astgen.src_hasher.final(&fields_hash); - - const body = block_scope.instructionsSlice(); - const body_len = astgen.countBodyLenAfterFixups(body); - - try gz.setEnum(decl_inst, .{ - .src_node = node, - .nonexhaustive = nonexhaustive, - .tag_type = arg_inst, - .captures_len = @intCast(namespace.captures.count()), - .body_len = body_len, - .fields_len = @intCast(counts.total_fields), - .decls_len = @intCast(counts.decls), - .fields_hash = fields_hash, - }); - - wip_members.finishBits(bits_per_field); - const decls_slice = wip_members.declsSlice(); - const fields_slice = wip_members.fieldsSlice(); - try astgen.extra.ensureUnusedCapacity(gpa, namespace.captures.count() + decls_slice.len + body_len + fields_slice.len); - astgen.extra.appendSliceAssumeCapacity(@ptrCast(namespace.captures.keys())); - astgen.extra.appendSliceAssumeCapacity(decls_slice); - astgen.appendBodyWithFixups(body); - astgen.extra.appendSliceAssumeCapacity(fields_slice); - - block_scope.unstack(); - return rvalue(gz, ri, decl_inst.toRef(), node); + const result = try enumDeclInner(gz, scope, node, container_decl.ast.members, container_decl.ast.arg); + return rvalue(gz, ri, result, node); }, .keyword_opaque => { assert(container_decl.ast.arg == 0); diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig index d399c58c9cd6..879e36411886 100644 --- a/lib/std/zig/parser_test.zig +++ b/lib/std/zig/parser_test.zig @@ -1188,7 +1188,7 @@ test "zig fmt: empty union decls" { \\const B = union(enum) {}; \\const C = union(Foo) {}; \\const D = extern union {}; - \\const E = packed union {}; + \\const E = packed union(u8) {}; \\ ); } diff --git a/src/Sema.zig b/src/Sema.zig index 57a4809f95c4..fd0be7bb68d3 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -3156,7 +3156,7 @@ fn zirUnionDecl( .layout = small.layout, .status = .none, .runtime_tag = if (small.has_tag_type or small.auto_enum_tag) - .tagged + if (small.layout == .@"packed") .none else .tagged // packed unions will never have a runtime tag else if (small.layout != .auto) .none else switch (block.wantSafety()) { @@ -36810,15 +36810,17 @@ fn unionFields( _ = try sema.analyzeInlineBody(&block_scope, body, zir_index); } + const layout = union_type.flagsUnordered(ip).layout; + var backing_int_size: ?u64 = null; + const tag_ty_src: LazySrcLoc = .{ + .base_node_inst = union_type.zir_index, + .offset = .{ .node_offset_container_tag = 0 }, + }; var int_tag_ty: Type = undefined; var enum_field_names: []InternPool.NullTerminatedString = &.{}; var enum_field_vals: std.AutoArrayHashMapUnmanaged(InternPool.Index, void) = .{}; var explicit_tags_seen: []bool = &.{}; if (tag_type_ref != .none) { - const tag_ty_src: LazySrcLoc = .{ - .base_node_inst = union_type.zir_index, - .offset = .{ .node_offset_container_tag = 0 }, - }; const provided_ty = try sema.resolveType(&block_scope, tag_ty_src, tag_type_ref); if (small.auto_enum_tag) { // The provided type is an integer type and we must construct the enum tag type here. @@ -36845,16 +36847,38 @@ fn unionFields( try enum_field_vals.ensureTotalCapacity(sema.arena, fields_len); } } else { - // The provided type is the enum tag type. - const enum_type = switch (ip.indexToKey(provided_ty.toIntern())) { - .enum_type => ip.loadEnumType(provided_ty.toIntern()), - else => return sema.fail(&block_scope, tag_ty_src, "expected enum tag type, found '{}'", .{provided_ty.fmt(pt)}), - }; - union_type.setTagType(ip, provided_ty.toIntern()); - // The fields of the union must match the enum exactly. - // A flag per field is used to check for missing and extraneous fields. - explicit_tags_seen = try sema.arena.alloc(bool, enum_type.names.len); - @memset(explicit_tags_seen, false); + // The provided type is the tag type. + switch (provided_ty.zigTypeTag(zcu)) { + .@"enum" => { + const enum_type = ip.loadEnumType(provided_ty.toIntern()); + union_type.setTagType(ip, provided_ty.toIntern()); + // The fields of the union must match the enum exactly. + // A flag per field is used to check for missing and extraneous fields. + explicit_tags_seen = try sema.arena.alloc(bool, enum_type.names.len); + @memset(explicit_tags_seen, false); + }, + .int => { + backing_int_size = provided_ty.bitSize(zcu); + enum_field_names = try sema.arena.alloc(InternPool.NullTerminatedString, fields_len); + }, + else => { + if (layout == .@"packed") { + return sema.fail( + &block_scope, + tag_ty_src, + "expected backing integer type, found '{}'", + .{provided_ty.fmt(pt)}, + ); + } else { + return sema.fail( + &block_scope, + tag_ty_src, + "expected enum tag type, found '{}'", + .{provided_ty.fmt(pt)}, + ); + } + }, + } } } else { // If auto_enum_tag is false, this is an untagged union. However, for semantic analysis @@ -36870,6 +36894,11 @@ fn unionFields( if (small.any_aligned_fields) try field_aligns.ensureTotalCapacityPrecise(sema.arena, fields_len); + // a list of gathered packed union field bitsize errors + // the u64 is the field bit size, the LazySrcLoc points to the field type + var mismatched_size_errs: std.ArrayListUnmanaged(struct { u64, LazySrcLoc }) = .{}; + defer mismatched_size_errs.deinit(gpa); + const bits_per_field = 4; const fields_per_u32 = 32 / bits_per_field; const bit_bags_count = std.math.divCeil(usize, fields_len, fields_per_u32) catch unreachable; @@ -36990,6 +37019,16 @@ fn unionFields( return error.GenericPoison; } + if (backing_int_size) |size| { + const field_bit_size = try field_ty.bitSizeSema(pt); + if (field_bit_size != size) { + try mismatched_size_errs.append(gpa, .{ + field_bit_size, + type_src, + }); + } + } + if (explicit_tags_seen.len > 0) { const tag_ty = union_type.tagTypeUnordered(ip); const tag_info = ip.loadEnumType(tag_ty); @@ -37032,7 +37071,6 @@ fn unionFields( }; return sema.failWithOwnedErrorMsg(&block_scope, msg); } - const layout = union_type.flagsUnordered(ip).layout; if (layout == .@"extern" and !try sema.validateExternType(field_ty, .union_field)) { @@ -37074,6 +37112,30 @@ fn unionFields( union_type.setFieldTypes(ip, field_types.items); union_type.setFieldAligns(ip, field_aligns.items); + if (mismatched_size_errs.items.len > 0) { + const msg = msg: { + const msg = try sema.errMsg( + tag_ty_src, + "all fields must have a bit size of {d}", + .{backing_int_size.?}, + ); + errdefer msg.destroy(gpa); + + for (mismatched_size_errs.items) |entry| { + const field_size, const field_ty_src = entry; + try sema.errNote( + field_ty_src, + msg, + "field with bit size {d} here", + .{field_size}, + ); + } + + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(&block_scope, msg); + } + if (explicit_tags_seen.len > 0) { const tag_ty = union_type.tagTypeUnordered(ip); const tag_info = ip.loadEnumType(tag_ty); diff --git a/stage1/zig1.wasm b/stage1/zig1.wasm index 32add10535cc..e0494d4fe60e 100644 Binary files a/stage1/zig1.wasm and b/stage1/zig1.wasm differ diff --git a/test/behavior/cast_int.zig b/test/behavior/cast_int.zig index 10bb445ca70e..d9a947c4f14b 100644 --- a/test/behavior/cast_int.zig +++ b/test/behavior/cast_int.zig @@ -139,6 +139,7 @@ test "coerce non byte-sized integers accross 32bits boundary" { const Piece = packed struct { color: Color, type: Type, + _: u4 = 0, const Type = enum(u3) { KING, QUEEN, BISHOP, KNIGHT, ROOK, PAWN }; const Color = enum(u1) { WHITE, BLACK }; @@ -221,7 +222,7 @@ test "load non byte-sized value in union" { // note: this bug is triggered by the == operator, expectEqual will hide it // using ptrCast not to depend on unitialised memory state - var union0: packed union { + var union0: packed union(u8) { p: Piece, int: u8, } = .{ .int = 0 }; diff --git a/test/behavior/comptime_memory.zig b/test/behavior/comptime_memory.zig index baca3da72dd4..4574b88fefe0 100644 --- a/test/behavior/comptime_memory.zig +++ b/test/behavior/comptime_memory.zig @@ -383,7 +383,7 @@ test "accessing reinterpreted memory of parent object" { } test "bitcast packed union to integer" { - const U = packed union { + const U = packed union(u2) { x: i2, y: u2, }; diff --git a/test/behavior/export_keyword.zig b/test/behavior/export_keyword.zig index 70839959d218..f6bdea733721 100644 --- a/test/behavior/export_keyword.zig +++ b/test/behavior/export_keyword.zig @@ -9,8 +9,6 @@ const builtin = @import("builtin"); // and generates code const vram = @as([*]volatile u8, @ptrFromInt(0x20000000))[0..0x8000]; export fn writeToVRam() void { - if (builtin.zig_backend == .stage2_riscv64) return; - vram[0] = 'X'; } @@ -18,24 +16,38 @@ const PackedStruct = packed struct { a: u8, b: u8, }; -const PackedUnion = packed union { - a: u8, +const PackedUnion = packed union(u32) { + a: i32, b: u32, }; +const PackedEnum = enum(u32) { + a, + b, + _, +}; test "packed struct, enum, union parameters in extern function" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; - testPackedStuff(&(PackedStruct{ - .a = 1, - .b = 2, - }), &(PackedUnion{ .a = 1 })); + testPackedStuff( + &(PackedStruct{ + .a = 1, + .b = 2, + }), + &(PackedUnion{ .a = 1 }), + &(PackedEnum.b), + ); } -export fn testPackedStuff(a: *const PackedStruct, b: *const PackedUnion) void { +export fn testPackedStuff( + a: *const PackedStruct, + b: *const PackedUnion, + c: *const PackedEnum, +) void { if (false) { a; b; + c; } } diff --git a/test/behavior/field_parent_ptr.zig b/test/behavior/field_parent_ptr.zig index 0488d941c444..e035116940ef 100644 --- a/test/behavior/field_parent_ptr.zig +++ b/test/behavior/field_parent_ptr.zig @@ -1753,28 +1753,30 @@ test "@fieldParentPtr packed union" { if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; if (builtin.target.cpu.arch.endian() == .big) return error.SkipZigTest; // TODO - const C = packed union { - a: bool, - b: f32, - c: packed struct { x: u8 }, - d: i32, + const C = packed union(u32) { + a: f32, + b: packed struct { + x: u8, + unused: u24 = 0, + }, + c: i32, }; { - const c: C = .{ .a = false }; + const c: C = .{ .a = 0 }; const pcf = &c.a; const pc: *const C = @alignCast(@fieldParentPtr("a", pcf)); try expect(pc == &c); } { - const c: C = .{ .a = false }; + const c: C = .{ .a = 0 }; const pcf = &c.a; var pc: *const C = undefined; pc = @alignCast(@fieldParentPtr("a", pcf)); try expect(pc == &c); } { - const c: C = .{ .a = false }; + const c: C = .{ .a = 0 }; var pcf: @TypeOf(&c.a) = undefined; pcf = &c.a; var pc: *const C = undefined; @@ -1783,7 +1785,7 @@ test "@fieldParentPtr packed union" { } { var c: C = undefined; - c = .{ .a = false }; + c = .{ .a = 0 }; var pcf: @TypeOf(&c.a) = undefined; pcf = &c.a; var pc: *C = undefined; @@ -1792,20 +1794,20 @@ test "@fieldParentPtr packed union" { } { - const c: C = .{ .b = 0 }; + const c: C = .{ .b = .{ .x = 255 } }; const pcf = &c.b; const pc: *const C = @alignCast(@fieldParentPtr("b", pcf)); try expect(pc == &c); } { - const c: C = .{ .b = 0 }; + const c: C = .{ .b = .{ .x = 255 } }; const pcf = &c.b; var pc: *const C = undefined; pc = @alignCast(@fieldParentPtr("b", pcf)); try expect(pc == &c); } { - const c: C = .{ .b = 0 }; + const c: C = .{ .b = .{ .x = 255 } }; var pcf: @TypeOf(&c.b) = undefined; pcf = &c.b; var pc: *const C = undefined; @@ -1814,7 +1816,7 @@ test "@fieldParentPtr packed union" { } { var c: C = undefined; - c = .{ .b = 0 }; + c = .{ .b = .{ .x = 255 } }; var pcf: @TypeOf(&c.b) = undefined; pcf = &c.b; var pc: *C = undefined; @@ -1823,20 +1825,20 @@ test "@fieldParentPtr packed union" { } { - const c: C = .{ .c = .{ .x = 255 } }; + const c: C = .{ .c = -1111111111 }; const pcf = &c.c; const pc: *const C = @alignCast(@fieldParentPtr("c", pcf)); try expect(pc == &c); } { - const c: C = .{ .c = .{ .x = 255 } }; + const c: C = .{ .c = -1111111111 }; const pcf = &c.c; var pc: *const C = undefined; pc = @alignCast(@fieldParentPtr("c", pcf)); try expect(pc == &c); } { - const c: C = .{ .c = .{ .x = 255 } }; + const c: C = .{ .c = -1111111111 }; var pcf: @TypeOf(&c.c) = undefined; pcf = &c.c; var pc: *const C = undefined; @@ -1845,44 +1847,13 @@ test "@fieldParentPtr packed union" { } { var c: C = undefined; - c = .{ .c = .{ .x = 255 } }; + c = .{ .c = -1111111111 }; var pcf: @TypeOf(&c.c) = undefined; pcf = &c.c; var pc: *C = undefined; pc = @alignCast(@fieldParentPtr("c", pcf)); try expect(pc == &c); } - - { - const c: C = .{ .d = -1111111111 }; - const pcf = &c.d; - const pc: *const C = @alignCast(@fieldParentPtr("d", pcf)); - try expect(pc == &c); - } - { - const c: C = .{ .d = -1111111111 }; - const pcf = &c.d; - var pc: *const C = undefined; - pc = @alignCast(@fieldParentPtr("d", pcf)); - try expect(pc == &c); - } - { - const c: C = .{ .d = -1111111111 }; - var pcf: @TypeOf(&c.d) = undefined; - pcf = &c.d; - var pc: *const C = undefined; - pc = @alignCast(@fieldParentPtr("d", pcf)); - try expect(pc == &c); - } - { - var c: C = undefined; - c = .{ .d = -1111111111 }; - var pcf: @TypeOf(&c.d) = undefined; - pcf = &c.d; - var pc: *C = undefined; - pc = @alignCast(@fieldParentPtr("d", pcf)); - try expect(pc == &c); - } } test "@fieldParentPtr tagged union all zero-bit fields" { diff --git a/test/behavior/packed-struct.zig b/test/behavior/packed-struct.zig index 8386f62f7782..24a94838076b 100644 --- a/test/behavior/packed-struct.zig +++ b/test/behavior/packed-struct.zig @@ -1243,8 +1243,8 @@ test "load flag from packed struct in union" { test "bitcasting a packed struct at comptime and using the result" { comptime { - const Struct = packed struct { - x: packed union { a: u63, b: i32 }, + const Struct = packed struct(u64) { + x: packed union(u63) { a: u63, b: i63 }, y: u1, pub fn bitcast(fd: u64) @This() { diff --git a/test/behavior/packed-union.zig b/test/behavior/packed-union.zig index 701c0484a41e..6131cf6c5a26 100644 --- a/test/behavior/packed-union.zig +++ b/test/behavior/packed-union.zig @@ -19,7 +19,7 @@ fn testFlagsInPackedUnion() !void { enable_2: bool = false, enable_3: bool = false, enable_4: bool = false, - other_flags: packed union { + other_flags: packed union(u4) { flags: packed struct(u4) { enable_1: bool = true, enable_2: bool = false, @@ -56,19 +56,19 @@ test "flags in packed union at offset" { } fn testFlagsInPackedUnionAtOffset() !void { - const FlagBits = packed union { - base_flags: packed union { + const FlagBits = packed union(u12) { + base_flags: packed struct(u12) { flags: packed struct(u4) { enable_1: bool = true, enable_2: bool = false, enable_3: bool = false, enable_4: bool = false, }, - bits: u4, + _: u8, }, adv_flags: packed struct(u12) { pad: u8 = 0, - adv: packed union { + adv: packed union(u4) { flags: packed struct(u4) { enable_1: bool = true, enable_2: bool = false, @@ -113,7 +113,7 @@ fn testPackedUnionInPackedStruct() !void { read, insert, }; - const RequestUnion = packed union { + const RequestUnion = packed union(u32) { read: ReadRequest, }; @@ -141,11 +141,11 @@ test "packed union initialized with a runtime value" { if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; - const Fields = packed struct { + const Fields = packed struct(u63) { timestamp: u50, random_bits: u13, }; - const ID = packed union { + const ID = packed union(u63) { value: u63, fields: Fields, @@ -164,7 +164,7 @@ test "packed union initialized with a runtime value" { test "assigning to non-active field at comptime" { comptime { - const FlagBits = packed union { + const FlagBits = packed union(u0) { flags: packed struct {}, bits: packed struct {}, }; @@ -177,7 +177,7 @@ test "assigning to non-active field at comptime" { test "comptime packed union of pointers" { if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; - const U = packed union { + const U = packed union(usize) { a: *const u32, b: *const [1]u32, }; diff --git a/test/behavior/union.zig b/test/behavior/union.zig index a952e9b9d31a..9f16b8aef2d4 100644 --- a/test/behavior/union.zig +++ b/test/behavior/union.zig @@ -222,9 +222,9 @@ test "packed union generates correctly aligned type" { if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; - const U = packed union { + const U = packed union(usize) { f1: *const fn () error{TestUnexpectedResult}!void, - f2: u32, + f2: usize, }; var foo = [_]U{ U{ .f1 = doTest }, @@ -359,12 +359,12 @@ test "simple union(enum(u32))" { try expect(@intFromEnum(@as(Tag(MultipleChoice), x)) == 60); } -const PackedPtrOrInt = packed union { +const PackedPtrOrInt = packed union(usize) { ptr: *u8, - int: u64, + int: usize, }; test "packed union size" { - comptime assert(@sizeOf(PackedPtrOrInt) == 8); + comptime assert(@sizeOf(PackedPtrOrInt) == @sizeOf(usize)); } const ZeroBits = union { @@ -1432,13 +1432,13 @@ test "packed union in packed struct" { if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; const S = packed struct { - nested: packed union { - val: usize, + nested: packed union(u32) { + val: i32, foo: u32, }, bar: u32, - fn unpack(self: @This()) usize { + fn unpack(self: @This()) u32 { return self.nested.foo; } }; @@ -1520,9 +1520,9 @@ test "packed union with zero-bit field" { if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; const S = packed struct { - nested: packed union { + nested: packed union(u0) { zero: void, - sized: u32, + sized: u0, }, bar: u32, @@ -1538,7 +1538,7 @@ test "reinterpreting enum value inside packed union" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; - const U = packed union { + const U = packed union(u8) { tag: enum(u8) { a, b }, val: u8, @@ -1607,7 +1607,7 @@ test "defined-layout union field pointer has correct alignment" { }; const U1 = extern union { x: u32 }; - const U2 = packed union { x: u32 }; + const U2 = packed union(u32) { x: u32 }; try S.doTheTest(U1); try S.doTheTest(U2); @@ -1656,7 +1656,7 @@ test "packed union field pointer has correct alignment" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; // TODO - const U = packed union { x: u20 }; + const U = packed union(u20) { x: u20 }; const S = packed struct(u24) { a: u2, u: U, b: u2 }; var a: S = undefined; @@ -1727,9 +1727,12 @@ test "memset extern union" { test "memset packed union" { if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; - const U = packed union { + const U = packed union(u32) { a: u32, - b: u8, + b: packed struct { + used: u8, + unused: u24 = 0, + }, }; const S = struct { @@ -1737,7 +1740,7 @@ test "memset packed union" { var u: U = undefined; @memset(std.mem.asBytes(&u), 42); try expectEqual(@as(u32, 0x2a2a2a2a), u.a); - try expectEqual(@as(u8, 0x2a), u.b); + try expectEqual(@as(u8, 0x2a), u.b.used); } }; @@ -1824,116 +1827,6 @@ test "reinterpret extern union" { try S.doTheTest(); } -test "reinterpret packed union" { - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; - - const U = packed union { - foo: u8, - bar: u29, - baz: u64, - qux: u12, - }; - - const S = struct { - fn doTheTest() !void { - { - const u = blk: { - var u: U = undefined; - @memset(std.mem.asBytes(&u), 0); - u.baz = 0xbbbbbbbb; - u.qux = 0xe2a; - break :blk u; - }; - - try expectEqual(@as(u8, 0x2a), u.foo); - try expectEqual(@as(u12, 0xe2a), u.qux); - - // https://github.com/ziglang/zig/issues/17360 - if (@inComptime()) { - try expectEqual(@as(u29, 0x1bbbbe2a), u.bar); - try expectEqual(@as(u64, 0xbbbbbe2a), u.baz); - } - } - - { - // Union initialization - var u: U = .{ .baz = 0 }; // ensure all bits are defined - u.qux = 0xe2a; - try expectEqual(@as(u8, 0x2a), u.foo); - try expectEqual(@as(u12, 0xe2a), u.qux); - try expectEqual(@as(u29, 0xe2a), u.bar & 0xfff); - try expectEqual(@as(u64, 0xe2a), u.baz & 0xfff); - - // Writing to a larger field - u.baz = 0xbbbbbbbb; - try expectEqual(@as(u8, 0xbb), u.foo); - try expectEqual(@as(u12, 0xbbb), u.qux); - try expectEqual(@as(u29, 0x1bbbbbbb), u.bar); - try expectEqual(@as(u64, 0xbbbbbbbb), u.baz); - - // Writing to the same field - u.baz = 0xcccccccc; - try expectEqual(@as(u8, 0xcc), u.foo); - try expectEqual(@as(u12, 0xccc), u.qux); - try expectEqual(@as(u29, 0x0ccccccc), u.bar); - try expectEqual(@as(u64, 0xcccccccc), u.baz); - - // Writing to a smaller field - u.foo = 0xdd; - try expectEqual(@as(u8, 0xdd), u.foo); - try expectEqual(@as(u12, 0xcdd), u.qux); - try expectEqual(@as(u29, 0x0cccccdd), u.bar); - try expectEqual(@as(u64, 0xccccccdd), u.baz); - } - } - }; - - try comptime S.doTheTest(); - - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO - if (builtin.cpu.arch.isPowerPC32()) return error.SkipZigTest; // TODO - if (builtin.cpu.arch.isMIPS()) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/21050 - if (builtin.cpu.arch.isWasm()) return error.SkipZigTest; // TODO - try S.doTheTest(); -} - -test "reinterpret packed union inside packed struct" { - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; - - const U = packed union { - a: u7, - b: u1, - }; - - const V = packed struct { - lo: U, - hi: U, - }; - - const S = struct { - fn doTheTest() !void { - var v: V = undefined; - @memset(std.mem.asBytes(&v), 0x55); - try expectEqual(@as(u7, 0x55), v.lo.a); - try expectEqual(@as(u1, 1), v.lo.b); - try expectEqual(@as(u7, 0x2a), v.hi.a); - try expectEqual(@as(u1, 0), v.hi.b); - - v.lo.b = 0; - try expectEqual(@as(u7, 0x54), v.lo.a); - try expectEqual(@as(u1, 0), v.lo.b); - v.hi.b = 1; - try expectEqual(@as(u7, 0x2b), v.hi.a); - try expectEqual(@as(u1, 1), v.hi.b); - } - }; - - try comptime S.doTheTest(); - - if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO - try S.doTheTest(); -} - test "inner struct initializer uses union layout" { const namespace = struct { const U = union { @@ -1962,12 +1855,13 @@ test "inner struct initializer uses packed union layout" { if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; const namespace = struct { - const U = packed union { + const U = packed union(u32) { a: packed struct { x: u32 = @alignOf(U) + 1, }, b: packed struct { y: u16 = @sizeOf(U) + 2, + unused: u16 = 0, }, }; }; @@ -2003,26 +1897,6 @@ test "extern union initialized via reintepreted struct field initializer" { try expect(s.u.b == 0xaa); } -test "packed union initialized via reintepreted struct field initializer" { - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; - - const bytes = [_]u8{ 0xaa, 0xbb, 0xcc, 0xdd }; - - const U = packed union { - a: u32, - b: u8, - }; - - const S = packed struct { - u: U = std.mem.bytesAsValue(U, &bytes).*, - }; - - var s: S = .{}; - _ = &s; - try expect(s.u.a == littleToNativeEndian(u32, 0xddccbbaa)); - try expect(s.u.b == if (endian == .little) 0xaa else 0xdd); -} - test "store of comptime reinterpreted memory to extern union" { if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; @@ -2045,28 +1919,6 @@ test "store of comptime reinterpreted memory to extern union" { try expect(u.b == 0xaa); } -test "store of comptime reinterpreted memory to packed union" { - if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; - - const bytes = [_]u8{ 0xaa, 0xbb, 0xcc, 0xdd }; - - const U = packed union { - a: u32, - b: u8, - }; - - const reinterpreted = comptime b: { - var u: U = undefined; - u = std.mem.bytesAsValue(U, &bytes).*; - break :b u; - }; - - var u: U = reinterpreted; - _ = &u; - try expect(u.a == littleToNativeEndian(u32, 0xddccbbaa)); - try expect(u.b == if (endian == .little) 0xaa else 0xdd); -} - test "union field is a pointer to an aligned version of itself" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; diff --git a/test/cases/compile_errors/packed_struct_with_fields_of_not_allowed_types.zig b/test/cases/compile_errors/packed_struct_with_fields_of_not_allowed_types.zig index fe86990d31ea..6439c2b97aa8 100644 --- a/test/cases/compile_errors/packed_struct_with_fields_of_not_allowed_types.zig +++ b/test/cases/compile_errors/packed_struct_with_fields_of_not_allowed_types.zig @@ -50,7 +50,7 @@ export fn entry10() void { } export fn entry11() void { _ = @sizeOf(packed struct { - x: packed union { A: i32, B: u32 }, + x: packed union(u32) { A: i32, B: u32 }, }); } const S = struct { diff --git a/test/cases/compile_errors/packed_union_left_over_bits.zig b/test/cases/compile_errors/packed_union_left_over_bits.zig new file mode 100644 index 000000000000..9c5ae1f1ebb0 --- /dev/null +++ b/test/cases/compile_errors/packed_union_left_over_bits.zig @@ -0,0 +1,23 @@ +const Foo = packed union(u32) { + x: u32, + y: u8, +}; + +const Bar = packed union(u6) { + x: u9, + y: u64, + z: u6, +}; + +export fn a(_: Foo) void {} +export fn b(_: Bar) void {} + +// error +// backend=stage2 +// target=native +// +// :1:26: error: all fields must have a bit size of 32 +// :3:8: note: field with bit size 8 here +// :6:26: error: all fields must have a bit size of 6 +// :7:8: note: field with bit size 9 here +// :8:8: note: field with bit size 64 here diff --git a/test/cases/compile_errors/packed_union_with_automatic_layout_field.zig b/test/cases/compile_errors/packed_union_with_automatic_layout_field.zig index 210f30a1b395..36ff2a7a7c89 100644 --- a/test/cases/compile_errors/packed_union_with_automatic_layout_field.zig +++ b/test/cases/compile_errors/packed_union_with_automatic_layout_field.zig @@ -2,7 +2,7 @@ const Foo = struct { a: u32, b: f32, }; -const Payload = packed union { +const Payload = packed union(u32) { A: Foo, B: bool, };