Commit 1ebe3bd01d

Mitchell Hashimoto <mitchell.hashimoto@gmail.com>
2022-03-14 18:47:35
stage2: reify structs and tuples (#11144)
Implements `@Type` for structs, anon structs, and tuples. This is another place that would probably benefit from a `.reified_struct` type tag but will defer for later in the interest of getting tests passing first.
1 parent 5919b10
Changed files (2)
src
test
behavior
src/Sema.zig
@@ -12459,7 +12459,6 @@ fn zirReify(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I
             const ty = try Type.array(sema.arena, len, sentinel, child_ty);
             return sema.addType(ty);
         },
-        .Struct => return sema.fail(block, src, "TODO: Sema.zirReify for Struct", .{}),
         .Optional => {
             const struct_val = union_val.val.castTag(.@"struct").?.data;
             // TODO use reflection instead of magic numbers here
@@ -12515,10 +12514,31 @@ fn zirReify(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I
             const ty = try Type.Tag.error_set_merged.create(sema.arena, names);
             return sema.addType(ty);
         },
+        .Struct => {
+            // TODO use reflection instead of magic numbers here
+            const struct_val = union_val.val.castTag(.@"struct").?.data;
+            // layout: containerlayout,
+            const layout_val = struct_val[0];
+            // fields: []const enumfield,
+            const fields_val = struct_val[1];
+            // decls: []const declaration,
+            const decls_val = struct_val[2];
+            // is_tuple: bool,
+            const is_tuple_val = struct_val[3];
+
+            // Decls
+            if (decls_val.sliceLen() > 0) {
+                return sema.fail(block, src, "reified structs must have no decls", .{});
+            }
+
+            return if (is_tuple_val.toBool())
+                try sema.reifyTuple(block, src, fields_val)
+            else
+                try sema.reifyStruct(block, inst, src, layout_val, fields_val);
+        },
         .Enum => {
             const struct_val = union_val.val.castTag(.@"struct").?.data;
             // TODO use reflection instead of magic numbers here
-            // error_set: type,
             // layout: ContainerLayout,
             const layout_val = struct_val[0];
             // tag_type: type,
@@ -12537,10 +12557,7 @@ fn zirReify(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I
             }
 
             // Decls
-            const decls_slice_val = decls_val.castTag(.slice).?.data;
-            const decls_decl = decls_slice_val.ptr.pointerDecl().?;
-            try sema.ensureDeclAnalyzed(decls_decl);
-            if (decls_decl.ty.arrayLen() > 0) {
+            if (decls_val.sliceLen() > 0) {
                 return sema.fail(block, src, "reified enums must have no decls", .{});
             }
 
@@ -12636,10 +12653,7 @@ fn zirReify(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I
             const decls_val = struct_val[0];
 
             // Decls
-            const decls_slice_val = decls_val.castTag(.slice).?.data;
-            const decls_decl = decls_slice_val.ptr.pointerDecl().?;
-            try sema.ensureDeclAnalyzed(decls_decl);
-            if (decls_decl.ty.arrayLen() > 0) {
+            if (decls_val.sliceLen() > 0) {
                 return sema.fail(block, src, "reified opaque must have no decls", .{});
             }
 
@@ -12684,6 +12698,172 @@ fn zirReify(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I
     }
 }
 
+fn reifyTuple(
+    sema: *Sema,
+    block: *Block,
+    src: LazySrcLoc,
+    fields_val: Value,
+) CompileError!Air.Inst.Ref {
+    const fields_len = try sema.usizeCast(block, src, fields_val.sliceLen());
+    if (fields_len == 0) return sema.addType(Type.initTag(.empty_struct_literal));
+
+    const types = try sema.arena.alloc(Type, fields_len);
+    const values = try sema.arena.alloc(Value, fields_len);
+
+    var used_fields: std.AutoArrayHashMapUnmanaged(u32, void) = .{};
+    defer used_fields.deinit(sema.gpa);
+    try used_fields.ensureTotalCapacity(sema.gpa, fields_len);
+
+    var i: usize = 0;
+    while (i < fields_len) : (i += 1) {
+        const elem_val = try fields_val.elemValue(sema.arena, i);
+        const field_struct_val = elem_val.castTag(.@"struct").?.data;
+        // TODO use reflection instead of magic numbers here
+        // name: []const u8
+        const name_val = field_struct_val[0];
+        // field_type: type,
+        const field_type_val = field_struct_val[1];
+        //default_value: ?*const anyopaque,
+        const default_value_val = field_struct_val[2];
+
+        const field_name = try name_val.toAllocatedBytes(
+            Type.initTag(.const_slice_u8),
+            sema.arena,
+        );
+
+        const field_index = std.fmt.parseUnsigned(u32, field_name, 10) catch |err| {
+            return sema.fail(
+                block,
+                src,
+                "tuple cannot have non-numeric field '{s}': {}",
+                .{ field_name, err },
+            );
+        };
+
+        if (field_index >= fields_len) {
+            return sema.fail(
+                block,
+                src,
+                "tuple field {} exceeds tuple field count",
+                .{field_index},
+            );
+        }
+
+        const gop = used_fields.getOrPutAssumeCapacity(field_index);
+        if (gop.found_existing) {
+            // TODO: better source location
+            return sema.fail(block, src, "duplicate tuple field {}", .{field_index});
+        }
+
+        const default_val = if (default_value_val.optionalValue()) |opt_val| blk: {
+            const payload_val = if (opt_val.pointerDecl()) |opt_decl|
+                opt_decl.val
+            else
+                opt_val;
+            break :blk try payload_val.copy(sema.arena);
+        } else Value.initTag(.unreachable_value);
+
+        var buffer: Value.ToTypeBuffer = undefined;
+        types[field_index] = try field_type_val.toType(&buffer).copy(sema.arena);
+        values[field_index] = default_val;
+    }
+
+    const ty = try Type.Tag.tuple.create(sema.arena, .{
+        .types = types,
+        .values = values,
+    });
+    return sema.addType(ty);
+}
+
+fn reifyStruct(
+    sema: *Sema,
+    block: *Block,
+    inst: Zir.Inst.Index,
+    src: LazySrcLoc,
+    layout_val: Value,
+    fields_val: Value,
+) CompileError!Air.Inst.Ref {
+    var new_decl_arena = std.heap.ArenaAllocator.init(sema.gpa);
+    errdefer new_decl_arena.deinit();
+    const new_decl_arena_allocator = new_decl_arena.allocator();
+
+    const struct_obj = try new_decl_arena_allocator.create(Module.Struct);
+    const struct_ty = try Type.Tag.@"struct".create(new_decl_arena_allocator, struct_obj);
+    const new_struct_val = try Value.Tag.ty.create(new_decl_arena_allocator, struct_ty);
+    const type_name = try sema.createTypeName(block, .anon);
+    const new_decl = try sema.mod.createAnonymousDeclNamed(block, .{
+        .ty = Type.type,
+        .val = new_struct_val,
+    }, type_name);
+    new_decl.owns_tv = true;
+    errdefer sema.mod.abortAnonDecl(new_decl);
+    struct_obj.* = .{
+        .owner_decl = new_decl,
+        .fields = .{},
+        .node_offset = src.node_offset,
+        .zir_index = inst,
+        .layout = layout_val.toEnum(std.builtin.Type.ContainerLayout),
+        .status = .have_field_types,
+        .known_non_opv = undefined,
+        .namespace = .{
+            .parent = block.namespace,
+            .ty = struct_ty,
+            .file_scope = block.getFileScope(),
+        },
+    };
+
+    // Fields
+    const fields_len = try sema.usizeCast(block, src, fields_val.sliceLen());
+    try struct_obj.fields.ensureTotalCapacity(new_decl_arena_allocator, fields_len);
+    var i: usize = 0;
+    while (i < fields_len) : (i += 1) {
+        const elem_val = try fields_val.elemValue(sema.arena, i);
+        const field_struct_val = elem_val.castTag(.@"struct").?.data;
+        // TODO use reflection instead of magic numbers here
+        // name: []const u8
+        const name_val = field_struct_val[0];
+        // field_type: type,
+        const field_type_val = field_struct_val[1];
+        //default_value: ?*const anyopaque,
+        const default_value_val = field_struct_val[2];
+        // is_comptime: bool,
+        const is_comptime_val = field_struct_val[3];
+        // alignment: comptime_int,
+        const alignment_val = field_struct_val[4];
+
+        const field_name = try name_val.toAllocatedBytes(
+            Type.initTag(.const_slice_u8),
+            new_decl_arena_allocator,
+        );
+
+        const gop = struct_obj.fields.getOrPutAssumeCapacity(field_name);
+        if (gop.found_existing) {
+            // TODO: better source location
+            return sema.fail(block, src, "duplicate struct field {s}", .{field_name});
+        }
+
+        const default_val = if (default_value_val.optionalValue()) |opt_val| blk: {
+            const payload_val = if (opt_val.pointerDecl()) |opt_decl|
+                opt_decl.val
+            else
+                opt_val;
+            break :blk try payload_val.copy(new_decl_arena_allocator);
+        } else Value.initTag(.unreachable_value);
+
+        var buffer: Value.ToTypeBuffer = undefined;
+        gop.value_ptr.* = .{
+            .ty = try field_type_val.toType(&buffer).copy(new_decl_arena_allocator),
+            .abi_align = try alignment_val.copy(new_decl_arena_allocator),
+            .default_val = default_val,
+            .is_comptime = is_comptime_val.toBool(),
+            .offset = undefined,
+        };
+    }
+
+    try new_decl.finalizeNewArena(&new_decl_arena);
+    return sema.analyzeDeclVal(block, src, new_decl);
+}
+
 fn zirTypeName(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
     const inst_data = sema.code.instructions.items(.data)[inst].un_node;
     const ty_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node };
test/behavior/type.zig
@@ -260,7 +260,11 @@ test "Type.ErrorSet" {
 }
 
 test "Type.Struct" {
-    if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
 
     const A = @Type(@typeInfo(struct { x: u8, y: u32 }));
     const infoA = @typeInfo(A).Struct;
@@ -303,6 +307,46 @@ test "Type.Struct" {
     try testing.expectEqual(@as(u32, 5), @ptrCast(*const u32, infoC.fields[1].default_value.?).*);
     try testing.expectEqual(@as(usize, 0), infoC.decls.len);
     try testing.expectEqual(@as(bool, false), infoC.is_tuple);
+
+    // anon structs
+    const D = @Type(@typeInfo(@TypeOf(.{ .x = 3, .y = 5 })));
+    const infoD = @typeInfo(D).Struct;
+    try testing.expectEqual(Type.ContainerLayout.Auto, infoD.layout);
+    try testing.expectEqualSlices(u8, "x", infoD.fields[0].name);
+    try testing.expectEqual(comptime_int, infoD.fields[0].field_type);
+    try testing.expectEqual(@as(comptime_int, 3), @ptrCast(*const comptime_int, infoD.fields[0].default_value.?).*);
+    try testing.expectEqualSlices(u8, "y", infoD.fields[1].name);
+    try testing.expectEqual(comptime_int, infoD.fields[1].field_type);
+    try testing.expectEqual(@as(comptime_int, 5), @ptrCast(*const comptime_int, infoD.fields[1].default_value.?).*);
+    try testing.expectEqual(@as(usize, 0), infoD.decls.len);
+    try testing.expectEqual(@as(bool, false), infoD.is_tuple);
+
+    // tuples
+    const E = @Type(@typeInfo(@TypeOf(.{ 1, 2 })));
+    const infoE = @typeInfo(E).Struct;
+    try testing.expectEqual(Type.ContainerLayout.Auto, infoE.layout);
+    try testing.expectEqualSlices(u8, "0", infoE.fields[0].name);
+    try testing.expectEqual(comptime_int, infoE.fields[0].field_type);
+    try testing.expectEqual(@as(comptime_int, 1), @ptrCast(*const comptime_int, infoE.fields[0].default_value.?).*);
+    try testing.expectEqualSlices(u8, "1", infoE.fields[1].name);
+    try testing.expectEqual(comptime_int, infoE.fields[1].field_type);
+    try testing.expectEqual(@as(comptime_int, 2), @ptrCast(*const comptime_int, infoE.fields[1].default_value.?).*);
+    try testing.expectEqual(@as(usize, 0), infoE.decls.len);
+    try testing.expectEqual(@as(bool, true), infoE.is_tuple);
+
+    // empty struct
+    const F = @Type(@typeInfo(struct {}));
+    const infoF = @typeInfo(F).Struct;
+    try testing.expectEqual(Type.ContainerLayout.Auto, infoF.layout);
+    try testing.expect(infoF.fields.len == 0);
+    try testing.expectEqual(@as(bool, false), infoF.is_tuple);
+
+    // empty tuple
+    const G = @Type(@typeInfo(@TypeOf(.{})));
+    const infoG = @typeInfo(G).Struct;
+    try testing.expectEqual(Type.ContainerLayout.Auto, infoG.layout);
+    try testing.expect(infoG.fields.len == 0);
+    try testing.expectEqual(@as(bool, true), infoG.is_tuple);
 }
 
 test "Type.Enum" {