Commit 95b014caea

Andrew Kelley <andrew@ziglang.org>
2021-05-04 03:35:37
Sema: implement struct_decl instruction
1 parent aa4e939
Changed files (3)
src/Module.zig
@@ -501,7 +501,9 @@ pub const Struct = struct {
     layout: std.builtin.TypeInfo.ContainerLayout,
     status: enum {
         none,
+        field_types_wip,
         have_field_types,
+        layout_wip,
         have_layout,
     },
 
src/Sema.zig
@@ -1163,7 +1163,6 @@ fn zirValidateStructInitPtr(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Ind
     // Maps field index to field_ptr index of where it was already initialized.
     const found_fields = try gpa.alloc(Zir.Inst.Index, struct_obj.fields.entries.items.len);
     defer gpa.free(found_fields);
-
     mem.set(Zir.Inst.Index, found_fields, 0);
 
     for (instrs) |field_ptr| {
@@ -1190,11 +1189,12 @@ fn zirValidateStructInitPtr(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Ind
 
     var root_msg: ?*Module.ErrorMsg = null;
 
+    // TODO handle default struct field values
     for (found_fields) |field_ptr, i| {
         if (field_ptr != 0) continue;
 
         const field_name = struct_obj.fields.entries.items[i].key;
-        const template = "mising struct field: {s}";
+        const template = "missing struct field: {s}";
         const args = .{field_name};
         if (root_msg) |msg| {
             try mod.errNote(&block.base, struct_init_src, msg, template, args);
@@ -4614,7 +4614,7 @@ fn zirTypeInfo(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) InnerErro
 }
 
 fn zirTypeof(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) InnerError!*Inst {
-    const inst_data = sema.code.instructions.items(.data)[inst].un_node;
+    const inst_data = sema.code.instructions.items(.data)[inst].un_tok;
     const src = inst_data.src();
     const operand = try sema.resolveInst(inst_data.operand);
     return sema.mod.constType(sema.arena, src, operand.ty);
@@ -5052,9 +5052,112 @@ fn zirUnionInitPtr(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Inner
 }
 
 fn zirStructInit(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index, is_ref: bool) InnerError!*Inst {
-    const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
+    const mod = sema.mod;
+    const gpa = sema.gpa;
+    const zir_datas = sema.code.instructions.items(.data);
+    const inst_data = zir_datas[inst].pl_node;
+    const extra = sema.code.extraData(Zir.Inst.StructInit, inst_data.payload_index);
     const src = inst_data.src();
-    return sema.mod.fail(&block.base, src, "TODO: Sema.zirStructInit", .{});
+
+    const first_item = sema.code.extraData(Zir.Inst.StructInit.Item, extra.end).data;
+    const first_field_type_data = zir_datas[first_item.field_type].pl_node;
+    const first_field_type_extra = sema.code.extraData(Zir.Inst.FieldType, first_field_type_data.payload_index).data;
+    const unresolved_struct_type = try sema.resolveType(block, src, first_field_type_extra.container_type);
+    const struct_ty = try sema.resolveTypeFields(block, src, unresolved_struct_type);
+    const struct_obj = struct_ty.castTag(.@"struct").?.data;
+
+    // Maps field index to field_type index of where it was already initialized.
+    // For making sure all fields are accounted for and no fields are duplicated.
+    const found_fields = try gpa.alloc(Zir.Inst.Index, struct_obj.fields.entries.items.len);
+    defer gpa.free(found_fields);
+    mem.set(Zir.Inst.Index, found_fields, 0);
+
+    // The init values to use for the struct instance.
+    const field_inits = try gpa.alloc(*ir.Inst, struct_obj.fields.entries.items.len);
+    defer gpa.free(field_inits);
+
+    var field_i: u32 = 0;
+    var extra_index = extra.end;
+
+    while (field_i < extra.data.fields_len) : (field_i += 1) {
+        const item = sema.code.extraData(Zir.Inst.StructInit.Item, extra_index);
+        extra_index = item.end;
+
+        const field_type_data = zir_datas[item.data.field_type].pl_node;
+        const field_src: LazySrcLoc = .{ .node_offset_back2tok = field_type_data.src_node };
+        const field_type_extra = sema.code.extraData(Zir.Inst.FieldType, field_type_data.payload_index).data;
+        const field_name = sema.code.nullTerminatedString(field_type_extra.name_start);
+        const field_index = struct_obj.fields.getIndex(field_name) orelse
+            return sema.failWithBadFieldAccess(block, struct_obj, field_src, field_name);
+        if (found_fields[field_index] != 0) {
+            const other_field_type = found_fields[field_index];
+            const other_field_type_data = zir_datas[other_field_type].pl_node;
+            const other_field_src: LazySrcLoc = .{ .node_offset_back2tok = other_field_type_data.src_node };
+            const msg = msg: {
+                const msg = try mod.errMsg(&block.base, field_src, "duplicate field", .{});
+                errdefer msg.destroy(gpa);
+                try mod.errNote(&block.base, other_field_src, msg, "other field here", .{});
+                break :msg msg;
+            };
+            return mod.failWithOwnedErrorMsg(&block.base, msg);
+        }
+        found_fields[field_index] = item.data.field_type;
+        field_inits[field_index] = try sema.resolveInst(item.data.init);
+    }
+
+    var root_msg: ?*Module.ErrorMsg = null;
+
+    for (found_fields) |field_type_inst, i| {
+        if (field_type_inst != 0) continue;
+
+        // Check if the field has a default init.
+        const field = struct_obj.fields.entries.items[i].value;
+        if (field.default_val.tag() == .unreachable_value) {
+            const field_name = struct_obj.fields.entries.items[i].key;
+            const template = "missing struct field: {s}";
+            const args = .{field_name};
+            if (root_msg) |msg| {
+                try mod.errNote(&block.base, src, msg, template, args);
+            } else {
+                root_msg = try mod.errMsg(&block.base, src, template, args);
+            }
+        } else {
+            field_inits[i] = try mod.constInst(sema.arena, src, .{
+                .ty = field.ty,
+                .val = field.default_val,
+            });
+        }
+    }
+    if (root_msg) |msg| {
+        const fqn = try struct_obj.getFullyQualifiedName(gpa);
+        defer gpa.free(fqn);
+        try mod.errNoteNonLazy(
+            struct_obj.srcLoc(),
+            msg,
+            "struct '{s}' declared here",
+            .{fqn},
+        );
+        return mod.failWithOwnedErrorMsg(&block.base, msg);
+    }
+
+    const is_comptime = for (field_inits) |field_init| {
+        if (field_init.value() == null) {
+            break false;
+        }
+    } else true;
+
+    if (is_comptime) {
+        const values = try sema.arena.alloc(Value, field_inits.len);
+        for (field_inits) |field_init, i| {
+            values[i] = field_init.value().?;
+        }
+        return mod.constInst(sema.arena, src, .{
+            .ty = struct_ty,
+            .val = try Value.Tag.@"struct".create(sema.arena, values.ptr),
+        });
+    }
+
+    return mod.fail(&block.base, src, "TODO: Sema.zirStructInit for runtime-known struct values", .{});
 }
 
 fn zirStructInitAnon(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index, is_ref: bool) InnerError!*Inst {
@@ -6043,17 +6146,18 @@ fn coerce(
             if (inst.ty.zigTypeTag() == .EnumLiteral) {
                 const val = try sema.resolveConstValue(block, inst_src, inst);
                 const bytes = val.castTag(.enum_literal).?.data;
-                const field_index = dest_type.enumFieldIndex(bytes) orelse {
+                const resolved_dest_type = try sema.resolveTypeFields(block, inst_src, dest_type);
+                const field_index = resolved_dest_type.enumFieldIndex(bytes) orelse {
                     const msg = msg: {
                         const msg = try mod.errMsg(
                             &block.base,
                             inst_src,
                             "enum '{}' has no field named '{s}'",
-                            .{ dest_type, bytes },
+                            .{ resolved_dest_type, bytes },
                         );
                         errdefer msg.destroy(sema.gpa);
                         try mod.errNoteNonLazy(
-                            dest_type.declSrcLoc(),
+                            resolved_dest_type.declSrcLoc(),
                             msg,
                             "enum declared here",
                             .{},
@@ -6063,7 +6167,7 @@ fn coerce(
                     return mod.failWithOwnedErrorMsg(&block.base, msg);
                 };
                 return mod.constInst(arena, inst_src, .{
-                    .ty = dest_type,
+                    .ty = resolved_dest_type,
                     .val = try Value.Tag.enum_field_index.create(arena, @intCast(u32, field_index)),
                 });
             }
@@ -6698,23 +6802,41 @@ fn resolveTypeFields(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, ty: Type
             const struct_obj = ty.castTag(.@"struct").?.data;
             switch (struct_obj.status) {
                 .none => {},
-                .have_field_types, .have_layout => return ty,
+                .field_types_wip => {
+                    return sema.mod.fail(&block.base, src, "struct {} depends on itself", .{
+                        ty,
+                    });
+                },
+                .have_field_types, .have_layout, .layout_wip => return ty,
             }
+            struct_obj.status = .field_types_wip;
             try sema.mod.analyzeStructFields(struct_obj);
+            struct_obj.status = .have_field_types;
             return ty;
         },
-        .extern_options => {
-            const extern_options_ty = try sema.getBuiltinType(block, src, "ExternOptions");
-            return sema.resolveTypeFields(block, src, extern_options_ty);
-        },
-        .export_options => {
-            const export_options_ty = try sema.getBuiltinType(block, src, "ExportOptions");
-            return sema.resolveTypeFields(block, src, export_options_ty);
-        },
+        .extern_options => return sema.resolveBuiltinTypeFields(block, src, ty, "ExternOptions"),
+        .export_options => return sema.resolveBuiltinTypeFields(block, src, ty, "ExportOptions"),
+        .atomic_ordering => return sema.resolveBuiltinTypeFields(block, src, ty, "AtomicOrdering"),
+        .atomic_rmw_op => return sema.resolveBuiltinTypeFields(block, src, ty, "AtomicRmwOp"),
+        .calling_convention => return sema.resolveBuiltinTypeFields(block, src, ty, "CallingConvention"),
+        .float_mode => return sema.resolveBuiltinTypeFields(block, src, ty, "FloatMode"),
+        .reduce_op => return sema.resolveBuiltinTypeFields(block, src, ty, "ReduceOp"),
+        .call_options => return sema.resolveBuiltinTypeFields(block, src, ty, "CallOptions"),
         else => return ty,
     }
 }
 
+fn resolveBuiltinTypeFields(
+    sema: *Sema,
+    block: *Scope.Block,
+    src: LazySrcLoc,
+    ty: Type,
+    name: []const u8,
+) InnerError!Type {
+    const resolved_ty = try sema.getBuiltinType(block, src, name);
+    return sema.resolveTypeFields(block, src, resolved_ty);
+}
+
 fn getBuiltinType(
     sema: *Sema,
     block: *Scope.Block,
src/value.zig
@@ -118,6 +118,8 @@ pub const Value = extern union {
         enum_field_index,
         @"error",
         error_union,
+        /// An instance of a struct.
+        @"struct",
         /// This is a special value that tracks a set of types that have been stored
         /// to an inferred allocation. It does not support any of the normal value queries.
         inferred_alloc,
@@ -225,6 +227,7 @@ pub const Value = extern union {
                 .float_128 => Payload.Float_128,
                 .@"error" => Payload.Error,
                 .inferred_alloc => Payload.InferredAlloc,
+                .@"struct" => Payload.Struct,
             };
         }
 
@@ -442,6 +445,7 @@ pub const Value = extern union {
                 };
                 return Value{ .ptr_otherwise = &new_payload.base };
             },
+            .@"struct" => @panic("TODO can't copy struct value without knowing the type"),
 
             .inferred_alloc => unreachable,
         }
@@ -521,6 +525,9 @@ pub const Value = extern union {
             .abi_align_default => return out_stream.writeAll("(default ABI alignment)"),
 
             .empty_struct_value => return out_stream.writeAll("struct {}{}"),
+            .@"struct" => {
+                return out_stream.writeAll("(struct value)");
+            },
             .null_value => return out_stream.writeAll("null"),
             .undef => return out_stream.writeAll("undefined"),
             .zero => return out_stream.writeAll("0"),
@@ -701,6 +708,7 @@ pub const Value = extern union {
             .@"error",
             .error_union,
             .empty_struct_value,
+            .@"struct",
             .inferred_alloc,
             .abi_align_default,
             => unreachable,
@@ -1207,6 +1215,7 @@ pub const Value = extern union {
             .call_options_type,
             .export_options_type,
             .extern_options_type,
+            .@"struct",
             => @panic("TODO this hash function looks pretty broken. audit it"),
         }
         return hasher.final();
@@ -1394,6 +1403,7 @@ pub const Value = extern union {
             .@"error",
             .error_union,
             .empty_struct_value,
+            .@"struct",
             .null_value,
             .abi_align_default,
             => false,
@@ -1537,6 +1547,14 @@ pub const Value = extern union {
                 stored_inst_list: std.ArrayListUnmanaged(*ir.Inst) = .{},
             },
         };
+
+        pub const Struct = struct {
+            pub const base_tag = Tag.@"struct";
+
+            base: Payload = .{ .tag = base_tag },
+            /// Field values. The number and type are according to the struct type.
+            data: [*]Value,
+        };
     };
 
     /// Big enough to fit any non-BigInt value