[clang] 6769a83 - [C23] Handle type compatibility of unnamed records (#141783)
via cfe-commits
cfe-commits at lists.llvm.org
Thu May 29 03:59:37 PDT 2025
Author: Aaron Ballman
Date: 2025-05-29T06:59:34-04:00
New Revision: 6769a836e97d5a09b239f78a8fd63cf4dac1fe13
URL: https://github.com/llvm/llvm-project/commit/6769a836e97d5a09b239f78a8fd63cf4dac1fe13
DIFF: https://github.com/llvm/llvm-project/commit/6769a836e97d5a09b239f78a8fd63cf4dac1fe13.diff
LOG: [C23] Handle type compatibility of unnamed records (#141783)
At the top-level, both types need to have a tag in order for them to be
compatible within the same TU in C23. An unnamed structure has no tag,
so it cannot be compatible with another type within the TU.
Fixes #141724
Added:
Modified:
clang/lib/AST/ASTStructuralEquivalence.cpp
clang/test/C/C23/n3037.c
Removed:
################################################################################
diff --git a/clang/lib/AST/ASTStructuralEquivalence.cpp b/clang/lib/AST/ASTStructuralEquivalence.cpp
index c6df340cbdf0a..704de8156132c 100644
--- a/clang/lib/AST/ASTStructuralEquivalence.cpp
+++ b/clang/lib/AST/ASTStructuralEquivalence.cpp
@@ -1751,9 +1751,20 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
// fulfill the preceding requirements. ... Otherwise, the structure, union,
// or enumerated types are incompatible.
- if (!NameIsStructurallyEquivalent(*D1, *D2)) {
+ // 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;
- }
if (D1->isUnion() != D2->isUnion()) {
if (Context.Complain) {
diff --git a/clang/test/C/C23/n3037.c b/clang/test/C/C23/n3037.c
index 121b220323e83..966b583c9f376 100644
--- a/clang/test/C/C23/n3037.c
+++ b/clang/test/C/C23/n3037.c
@@ -140,7 +140,7 @@ struct quals_matter { // c17-note {{previous definition is here}}
};
struct quals_matter { // c17-error {{redefinition of 'quals_matter'}} \
- c23-error {{type 'struct quals_matter' has incompatible definitions}}
+ c23-error {{type 'struct quals_matter' has incompatible definitions}}
const int x; // c23-note {{field 'x' has type 'const int' here}}
};
@@ -359,3 +359,45 @@ struct alignment { // c17-error {{redefinition of 'alignment'}} \
c23-error {{type 'struct alignment' has a member with an attribute which currently causes the types to be treated as though they are incompatible}}
int x;
};
+
+// Both structures need to have a tag in order to be compatible within the same
+// translation unit.
+struct {int i;} nontag;
+struct tag {int i;} tagged; // c17-note 2 {{previous definition is here}}
+
+_Static_assert(1 == _Generic(tagged, struct tag {int i;}:1, default:0)); // c17-error {{redefinition of 'tag'}} \
+ c17-error {{static assertion failed}}
+_Static_assert(0 == _Generic(tagged, struct {int i;}:1, default:0));
+_Static_assert(0 == _Generic(nontag, struct tag {int i;}:1, default:0)); // c17-error {{redefinition of 'tag'}}
+// That means these two structures are not actually compatible; see GH141724.
+_Static_assert(0 == _Generic(nontag, struct {int i;}:1, default:0));
+
+// Also test the behavior within a function (so the declaration context is not
+// at the translation unit level).
+void nontag_func_test(void) {
+ struct { int i; } test;
+ _Static_assert(0 == _Generic(test, struct { int i; } : 1, default : 0));
+}
+
+// Same kind of test, but this time for a declaration in the parameter list.
+void nontag_param_test(struct { int i; } herp) {
+ _Static_assert(0 == _Generic(herp, struct { int i; } : 1, default : 0));
+}
+
+// Same kind of test, but demonstrating that these still aren't compatible.
+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 {
+ int i;
+ } untagged;
+} inner_anon_tagged;
+
+_Static_assert(0 == _Generic(inner_anon_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).
+enum { E_Untagged1 } nontag_enum; // both-note {{previous definition is here}}
+_Static_assert(0 == _Generic(nontag_enum, enum { E_Untagged1 } : 1, default : 0)); // both-error {{redefinition of enumerator 'E_Untagged1'}}
More information about the cfe-commits
mailing list