authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-10-13 21:20:38-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-10-13 21:20:38-07:00
log0d4a94f32fc71f81d54db038f013854b8e9f0ac4
tree163668781596539d8d6f95e2de506aad544be355
parentb0f80ef0d5517b5ce8ba60dfae6dd986346f8d4c

stage2: improve handling of 0-bit types and arrays

* Make `alloc` AIR instructions call `resolveTypeLayout`. * `Sema.zirResolveInferredAlloc` now calls `requireRuntimeBlock` in the case that it operates on a non-comptime instruction. * `Type.abiSize` and `Type.abiAlignment` now return 0 for `void` * Sema: implement `resolveTypeFields` for unions. * LLVM Backend: support `ptr_elem_ptr` when the element type is 0-bit. * Type: improve `abiAlignment` implementation for structs to properly handle fields with non-default alignment. * Value: implement hashing array, vector, and structs.

6 files changed, 173 insertions(+), 95 deletions(-)

src/Sema.zig+74-46
......@@ -2035,6 +2035,7 @@ fn zirAllocExtended(
20352035 .@"addrspace" = target_util.defaultAddressSpace(sema.mod.getTarget(), .local),
20362036 });
20372037 try sema.requireRuntimeBlock(block, src);
2038 try sema.resolveTypeLayout(block, src, var_ty);
20382039 return block.addTy(.alloc, ptr_type);
20392040}
20402041
......@@ -2044,8 +2045,8 @@ fn zirAllocComptime(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErr
20442045
20452046 const inst_data = sema.code.instructions.items(.data)[inst].un_node;
20462047 const ty_src: LazySrcLoc = .{ .node_offset_var_decl_ty = inst_data.src_node };
2047 const var_type = try sema.resolveType(block, ty_src, inst_data.operand);
2048 return sema.analyzeComptimeAlloc(block, var_type);
2048 const var_ty = try sema.resolveType(block, ty_src, inst_data.operand);
2049 return sema.analyzeComptimeAlloc(block, var_ty);
20492050}
20502051
20512052fn zirAllocInferredComptime(sema: *Sema, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
......@@ -2065,15 +2066,16 @@ fn zirAlloc(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I
20652066 const inst_data = sema.code.instructions.items(.data)[inst].un_node;
20662067 const ty_src: LazySrcLoc = .{ .node_offset_var_decl_ty = inst_data.src_node };
20672068 const var_decl_src = inst_data.src();
2068 const var_type = try sema.resolveType(block, ty_src, inst_data.operand);
2069 const var_ty = try sema.resolveType(block, ty_src, inst_data.operand);
20692070 if (block.is_comptime) {
2070 return sema.analyzeComptimeAlloc(block, var_type);
2071 return sema.analyzeComptimeAlloc(block, var_ty);
20712072 }
20722073 const ptr_type = try Type.ptr(sema.arena, .{
2073 .pointee_type = var_type,
2074 .pointee_type = var_ty,
20742075 .@"addrspace" = target_util.defaultAddressSpace(sema.mod.getTarget(), .local),
20752076 });
20762077 try sema.requireRuntimeBlock(block, var_decl_src);
2078 try sema.resolveTypeLayout(block, ty_src, var_ty);
20772079 return block.addTy(.alloc, ptr_type);
20782080}
20792081
......@@ -2084,16 +2086,17 @@ fn zirAllocMut(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
20842086 const inst_data = sema.code.instructions.items(.data)[inst].un_node;
20852087 const var_decl_src = inst_data.src();
20862088 const ty_src: LazySrcLoc = .{ .node_offset_var_decl_ty = inst_data.src_node };
2087 const var_type = try sema.resolveType(block, ty_src, inst_data.operand);
2089 const var_ty = try sema.resolveType(block, ty_src, inst_data.operand);
20882090 if (block.is_comptime) {
2089 return sema.analyzeComptimeAlloc(block, var_type);
2091 return sema.analyzeComptimeAlloc(block, var_ty);
20902092 }
2091 try sema.validateVarType(block, ty_src, var_type, false);
2093 try sema.validateVarType(block, ty_src, var_ty, false);
20922094 const ptr_type = try Type.ptr(sema.arena, .{
2093 .pointee_type = var_type,
2095 .pointee_type = var_ty,
20942096 .@"addrspace" = target_util.defaultAddressSpace(sema.mod.getTarget(), .local),
20952097 });
20962098 try sema.requireRuntimeBlock(block, var_decl_src);
2099 try sema.resolveTypeLayout(block, ty_src, var_ty);
20972100 return block.addTy(.alloc, ptr_type);
20982101}
20992102
......@@ -2135,6 +2138,7 @@ fn zirResolveInferredAlloc(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Com
21352138 defer tracy.end();
21362139
21372140 const inst_data = sema.code.instructions.items(.data)[inst].un_node;
2141 const src = inst_data.src();
21382142 const ty_src: LazySrcLoc = .{ .node_offset_var_decl_ty = inst_data.src_node };
21392143 const ptr = sema.resolveInst(inst_data.operand);
21402144 const ptr_inst = Air.refToIndex(ptr).?;
......@@ -2146,46 +2150,53 @@ fn zirResolveInferredAlloc(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Com
21462150 .inferred_alloc_mut => true,
21472151 else => unreachable,
21482152 };
2153 const target = sema.mod.getTarget();
21492154
2150 if (ptr_val.castTag(.inferred_alloc_comptime)) |iac| {
2151 const decl = iac.data;
2152 try sema.mod.declareDeclDependency(sema.owner_decl, decl);
2155 switch (ptr_val.tag()) {
2156 .inferred_alloc_comptime => {
2157 const iac = ptr_val.castTag(.inferred_alloc_comptime).?;
2158 const decl = iac.data;
2159 try sema.mod.declareDeclDependency(sema.owner_decl, decl);
2160
2161 const final_elem_ty = try decl.ty.copy(sema.arena);
2162 const final_ptr_ty = try Type.ptr(sema.arena, .{
2163 .pointee_type = final_elem_ty,
2164 .@"addrspace" = target_util.defaultAddressSpace(target, .local),
2165 });
2166 const final_ptr_ty_inst = try sema.addType(final_ptr_ty);
2167 sema.air_instructions.items(.data)[ptr_inst].ty_pl.ty = final_ptr_ty_inst;
21532168
2154 const final_elem_ty = try decl.ty.copy(sema.arena);
2155 const final_ptr_ty = try Type.ptr(sema.arena, .{
2156 .pointee_type = final_elem_ty,
2157 .@"addrspace" = target_util.defaultAddressSpace(sema.mod.getTarget(), .local),
2158 });
2159 const final_ptr_ty_inst = try sema.addType(final_ptr_ty);
2160 sema.air_instructions.items(.data)[ptr_inst].ty_pl.ty = final_ptr_ty_inst;
2169 if (var_is_mut) {
2170 sema.air_values.items[value_index] = try Value.Tag.decl_ref_mut.create(sema.arena, .{
2171 .decl = decl,
2172 .runtime_index = block.runtime_index,
2173 });
2174 } else {
2175 sema.air_values.items[value_index] = try Value.Tag.decl_ref.create(sema.arena, decl);
2176 }
2177 },
2178 .inferred_alloc => {
2179 const inferred_alloc = ptr_val.castTag(.inferred_alloc).?;
2180 const peer_inst_list = inferred_alloc.data.stored_inst_list.items;
2181 const final_elem_ty = try sema.resolvePeerTypes(block, ty_src, peer_inst_list, .none);
21612182
2162 if (var_is_mut) {
2163 sema.air_values.items[value_index] = try Value.Tag.decl_ref_mut.create(sema.arena, .{
2164 .decl = decl,
2165 .runtime_index = block.runtime_index,
2166 });
2167 } else {
2168 sema.air_values.items[value_index] = try Value.Tag.decl_ref.create(sema.arena, decl);
2169 }
2170 return;
2171 }
2183 try sema.requireRuntimeBlock(block, src);
2184 try sema.resolveTypeLayout(block, ty_src, final_elem_ty);
21722185
2173 if (ptr_val.castTag(.inferred_alloc)) |inferred_alloc| {
2174 const peer_inst_list = inferred_alloc.data.stored_inst_list.items;
2175 const final_elem_ty = try sema.resolvePeerTypes(block, ty_src, peer_inst_list, .none);
2176 if (var_is_mut) {
2177 try sema.validateVarType(block, ty_src, final_elem_ty, false);
2178 }
2179 // Change it to a normal alloc.
2180 const final_ptr_ty = try Type.ptr(sema.arena, .{
2181 .pointee_type = final_elem_ty,
2182 .@"addrspace" = target_util.defaultAddressSpace(sema.mod.getTarget(), .local),
2183 });
2184 sema.air_instructions.set(ptr_inst, .{
2185 .tag = .alloc,
2186 .data = .{ .ty = final_ptr_ty },
2187 });
2188 return;
2186 if (var_is_mut) {
2187 try sema.validateVarType(block, ty_src, final_elem_ty, false);
2188 }
2189 // Change it to a normal alloc.
2190 const final_ptr_ty = try Type.ptr(sema.arena, .{
2191 .pointee_type = final_elem_ty,
2192 .@"addrspace" = target_util.defaultAddressSpace(target, .local),
2193 });
2194 sema.air_instructions.set(ptr_inst, .{
2195 .tag = .alloc,
2196 .data = .{ .ty = final_ptr_ty },
2197 });
2198 },
2199 else => unreachable,
21892200 }
21902201}
21912202
......@@ -8844,6 +8855,7 @@ fn zirArrayInit(
88448855 };
88458856
88468857 try sema.requireRuntimeBlock(block, runtime_src);
8858 try sema.resolveTypeLayout(block, src, elem_ty);
88478859
88488860 const alloc_ty = try Type.ptr(sema.arena, .{
88498861 .pointee_type = array_ty,
......@@ -10194,6 +10206,7 @@ fn validateVarType(
1019410206 .Enum,
1019510207 .Frame,
1019610208 .AnyFrame,
10209 .Void,
1019710210 => return,
1019810211
1019910212 .BoundFn,
......@@ -10202,7 +10215,6 @@ fn validateVarType(
1020210215 .EnumLiteral,
1020310216 .NoReturn,
1020410217 .Type,
10205 .Void,
1020610218 .Undefined,
1020710219 .Null,
1020810220 => break,
......@@ -12585,6 +12597,22 @@ pub fn resolveTypeLayout(
1258512597 }
1258612598 struct_obj.status = .have_layout;
1258712599 },
12600 .Union => {
12601 const resolved_ty = try sema.resolveTypeFields(block, src, ty);
12602 const union_obj = resolved_ty.cast(Type.Payload.Union).?.data;
12603 switch (union_obj.status) {
12604 .none, .have_field_types => {},
12605 .field_types_wip, .layout_wip => {
12606 return sema.fail(block, src, "union {} depends on itself", .{ty});
12607 },
12608 .have_layout => return,
12609 }
12610 union_obj.status = .layout_wip;
12611 for (union_obj.fields.values()) |field| {
12612 try sema.resolveTypeLayout(block, src, field.ty);
12613 }
12614 union_obj.status = .have_layout;
12615 },
1258812616 else => {},
1258912617 }
1259012618}
src/codegen/llvm.zig+5-3
......@@ -1863,14 +1863,16 @@ pub const FuncGen = struct {
18631863 }
18641864
18651865 fn airPtrElemPtr(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
1866 if (self.liveness.isUnused(inst))
1867 return null;
1866 if (self.liveness.isUnused(inst)) return null;
18681867
18691868 const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
18701869 const bin_op = self.air.extraData(Air.Bin, ty_pl.payload).data;
1870 const lhs_ty = self.air.typeOf(bin_op.lhs);
1871 if (!lhs_ty.hasCodeGenBits()) return null;
1872
18711873 const base_ptr = try self.resolveInst(bin_op.lhs);
18721874 const rhs = try self.resolveInst(bin_op.rhs);
1873 if (self.air.typeOf(bin_op.lhs).isSinglePointer()) {
1875 if (lhs_ty.isSinglePointer()) {
18741876 // If this is a single-item pointer to an array, we need another index in the GEP.
18751877 const indices: [2]*const llvm.Value = .{ self.context.intType(32).constNull(), rhs };
18761878 return self.builder.buildInBoundsGEP(base_ptr, &indices, indices.len, "");
src/type.zig+39-27
......@@ -1579,7 +1579,7 @@ pub const Type = extern union {
15791579 };
15801580 }
15811581
1582 /// Asserts that hasCodeGenBits() is true.
1582 /// Returns 0 for 0-bit types.
15831583 pub fn abiAlignment(self: Type, target: Target) u32 {
15841584 return switch (self.tag()) {
15851585 .u1,
......@@ -1667,6 +1667,7 @@ pub const Type = extern union {
16671667
16681668 .int_signed, .int_unsigned => {
16691669 const bits: u16 = self.cast(Payload.Bits).?.data;
1670 if (bits == 0) return 0;
16701671 if (bits <= 8) return 1;
16711672 if (bits <= 16) return 2;
16721673 if (bits <= 32) return 4;
......@@ -1699,23 +1700,27 @@ pub const Type = extern union {
16991700 },
17001701
17011702 .@"struct" => {
1702 // TODO take into account field alignment
1703 // also make this possible to fail, and lazy
1704 // I think we need to move all the functions from type.zig which can
1705 // fail into Sema.
1706 // Probably will need to introduce multi-stage struct resolution just
1707 // like we have in stage1.
1708 const struct_obj = self.castTag(.@"struct").?.data;
1709 var biggest: u32 = 0;
1710 for (struct_obj.fields.values()) |field| {
1703 const fields = self.structFields();
1704 if (self.castTag(.@"struct")) |payload| {
1705 const struct_obj = payload.data;
1706 assert(struct_obj.status == .have_layout);
1707 const is_packed = struct_obj.layout == .Packed;
1708 if (is_packed) @panic("TODO packed structs");
1709 }
1710 var big_align: u32 = 0;
1711 for (fields.values()) |field| {
17111712 if (!field.ty.hasCodeGenBits()) continue;
1712 const field_align = field.ty.abiAlignment(target);
1713 if (field_align > biggest) {
1714 return field_align;
1715 }
1713
1714 const field_align = a: {
1715 if (field.abi_align.tag() == .abi_align_default) {
1716 break :a field.ty.abiAlignment(target);
1717 } else {
1718 break :a @intCast(u32, field.abi_align.toUnsignedInt());
1719 }
1720 };
1721 big_align = @maximum(big_align, field_align);
17161722 }
1717 assert(biggest != 0);
1718 return biggest;
1723 return big_align;
17191724 },
17201725 .enum_full, .enum_nonexhaustive, .enum_simple, .enum_numbered => {
17211726 var buffer: Payload.Bits = undefined;
......@@ -1726,8 +1731,12 @@ pub const Type = extern union {
17261731 .@"union" => return self.castTag(.@"union").?.data.abiAlignment(target, false),
17271732 .union_tagged => return self.castTag(.union_tagged).?.data.abiAlignment(target, true),
17281733
1729 .c_void,
1734 .empty_struct,
17301735 .void,
1736 => return 0,
1737
1738 .empty_struct_literal,
1739 .c_void,
17311740 .type,
17321741 .comptime_int,
17331742 .comptime_float,
......@@ -1735,8 +1744,6 @@ pub const Type = extern union {
17351744 .@"null",
17361745 .@"undefined",
17371746 .enum_literal,
1738 .empty_struct,
1739 .empty_struct_literal,
17401747 .inferred_alloc_const,
17411748 .inferred_alloc_mut,
17421749 .@"opaque",
......@@ -1758,7 +1765,6 @@ pub const Type = extern union {
17581765 .fn_ccc_void_no_args => unreachable, // represents machine code; not a pointer
17591766 .function => unreachable, // represents machine code; not a pointer
17601767 .c_void => unreachable,
1761 .void => unreachable,
17621768 .type => unreachable,
17631769 .comptime_int => unreachable,
17641770 .comptime_float => unreachable,
......@@ -1767,7 +1773,6 @@ pub const Type = extern union {
17671773 .@"undefined" => unreachable,
17681774 .enum_literal => unreachable,
17691775 .single_const_pointer_to_comptime_int => unreachable,
1770 .empty_struct => unreachable,
17711776 .empty_struct_literal => unreachable,
17721777 .inferred_alloc_const => unreachable,
17731778 .inferred_alloc_mut => unreachable,
......@@ -1777,14 +1782,19 @@ pub const Type = extern union {
17771782 .type_info => unreachable,
17781783 .bound_fn => unreachable,
17791784
1785 .empty_struct, .void => 0,
1786
17801787 .@"struct" => {
1781 const s = self.castTag(.@"struct").?.data;
1782 assert(s.status == .have_layout);
1783 const is_packed = s.layout == .Packed;
1784 if (is_packed) @panic("TODO packed structs");
1788 const fields = self.structFields();
1789 if (self.castTag(.@"struct")) |payload| {
1790 const struct_obj = payload.data;
1791 assert(struct_obj.status == .have_layout);
1792 const is_packed = struct_obj.layout == .Packed;
1793 if (is_packed) @panic("TODO packed structs");
1794 }
17851795 var size: u64 = 0;
17861796 var big_align: u32 = 0;
1787 for (s.fields.values()) |field| {
1797 for (fields.values()) |field| {
17881798 if (!field.ty.hasCodeGenBits()) continue;
17891799
17901800 const field_align = a: {
......@@ -1829,7 +1839,7 @@ pub const Type = extern union {
18291839 .array_u8_sentinel_0 => self.castTag(.array_u8_sentinel_0).?.data + 1,
18301840 .array, .vector => {
18311841 const payload = self.cast(Payload.Array).?.data;
1832 const elem_size = std.math.max(payload.elem_type.abiAlignment(target), payload.elem_type.abiSize(target));
1842 const elem_size = @maximum(payload.elem_type.abiAlignment(target), payload.elem_type.abiSize(target));
18331843 return payload.len * elem_size;
18341844 },
18351845 .array_sentinel => {
......@@ -3331,6 +3341,7 @@ pub const Type = extern union {
33313341
33323342 pub fn structFields(ty: Type) Module.Struct.Fields {
33333343 switch (ty.tag()) {
3344 .empty_struct => return .{},
33343345 .@"struct" => {
33353346 const struct_obj = ty.castTag(.@"struct").?.data;
33363347 return struct_obj.fields;
......@@ -3345,6 +3356,7 @@ pub const Type = extern union {
33453356 const struct_obj = ty.castTag(.@"struct").?.data;
33463357 return struct_obj.fields.count();
33473358 },
3359 .empty_struct => return 0,
33483360 else => unreachable,
33493361 }
33503362 }
src/value.zig+47-11
......@@ -1399,6 +1399,7 @@ pub const Value = extern union {
13991399
14001400 switch (zig_ty_tag) {
14011401 .BoundFn => unreachable, // TODO remove this from the language
1402 .Opaque => unreachable, // Cannot hash opaque types
14021403
14031404 .Void,
14041405 .NoReturn,
......@@ -1451,10 +1452,22 @@ pub const Value = extern union {
14511452 else => unreachable,
14521453 },
14531454 .Array, .Vector => {
1454 @panic("TODO implement hashing array/vector values");
1455 const len = ty.arrayLen();
1456 const elem_ty = ty.childType();
1457 var index: usize = 0;
1458 var elem_value_buf: ElemValueBuffer = undefined;
1459 while (index < len) : (index += 1) {
1460 const elem_val = val.elemValueBuffer(index, &elem_value_buf);
1461 elem_val.hash(elem_ty, hasher);
1462 }
14551463 },
14561464 .Struct => {
1457 @panic("TODO implement hashing struct values");
1465 const fields = ty.structFields().values();
1466 if (fields.len == 0) return;
1467 const field_values = val.castTag(.@"struct").?.data;
1468 for (field_values) |field_val, i| {
1469 field_val.hash(fields[i].ty, hasher);
1470 }
14581471 },
14591472 .Optional => {
14601473 if (val.castTag(.opt_payload)) |payload| {
......@@ -1486,7 +1499,7 @@ pub const Value = extern union {
14861499 }
14871500 },
14881501 .Union => {
1489 const union_obj = val.castTag(.@"union").?.data;
1502 const union_obj = val.cast(Payload.Union).?.data;
14901503 if (ty.unionTagType()) |tag_ty| {
14911504 union_obj.tag.hash(tag_ty, hasher);
14921505 }
......@@ -1496,9 +1509,6 @@ pub const Value = extern union {
14961509 .Fn => {
14971510 @panic("TODO implement hashing function values");
14981511 },
1499 .Opaque => {
1500 @panic("TODO implement hashing opaque values");
1501 },
15021512 .Frame => {
15031513 @panic("TODO implement hashing frame values");
15041514 },
......@@ -1633,7 +1643,22 @@ pub const Value = extern union {
16331643
16341644 /// Asserts the value is a single-item pointer to an array, or an array,
16351645 /// or an unknown-length pointer, and returns the element value at the index.
1636 pub fn elemValue(val: Value, arena: *Allocator, index: usize) error{OutOfMemory}!Value {
1646 pub fn elemValue(val: Value, arena: *Allocator, index: usize) !Value {
1647 return elemValueAdvanced(val, index, arena, undefined);
1648 }
1649
1650 pub const ElemValueBuffer = Payload.U64;
1651
1652 pub fn elemValueBuffer(val: Value, index: usize, buffer: *ElemValueBuffer) Value {
1653 return elemValueAdvanced(val, index, null, buffer) catch unreachable;
1654 }
1655
1656 pub fn elemValueAdvanced(
1657 val: Value,
1658 index: usize,
1659 arena: ?*Allocator,
1660 buffer: *ElemValueBuffer,
1661 ) error{OutOfMemory}!Value {
16371662 switch (val.tag()) {
16381663 .empty_array => unreachable, // out of bounds array index
16391664 .empty_struct_value => unreachable, // out of bounds array index
......@@ -1643,16 +1668,27 @@ pub const Value = extern union {
16431668 return val.castTag(.empty_array_sentinel).?.data;
16441669 },
16451670
1646 .bytes => return Tag.int_u64.create(arena, val.castTag(.bytes).?.data[index]),
1671 .bytes => {
1672 const byte = val.castTag(.bytes).?.data[index];
1673 if (arena) |a| {
1674 return Tag.int_u64.create(a, byte);
1675 } else {
1676 buffer.* = .{
1677 .base = .{ .tag = .int_u64 },
1678 .data = byte,
1679 };
1680 return initPayload(&buffer.base);
1681 }
1682 },
16471683
16481684 // No matter the index; all the elements are the same!
16491685 .repeated => return val.castTag(.repeated).?.data,
16501686
16511687 .array => return val.castTag(.array).?.data[index],
1652 .slice => return val.castTag(.slice).?.data.ptr.elemValue(arena, index),
1688 .slice => return val.castTag(.slice).?.data.ptr.elemValueAdvanced(index, arena, buffer),
16531689
1654 .decl_ref => return val.castTag(.decl_ref).?.data.val.elemValue(arena, index),
1655 .decl_ref_mut => return val.castTag(.decl_ref_mut).?.data.decl.val.elemValue(arena, index),
1690 .decl_ref => return val.castTag(.decl_ref).?.data.val.elemValueAdvanced(index, arena, buffer),
1691 .decl_ref_mut => return val.castTag(.decl_ref_mut).?.data.decl.val.elemValueAdvanced(index, arena, buffer),
16561692
16571693 else => unreachable,
16581694 }
test/behavior/array.zig+8
......@@ -104,3 +104,11 @@ test "array with sentinels" {
104104 try S.doTheTest(false);
105105 comptime try S.doTheTest(true);
106106}
107
108test "void arrays" {
109 var array: [4]void = undefined;
110 array[0] = void{};
111 array[1] = array[2];
112 try expect(@sizeOf(@TypeOf(array)) == 0);
113 try expect(array.len == 4);
114}
test/behavior/array_stage1.zig-8
......@@ -4,14 +4,6 @@ const mem = std.mem;
44const expect = testing.expect;
55const expectEqual = testing.expectEqual;
66
7test "void arrays" {
8 var array: [4]void = undefined;
9 array[0] = void{};
10 array[1] = array[2];
11 try expect(@sizeOf(@TypeOf(array)) == 0);
12 try expect(array.len == 4);
13}
14
157test "nested arrays" {
168 const array_of_strings = [_][]const u8{ "hello", "this", "is", "my", "thing" };
179 for (array_of_strings) |s, i| {