Commit b922caf169

John Schmidt <john.schmidt.h@gmail.com>
2022-03-19 19:32:31
sema: add compile error for missing/extra enum fields in union decl
1 parent f7f4702
Changed files (2)
src/Sema.zig
@@ -21727,7 +21727,7 @@ fn resolveTypeFieldsUnion(
     }
 
     union_obj.status = .field_types_wip;
-    try semaUnionFields(sema.mod, union_obj);
+    try semaUnionFields(block, sema.mod, union_obj);
     union_obj.status = .have_field_types;
 }
 
@@ -21967,7 +21967,7 @@ fn semaStructFields(
     }
 }
 
-fn semaUnionFields(mod: *Module, union_obj: *Module.Union) CompileError!void {
+fn semaUnionFields(block: *Block, mod: *Module, union_obj: *Module.Union) CompileError!void {
     const tracy = trace(@src());
     defer tracy.end();
 
@@ -22067,6 +22067,7 @@ fn semaUnionFields(mod: *Module, union_obj: *Module.Union) CompileError!void {
     var int_tag_ty: Type = undefined;
     var enum_field_names: ?*Module.EnumNumbered.NameMap = null;
     var enum_value_map: ?*Module.EnumNumbered.ValueMap = null;
+    var tag_ty_field_names: ?Module.EnumFull.NameMap = null;
     if (tag_type_ref != .none) {
         const provided_ty = try sema.resolveType(&block_scope, src, tag_type_ref);
         if (small.auto_enum_tag) {
@@ -22079,6 +22080,10 @@ fn semaUnionFields(mod: *Module, union_obj: *Module.Union) CompileError!void {
         } else {
             // The provided type is the enum tag type.
             union_obj.tag_ty = try provided_ty.copy(decl_arena_allocator);
+            // The fields of the union must match the enum exactly.
+            // Store a copy of the enum field names so we can check for
+            // missing or extraneous fields later.
+            tag_ty_field_names = try union_obj.tag_ty.enumFields().clone(decl_arena_allocator);
         }
     } else {
         // If auto_enum_tag is false, this is an untagged union. However, for semantic analysis
@@ -22172,6 +22177,20 @@ fn semaUnionFields(mod: *Module, union_obj: *Module.Union) CompileError!void {
             set.putAssumeCapacity(field_name, {});
         }
 
+        if (tag_ty_field_names) |*names| {
+            const enum_has_field = names.contains(field_name);
+            if (!enum_has_field) {
+                const msg = msg: {
+                    const msg = try sema.errMsg(block, src, "enum '{}' has no field named '{s}'", .{ union_obj.tag_ty.fmt(target), field_name });
+                    errdefer msg.destroy(sema.gpa);
+                    try sema.addDeclaredHereNote(msg, union_obj.tag_ty);
+                    break :msg msg;
+                };
+                return sema.failWithOwnedErrorMsg(block, msg);
+            }
+            _ = names.orderedRemove(field_name);
+        }
+
         const field_ty: Type = if (!has_type)
             Type.void
         else if (field_type_ref == .none)
@@ -22202,6 +22221,27 @@ fn semaUnionFields(mod: *Module, union_obj: *Module.Union) CompileError!void {
             gop.value_ptr.abi_align = 0;
         }
     }
+
+    if (tag_ty_field_names) |names| {
+        if (names.count() > 0) {
+            const msg = msg: {
+                const msg = try sema.errMsg(block, src, "enum field(s) missing in union", .{});
+                errdefer msg.destroy(sema.gpa);
+
+                const enum_ty = union_obj.tag_ty;
+                const tree = try sema.getAstTree(block);
+                const enum_decl = enum_ty.getOwnerDecl();
+                for (names.keys()) |field_name| {
+                    const field_index = enum_ty.enumFieldIndex(field_name).?;
+                    const field_src = enumFieldSrcLoc(enum_decl, tree.*, enum_ty.getNodeOffset(), field_index);
+                    try sema.mod.errNoteNonLazy(field_src.toSrcLoc(enum_decl), msg, "field '{s}' missing, declared here", .{field_name});
+                }
+                try sema.addDeclaredHereNote(msg, union_obj.tag_ty);
+                break :msg msg;
+            };
+            return sema.failWithOwnedErrorMsg(block, msg);
+        }
+    }
 }
 
 fn generateUnionTagTypeNumbered(
src/type.zig
@@ -5305,6 +5305,50 @@ pub const Type = extern union {
         }
     }
 
+    pub fn getNodeOffset(ty: Type) i32 {
+        switch (ty.tag()) {
+            .enum_full, .enum_nonexhaustive => {
+                const enum_full = ty.cast(Payload.EnumFull).?.data;
+                return enum_full.node_offset;
+            },
+            .enum_numbered => return ty.castTag(.enum_numbered).?.data.node_offset,
+            .enum_simple => {
+                const enum_simple = ty.castTag(.enum_simple).?.data;
+                return enum_simple.node_offset;
+            },
+            .@"struct" => {
+                const struct_obj = ty.castTag(.@"struct").?.data;
+                return struct_obj.node_offset;
+            },
+            .error_set => {
+                const error_set = ty.castTag(.error_set).?.data;
+                return error_set.node_offset;
+            },
+            .@"union", .union_tagged => {
+                const union_obj = ty.cast(Payload.Union).?.data;
+                return union_obj.node_offset;
+            },
+            .@"opaque" => {
+                const opaque_obj = ty.cast(Payload.Opaque).?.data;
+                return opaque_obj.node_offset;
+            },
+            .atomic_order,
+            .atomic_rmw_op,
+            .calling_convention,
+            .address_space,
+            .float_mode,
+            .reduce_op,
+            .call_options,
+            .prefetch_options,
+            .export_options,
+            .extern_options,
+            .type_info,
+            => unreachable, // These need to be resolved earlier.
+
+            else => unreachable,
+        }
+    }
+
     /// Asserts the type is an enum.
     pub fn enumHasInt(ty: Type, int: Value, target: Target) bool {
         const S = struct {