From 00dd5fdac11400456ef762d73d9cf7e47cf08db9 Mon Sep 17 00:00:00 2001 From: Volodymyr Sapsai Date: Fri, 10 Oct 2025 13:59:10 -0700 Subject: [PATCH 1/3] Add more tests to ensure nothing is broken by subsequent changes. --- clang/test/C/C23/n3037.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/clang/test/C/C23/n3037.c b/clang/test/C/C23/n3037.c index 3748375692430..f9283a0da7fdc 100644 --- a/clang/test/C/C23/n3037.c +++ b/clang/test/C/C23/n3037.c @@ -30,11 +30,24 @@ void func2(PRODUCT(int, SUM(float, double)) y) { // c17-warning {{declaration of struct foop { struct { int x; }; }; // c17-note {{previous definition is here}} struct foop { struct { int x; }; }; // c17-error {{redefinition of 'foop'}} +// Test the field lookup compatibility isn't sufficient, the structure of types should be compatible. +struct AnonymousStructNotMatchingFields { // c17-note {{previous definition is here}} + struct { // c23-note {{field has name '' here}} + int x; + }; +}; +struct AnonymousStructNotMatchingFields { // c23-error {{type 'struct AnonymousStructNotMatchingFields' has incompatible definitions}} \ + c17-error {{redefinition of 'AnonymousStructNotMatchingFields'}} + int x; // c23-note {{field has name 'x' here}} +}; + union barp { int x; float y; }; // c17-note {{previous definition is here}} union barp { int x; float y; }; // c17-error {{redefinition of 'barp'}} typedef struct q { int x; } q_t; // c17-note 2 {{previous definition is here}} typedef struct q { int x; } q_t; // c17-error {{redefinition of 'q'}} \ c17-error-re {{typedef redefinition with different types ('struct (unnamed struct at {{.*}})' vs 'struct q')}} +typedef struct { int x; } untagged_q_t; // both-note {{previous definition is here}} +typedef struct { int x; } untagged_q_t; // both-error {{typedef redefinition with different types}} void func3(void) { struct S { int x; }; // c17-note {{previous definition is here}} struct T { struct S s; }; // c17-note {{previous definition is here}} From 2342f7aa440f4868369c75b5bfca14b442cd117a Mon Sep 17 00:00:00 2001 From: Volodymyr Sapsai Date: Fri, 10 Oct 2025 14:01:56 -0700 Subject: [PATCH 2/3] Tweak naming in the test to keep the distinction between anonymous and unnamed records. --- clang/test/C/C23/n3037.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/clang/test/C/C23/n3037.c b/clang/test/C/C23/n3037.c index f9283a0da7fdc..cf4d3a3c340bf 100644 --- a/clang/test/C/C23/n3037.c +++ b/clang/test/C/C23/n3037.c @@ -402,13 +402,13 @@ void nontag_both_in_params(struct { int i; } Arg1, struct { int i; } Arg2) { _Static_assert(0 == _Generic(__typeof__(Arg1), __typeof__(Arg2) : 1, default : 0)); // both-warning {{passing a type argument as the first operand to '_Generic' is a C2y extension}} } -struct InnerAnonStruct { +struct InnerUnnamedStruct { struct { int i; } untagged; -} inner_anon_tagged; +} inner_unnamed_tagged; -_Static_assert(0 == _Generic(inner_anon_tagged.untagged, struct { int i; } : 1, default : 0)); +_Static_assert(0 == _Generic(inner_unnamed_tagged.untagged, struct { int i; } : 1, default : 0)); // Test the same thing with enumerations (test for unions is omitted because // unions and structures are both RecordDecl objects, whereas EnumDecl is not). From 3016c5ea6d9087e9f5fc7eddedec1a5e1b9ddda4 Mon Sep 17 00:00:00 2001 From: Volodymyr Sapsai Date: Fri, 10 Oct 2025 15:16:03 -0700 Subject: [PATCH 3/3] [C23] Fix treating unnamed records nested in different types as compatible. Don't compare and accept unnamed records from different types only because they are defined in `RecordDecl` `DeclContext`. During recursive comparison don't reject unnamed records defined inside other ordered containers like Objective-C classes. rdar://161592007 --- clang/lib/AST/ASTContext.cpp | 6 +++++ clang/lib/AST/ASTStructuralEquivalence.cpp | 13 ---------- clang/test/C/C23/n3037.c | 29 +++++++++++++++++++++- 3 files changed, 34 insertions(+), 14 deletions(-) diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index a8b41ba18fa01..3603e9cd87978 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -11581,6 +11581,12 @@ QualType ASTContext::mergeTagDefinitions(QualType LHS, QualType RHS) { if (LangOpts.CPlusPlus || !LangOpts.C23) return {}; + // Nameless tags are comparable only within outer definitions. At the top + // level they are not comparable. + const TagDecl *LTagD = LHS->castAsTagDecl(), *RTagD = RHS->castAsTagDecl(); + if (!LTagD->getIdentifier() || !RTagD->getIdentifier()) + return {}; + // C23, on the other hand, requires the members to be "the same enough", so // we use a structural equivalence check. StructuralEquivalenceContext::NonEquivalentDeclSet NonEquivalentDecls; diff --git a/clang/lib/AST/ASTStructuralEquivalence.cpp b/clang/lib/AST/ASTStructuralEquivalence.cpp index 155734679b2da..b17cd6f9cf68e 100644 --- a/clang/lib/AST/ASTStructuralEquivalence.cpp +++ b/clang/lib/AST/ASTStructuralEquivalence.cpp @@ -1763,19 +1763,6 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context, // another anonymous structure or union, respectively, if their members // fulfill the preceding requirements. ... Otherwise, the structure, union, // or enumerated types are incompatible. - - // Note: "the same tag" refers to the identifier for the structure; two - // structures without names are not compatible within a TU. In C23, if either - // declaration has no name, they're not equivalent. However, the paragraph - // after the bulleted list goes on to talk about compatibility of anonymous - // structure and union members, so this prohibition only applies to top-level - // declarations; if either declaration is not a member, they cannot be - // compatible. - if (Context.LangOpts.C23 && (!D1->getIdentifier() || !D2->getIdentifier()) && - (!D1->getDeclContext()->isRecord() || !D2->getDeclContext()->isRecord())) - return false; - - // Otherwise, check the names for equivalence. if (!NameIsStructurallyEquivalent(*D1, *D2)) return false; diff --git a/clang/test/C/C23/n3037.c b/clang/test/C/C23/n3037.c index cf4d3a3c340bf..113ecf74d8bef 100644 --- a/clang/test/C/C23/n3037.c +++ b/clang/test/C/C23/n3037.c @@ -407,9 +407,36 @@ struct InnerUnnamedStruct { int i; } untagged; } inner_unnamed_tagged; - _Static_assert(0 == _Generic(inner_unnamed_tagged.untagged, struct { int i; } : 1, default : 0)); +struct InnerUnnamedStruct_same { + struct { + int i; + } untagged; +}; +struct InnerUnnamedStruct_differentNaming { + struct { + int i; + } untaggedDifferent; +}; +struct InnerUnnamedStruct_differentShape { + float x; + struct { + int i; + } untagged; + int y; +}; +void compare_unnamed_struct_from_different_outer_type( + struct InnerUnnamedStruct sameOuterType, + struct InnerUnnamedStruct_same matchingType, + struct InnerUnnamedStruct_differentNaming differentFieldName, + struct InnerUnnamedStruct_differentShape differentType) { + inner_unnamed_tagged.untagged = sameOuterType.untagged; + inner_unnamed_tagged.untagged = matchingType.untagged; // both-error-re {{assigning to 'struct (unnamed struct at {{.*}})' from incompatible type 'struct (unnamed struct at {{.*}})'}} + inner_unnamed_tagged.untagged = differentFieldName.untaggedDifferent; // both-error-re {{assigning to 'struct (unnamed struct at {{.*}})' from incompatible type 'struct (unnamed struct at {{.*}})'}} + inner_unnamed_tagged.untagged = differentType.untagged; // both-error-re {{assigning to 'struct (unnamed struct at {{.*}})' from incompatible type 'struct (unnamed struct at {{.*}})'}} +} + // Test the same thing with enumerations (test for unions is omitted because // unions and structures are both RecordDecl objects, whereas EnumDecl is not). enum { E_Untagged1 } nontag_enum; // both-note {{previous definition is here}}