-
Notifications
You must be signed in to change notification settings - Fork 14.8k
[C23] Fix treating unnamed records nested in different types as compatible. #162933
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
…d unnamed records.
…tible. 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
@llvm/pr-subscribers-clang Author: Volodymyr Sapsai (vsapsai) ChangesDon't compare and accept unnamed records from different types only rdar://161592007 Full diff: https://github.com/llvm/llvm-project/pull/162933.diff 3 Files Affected:
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 3748375692430..113ecf74d8bef 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}}
@@ -389,13 +402,40 @@ 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_unnamed_tagged.untagged, struct { int i; } : 1, default : 0));
-_Static_assert(0 == _Generic(inner_anon_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).
|
It is a follow-up to #141783 GCC behavior can be checked at https://godbolt.org/z/dPq9aTq51 I think according to N3037 unnamed records are still incompatible in C23 unless the opposite is specified. And structs like A few patches are a preliminary clean-up for simple review. Planned to squash them with the actual code change because they are so small. But can do a separate PR for them. |
Not sure need to test with |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the fix! I think the only thing missing is a release note in clang/docs/ReleaseNotes.rst
Feel free to add the release note and land without further review once CI passes.
|
||
// Nameless tags are comparable only within outer definitions. At the top | ||
// level they are not comparable. | ||
const TagDecl *LTagD = LHS->castAsTagDecl(), *RTagD = RHS->castAsTagDecl(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const TagDecl *LTagD = LHS->castAsTagDecl(), *RTagD = RHS->castAsTagDecl(); | |
const auto *LTagD = LHS->castAs<TagDecl>(), *RTagD = RHS->castAs<TagDecl>(); |
Uses the more regular form of the API (we should probably remove castAsTagDecl
entirely?).
Don't compare and accept unnamed records from different types only
because they are defined in
RecordDecl
DeclContext
. During recursivecomparison don't reject unnamed records defined inside other ordered
\containers like Objective-C classes.
rdar://161592007