diff --git a/src/Sema.zig b/src/Sema.zig index e7bc442f8411ca4a5efc2b1366777fc0a1929e15..4e8acb003052eb3fc76ab9c2cb8a52db83fae102 100644 --- a/src/Sema.zig +++ b/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( diff --git a/src/type.zig b/src/type.zig index bcb6e63f6e4d4c1f16458ae6ddef8596c3f41e9e..735227f9d2fd4a0da9dbb53b59097451a864fb8c 100644 --- a/src/type.zig +++ b/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 {