Commit 9ea253b9c4

Andrew Kelley <andrew@ziglang.org>
2022-02-18 04:47:51
Sema: implement type info for structs
1 parent 23567cd
Changed files (2)
src
test
behavior
src/Sema.zig
@@ -9958,7 +9958,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                 // layout: ContainerLayout,
                 try Value.Tag.enum_field_index.create(
                     sema.arena,
-                    @enumToInt(std.builtin.TypeInfo.ContainerLayout.Auto),
+                    @enumToInt(union_ty.containerLayout()),
                 ),
 
                 // tag_type: ?type,
@@ -9977,13 +9977,112 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                 }),
             );
         },
-        .Struct => return sema.fail(block, src, "TODO: implement zirTypeInfo for Struct", .{}),
-        .Opaque => {
+        .Struct => {
             // TODO: look into memoizing this result.
 
             var fields_anon_decl = try block.startAnonDecl(src);
             defer fields_anon_decl.deinit();
 
+            const struct_field_ty = t: {
+                const struct_field_ty_decl = (try sema.namespaceLookup(
+                    block,
+                    src,
+                    type_info_ty.getNamespace().?,
+                    "StructField",
+                )).?;
+                try sema.mod.declareDeclDependency(sema.owner_decl, struct_field_ty_decl);
+                try sema.ensureDeclAnalyzed(struct_field_ty_decl);
+                var buffer: Value.ToTypeBuffer = undefined;
+                break :t try struct_field_ty_decl.val.toType(&buffer).copy(fields_anon_decl.arena());
+            };
+
+            const struct_ty = try sema.resolveTypeFields(block, src, ty);
+            const struct_fields = struct_ty.structFields();
+            const struct_field_vals = try fields_anon_decl.arena().alloc(Value, struct_fields.count());
+            const layout = struct_ty.containerLayout();
+
+            for (struct_field_vals) |*field_val, i| {
+                const field = struct_fields.values()[i];
+                const name = struct_fields.keys()[i];
+                const name_val = v: {
+                    var anon_decl = try block.startAnonDecl(src);
+                    defer anon_decl.deinit();
+                    const bytes = try anon_decl.arena().dupeZ(u8, name);
+                    const new_decl = try anon_decl.finish(
+                        try Type.Tag.array_u8_sentinel_0.create(anon_decl.arena(), bytes.len),
+                        try Value.Tag.bytes.create(anon_decl.arena(), bytes[0 .. bytes.len + 1]),
+                    );
+                    break :v try Value.Tag.decl_ref.create(fields_anon_decl.arena(), new_decl);
+                };
+
+                const struct_field_fields = try fields_anon_decl.arena().create([5]Value);
+                const opt_default_val = if (field.default_val.tag() == .unreachable_value)
+                    null
+                else
+                    field.default_val;
+                const default_val_ptr = try sema.optRefValue(block, src, field.ty, opt_default_val);
+                const alignment = switch (layout) {
+                    .Auto, .Extern => field.normalAlignment(target),
+                    .Packed => field.packedAlignment(),
+                };
+
+                struct_field_fields.* = .{
+                    // name: []const u8,
+                    name_val,
+                    // field_type: type,
+                    try Value.Tag.ty.create(fields_anon_decl.arena(), field.ty),
+                    // default_value: ?*const anyopaque,
+                    try default_val_ptr.copy(fields_anon_decl.arena()),
+                    // is_comptime: bool,
+                    Value.makeBool(field.is_comptime),
+                    // alignment: comptime_int,
+                    try Value.Tag.int_u64.create(fields_anon_decl.arena(), alignment),
+                };
+                field_val.* = try Value.Tag.@"struct".create(fields_anon_decl.arena(), struct_field_fields);
+            }
+
+            const fields_val = v: {
+                const new_decl = try fields_anon_decl.finish(
+                    try Type.Tag.array.create(fields_anon_decl.arena(), .{
+                        .len = struct_field_vals.len,
+                        .elem_type = struct_field_ty,
+                    }),
+                    try Value.Tag.array.create(
+                        fields_anon_decl.arena(),
+                        try fields_anon_decl.arena().dupe(Value, struct_field_vals),
+                    ),
+                );
+                break :v try Value.Tag.decl_ref.create(sema.arena, new_decl);
+            };
+
+            const decls_val = try sema.typeInfoDecls(block, src, type_info_ty, struct_ty.getNamespace());
+
+            const field_values = try sema.arena.create([4]Value);
+            field_values.* = .{
+                // layout: ContainerLayout,
+                try Value.Tag.enum_field_index.create(
+                    sema.arena,
+                    @enumToInt(layout),
+                ),
+                // fields: []const StructField,
+                fields_val,
+                // decls: []const Declaration,
+                decls_val,
+                // is_tuple: bool,
+                Value.makeBool(struct_ty.isTuple()),
+            };
+
+            return sema.addConstant(
+                type_info_ty,
+                try Value.Tag.@"union".create(sema.arena, .{
+                    .tag = try Value.Tag.enum_field_index.create(sema.arena, @enumToInt(std.builtin.TypeId.Struct)),
+                    .val = try Value.Tag.@"struct".create(sema.arena, field_values),
+                }),
+            );
+        },
+        .Opaque => {
+            // TODO: look into memoizing this result.
+
             const opaque_ty = try sema.resolveTypeFields(block, src, ty);
             const decls_val = try sema.typeInfoDecls(block, src, type_info_ty, opaque_ty.getNamespace());
 
test/behavior/type_info.zig
@@ -249,26 +249,38 @@ fn testUnion() !void {
 }
 
 test "type info: struct info" {
-    if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
-
     try testStruct();
     comptime try testStruct();
 }
 
 fn testStruct() !void {
-    const unpacked_struct_info = @typeInfo(TestUnpackedStruct);
+    const unpacked_struct_info = @typeInfo(TestStruct);
     try expect(unpacked_struct_info.Struct.is_tuple == false);
     try expect(unpacked_struct_info.Struct.fields[0].alignment == @alignOf(u32));
     try expect(@ptrCast(*const u32, unpacked_struct_info.Struct.fields[0].default_value.?).* == 4);
-    try expectEqualStrings("foobar", @ptrCast(*const *const [6:0]u8, unpacked_struct_info.Struct.fields[1].default_value.?).*);
+    try expect(mem.eql(u8, "foobar", @ptrCast(*const *const [6:0]u8, unpacked_struct_info.Struct.fields[1].default_value.?).*));
+}
+
+const TestStruct = struct {
+    fieldA: u32 = 4,
+    fieldB: *const [6:0]u8 = "foobar",
+};
 
-    const struct_info = @typeInfo(TestStruct);
+test "type info: packed struct info" {
+    if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
+
+    try testPackedStruct();
+    comptime try testPackedStruct();
+}
+
+fn testPackedStruct() !void {
+    const struct_info = @typeInfo(TestPackedStruct);
     try expect(struct_info == .Struct);
     try expect(struct_info.Struct.is_tuple == false);
     try expect(struct_info.Struct.layout == .Packed);
     try expect(struct_info.Struct.fields.len == 4);
     try expect(struct_info.Struct.fields[0].alignment == 2 * @alignOf(usize));
-    try expect(struct_info.Struct.fields[2].field_type == *TestStruct);
+    try expect(struct_info.Struct.fields[2].field_type == *TestPackedStruct);
     try expect(struct_info.Struct.fields[2].default_value == null);
     try expect(@ptrCast(*const u32, struct_info.Struct.fields[3].default_value.?).* == 4);
     try expect(struct_info.Struct.fields[3].alignment == 1);
@@ -276,12 +288,7 @@ fn testStruct() !void {
     try expect(struct_info.Struct.decls[0].is_pub);
 }
 
-const TestUnpackedStruct = struct {
-    fieldA: u32 = 4,
-    fieldB: *const [6:0]u8 = "foobar",
-};
-
-const TestStruct = packed struct {
+const TestPackedStruct = packed struct {
     fieldA: usize align(2 * @alignOf(usize)),
     fieldB: void,
     fieldC: *Self,
@@ -329,18 +336,16 @@ fn testFunction() !void {
     const fn_aligned_info = @typeInfo(@TypeOf(fooAligned));
     try expect(fn_aligned_info.Fn.alignment == 4);
 
-    const test_instance: TestStruct = undefined;
+    const test_instance: TestPackedStruct = undefined;
     const bound_fn_info = @typeInfo(@TypeOf(test_instance.foo));
     try expect(bound_fn_info == .BoundFn);
-    try expect(bound_fn_info.BoundFn.args[0].arg_type.? == *const TestStruct);
+    try expect(bound_fn_info.BoundFn.args[0].arg_type.? == *const TestPackedStruct);
 }
 
 extern fn foo(a: usize, b: bool, ...) callconv(.C) usize;
 extern fn fooAligned(a: usize, b: bool, ...) align(4) callconv(.C) usize;
 
 test "typeInfo with comptime parameter in struct fn def" {
-    if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
-
     const S = struct {
         pub fn func(comptime x: f32) void {
             _ = x;
@@ -408,8 +413,6 @@ test "sentinel of opaque pointer type" {
 }
 
 test "@typeInfo does not force declarations into existence" {
-    if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
-
     const S = struct {
         x: i32,
 
@@ -436,8 +439,6 @@ test "type info for async frames" {
 }
 
 test "Declarations are returned in declaration order" {
-    if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
-
     const S = struct {
         const a = 1;
         const b = 2;
@@ -461,16 +462,12 @@ test "Struct.is_tuple" {
 }
 
 test "StructField.is_comptime" {
-    if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
-
     const info = @typeInfo(struct { x: u8 = 3, comptime y: u32 = 5 }).Struct;
     try expect(!info.fields[0].is_comptime);
     try expect(info.fields[1].is_comptime);
 }
 
 test "typeInfo resolves usingnamespace declarations" {
-    if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
-
     const A = struct {
         pub const f1 = 42;
     };