| author | |
| committer | |
| log | c0aa4a1a42b3e0d312bd274799be67d60a1c0238 |
| tree | 6af32089a5fd3b6f79d7eb8a5fcb26bd0a46c39b |
| parent | 25266d08046df6032007b46346faf01a2f40ef31 |
* AIR instructions struct_field_ptr and related functions now are also
emitted by the frontend for unions. Backends must inspect the type
of the pointer operand to lower the instructions correctly.
- These will be renamed to `agg_field_ptr` (short for "aggregate") in
the future.
* Introduce the new `set_union_tag` AIR instruction.
* Introduce `Module.EnumNumbered` and associated `Type` methods. This
is for enums which have no decls, but do have the possibility of
overriding the integer tag type and tag values.
* Sema: Implement support for union tag types in both the
auto-generated and explicitly-provided cases, as well as explicitly
provided enum tag values in union declarations.
* LLVM backend: implement lowering union types, union field pointer
instructions, and the new `set_union_tag` instruction.11 files changed, 576 insertions(+), 161 deletions(-)
src/Air.zig+11-3| ... | ... | @@ -270,19 +270,26 @@ pub const Inst = struct { |
| 270 | 270 | /// wrap from E to E!T |
| 271 | 271 | /// Uses the `ty_op` field. |
| 272 | 272 | wrap_errunion_err, |
| 273 | /// Given a pointer to a struct and a field index, returns a pointer to the field. | |
| 273 | /// Given a pointer to a struct or union and a field index, returns a pointer to the field. | |
| 274 | 274 | /// Uses the `ty_pl` field, payload is `StructField`. |
| 275 | /// TODO rename to `agg_field_ptr`. | |
| 275 | 276 | struct_field_ptr, |
| 276 | /// Given a pointer to a struct, returns a pointer to the field. | |
| 277 | /// Given a pointer to a struct or union, returns a pointer to the field. | |
| 277 | 278 | /// The field index is the number at the end of the name. |
| 278 | 279 | /// Uses `ty_op` field. |
| 280 | /// TODO rename to `agg_field_ptr_index_X` | |
| 279 | 281 | struct_field_ptr_index_0, |
| 280 | 282 | struct_field_ptr_index_1, |
| 281 | 283 | struct_field_ptr_index_2, |
| 282 | 284 | struct_field_ptr_index_3, |
| 283 | /// Given a byval struct and a field index, returns the field byval. | |
| 285 | /// Given a byval struct or union and a field index, returns the field byval. | |
| 284 | 286 | /// Uses the `ty_pl` field, payload is `StructField`. |
| 287 | /// TODO rename to `agg_field_val` | |
| 285 | 288 | struct_field_val, |
| 289 | /// Given a pointer to a tagged union, set its tag to the provided value. | |
| 290 | /// Result type is always void. | |
| 291 | /// Uses the `bin_op` field. LHS is union pointer, RHS is new tag value. | |
| 292 | set_union_tag, | |
| 286 | 293 | /// Given a slice value, return the length. |
| 287 | 294 | /// Result type is always usize. |
| 288 | 295 | /// Uses the `ty_op` field. |
| ... | ... | @@ -643,6 +650,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { |
| 643 | 650 | .atomic_store_seq_cst, |
| 644 | 651 | .memset, |
| 645 | 652 | .memcpy, |
| 653 | .set_union_tag, | |
| 646 | 654 | => return Type.initTag(.void), |
| 647 | 655 | |
| 648 | 656 | .ptrtoint, |
src/Liveness.zig+1| ... | ... | @@ -256,6 +256,7 @@ fn analyzeInst( |
| 256 | 256 | .atomic_store_monotonic, |
| 257 | 257 | .atomic_store_release, |
| 258 | 258 | .atomic_store_seq_cst, |
| 259 | .set_union_tag, | |
| 259 | 260 | => { |
| 260 | 261 | const o = inst_datas[inst].bin_op; |
| 261 | 262 | return trackOperands(a, new_set, inst, main_tomb, .{ o.lhs, o.rhs, .none }); |
src/Module.zig+105-2| ... | ... | @@ -859,6 +859,36 @@ pub const EnumSimple = struct { |
| 859 | 859 | } |
| 860 | 860 | }; |
| 861 | 861 | |
| 862 | /// Represents the data that an enum declaration provides, when there are no | |
| 863 | /// declarations. However an integer tag type is provided, and the enum tag values | |
| 864 | /// are explicitly provided. | |
| 865 | pub const EnumNumbered = struct { | |
| 866 | /// The Decl that corresponds to the enum itself. | |
| 867 | owner_decl: *Decl, | |
| 868 | /// An integer type which is used for the numerical value of the enum. | |
| 869 | /// Whether zig chooses this type or the user specifies it, it is stored here. | |
| 870 | tag_ty: Type, | |
| 871 | /// Set of field names in declaration order. | |
| 872 | fields: NameMap, | |
| 873 | /// Maps integer tag value to field index. | |
| 874 | /// Entries are in declaration order, same as `fields`. | |
| 875 | /// If this hash map is empty, it means the enum tags are auto-numbered. | |
| 876 | values: ValueMap, | |
| 877 | /// Offset from `owner_decl`, points to the enum decl AST node. | |
| 878 | node_offset: i32, | |
| 879 | ||
| 880 | pub const NameMap = EnumFull.NameMap; | |
| 881 | pub const ValueMap = EnumFull.ValueMap; | |
| 882 | ||
| 883 | pub fn srcLoc(self: EnumNumbered) SrcLoc { | |
| 884 | return .{ | |
| 885 | .file_scope = self.owner_decl.getFileScope(), | |
| 886 | .parent_decl_node = self.owner_decl.src_node, | |
| 887 | .lazy = .{ .node_offset = self.node_offset }, | |
| 888 | }; | |
| 889 | } | |
| 890 | }; | |
| 891 | ||
| 862 | 892 | /// Represents the data that an enum declaration provides, when there is |
| 863 | 893 | /// at least one tag value explicitly specified, or at least one declaration. |
| 864 | 894 | pub const EnumFull = struct { |
| ... | ... | @@ -868,16 +898,17 @@ pub const EnumFull = struct { |
| 868 | 898 | /// Whether zig chooses this type or the user specifies it, it is stored here. |
| 869 | 899 | tag_ty: Type, |
| 870 | 900 | /// Set of field names in declaration order. |
| 871 | fields: std.StringArrayHashMapUnmanaged(void), | |
| 901 | fields: NameMap, | |
| 872 | 902 | /// Maps integer tag value to field index. |
| 873 | 903 | /// Entries are in declaration order, same as `fields`. |
| 874 | 904 | /// If this hash map is empty, it means the enum tags are auto-numbered. |
| 875 | 905 | values: ValueMap, |
| 876 | /// Represents the declarations inside this struct. | |
| 906 | /// Represents the declarations inside this enum. | |
| 877 | 907 | namespace: Scope.Namespace, |
| 878 | 908 | /// Offset from `owner_decl`, points to the enum decl AST node. |
| 879 | 909 | node_offset: i32, |
| 880 | 910 | |
| 911 | pub const NameMap = std.StringArrayHashMapUnmanaged(void); | |
| 881 | 912 | pub const ValueMap = std.ArrayHashMapUnmanaged(Value, void, Value.ArrayHashContext, false); |
| 882 | 913 | |
| 883 | 914 | pub fn srcLoc(self: EnumFull) SrcLoc { |
| ... | ... | @@ -933,6 +964,44 @@ pub const Union = struct { |
| 933 | 964 | .lazy = .{ .node_offset = self.node_offset }, |
| 934 | 965 | }; |
| 935 | 966 | } |
| 967 | ||
| 968 | pub fn haveFieldTypes(u: Union) bool { | |
| 969 | return switch (u.status) { | |
| 970 | .none, | |
| 971 | .field_types_wip, | |
| 972 | => false, | |
| 973 | .have_field_types, | |
| 974 | .layout_wip, | |
| 975 | .have_layout, | |
| 976 | => true, | |
| 977 | }; | |
| 978 | } | |
| 979 | ||
| 980 | pub fn onlyTagHasCodegenBits(u: Union) bool { | |
| 981 | assert(u.haveFieldTypes()); | |
| 982 | for (u.fields.values()) |field| { | |
| 983 | if (field.ty.hasCodeGenBits()) return false; | |
| 984 | } | |
| 985 | return true; | |
| 986 | } | |
| 987 | ||
| 988 | pub fn mostAlignedField(u: Union, target: Target) u32 { | |
| 989 | assert(u.haveFieldTypes()); | |
| 990 | var most_alignment: u64 = 0; | |
| 991 | var most_index: usize = undefined; | |
| 992 | for (u.fields.values()) |field, i| { | |
| 993 | if (!field.ty.hasCodeGenBits()) continue; | |
| 994 | const field_align = if (field.abi_align.tag() == .abi_align_default) | |
| 995 | field.ty.abiAlignment(target) | |
| 996 | else | |
| 997 | field.abi_align.toUnsignedInt(); | |
| 998 | if (field_align > most_alignment) { | |
| 999 | most_alignment = field_align; | |
| 1000 | most_index = i; | |
| 1001 | } | |
| 1002 | } | |
| 1003 | return @intCast(u32, most_index); | |
| 1004 | } | |
| 936 | 1005 | }; |
| 937 | 1006 | |
| 938 | 1007 | /// Some Fn struct memory is owned by the Decl's TypedValue.Managed arena allocator. |
| ... | ... | @@ -1543,6 +1612,40 @@ pub const Scope = struct { |
| 1543 | 1612 | }); |
| 1544 | 1613 | } |
| 1545 | 1614 | |
| 1615 | pub fn addStructFieldPtr( | |
| 1616 | block: *Block, | |
| 1617 | struct_ptr: Air.Inst.Ref, | |
| 1618 | field_index: u32, | |
| 1619 | ptr_field_ty: Type, | |
| 1620 | ) !Air.Inst.Ref { | |
| 1621 | const ty = try block.sema.addType(ptr_field_ty); | |
| 1622 | const tag: Air.Inst.Tag = switch (field_index) { | |
| 1623 | 0 => .struct_field_ptr_index_0, | |
| 1624 | 1 => .struct_field_ptr_index_1, | |
| 1625 | 2 => .struct_field_ptr_index_2, | |
| 1626 | 3 => .struct_field_ptr_index_3, | |
| 1627 | else => { | |
| 1628 | return block.addInst(.{ | |
| 1629 | .tag = .struct_field_ptr, | |
| 1630 | .data = .{ .ty_pl = .{ | |
| 1631 | .ty = ty, | |
| 1632 | .payload = try block.sema.addExtra(Air.StructField{ | |
| 1633 | .struct_operand = struct_ptr, | |
| 1634 | .field_index = @intCast(u32, field_index), | |
| 1635 | }), | |
| 1636 | } }, | |
| 1637 | }); | |
| 1638 | }, | |
| 1639 | }; | |
| 1640 | return block.addInst(.{ | |
| 1641 | .tag = tag, | |
| 1642 | .data = .{ .ty_op = .{ | |
| 1643 | .ty = ty, | |
| 1644 | .operand = struct_ptr, | |
| 1645 | } }, | |
| 1646 | }); | |
| 1647 | } | |
| 1648 | ||
| 1546 | 1649 | pub fn addInst(block: *Block, inst: Air.Inst) error{OutOfMemory}!Air.Inst.Ref { |
| 1547 | 1650 | return Air.indexToRef(try block.addInstAsIndex(inst)); |
| 1548 | 1651 | } |
src/Sema.zig+263-57| ... | ... | @@ -1625,7 +1625,7 @@ fn zirAllocMut(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileEr |
| 1625 | 1625 | if (block.is_comptime) { |
| 1626 | 1626 | return sema.analyzeComptimeAlloc(block, var_type); |
| 1627 | 1627 | } |
| 1628 | try sema.validateVarType(block, ty_src, var_type); | |
| 1628 | try sema.validateVarType(block, ty_src, var_type, false); | |
| 1629 | 1629 | const ptr_type = try Type.ptr(sema.arena, .{ |
| 1630 | 1630 | .pointee_type = var_type, |
| 1631 | 1631 | .@"addrspace" = target_util.defaultAddressSpace(sema.mod.getTarget(), .local), |
| ... | ... | @@ -1711,7 +1711,7 @@ fn zirResolveInferredAlloc(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Inde |
| 1711 | 1711 | const peer_inst_list = inferred_alloc.data.stored_inst_list.items; |
| 1712 | 1712 | const final_elem_ty = try sema.resolvePeerTypes(block, ty_src, peer_inst_list, .none); |
| 1713 | 1713 | if (var_is_mut) { |
| 1714 | try sema.validateVarType(block, ty_src, final_elem_ty); | |
| 1714 | try sema.validateVarType(block, ty_src, final_elem_ty, false); | |
| 1715 | 1715 | } |
| 1716 | 1716 | // Change it to a normal alloc. |
| 1717 | 1717 | const final_ptr_ty = try Type.ptr(sema.arena, .{ |
| ... | ... | @@ -1730,19 +1730,82 @@ fn zirValidateStructInitPtr(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Ind |
| 1730 | 1730 | const tracy = trace(@src()); |
| 1731 | 1731 | defer tracy.end(); |
| 1732 | 1732 | |
| 1733 | const gpa = sema.gpa; | |
| 1734 | const mod = sema.mod; | |
| 1735 | 1733 | const validate_inst = sema.code.instructions.items(.data)[inst].pl_node; |
| 1736 | const struct_init_src = validate_inst.src(); | |
| 1734 | const init_src = validate_inst.src(); | |
| 1737 | 1735 | const validate_extra = sema.code.extraData(Zir.Inst.Block, validate_inst.payload_index); |
| 1738 | 1736 | const instrs = sema.code.extra[validate_extra.end..][0..validate_extra.data.body_len]; |
| 1737 | const field_ptr_data = sema.code.instructions.items(.data)[instrs[0]].pl_node; | |
| 1738 | const field_ptr_extra = sema.code.extraData(Zir.Inst.Field, field_ptr_data.payload_index).data; | |
| 1739 | const object_ptr = sema.resolveInst(field_ptr_extra.lhs); | |
| 1740 | const agg_ty = sema.typeOf(object_ptr).elemType(); | |
| 1741 | switch (agg_ty.zigTypeTag()) { | |
| 1742 | .Struct => return sema.validateStructInitPtr( | |
| 1743 | block, | |
| 1744 | agg_ty.castTag(.@"struct").?.data, | |
| 1745 | init_src, | |
| 1746 | instrs, | |
| 1747 | ), | |
| 1748 | .Union => return sema.validateUnionInitPtr( | |
| 1749 | block, | |
| 1750 | agg_ty.cast(Type.Payload.Union).?.data, | |
| 1751 | init_src, | |
| 1752 | instrs, | |
| 1753 | object_ptr, | |
| 1754 | ), | |
| 1755 | else => unreachable, | |
| 1756 | } | |
| 1757 | } | |
| 1739 | 1758 | |
| 1740 | const struct_obj: *Module.Struct = s: { | |
| 1741 | const field_ptr_data = sema.code.instructions.items(.data)[instrs[0]].pl_node; | |
| 1742 | const field_ptr_extra = sema.code.extraData(Zir.Inst.Field, field_ptr_data.payload_index).data; | |
| 1743 | const object_ptr = sema.resolveInst(field_ptr_extra.lhs); | |
| 1744 | break :s sema.typeOf(object_ptr).elemType().castTag(.@"struct").?.data; | |
| 1745 | }; | |
| 1759 | fn validateUnionInitPtr( | |
| 1760 | sema: *Sema, | |
| 1761 | block: *Scope.Block, | |
| 1762 | union_obj: *Module.Union, | |
| 1763 | init_src: LazySrcLoc, | |
| 1764 | instrs: []const Zir.Inst.Index, | |
| 1765 | union_ptr: Air.Inst.Ref, | |
| 1766 | ) CompileError!void { | |
| 1767 | const mod = sema.mod; | |
| 1768 | ||
| 1769 | if (instrs.len != 1) { | |
| 1770 | // TODO add note for other field | |
| 1771 | // TODO add note for union declared here | |
| 1772 | return mod.fail(&block.base, init_src, "only one union field can be active at once", .{}); | |
| 1773 | } | |
| 1774 | ||
| 1775 | const field_ptr = instrs[0]; | |
| 1776 | const field_ptr_data = sema.code.instructions.items(.data)[field_ptr].pl_node; | |
| 1777 | const field_src: LazySrcLoc = .{ .node_offset_back2tok = field_ptr_data.src_node }; | |
| 1778 | const field_ptr_extra = sema.code.extraData(Zir.Inst.Field, field_ptr_data.payload_index).data; | |
| 1779 | const field_name = sema.code.nullTerminatedString(field_ptr_extra.field_name_start); | |
| 1780 | const field_index_big = union_obj.fields.getIndex(field_name) orelse | |
| 1781 | return sema.failWithBadUnionFieldAccess(block, union_obj, field_src, field_name); | |
| 1782 | const field_index = @intCast(u32, field_index_big); | |
| 1783 | ||
| 1784 | // TODO here we need to go back and see if we need to convert the union | |
| 1785 | // to a comptime-known value. This will involve editing the AIR code we have | |
| 1786 | // generated so far - in particular deleting some runtime pointer bitcast | |
| 1787 | // instructions which are not actually needed if the initialization expression | |
| 1788 | // ends up being comptime-known. | |
| 1789 | ||
| 1790 | // Otherwise, we set the new union tag now. | |
| 1791 | const new_tag = try sema.addConstant( | |
| 1792 | union_obj.tag_ty, | |
| 1793 | try Value.Tag.enum_field_index.create(sema.arena, field_index), | |
| 1794 | ); | |
| 1795 | ||
| 1796 | try sema.requireRuntimeBlock(block, init_src); | |
| 1797 | _ = try block.addBinOp(.set_union_tag, union_ptr, new_tag); | |
| 1798 | } | |
| 1799 | ||
| 1800 | fn validateStructInitPtr( | |
| 1801 | sema: *Sema, | |
| 1802 | block: *Scope.Block, | |
| 1803 | struct_obj: *Module.Struct, | |
| 1804 | init_src: LazySrcLoc, | |
| 1805 | instrs: []const Zir.Inst.Index, | |
| 1806 | ) CompileError!void { | |
| 1807 | const gpa = sema.gpa; | |
| 1808 | const mod = sema.mod; | |
| 1746 | 1809 | |
| 1747 | 1810 | // Maps field index to field_ptr index of where it was already initialized. |
| 1748 | 1811 | const found_fields = try gpa.alloc(Zir.Inst.Index, struct_obj.fields.count()); |
| ... | ... | @@ -1781,9 +1844,9 @@ fn zirValidateStructInitPtr(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Ind |
| 1781 | 1844 | const template = "missing struct field: {s}"; |
| 1782 | 1845 | const args = .{field_name}; |
| 1783 | 1846 | if (root_msg) |msg| { |
| 1784 | try mod.errNote(&block.base, struct_init_src, msg, template, args); | |
| 1847 | try mod.errNote(&block.base, init_src, msg, template, args); | |
| 1785 | 1848 | } else { |
| 1786 | root_msg = try mod.errMsg(&block.base, struct_init_src, template, args); | |
| 1849 | root_msg = try mod.errMsg(&block.base, init_src, template, args); | |
| 1787 | 1850 | } |
| 1788 | 1851 | } |
| 1789 | 1852 | if (root_msg) |msg| { |
| ... | ... | @@ -8037,7 +8100,7 @@ fn checkAtomicOperandType( |
| 8037 | 8100 | const max_atomic_bits = target_util.largestAtomicBits(target); |
| 8038 | 8101 | const int_ty = switch (ty.zigTypeTag()) { |
| 8039 | 8102 | .Int => ty, |
| 8040 | .Enum => ty.enumTagType(&buffer), | |
| 8103 | .Enum => ty.intTagType(&buffer), | |
| 8041 | 8104 | .Float => { |
| 8042 | 8105 | const bit_count = ty.floatBits(target); |
| 8043 | 8106 | if (bit_count > max_atomic_bits) { |
| ... | ... | @@ -8621,11 +8684,7 @@ fn zirVarExtended( |
| 8621 | 8684 | return sema.failWithNeededComptime(block, init_src); |
| 8622 | 8685 | } else Value.initTag(.unreachable_value); |
| 8623 | 8686 | |
| 8624 | if (!var_ty.isValidVarType(small.is_extern)) { | |
| 8625 | return sema.mod.fail(&block.base, mut_src, "variable of type '{}' must be const", .{ | |
| 8626 | var_ty, | |
| 8627 | }); | |
| 8628 | } | |
| 8687 | try sema.validateVarType(block, mut_src, var_ty, small.is_extern); | |
| 8629 | 8688 | |
| 8630 | 8689 | if (lib_name != null) { |
| 8631 | 8690 | // Look at the sema code for functions which has this logic, it just needs to |
| ... | ... | @@ -8810,9 +8869,54 @@ fn requireIntegerType(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, ty: Typ |
| 8810 | 8869 | } |
| 8811 | 8870 | } |
| 8812 | 8871 | |
| 8813 | fn validateVarType(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, ty: Type) !void { | |
| 8814 | if (!ty.isValidVarType(false)) { | |
| 8815 | return sema.mod.fail(&block.base, src, "variable of type '{}' must be const or comptime", .{ty}); | |
| 8872 | /// Emit a compile error if type cannot be used for a runtime variable. | |
| 8873 | fn validateVarType( | |
| 8874 | sema: *Sema, | |
| 8875 | block: *Scope.Block, | |
| 8876 | src: LazySrcLoc, | |
| 8877 | var_ty: Type, | |
| 8878 | is_extern: bool, | |
| 8879 | ) CompileError!void { | |
| 8880 | var ty = var_ty; | |
| 8881 | const ok: bool = while (true) switch (ty.zigTypeTag()) { | |
| 8882 | .Bool, | |
| 8883 | .Int, | |
| 8884 | .Float, | |
| 8885 | .ErrorSet, | |
| 8886 | .Enum, | |
| 8887 | .Frame, | |
| 8888 | .AnyFrame, | |
| 8889 | => break true, | |
| 8890 | ||
| 8891 | .BoundFn, | |
| 8892 | .ComptimeFloat, | |
| 8893 | .ComptimeInt, | |
| 8894 | .EnumLiteral, | |
| 8895 | .NoReturn, | |
| 8896 | .Type, | |
| 8897 | .Void, | |
| 8898 | .Undefined, | |
| 8899 | .Null, | |
| 8900 | => break false, | |
| 8901 | ||
| 8902 | .Opaque => break is_extern, | |
| 8903 | ||
| 8904 | .Optional => { | |
| 8905 | var buf: Type.Payload.ElemType = undefined; | |
| 8906 | const child_ty = ty.optionalChild(&buf); | |
| 8907 | return validateVarType(sema, block, src, child_ty, is_extern); | |
| 8908 | }, | |
| 8909 | .Pointer, .Array, .Vector => ty = ty.elemType(), | |
| 8910 | .ErrorUnion => ty = ty.errorUnionPayload(), | |
| 8911 | ||
| 8912 | .Fn => @panic("TODO fn validateVarType"), | |
| 8913 | .Struct, .Union => { | |
| 8914 | const resolved_ty = try sema.resolveTypeFields(block, src, ty); | |
| 8915 | break !resolved_ty.requiresComptime(); | |
| 8916 | }, | |
| 8917 | } else unreachable; // TODO should not need else unreachable | |
| 8918 | if (!ok) { | |
| 8919 | return sema.mod.fail(&block.base, src, "variable of type '{}' must be const or comptime", .{var_ty}); | |
| 8816 | 8920 | } |
| 8817 | 8921 | } |
| 8818 | 8922 | |
| ... | ... | @@ -9393,8 +9497,9 @@ fn structFieldPtr( |
| 9393 | 9497 | const struct_ty = try sema.resolveTypeFields(block, src, unresolved_struct_ty); |
| 9394 | 9498 | const struct_obj = struct_ty.castTag(.@"struct").?.data; |
| 9395 | 9499 | |
| 9396 | const field_index = struct_obj.fields.getIndex(field_name) orelse | |
| 9500 | const field_index_big = struct_obj.fields.getIndex(field_name) orelse | |
| 9397 | 9501 | return sema.failWithBadFieldAccess(block, struct_obj, field_name_src, field_name); |
| 9502 | const field_index = @intCast(u32, field_index_big); | |
| 9398 | 9503 | const field = struct_obj.fields.values()[field_index]; |
| 9399 | 9504 | const ptr_field_ty = try Type.ptr(arena, .{ |
| 9400 | 9505 | .pointee_type = field.ty, |
| ... | ... | @@ -9413,31 +9518,7 @@ fn structFieldPtr( |
| 9413 | 9518 | } |
| 9414 | 9519 | |
| 9415 | 9520 | try sema.requireRuntimeBlock(block, src); |
| 9416 | const tag: Air.Inst.Tag = switch (field_index) { | |
| 9417 | 0 => .struct_field_ptr_index_0, | |
| 9418 | 1 => .struct_field_ptr_index_1, | |
| 9419 | 2 => .struct_field_ptr_index_2, | |
| 9420 | 3 => .struct_field_ptr_index_3, | |
| 9421 | else => { | |
| 9422 | return block.addInst(.{ | |
| 9423 | .tag = .struct_field_ptr, | |
| 9424 | .data = .{ .ty_pl = .{ | |
| 9425 | .ty = try sema.addType(ptr_field_ty), | |
| 9426 | .payload = try sema.addExtra(Air.StructField{ | |
| 9427 | .struct_operand = struct_ptr, | |
| 9428 | .field_index = @intCast(u32, field_index), | |
| 9429 | }), | |
| 9430 | } }, | |
| 9431 | }); | |
| 9432 | }, | |
| 9433 | }; | |
| 9434 | return block.addInst(.{ | |
| 9435 | .tag = tag, | |
| 9436 | .data = .{ .ty_op = .{ | |
| 9437 | .ty = try sema.addType(ptr_field_ty), | |
| 9438 | .operand = struct_ptr, | |
| 9439 | } }, | |
| 9440 | }); | |
| 9521 | return block.addStructFieldPtr(struct_ptr, field_index, ptr_field_ty); | |
| 9441 | 9522 | } |
| 9442 | 9523 | |
| 9443 | 9524 | fn structFieldVal( |
| ... | ... | @@ -9487,7 +9568,6 @@ fn unionFieldPtr( |
| 9487 | 9568 | field_name_src: LazySrcLoc, |
| 9488 | 9569 | unresolved_union_ty: Type, |
| 9489 | 9570 | ) CompileError!Air.Inst.Ref { |
| 9490 | const mod = sema.mod; | |
| 9491 | 9571 | const arena = sema.arena; |
| 9492 | 9572 | assert(unresolved_union_ty.zigTypeTag() == .Union); |
| 9493 | 9573 | |
| ... | ... | @@ -9495,8 +9575,9 @@ fn unionFieldPtr( |
| 9495 | 9575 | const union_ty = try sema.resolveTypeFields(block, src, unresolved_union_ty); |
| 9496 | 9576 | const union_obj = union_ty.cast(Type.Payload.Union).?.data; |
| 9497 | 9577 | |
| 9498 | const field_index = union_obj.fields.getIndex(field_name) orelse | |
| 9578 | const field_index_big = union_obj.fields.getIndex(field_name) orelse | |
| 9499 | 9579 | return sema.failWithBadUnionFieldAccess(block, union_obj, field_name_src, field_name); |
| 9580 | const field_index = @intCast(u32, field_index_big); | |
| 9500 | 9581 | |
| 9501 | 9582 | const field = union_obj.fields.values()[field_index]; |
| 9502 | 9583 | const ptr_field_ty = try Type.ptr(arena, .{ |
| ... | ... | @@ -9517,7 +9598,7 @@ fn unionFieldPtr( |
| 9517 | 9598 | } |
| 9518 | 9599 | |
| 9519 | 9600 | try sema.requireRuntimeBlock(block, src); |
| 9520 | return mod.fail(&block.base, src, "TODO implement runtime union field access", .{}); | |
| 9601 | return block.addStructFieldPtr(union_ptr, field_index, ptr_field_ty); | |
| 9521 | 9602 | } |
| 9522 | 9603 | |
| 9523 | 9604 | fn unionFieldVal( |
| ... | ... | @@ -11160,6 +11241,28 @@ fn analyzeUnionFields( |
| 11160 | 11241 | if (body.len != 0) { |
| 11161 | 11242 | _ = try sema.analyzeBody(block, body); |
| 11162 | 11243 | } |
| 11244 | var int_tag_ty: Type = undefined; | |
| 11245 | var enum_field_names: ?*Module.EnumNumbered.NameMap = null; | |
| 11246 | var enum_value_map: ?*Module.EnumNumbered.ValueMap = null; | |
| 11247 | if (tag_type_ref != .none) { | |
| 11248 | const provided_ty = try sema.resolveType(block, src, tag_type_ref); | |
| 11249 | if (small.auto_enum_tag) { | |
| 11250 | // The provided type is an integer type and we must construct the enum tag type here. | |
| 11251 | int_tag_ty = provided_ty; | |
| 11252 | union_obj.tag_ty = try sema.generateUnionTagTypeNumbered(block, fields_len, provided_ty); | |
| 11253 | enum_field_names = &union_obj.tag_ty.castTag(.enum_numbered).?.data.fields; | |
| 11254 | enum_value_map = &union_obj.tag_ty.castTag(.enum_numbered).?.data.values; | |
| 11255 | } else { | |
| 11256 | // The provided type is the enum tag type. | |
| 11257 | union_obj.tag_ty = provided_ty; | |
| 11258 | } | |
| 11259 | } else { | |
| 11260 | // If auto_enum_tag is false, this is an untagged union. However, for semantic analysis | |
| 11261 | // purposes, we still auto-generate an enum tag type the same way. That the union is | |
| 11262 | // untagged is represented by the Type tag (union vs union_tagged). | |
| 11263 | union_obj.tag_ty = try sema.generateUnionTagTypeSimple(block, fields_len); | |
| 11264 | enum_field_names = &union_obj.tag_ty.castTag(.enum_simple).?.data.fields; | |
| 11265 | } | |
| 11163 | 11266 | |
| 11164 | 11267 | const bits_per_field = 4; |
| 11165 | 11268 | const fields_per_u32 = 32 / bits_per_field; |
| ... | ... | @@ -11198,12 +11301,25 @@ fn analyzeUnionFields( |
| 11198 | 11301 | break :blk align_ref; |
| 11199 | 11302 | } else .none; |
| 11200 | 11303 | |
| 11201 | if (has_tag) { | |
| 11304 | const tag_ref: Zir.Inst.Ref = if (has_tag) blk: { | |
| 11305 | const tag_ref = @intToEnum(Zir.Inst.Ref, zir.extra[extra_index]); | |
| 11202 | 11306 | extra_index += 1; |
| 11307 | break :blk tag_ref; | |
| 11308 | } else .none; | |
| 11309 | ||
| 11310 | if (enum_value_map) |map| { | |
| 11311 | const tag_src = src; // TODO better source location | |
| 11312 | const coerced = try sema.coerce(block, int_tag_ty, tag_ref, tag_src); | |
| 11313 | const val = try sema.resolveConstValue(block, tag_src, coerced); | |
| 11314 | map.putAssumeCapacityContext(val, {}, .{ .ty = int_tag_ty }); | |
| 11203 | 11315 | } |
| 11204 | 11316 | |
| 11205 | 11317 | // This string needs to outlive the ZIR code. |
| 11206 | 11318 | const field_name = try decl_arena.allocator.dupe(u8, field_name_zir); |
| 11319 | if (enum_field_names) |set| { | |
| 11320 | set.putAssumeCapacity(field_name, {}); | |
| 11321 | } | |
| 11322 | ||
| 11207 | 11323 | const field_ty: Type = if (field_type_ref == .none) |
| 11208 | 11324 | Type.initTag(.void) |
| 11209 | 11325 | else |
| ... | ... | @@ -11225,11 +11341,84 @@ fn analyzeUnionFields( |
| 11225 | 11341 | // But only resolve the source location if we need to emit a compile error. |
| 11226 | 11342 | const abi_align_val = (try sema.resolveInstConst(block, src, align_ref)).val; |
| 11227 | 11343 | gop.value_ptr.abi_align = try abi_align_val.copy(&decl_arena.allocator); |
| 11344 | } else { | |
| 11345 | gop.value_ptr.abi_align = Value.initTag(.abi_align_default); | |
| 11228 | 11346 | } |
| 11229 | 11347 | } |
| 11348 | } | |
| 11349 | ||
| 11350 | fn generateUnionTagTypeNumbered( | |
| 11351 | sema: *Sema, | |
| 11352 | block: *Scope.Block, | |
| 11353 | fields_len: u32, | |
| 11354 | int_ty: Type, | |
| 11355 | ) !Type { | |
| 11356 | const mod = sema.mod; | |
| 11357 | ||
| 11358 | var new_decl_arena = std.heap.ArenaAllocator.init(sema.gpa); | |
| 11359 | errdefer new_decl_arena.deinit(); | |
| 11230 | 11360 | |
| 11231 | // TODO resolve the union tag_type_ref | |
| 11232 | _ = tag_type_ref; | |
| 11361 | const enum_obj = try new_decl_arena.allocator.create(Module.EnumNumbered); | |
| 11362 | const enum_ty_payload = try new_decl_arena.allocator.create(Type.Payload.EnumNumbered); | |
| 11363 | enum_ty_payload.* = .{ | |
| 11364 | .base = .{ .tag = .enum_numbered }, | |
| 11365 | .data = enum_obj, | |
| 11366 | }; | |
| 11367 | const enum_ty = Type.initPayload(&enum_ty_payload.base); | |
| 11368 | const enum_val = try Value.Tag.ty.create(&new_decl_arena.allocator, enum_ty); | |
| 11369 | // TODO better type name | |
| 11370 | const new_decl = try mod.createAnonymousDecl(&block.base, .{ | |
| 11371 | .ty = Type.initTag(.type), | |
| 11372 | .val = enum_val, | |
| 11373 | }); | |
| 11374 | new_decl.owns_tv = true; | |
| 11375 | errdefer sema.mod.deleteAnonDecl(&block.base, new_decl); | |
| 11376 | ||
| 11377 | enum_obj.* = .{ | |
| 11378 | .owner_decl = new_decl, | |
| 11379 | .tag_ty = int_ty, | |
| 11380 | .fields = .{}, | |
| 11381 | .values = .{}, | |
| 11382 | .node_offset = 0, | |
| 11383 | }; | |
| 11384 | // Here we pre-allocate the maps using the decl arena. | |
| 11385 | try enum_obj.fields.ensureTotalCapacity(&new_decl_arena.allocator, fields_len); | |
| 11386 | try enum_obj.values.ensureTotalCapacityContext(&new_decl_arena.allocator, fields_len, .{ .ty = int_ty }); | |
| 11387 | try new_decl.finalizeNewArena(&new_decl_arena); | |
| 11388 | return enum_ty; | |
| 11389 | } | |
| 11390 | ||
| 11391 | fn generateUnionTagTypeSimple(sema: *Sema, block: *Scope.Block, fields_len: u32) !Type { | |
| 11392 | const mod = sema.mod; | |
| 11393 | ||
| 11394 | var new_decl_arena = std.heap.ArenaAllocator.init(sema.gpa); | |
| 11395 | errdefer new_decl_arena.deinit(); | |
| 11396 | ||
| 11397 | const enum_obj = try new_decl_arena.allocator.create(Module.EnumSimple); | |
| 11398 | const enum_ty_payload = try new_decl_arena.allocator.create(Type.Payload.EnumSimple); | |
| 11399 | enum_ty_payload.* = .{ | |
| 11400 | .base = .{ .tag = .enum_simple }, | |
| 11401 | .data = enum_obj, | |
| 11402 | }; | |
| 11403 | const enum_ty = Type.initPayload(&enum_ty_payload.base); | |
| 11404 | const enum_val = try Value.Tag.ty.create(&new_decl_arena.allocator, enum_ty); | |
| 11405 | // TODO better type name | |
| 11406 | const new_decl = try mod.createAnonymousDecl(&block.base, .{ | |
| 11407 | .ty = Type.initTag(.type), | |
| 11408 | .val = enum_val, | |
| 11409 | }); | |
| 11410 | new_decl.owns_tv = true; | |
| 11411 | errdefer sema.mod.deleteAnonDecl(&block.base, new_decl); | |
| 11412 | ||
| 11413 | enum_obj.* = .{ | |
| 11414 | .owner_decl = new_decl, | |
| 11415 | .fields = .{}, | |
| 11416 | .node_offset = 0, | |
| 11417 | }; | |
| 11418 | // Here we pre-allocate the maps using the decl arena. | |
| 11419 | try enum_obj.fields.ensureTotalCapacity(&new_decl_arena.allocator, fields_len); | |
| 11420 | try new_decl.finalizeNewArena(&new_decl_arena); | |
| 11421 | return enum_ty; | |
| 11233 | 11422 | } |
| 11234 | 11423 | |
| 11235 | 11424 | fn getBuiltin( |
| ... | ... | @@ -11367,11 +11556,28 @@ fn typeHasOnePossibleValue( |
| 11367 | 11556 | } |
| 11368 | 11557 | return Value.initTag(.empty_struct_value); |
| 11369 | 11558 | }, |
| 11559 | .enum_numbered => { | |
| 11560 | const resolved_ty = try sema.resolveTypeFields(block, src, ty); | |
| 11561 | const enum_obj = resolved_ty.castTag(.enum_numbered).?.data; | |
| 11562 | if (enum_obj.fields.count() == 1) { | |
| 11563 | if (enum_obj.values.count() == 0) { | |
| 11564 | return Value.initTag(.zero); // auto-numbered | |
| 11565 | } else { | |
| 11566 | return enum_obj.values.keys()[0]; | |
| 11567 | } | |
| 11568 | } else { | |
| 11569 | return null; | |
| 11570 | } | |
| 11571 | }, | |
| 11370 | 11572 | .enum_full => { |
| 11371 | 11573 | const resolved_ty = try sema.resolveTypeFields(block, src, ty); |
| 11372 | const enum_full = resolved_ty.castTag(.enum_full).?.data; | |
| 11373 | if (enum_full.fields.count() == 1) { | |
| 11374 | return enum_full.values.keys()[0]; | |
| 11574 | const enum_obj = resolved_ty.castTag(.enum_full).?.data; | |
| 11575 | if (enum_obj.fields.count() == 1) { | |
| 11576 | if (enum_obj.values.count() == 0) { | |
| 11577 | return Value.initTag(.zero); // auto-numbered | |
| 11578 | } else { | |
| 11579 | return enum_obj.values.keys()[0]; | |
| 11580 | } | |
| 11375 | 11581 | } else { |
| 11376 | 11582 | return null; |
| 11377 | 11583 | } |
src/codegen.zig+9| ... | ... | @@ -889,6 +889,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 889 | 889 | .atomic_load => try self.airAtomicLoad(inst), |
| 890 | 890 | .memcpy => try self.airMemcpy(inst), |
| 891 | 891 | .memset => try self.airMemset(inst), |
| 892 | .set_union_tag => try self.airSetUnionTag(inst), | |
| 892 | 893 | |
| 893 | 894 | .atomic_store_unordered => try self.airAtomicStore(inst, .Unordered), |
| 894 | 895 | .atomic_store_monotonic => try self.airAtomicStore(inst, .Monotonic), |
| ... | ... | @@ -1543,6 +1544,14 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1543 | 1544 | return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); |
| 1544 | 1545 | } |
| 1545 | 1546 | |
| 1547 | fn airSetUnionTag(self: *Self, inst: Air.Inst.Index) !void { | |
| 1548 | const bin_op = self.air.instructions.items(.data)[inst].bin_op; | |
| 1549 | const result: MCValue = switch (arch) { | |
| 1550 | else => return self.fail("TODO implement airSetUnionTag for {}", .{self.target.cpu.arch}), | |
| 1551 | }; | |
| 1552 | return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); | |
| 1553 | } | |
| 1554 | ||
| 1546 | 1555 | fn reuseOperand(self: *Self, inst: Air.Inst.Index, operand: Air.Inst.Ref, op_index: Liveness.OperandInt, mcv: MCValue) bool { |
| 1547 | 1556 | if (!self.liveness.operandDies(inst, op_index)) |
| 1548 | 1557 | return false; |
src/codegen/c.zig+16| ... | ... | @@ -955,6 +955,7 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO |
| 955 | 955 | .atomic_load => try airAtomicLoad(f, inst), |
| 956 | 956 | .memset => try airMemset(f, inst), |
| 957 | 957 | .memcpy => try airMemcpy(f, inst), |
| 958 | .set_union_tag => try airSetUnionTag(f, inst), | |
| 958 | 959 | |
| 959 | 960 | .int_to_float, |
| 960 | 961 | .float_to_int, |
| ... | ... | @@ -2080,6 +2081,21 @@ fn airMemcpy(f: *Function, inst: Air.Inst.Index) !CValue { |
| 2080 | 2081 | return CValue.none; |
| 2081 | 2082 | } |
| 2082 | 2083 | |
| 2084 | fn airSetUnionTag(f: *Function, inst: Air.Inst.Index) !CValue { | |
| 2085 | const bin_op = f.air.instructions.items(.data)[inst].bin_op; | |
| 2086 | const union_ptr = try f.resolveInst(bin_op.lhs); | |
| 2087 | const new_tag = try f.resolveInst(bin_op.rhs); | |
| 2088 | const writer = f.object.writer(); | |
| 2089 | ||
| 2090 | try writer.writeAll("*"); | |
| 2091 | try f.writeCValue(writer, union_ptr); | |
| 2092 | try writer.writeAll(" = "); | |
| 2093 | try f.writeCValue(writer, new_tag); | |
| 2094 | try writer.writeAll(";\n"); | |
| 2095 | ||
| 2096 | return CValue.none; | |
| 2097 | } | |
| 2098 | ||
| 2083 | 2099 | fn toMemoryOrder(order: std.builtin.AtomicOrder) [:0]const u8 { |
| 2084 | 2100 | return switch (order) { |
| 2085 | 2101 | .Unordered => "memory_order_relaxed", |
src/codegen/llvm.zig+74-6| ... | ... | @@ -735,7 +735,7 @@ pub const DeclGen = struct { |
| 735 | 735 | }, |
| 736 | 736 | .Enum => { |
| 737 | 737 | var buffer: Type.Payload.Bits = undefined; |
| 738 | const int_ty = t.enumTagType(&buffer); | |
| 738 | const int_ty = t.intTagType(&buffer); | |
| 739 | 739 | const bit_count = int_ty.intInfo(self.module.getTarget()).bits; |
| 740 | 740 | return self.context.intType(bit_count); |
| 741 | 741 | }, |
| ... | ... | @@ -812,6 +812,29 @@ pub const DeclGen = struct { |
| 812 | 812 | .False, |
| 813 | 813 | ); |
| 814 | 814 | }, |
| 815 | .Union => { | |
| 816 | const union_obj = t.castTag(.@"union").?.data; | |
| 817 | assert(union_obj.haveFieldTypes()); | |
| 818 | ||
| 819 | const enum_tag_ty = union_obj.tag_ty; | |
| 820 | const enum_tag_llvm_ty = try self.llvmType(enum_tag_ty); | |
| 821 | ||
| 822 | if (union_obj.onlyTagHasCodegenBits()) { | |
| 823 | return enum_tag_llvm_ty; | |
| 824 | } | |
| 825 | ||
| 826 | const target = self.module.getTarget(); | |
| 827 | const most_aligned_field_index = union_obj.mostAlignedField(target); | |
| 828 | const most_aligned_field = union_obj.fields.values()[most_aligned_field_index]; | |
| 829 | // TODO handle when the most aligned field is different than the | |
| 830 | // biggest sized field. | |
| 831 | ||
| 832 | const llvm_fields = [_]*const llvm.Type{ | |
| 833 | try self.llvmType(most_aligned_field.ty), | |
| 834 | enum_tag_llvm_ty, | |
| 835 | }; | |
| 836 | return self.context.structType(&llvm_fields, llvm_fields.len, .False); | |
| 837 | }, | |
| 815 | 838 | .Fn => { |
| 816 | 839 | const ret_ty = try self.llvmType(t.fnReturnType()); |
| 817 | 840 | const params_len = t.fnParamLen(); |
| ... | ... | @@ -840,7 +863,6 @@ pub const DeclGen = struct { |
| 840 | 863 | |
| 841 | 864 | .BoundFn => @panic("TODO remove BoundFn from the language"), |
| 842 | 865 | |
| 843 | .Union, | |
| 844 | 866 | .Opaque, |
| 845 | 867 | .Frame, |
| 846 | 868 | .AnyFrame, |
| ... | ... | @@ -1131,7 +1153,7 @@ pub const DeclGen = struct { |
| 1131 | 1153 | var buffer: Type.Payload.Bits = undefined; |
| 1132 | 1154 | const int_ty = switch (ty.zigTypeTag()) { |
| 1133 | 1155 | .Int => ty, |
| 1134 | .Enum => ty.enumTagType(&buffer), | |
| 1156 | .Enum => ty.intTagType(&buffer), | |
| 1135 | 1157 | .Float => { |
| 1136 | 1158 | if (!is_rmw_xchg) return null; |
| 1137 | 1159 | return dg.context.intType(@intCast(c_uint, ty.abiSize(target) * 8)); |
| ... | ... | @@ -1281,6 +1303,7 @@ pub const FuncGen = struct { |
| 1281 | 1303 | .atomic_load => try self.airAtomicLoad(inst), |
| 1282 | 1304 | .memset => try self.airMemset(inst), |
| 1283 | 1305 | .memcpy => try self.airMemcpy(inst), |
| 1306 | .set_union_tag => try self.airSetUnionTag(inst), | |
| 1284 | 1307 | |
| 1285 | 1308 | .atomic_store_unordered => try self.airAtomicStore(inst, .Unordered), |
| 1286 | 1309 | .atomic_store_monotonic => try self.airAtomicStore(inst, .Monotonic), |
| ... | ... | @@ -1381,7 +1404,7 @@ pub const FuncGen = struct { |
| 1381 | 1404 | const int_ty = switch (operand_ty.zigTypeTag()) { |
| 1382 | 1405 | .Enum => blk: { |
| 1383 | 1406 | var buffer: Type.Payload.Bits = undefined; |
| 1384 | const int_ty = operand_ty.enumTagType(&buffer); | |
| 1407 | const int_ty = operand_ty.intTagType(&buffer); | |
| 1385 | 1408 | break :blk int_ty; |
| 1386 | 1409 | }, |
| 1387 | 1410 | .Int, .Bool, .Pointer, .ErrorSet => operand_ty, |
| ... | ... | @@ -1660,8 +1683,9 @@ pub const FuncGen = struct { |
| 1660 | 1683 | const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; |
| 1661 | 1684 | const struct_field = self.air.extraData(Air.StructField, ty_pl.payload).data; |
| 1662 | 1685 | const struct_ptr = try self.resolveInst(struct_field.struct_operand); |
| 1686 | const struct_ptr_ty = self.air.typeOf(struct_field.struct_operand); | |
| 1663 | 1687 | const field_index = @intCast(c_uint, struct_field.field_index); |
| 1664 | return self.builder.buildStructGEP(struct_ptr, field_index, ""); | |
| 1688 | return self.fieldPtr(inst, struct_ptr, struct_ptr_ty, field_index); | |
| 1665 | 1689 | } |
| 1666 | 1690 | |
| 1667 | 1691 | fn airStructFieldPtrIndex(self: *FuncGen, inst: Air.Inst.Index, field_index: c_uint) !?*const llvm.Value { |
| ... | ... | @@ -1670,7 +1694,8 @@ pub const FuncGen = struct { |
| 1670 | 1694 | |
| 1671 | 1695 | const ty_op = self.air.instructions.items(.data)[inst].ty_op; |
| 1672 | 1696 | const struct_ptr = try self.resolveInst(ty_op.operand); |
| 1673 | return self.builder.buildStructGEP(struct_ptr, field_index, ""); | |
| 1697 | const struct_ptr_ty = self.air.typeOf(ty_op.operand); | |
| 1698 | return self.fieldPtr(inst, struct_ptr, struct_ptr_ty, field_index); | |
| 1674 | 1699 | } |
| 1675 | 1700 | |
| 1676 | 1701 | fn airStructFieldVal(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { |
| ... | ... | @@ -2521,6 +2546,49 @@ pub const FuncGen = struct { |
| 2521 | 2546 | return null; |
| 2522 | 2547 | } |
| 2523 | 2548 | |
| 2549 | fn airSetUnionTag(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { | |
| 2550 | const bin_op = self.air.instructions.items(.data)[inst].bin_op; | |
| 2551 | const union_ptr = try self.resolveInst(bin_op.lhs); | |
| 2552 | // TODO handle when onlyTagHasCodegenBits() == true | |
| 2553 | const new_tag = try self.resolveInst(bin_op.rhs); | |
| 2554 | const tag_field_ptr = self.builder.buildStructGEP(union_ptr, 1, ""); | |
| 2555 | ||
| 2556 | _ = self.builder.buildStore(new_tag, tag_field_ptr); | |
| 2557 | return null; | |
| 2558 | } | |
| 2559 | ||
| 2560 | fn fieldPtr( | |
| 2561 | self: *FuncGen, | |
| 2562 | inst: Air.Inst.Index, | |
| 2563 | struct_ptr: *const llvm.Value, | |
| 2564 | struct_ptr_ty: Type, | |
| 2565 | field_index: c_uint, | |
| 2566 | ) !?*const llvm.Value { | |
| 2567 | const struct_ty = struct_ptr_ty.childType(); | |
| 2568 | switch (struct_ty.zigTypeTag()) { | |
| 2569 | .Struct => return self.builder.buildStructGEP(struct_ptr, field_index, ""), | |
| 2570 | .Union => return self.unionFieldPtr(inst, struct_ptr, struct_ty, field_index), | |
| 2571 | else => unreachable, | |
| 2572 | } | |
| 2573 | } | |
| 2574 | ||
| 2575 | fn unionFieldPtr( | |
| 2576 | self: *FuncGen, | |
| 2577 | inst: Air.Inst.Index, | |
| 2578 | union_ptr: *const llvm.Value, | |
| 2579 | union_ty: Type, | |
| 2580 | field_index: c_uint, | |
| 2581 | ) !?*const llvm.Value { | |
| 2582 | const union_obj = union_ty.cast(Type.Payload.Union).?.data; | |
| 2583 | const field = &union_obj.fields.values()[field_index]; | |
| 2584 | const result_llvm_ty = try self.dg.llvmType(self.air.typeOfIndex(inst)); | |
| 2585 | if (!field.ty.hasCodeGenBits()) { | |
| 2586 | return null; | |
| 2587 | } | |
| 2588 | const union_field_ptr = self.builder.buildStructGEP(union_ptr, 0, ""); | |
| 2589 | return self.builder.buildBitCast(union_field_ptr, result_llvm_ty, ""); | |
| 2590 | } | |
| 2591 | ||
| 2524 | 2592 | fn getIntrinsic(self: *FuncGen, name: []const u8) *const llvm.Value { |
| 2525 | 2593 | const id = llvm.lookupIntrinsicID(name.ptr, name.len); |
| 2526 | 2594 | assert(id != 0); |
src/print_air.zig+1| ... | ... | @@ -130,6 +130,7 @@ const Writer = struct { |
| 130 | 130 | .ptr_ptr_elem_val, |
| 131 | 131 | .shl, |
| 132 | 132 | .shr, |
| 133 | .set_union_tag, | |
| 133 | 134 | => try w.writeBinOp(s, inst), |
| 134 | 135 | |
| 135 | 136 | .is_null, |
src/type.zig+84-86| ... | ... | @@ -124,6 +124,7 @@ pub const Type = extern union { |
| 124 | 124 | .enum_full, |
| 125 | 125 | .enum_nonexhaustive, |
| 126 | 126 | .enum_simple, |
| 127 | .enum_numbered, | |
| 127 | 128 | .atomic_order, |
| 128 | 129 | .atomic_rmw_op, |
| 129 | 130 | .calling_convention, |
| ... | ... | @@ -874,6 +875,7 @@ pub const Type = extern union { |
| 874 | 875 | .@"struct" => return self.copyPayloadShallow(allocator, Payload.Struct), |
| 875 | 876 | .@"union", .union_tagged => return self.copyPayloadShallow(allocator, Payload.Union), |
| 876 | 877 | .enum_simple => return self.copyPayloadShallow(allocator, Payload.EnumSimple), |
| 878 | .enum_numbered => return self.copyPayloadShallow(allocator, Payload.EnumNumbered), | |
| 877 | 879 | .enum_full, .enum_nonexhaustive => return self.copyPayloadShallow(allocator, Payload.EnumFull), |
| 878 | 880 | .@"opaque" => return self.copyPayloadShallow(allocator, Payload.Opaque), |
| 879 | 881 | } |
| ... | ... | @@ -958,6 +960,10 @@ pub const Type = extern union { |
| 958 | 960 | const enum_simple = ty.castTag(.enum_simple).?.data; |
| 959 | 961 | return enum_simple.owner_decl.renderFullyQualifiedName(writer); |
| 960 | 962 | }, |
| 963 | .enum_numbered => { | |
| 964 | const enum_numbered = ty.castTag(.enum_numbered).?.data; | |
| 965 | return enum_numbered.owner_decl.renderFullyQualifiedName(writer); | |
| 966 | }, | |
| 961 | 967 | .@"opaque" => { |
| 962 | 968 | // TODO use declaration name |
| 963 | 969 | return writer.writeAll("opaque {}"); |
| ... | ... | @@ -1268,6 +1274,7 @@ pub const Type = extern union { |
| 1268 | 1274 | .@"union", |
| 1269 | 1275 | .union_tagged, |
| 1270 | 1276 | .enum_simple, |
| 1277 | .enum_numbered, | |
| 1271 | 1278 | .enum_full, |
| 1272 | 1279 | .enum_nonexhaustive, |
| 1273 | 1280 | => false, // TODO some of these should be `true` depending on their child types |
| ... | ... | @@ -1421,7 +1428,7 @@ pub const Type = extern union { |
| 1421 | 1428 | const enum_simple = self.castTag(.enum_simple).?.data; |
| 1422 | 1429 | return enum_simple.fields.count() >= 2; |
| 1423 | 1430 | }, |
| 1424 | .enum_nonexhaustive => { | |
| 1431 | .enum_numbered, .enum_nonexhaustive => { | |
| 1425 | 1432 | var buffer: Payload.Bits = undefined; |
| 1426 | 1433 | const int_tag_ty = self.intTagType(&buffer); |
| 1427 | 1434 | return int_tag_ty.hasCodeGenBits(); |
| ... | ... | @@ -1682,7 +1689,7 @@ pub const Type = extern union { |
| 1682 | 1689 | assert(biggest != 0); |
| 1683 | 1690 | return biggest; |
| 1684 | 1691 | }, |
| 1685 | .enum_full, .enum_nonexhaustive, .enum_simple => { | |
| 1692 | .enum_full, .enum_nonexhaustive, .enum_simple, .enum_numbered => { | |
| 1686 | 1693 | var buffer: Payload.Bits = undefined; |
| 1687 | 1694 | const int_tag_ty = self.intTagType(&buffer); |
| 1688 | 1695 | return int_tag_ty.abiAlignment(target); |
| ... | ... | @@ -1781,7 +1788,7 @@ pub const Type = extern union { |
| 1781 | 1788 | } |
| 1782 | 1789 | return size; |
| 1783 | 1790 | }, |
| 1784 | .enum_simple, .enum_full, .enum_nonexhaustive => { | |
| 1791 | .enum_simple, .enum_full, .enum_nonexhaustive, .enum_numbered => { | |
| 1785 | 1792 | var buffer: Payload.Bits = undefined; |
| 1786 | 1793 | const int_tag_ty = self.intTagType(&buffer); |
| 1787 | 1794 | return int_tag_ty.abiSize(target); |
| ... | ... | @@ -1948,7 +1955,7 @@ pub const Type = extern union { |
| 1948 | 1955 | .@"struct" => { |
| 1949 | 1956 | @panic("TODO bitSize struct"); |
| 1950 | 1957 | }, |
| 1951 | .enum_simple, .enum_full, .enum_nonexhaustive => { | |
| 1958 | .enum_simple, .enum_full, .enum_nonexhaustive, .enum_numbered => { | |
| 1952 | 1959 | var buffer: Payload.Bits = undefined; |
| 1953 | 1960 | const int_tag_ty = self.intTagType(&buffer); |
| 1954 | 1961 | return int_tag_ty.bitSize(target); |
| ... | ... | @@ -2094,23 +2101,6 @@ pub const Type = extern union { |
| 2094 | 2101 | }; |
| 2095 | 2102 | } |
| 2096 | 2103 | |
| 2097 | /// Asserts the type is an enum. | |
| 2098 | pub fn intTagType(self: Type, buffer: *Payload.Bits) Type { | |
| 2099 | switch (self.tag()) { | |
| 2100 | .enum_full, .enum_nonexhaustive => return self.cast(Payload.EnumFull).?.data.tag_ty, | |
| 2101 | .enum_simple => { | |
| 2102 | const enum_simple = self.castTag(.enum_simple).?.data; | |
| 2103 | const bits = std.math.log2_int_ceil(usize, enum_simple.fields.count()); | |
| 2104 | buffer.* = .{ | |
| 2105 | .base = .{ .tag = .int_unsigned }, | |
| 2106 | .data = bits, | |
| 2107 | }; | |
| 2108 | return Type.initPayload(&buffer.base); | |
| 2109 | }, | |
| 2110 | else => unreachable, | |
| 2111 | } | |
| 2112 | } | |
| 2113 | ||
| 2114 | 2104 | pub fn isSinglePointer(self: Type) bool { |
| 2115 | 2105 | return switch (self.tag()) { |
| 2116 | 2106 | .single_const_pointer, |
| ... | ... | @@ -2363,48 +2353,6 @@ pub const Type = extern union { |
| 2363 | 2353 | } |
| 2364 | 2354 | } |
| 2365 | 2355 | |
| 2366 | /// Returns if type can be used for a runtime variable | |
| 2367 | pub fn isValidVarType(self: Type, is_extern: bool) bool { | |
| 2368 | var ty = self; | |
| 2369 | while (true) switch (ty.zigTypeTag()) { | |
| 2370 | .Bool, | |
| 2371 | .Int, | |
| 2372 | .Float, | |
| 2373 | .ErrorSet, | |
| 2374 | .Enum, | |
| 2375 | .Frame, | |
| 2376 | .AnyFrame, | |
| 2377 | => return true, | |
| 2378 | ||
| 2379 | .Opaque => return is_extern, | |
| 2380 | .BoundFn, | |
| 2381 | .ComptimeFloat, | |
| 2382 | .ComptimeInt, | |
| 2383 | .EnumLiteral, | |
| 2384 | .NoReturn, | |
| 2385 | .Type, | |
| 2386 | .Void, | |
| 2387 | .Undefined, | |
| 2388 | .Null, | |
| 2389 | => return false, | |
| 2390 | ||
| 2391 | .Optional => { | |
| 2392 | var buf: Payload.ElemType = undefined; | |
| 2393 | return ty.optionalChild(&buf).isValidVarType(is_extern); | |
| 2394 | }, | |
| 2395 | .Pointer, .Array, .Vector => ty = ty.elemType(), | |
| 2396 | .ErrorUnion => ty = ty.errorUnionPayload(), | |
| 2397 | ||
| 2398 | .Fn => @panic("TODO fn isValidVarType"), | |
| 2399 | .Struct => { | |
| 2400 | // TODO this is not always correct; introduce lazy value mechanism | |
| 2401 | // and here we need to force a resolve of "type requires comptime". | |
| 2402 | return true; | |
| 2403 | }, | |
| 2404 | .Union => @panic("TODO union isValidVarType"), | |
| 2405 | }; | |
| 2406 | } | |
| 2407 | ||
| 2408 | 2356 | pub fn childType(ty: Type) Type { |
| 2409 | 2357 | return switch (ty.tag()) { |
| 2410 | 2358 | .vector => ty.castTag(.vector).?.data.elem_type, |
| ... | ... | @@ -2530,6 +2478,15 @@ pub const Type = extern union { |
| 2530 | 2478 | } |
| 2531 | 2479 | } |
| 2532 | 2480 | |
| 2481 | /// Returns the tag type of a union, if the type is a union and it has a tag type. | |
| 2482 | /// Otherwise, returns `null`. | |
| 2483 | pub fn unionTagType(ty: Type) ?Type { | |
| 2484 | return switch (ty.tag()) { | |
| 2485 | .union_tagged => ty.castTag(.union_tagged).?.data.tag_ty, | |
| 2486 | else => null, | |
| 2487 | }; | |
| 2488 | } | |
| 2489 | ||
| 2533 | 2490 | /// Asserts that the type is an error union. |
| 2534 | 2491 | pub fn errorUnionPayload(self: Type) Type { |
| 2535 | 2492 | return switch (self.tag()) { |
| ... | ... | @@ -3000,6 +2957,7 @@ pub const Type = extern union { |
| 3000 | 2957 | } |
| 3001 | 2958 | }, |
| 3002 | 2959 | .enum_nonexhaustive => ty = ty.castTag(.enum_nonexhaustive).?.data.tag_ty, |
| 2960 | .enum_numbered => ty = ty.castTag(.enum_numbered).?.data.tag_ty, | |
| 3003 | 2961 | .@"union" => { |
| 3004 | 2962 | return null; // TODO |
| 3005 | 2963 | }, |
| ... | ... | @@ -3114,31 +3072,21 @@ pub const Type = extern union { |
| 3114 | 3072 | } |
| 3115 | 3073 | } |
| 3116 | 3074 | |
| 3117 | /// Returns the integer tag type of the enum. | |
| 3118 | pub fn enumTagType(ty: Type, buffer: *Payload.Bits) Type { | |
| 3119 | switch (ty.tag()) { | |
| 3120 | .enum_full, .enum_nonexhaustive => { | |
| 3121 | const enum_full = ty.cast(Payload.EnumFull).?.data; | |
| 3122 | return enum_full.tag_ty; | |
| 3123 | }, | |
| 3075 | /// Asserts the type is an enum or a union. | |
| 3076 | /// TODO support unions | |
| 3077 | pub fn intTagType(self: Type, buffer: *Payload.Bits) Type { | |
| 3078 | switch (self.tag()) { | |
| 3079 | .enum_full, .enum_nonexhaustive => return self.cast(Payload.EnumFull).?.data.tag_ty, | |
| 3080 | .enum_numbered => return self.castTag(.enum_numbered).?.data.tag_ty, | |
| 3124 | 3081 | .enum_simple => { |
| 3125 | const enum_simple = ty.castTag(.enum_simple).?.data; | |
| 3082 | const enum_simple = self.castTag(.enum_simple).?.data; | |
| 3083 | const bits = std.math.log2_int_ceil(usize, enum_simple.fields.count()); | |
| 3126 | 3084 | buffer.* = .{ |
| 3127 | 3085 | .base = .{ .tag = .int_unsigned }, |
| 3128 | .data = std.math.log2_int_ceil(usize, enum_simple.fields.count()), | |
| 3086 | .data = bits, | |
| 3129 | 3087 | }; |
| 3130 | 3088 | return Type.initPayload(&buffer.base); |
| 3131 | 3089 | }, |
| 3132 | .atomic_order, | |
| 3133 | .atomic_rmw_op, | |
| 3134 | .calling_convention, | |
| 3135 | .float_mode, | |
| 3136 | .reduce_op, | |
| 3137 | .call_options, | |
| 3138 | .export_options, | |
| 3139 | .extern_options, | |
| 3140 | => @panic("TODO resolve std.builtin types"), | |
| 3141 | ||
| 3142 | 3090 | else => unreachable, |
| 3143 | 3091 | } |
| 3144 | 3092 | } |
| ... | ... | @@ -3156,10 +3104,8 @@ pub const Type = extern union { |
| 3156 | 3104 | const enum_full = ty.cast(Payload.EnumFull).?.data; |
| 3157 | 3105 | return enum_full.fields.count(); |
| 3158 | 3106 | }, |
| 3159 | .enum_simple => { | |
| 3160 | const enum_simple = ty.castTag(.enum_simple).?.data; | |
| 3161 | return enum_simple.fields.count(); | |
| 3162 | }, | |
| 3107 | .enum_simple => return ty.castTag(.enum_simple).?.data.fields.count(), | |
| 3108 | .enum_numbered => return ty.castTag(.enum_numbered).?.data.fields.count(), | |
| 3163 | 3109 | .atomic_order, |
| 3164 | 3110 | .atomic_rmw_op, |
| 3165 | 3111 | .calling_convention, |
| ... | ... | @@ -3185,6 +3131,10 @@ pub const Type = extern union { |
| 3185 | 3131 | const enum_simple = ty.castTag(.enum_simple).?.data; |
| 3186 | 3132 | return enum_simple.fields.keys()[field_index]; |
| 3187 | 3133 | }, |
| 3134 | .enum_numbered => { | |
| 3135 | const enum_numbered = ty.castTag(.enum_numbered).?.data; | |
| 3136 | return enum_numbered.fields.keys()[field_index]; | |
| 3137 | }, | |
| 3188 | 3138 | .atomic_order, |
| 3189 | 3139 | .atomic_rmw_op, |
| 3190 | 3140 | .calling_convention, |
| ... | ... | @@ -3209,6 +3159,10 @@ pub const Type = extern union { |
| 3209 | 3159 | const enum_simple = ty.castTag(.enum_simple).?.data; |
| 3210 | 3160 | return enum_simple.fields.getIndex(field_name); |
| 3211 | 3161 | }, |
| 3162 | .enum_numbered => { | |
| 3163 | const enum_numbered = ty.castTag(.enum_numbered).?.data; | |
| 3164 | return enum_numbered.fields.getIndex(field_name); | |
| 3165 | }, | |
| 3212 | 3166 | .atomic_order, |
| 3213 | 3167 | .atomic_rmw_op, |
| 3214 | 3168 | .calling_convention, |
| ... | ... | @@ -3252,6 +3206,15 @@ pub const Type = extern union { |
| 3252 | 3206 | return enum_full.values.getIndexContext(enum_tag, .{ .ty = tag_ty }); |
| 3253 | 3207 | } |
| 3254 | 3208 | }, |
| 3209 | .enum_numbered => { | |
| 3210 | const enum_obj = ty.castTag(.enum_numbered).?.data; | |
| 3211 | const tag_ty = enum_obj.tag_ty; | |
| 3212 | if (enum_obj.values.count() == 0) { | |
| 3213 | return S.fieldWithRange(tag_ty, enum_tag, enum_obj.fields.count()); | |
| 3214 | } else { | |
| 3215 | return enum_obj.values.getIndexContext(enum_tag, .{ .ty = tag_ty }); | |
| 3216 | } | |
| 3217 | }, | |
| 3255 | 3218 | .enum_simple => { |
| 3256 | 3219 | const enum_simple = ty.castTag(.enum_simple).?.data; |
| 3257 | 3220 | const fields_len = enum_simple.fields.count(); |
| ... | ... | @@ -3303,6 +3266,7 @@ pub const Type = extern union { |
| 3303 | 3266 | const enum_full = ty.cast(Payload.EnumFull).?.data; |
| 3304 | 3267 | return enum_full.srcLoc(); |
| 3305 | 3268 | }, |
| 3269 | .enum_numbered => return ty.castTag(.enum_numbered).?.data.srcLoc(), | |
| 3306 | 3270 | .enum_simple => { |
| 3307 | 3271 | const enum_simple = ty.castTag(.enum_simple).?.data; |
| 3308 | 3272 | return enum_simple.srcLoc(); |
| ... | ... | @@ -3340,6 +3304,7 @@ pub const Type = extern union { |
| 3340 | 3304 | const enum_full = ty.cast(Payload.EnumFull).?.data; |
| 3341 | 3305 | return enum_full.owner_decl; |
| 3342 | 3306 | }, |
| 3307 | .enum_numbered => return ty.castTag(.enum_numbered).?.data.owner_decl, | |
| 3343 | 3308 | .enum_simple => { |
| 3344 | 3309 | const enum_simple = ty.castTag(.enum_simple).?.data; |
| 3345 | 3310 | return enum_simple.owner_decl; |
| ... | ... | @@ -3397,6 +3362,15 @@ pub const Type = extern union { |
| 3397 | 3362 | return enum_full.values.containsContext(int, .{ .ty = tag_ty }); |
| 3398 | 3363 | } |
| 3399 | 3364 | }, |
| 3365 | .enum_numbered => { | |
| 3366 | const enum_obj = ty.castTag(.enum_numbered).?.data; | |
| 3367 | const tag_ty = enum_obj.tag_ty; | |
| 3368 | if (enum_obj.values.count() == 0) { | |
| 3369 | return S.intInRange(tag_ty, int, enum_obj.fields.count()); | |
| 3370 | } else { | |
| 3371 | return enum_obj.values.containsContext(int, .{ .ty = tag_ty }); | |
| 3372 | } | |
| 3373 | }, | |
| 3400 | 3374 | .enum_simple => { |
| 3401 | 3375 | const enum_simple = ty.castTag(.enum_simple).?.data; |
| 3402 | 3376 | const fields_len = enum_simple.fields.count(); |
| ... | ... | @@ -3534,6 +3508,7 @@ pub const Type = extern union { |
| 3534 | 3508 | @"union", |
| 3535 | 3509 | union_tagged, |
| 3536 | 3510 | enum_simple, |
| 3511 | enum_numbered, | |
| 3537 | 3512 | enum_full, |
| 3538 | 3513 | enum_nonexhaustive, |
| 3539 | 3514 | |
| ... | ... | @@ -3642,6 +3617,7 @@ pub const Type = extern union { |
| 3642 | 3617 | .@"union", .union_tagged => Payload.Union, |
| 3643 | 3618 | .enum_full, .enum_nonexhaustive => Payload.EnumFull, |
| 3644 | 3619 | .enum_simple => Payload.EnumSimple, |
| 3620 | .enum_numbered => Payload.EnumNumbered, | |
| 3645 | 3621 | .empty_struct => Payload.ContainerScope, |
| 3646 | 3622 | }; |
| 3647 | 3623 | } |
| ... | ... | @@ -3818,6 +3794,11 @@ pub const Type = extern union { |
| 3818 | 3794 | base: Payload = .{ .tag = .enum_simple }, |
| 3819 | 3795 | data: *Module.EnumSimple, |
| 3820 | 3796 | }; |
| 3797 | ||
| 3798 | pub const EnumNumbered = struct { | |
| 3799 | base: Payload = .{ .tag = .enum_numbered }, | |
| 3800 | data: *Module.EnumNumbered, | |
| 3801 | }; | |
| 3821 | 3802 | }; |
| 3822 | 3803 | |
| 3823 | 3804 | pub fn ptr(arena: *Allocator, d: Payload.Pointer.Data) !Type { |
| ... | ... | @@ -3850,6 +3831,23 @@ pub const Type = extern union { |
| 3850 | 3831 | }; |
| 3851 | 3832 | return Type.initPayload(&type_payload.base); |
| 3852 | 3833 | } |
| 3834 | ||
| 3835 | pub fn smallestUnsignedInt(arena: *Allocator, max: u64) !Type { | |
| 3836 | const bits = bits: { | |
| 3837 | if (max == 0) break :bits 0; | |
| 3838 | const base = std.math.log2(max); | |
| 3839 | const upper = (@as(u64, 1) << base) - 1; | |
| 3840 | break :bits base + @boolToInt(upper < max); | |
| 3841 | }; | |
| 3842 | return switch (bits) { | |
| 3843 | 1 => initTag(.u1), | |
| 3844 | 8 => initTag(.u8), | |
| 3845 | 16 => initTag(.u16), | |
| 3846 | 32 => initTag(.u32), | |
| 3847 | 64 => initTag(.u64), | |
| 3848 | else => return Tag.int_unsigned.create(arena, bits), | |
| 3849 | }; | |
| 3850 | } | |
| 3853 | 3851 | }; |
| 3854 | 3852 | |
| 3855 | 3853 | pub const CType = enum { |
test/behavior/union.zig+12| ... | ... | @@ -2,3 +2,15 @@ const std = @import("std"); |
| 2 | 2 | const expect = std.testing.expect; |
| 3 | 3 | const expectEqual = std.testing.expectEqual; |
| 4 | 4 | const Tag = std.meta.Tag; |
| 5 | ||
| 6 | const Foo = union { | |
| 7 | float: f64, | |
| 8 | int: i32, | |
| 9 | }; | |
| 10 | ||
| 11 | test "basic unions" { | |
| 12 | var foo = Foo{ .int = 1 }; | |
| 13 | try expect(foo.int == 1); | |
| 14 | foo = Foo{ .float = 12.34 }; | |
| 15 | try expect(foo.float == 12.34); | |
| 16 | } |
test/behavior/union_stage1.zig-7| ... | ... | @@ -39,13 +39,6 @@ const Foo = union { |
| 39 | 39 | int: i32, |
| 40 | 40 | }; |
| 41 | 41 | |
| 42 | test "basic unions" { | |
| 43 | var foo = Foo{ .int = 1 }; | |
| 44 | try expect(foo.int == 1); | |
| 45 | foo = Foo{ .float = 12.34 }; | |
| 46 | try expect(foo.float == 12.34); | |
| 47 | } | |
| 48 | ||
| 49 | 42 | test "comptime union field access" { |
| 50 | 43 | comptime { |
| 51 | 44 | var foo = Foo{ .int = 0 }; |