[clang] [C23] Fix treating unnamed records nested in different types as compatible. (PR #162933)
Volodymyr Sapsai via cfe-commits
cfe-commits at lists.llvm.org
Fri Oct 10 15:20:24 PDT 2025
https://github.com/vsapsai created https://github.com/llvm/llvm-project/pull/162933
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
>From 00dd5fdac11400456ef762d73d9cf7e47cf08db9 Mon Sep 17 00:00:00 2001
From: Volodymyr Sapsai <vsapsai at apple.com>
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 <vsapsai at apple.com>
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 <vsapsai at apple.com>
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}}
More information about the cfe-commits
mailing list