[clang] [C23] Implement WG14 N3037 (PR #132939)
Aaron Ballman via cfe-commits
cfe-commits at lists.llvm.org
Wed Mar 26 05:31:56 PDT 2025
https://github.com/AaronBallman updated https://github.com/llvm/llvm-project/pull/132939
>From 8a8dcd326eecc2bac271bc2355fd58f97c523b03 Mon Sep 17 00:00:00 2001
From: Aaron Ballman <aaron at aaronballman.com>
Date: Fri, 21 Mar 2025 12:32:28 -0400
Subject: [PATCH 01/20] [C23] Implement WG14 N3037
This changes the type compatibility rules so that it is permitted to
redefine tag types within the same TU so long as they are equivalent
definitions.
---
clang/include/clang/AST/ASTContext.h | 1 +
clang/lib/AST/ASTContext.cpp | 65 ++++++++++++++++++++++++++++
clang/test/C/C23/n3037.c | 43 ++++++++++++++++++
3 files changed, 109 insertions(+)
create mode 100644 clang/test/C/C23/n3037.c
diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h
index af8c49e99a7ce..f4a475b193884 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -3127,6 +3127,7 @@ class ASTContext : public RefCountedBase<ASTContext> {
QualType mergeTransparentUnionType(QualType, QualType,
bool OfBlockPointer=false,
bool Unqualified = false);
+ QualType mergeRecordTypes(QualType, QualType);
QualType mergeObjCGCQualifiers(QualType, QualType);
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index de868ac821745..95b3eb784b217 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -11432,6 +11432,70 @@ static QualType mergeEnumWithInteger(ASTContext &Context, const EnumType *ET,
return {};
}
+QualType ASTContext::mergeRecordTypes(QualType LHS, QualType RHS) {
+ // C17 and earlier and C++ disallow two struct or union definitions within
+ // the same TU from being compatible.
+ if (LangOpts.CPlusPlus || !LangOpts.C23)
+ return {};
+
+ // C23, on the other hand, requires the members to be "the same enough".
+ // C23 6.2.7p1:
+ // ... Moreover, two complete structure, union, or enumerated types declared
+ // with the same tag are compatible if members satisfy the following
+ // requirements:
+ // - there shall be a one-to-one correspondence between their members such
+ // that each pair of corresponding members are declared with compatible
+ // types;
+ // - if one member of the pair is declared with an alignment specifier, the
+ // other is declared with an equivalent alignment specifier;
+ // - and, if one member of the pair is declared with a name, the other is
+ // declared with the same name.
+ // For two structures, corresponding members shall be declared in the same
+ // order. For two unions declared in the same translation unit, corresponding
+ // members shall be declared in the same order. For two structures or unions,
+ // corresponding bit-fields shall have the same widths. ... For determining
+ // type compatibility, anonymous structures and unions are considered a
+ // regular member of the containing structure or union type, and the type of
+ // an anonymous structure or union is considered compatible to the type of
+ // another anonymous structure or union, respectively, if their members
+ // fulfill the preceding requirements. ... Otherwise, the structure, union,
+ // or enumerated types are incompatible.
+
+ const RecordDecl *LHSRecord = LHS->getAsRecordDecl(),
+ *RHSRecord = RHS->getAsRecordDecl();
+
+ // Test for different tag kinds; that makes them incompatible.
+ if (LHSRecord->getTagKind() != RHSRecord->getTagKind())
+ return {};
+
+ // Walk over both lists of members. It would be nice to use a zip iterator
+ // for this, but we don't know whether the two records have the same number
+ // of fields and we don't want to walk the list of fields more often than we
+ // need to.
+ auto LHSIter = LHSRecord->field_begin(), LHSEnd = LHSRecord->field_end(),
+ RHSIter = RHSRecord->field_begin(), RHSEnd = RHSRecord->field_end();
+ for (; LHSIter != LHSEnd && RHSIter != RHSEnd; ++LHSIter, ++RHSIter) {
+ const FieldDecl *LField = *LHSIter;
+ const FieldDecl *RField = *RHSIter;
+
+ // Check that the field names are identical.
+ if (LField->getIdentifier() != RField->getIdentifier())
+ return {};
+ // Check alignment specifiers.
+ if (LField->getMaxAlignment() != RField->getMaxAlignment())
+ return {};
+ // Finally, check if the field types cannot be merged.
+ if (mergeTypes(LField->getType(), RField->getType()).isNull())
+ return {};
+ }
+ // Different number of fields between the two structures, so they're not
+ // compatible.
+ if (LHSIter != LHSEnd || RHSIter != RHSEnd)
+ return {};
+
+ return LHS;
+}
+
QualType ASTContext::mergeTypes(QualType LHS, QualType RHS, bool OfBlockPointer,
bool Unqualified, bool BlockReturnType,
bool IsConditionalOperator) {
@@ -11727,6 +11791,7 @@ QualType ASTContext::mergeTypes(QualType LHS, QualType RHS, bool OfBlockPointer,
return mergeFunctionTypes(LHS, RHS, OfBlockPointer, Unqualified,
/*AllowCXX=*/false, IsConditionalOperator);
case Type::Record:
+ return mergeRecordTypes(LHS, RHS);
case Type::Enum:
return {};
case Type::Builtin:
diff --git a/clang/test/C/C23/n3037.c b/clang/test/C/C23/n3037.c
new file mode 100644
index 0000000000000..34822c362255e
--- /dev/null
+++ b/clang/test/C/C23/n3037.c
@@ -0,0 +1,43 @@
+// RUN: %clang_cc1 -fsyntax-only -std=c23 -verify=both %s
+// RUN: %clang_cc1 -fsyntax-only -std=c17 -verify=both,c17 %s
+
+/* WG14 N3037:
+ * Improved tag compatibility
+ *
+ * Identical tag types have always been compatible across TU boundaries. This
+ * paper made identical tag types compatible within the same TU.
+ */
+
+struct foo { int a; } p;
+
+void baz(struct foo f); // c17-note {{passing argument to parameter 'f' here}}
+
+void bar(void) {
+ struct foo { int a; } q;
+ baz(q); // c17-error {{passing 'struct foo' to parameter of incompatible type 'struct foo'}}
+}
+
+#define PRODUCT(A ,B) struct prod { A a; B b; } // expected-note 2 {{expanded from macro 'PRODUCT'}}
+#define SUM(A, B) struct sum { _Bool flag; union { A a; B b; }; } // expected-note 2 {{expanded from macro 'SUM'}}
+
+void func1(PRODUCT(int, SUM(float, double)) x); // both-warning {{declaration of 'struct prod' will not be visible outside of this function}} \
+ both-warning {{declaration of 'struct sum' will not be visible outside of this function}} \
+ c17-note {{passing argument to parameter 'x' here}}
+void func2(PRODUCT(int, SUM(float, double)) y) { // both-warning {{declaration of 'struct prod' will not be visible outside of this function}} \
+ both-warning {{declaration of 'struct sum' will not be visible outside of this function}}
+ func1(y); // c17-error {{passing 'struct prod' to parameter of incompatible type 'struct prod'}}
+}
+
+struct foop { struct { int x; }; }; // c17-note {{previous definition is here}}
+struct foop { struct { int x; }; }; // c17-error {{redefinition of 'foop'}}
+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')}}
+void func3(void) {
+ struct S { int x; }; // c17-note {{previous definition is here}}
+ struct T { struct S s; }; // c17-note {{previous definition is here}}
+ struct S { int x; }; // c17-error {{redefinition of 'S'}}
+ struct T { struct S s; }; // c17-error {{redefinition of 'T'}}
+}
>From c0cc59bdba97d351f6c12867d33be5e1f4cd046c Mon Sep 17 00:00:00 2001
From: Aaron Ballman <aaron at aaronballman.com>
Date: Mon, 24 Mar 2025 07:43:51 -0400
Subject: [PATCH 02/20] Start using structural equivalence checking.
---
.../clang/AST/ASTStructuralEquivalence.h | 11 ++-
.../include/clang/Basic/DiagnosticASTKinds.td | 8 +--
clang/lib/AST/ASTContext.cpp | 8 +++
clang/lib/AST/ASTImporter.cpp | 14 ++--
clang/lib/AST/ASTStructuralEquivalence.cpp | 46 +++++++-----
clang/lib/Sema/SemaDecl.cpp | 3 +-
clang/lib/Sema/SemaType.cpp | 4 +-
clang/lib/Serialization/ASTReader.cpp | 2 +-
clang/lib/Serialization/ASTReaderDecl.cpp | 5 +-
clang/test/C/C23/n3037.c | 65 ++++++++++++++++-
.../AST/StructuralEquivalenceTest.cpp | 70 +++++++++++--------
11 files changed, 170 insertions(+), 66 deletions(-)
diff --git a/clang/include/clang/AST/ASTStructuralEquivalence.h b/clang/include/clang/AST/ASTStructuralEquivalence.h
index 67aa0023c25d0..18fdc65624db3 100644
--- a/clang/include/clang/AST/ASTStructuralEquivalence.h
+++ b/clang/include/clang/AST/ASTStructuralEquivalence.h
@@ -43,6 +43,9 @@ struct StructuralEquivalenceContext {
/// key: (from, to, IgnoreTemplateParmDepth)
using NonEquivalentDeclSet = llvm::DenseSet<std::tuple<Decl *, Decl *, int>>;
+ /// The language options to use for making a structural equivalence check.
+ const LangOptions &LangOpts;
+
/// AST contexts for which we are checking structural equivalence.
ASTContext &FromCtx, &ToCtx;
@@ -76,15 +79,17 @@ struct StructuralEquivalenceContext {
/// Whether to ignore comparing the depth of template param(TemplateTypeParm)
bool IgnoreTemplateParmDepth;
- StructuralEquivalenceContext(ASTContext &FromCtx, ASTContext &ToCtx,
+ StructuralEquivalenceContext(const LangOptions &LangOpts, ASTContext &FromCtx,
+ ASTContext &ToCtx,
NonEquivalentDeclSet &NonEquivalentDecls,
StructuralEquivalenceKind EqKind,
bool StrictTypeSpelling = false,
bool Complain = true,
bool ErrorOnTagTypeMismatch = false,
bool IgnoreTemplateParmDepth = false)
- : FromCtx(FromCtx), ToCtx(ToCtx), NonEquivalentDecls(NonEquivalentDecls),
- EqKind(EqKind), StrictTypeSpelling(StrictTypeSpelling),
+ : LangOpts(LangOpts), FromCtx(FromCtx), ToCtx(ToCtx),
+ NonEquivalentDecls(NonEquivalentDecls), EqKind(EqKind),
+ StrictTypeSpelling(StrictTypeSpelling),
ErrorOnTagTypeMismatch(ErrorOnTagTypeMismatch), Complain(Complain),
IgnoreTemplateParmDepth(IgnoreTemplateParmDepth) {}
diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td
index 8d69a2f2cf4a3..daa379ca4162b 100644
--- a/clang/include/clang/Basic/DiagnosticASTKinds.td
+++ b/clang/include/clang/Basic/DiagnosticASTKinds.td
@@ -479,11 +479,11 @@ def warn_odr_function_type_inconsistent : Warning<
"translation units (%1 vs. %2)">,
InGroup<ODR>;
def err_odr_tag_type_inconsistent
- : Error<"type %0 has incompatible definitions in different translation "
- "units">;
+ : Error<"type %0 has incompatible definitions%select{| in different "
+ "translation units}1">;
def warn_odr_tag_type_inconsistent
- : Warning<"type %0 has incompatible definitions in different translation "
- "units">,
+ : Warning<"type %0 has incompatible definitions%select{| in different "
+ "translation units}1">,
InGroup<ODR>;
def note_odr_tag_kind_here: Note<
"%0 is a %select{struct|interface|union|class|enum}1 here">;
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index 95b3eb784b217..df40d9819413b 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -16,6 +16,7 @@
#include "clang/AST/APValue.h"
#include "clang/AST/ASTConcept.h"
#include "clang/AST/ASTMutationListener.h"
+#include "clang/AST/ASTStructuralEquivalence.h"
#include "clang/AST/ASTTypeTraits.h"
#include "clang/AST/Attr.h"
#include "clang/AST/AttrIterator.h"
@@ -11438,6 +11439,13 @@ QualType ASTContext::mergeRecordTypes(QualType LHS, QualType RHS) {
if (LangOpts.CPlusPlus || !LangOpts.C23)
return {};
+ StructuralEquivalenceContext::NonEquivalentDeclSet NonEquivalentDecls;
+ StructuralEquivalenceContext Ctx(
+ getLangOpts(), *this, *this, NonEquivalentDecls,
+ StructuralEquivalenceKind::Default, false /*StrictTypeSpelling*/,
+ false /*Complain*/, true /*ErrorOnTagTypeMismatch*/);
+ return Ctx.IsEquivalent(LHS, RHS) ? LHS : QualType{};
+
// C23, on the other hand, requires the members to be "the same enough".
// C23 6.2.7p1:
// ... Moreover, two complete structure, union, or enumerated types declared
diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp
index 1db30b3f3f76f..f7dba4163d1cb 100644
--- a/clang/lib/AST/ASTImporter.cpp
+++ b/clang/lib/AST/ASTImporter.cpp
@@ -2497,8 +2497,9 @@ bool ASTNodeImporter::IsStructuralMatch(Decl *From, Decl *To, bool Complain,
}
StructuralEquivalenceContext Ctx(
- Importer.getFromContext(), Importer.getToContext(),
- Importer.getNonEquivalentDecls(), getStructuralEquivalenceKind(Importer),
+ Importer.getToContext().getLangOpts(), Importer.getFromContext(),
+ Importer.getToContext(), Importer.getNonEquivalentDecls(),
+ getStructuralEquivalenceKind(Importer),
/*StrictTypeSpelling=*/false, Complain, /*ErrorOnTagTypeMismatch=*/false,
IgnoreTemplateParmDepth);
return Ctx.IsEquivalent(From, To);
@@ -4352,7 +4353,8 @@ static bool IsEquivalentFriend(ASTImporter &Importer, FriendDecl *FD1,
ASTImporter::NonEquivalentDeclSet NonEquivalentDecls;
StructuralEquivalenceContext Ctx(
- FD1->getASTContext(), FD2->getASTContext(), NonEquivalentDecls,
+ Importer.getToContext().getLangOpts(), FD1->getASTContext(),
+ FD2->getASTContext(), NonEquivalentDecls,
StructuralEquivalenceKind::Default,
/* StrictTypeSpelling = */ false, /* Complain = */ false);
return Ctx.IsEquivalent(FD1, FD2);
@@ -10580,8 +10582,8 @@ bool ASTImporter::IsStructurallyEquivalent(QualType From, QualType To,
}
}
- StructuralEquivalenceContext Ctx(FromContext, ToContext, NonEquivalentDecls,
- getStructuralEquivalenceKind(*this), false,
- Complain);
+ StructuralEquivalenceContext Ctx(
+ getToContext().getLangOpts(), FromContext, ToContext, NonEquivalentDecls,
+ getStructuralEquivalenceKind(*this), false, Complain);
return Ctx.IsEquivalent(From, To);
}
diff --git a/clang/lib/AST/ASTStructuralEquivalence.cpp b/clang/lib/AST/ASTStructuralEquivalence.cpp
index 2c2c8fd677500..bf8281fc9cba2 100644
--- a/clang/lib/AST/ASTStructuralEquivalence.cpp
+++ b/clang/lib/AST/ASTStructuralEquivalence.cpp
@@ -1472,7 +1472,7 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
Context.Diag2(
Owner2->getLocation(),
Context.getApplicableDiagnostic(diag::err_odr_tag_type_inconsistent))
- << Owner2Type;
+ << Owner2Type << (&Context.FromCtx != &Context.ToCtx);
Context.Diag2(Field2->getLocation(), diag::note_odr_field_name)
<< Field2->getDeclName();
Context.Diag1(Field1->getLocation(), diag::note_odr_field_name)
@@ -1487,7 +1487,7 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
Context.Diag2(
Owner2->getLocation(),
Context.getApplicableDiagnostic(diag::err_odr_tag_type_inconsistent))
- << Owner2Type;
+ << Owner2Type << (&Context.FromCtx != &Context.ToCtx);
Context.Diag2(Field2->getLocation(), diag::note_odr_field)
<< Field2->getDeclName() << Field2->getType();
Context.Diag1(Field1->getLocation(), diag::note_odr_field)
@@ -1643,7 +1643,8 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
if (Context.Complain) {
Context.Diag2(D2->getLocation(), Context.getApplicableDiagnostic(
diag::err_odr_tag_type_inconsistent))
- << Context.ToCtx.getTypeDeclType(D2);
+ << Context.ToCtx.getTypeDeclType(D2)
+ << (&Context.FromCtx != &Context.ToCtx);
Context.Diag1(D1->getLocation(), diag::note_odr_tag_kind_here)
<< D1->getDeclName() << (unsigned)D1->getTagKind();
}
@@ -1667,7 +1668,8 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
// If the records occur in different context (namespace), these should be
// different. This is specially important if the definition of one or both
// records is missing.
- if (!IsRecordContextStructurallyEquivalent(Context, D1, D2))
+ if (!Context.LangOpts.C23 &&
+ !IsRecordContextStructurallyEquivalent(Context, D1, D2))
return false;
// If both declarations are class template specializations, we know
@@ -1735,7 +1737,8 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
Context.Diag2(D2->getLocation(),
Context.getApplicableDiagnostic(
diag::err_odr_tag_type_inconsistent))
- << Context.ToCtx.getTypeDeclType(D2);
+ << Context.ToCtx.getTypeDeclType(D2)
+ << (&Context.FromCtx != &Context.ToCtx);
Context.Diag2(D2->getLocation(), diag::note_odr_number_of_bases)
<< D2CXX->getNumBases();
Context.Diag1(D1->getLocation(), diag::note_odr_number_of_bases)
@@ -1755,7 +1758,8 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
Context.Diag2(D2->getLocation(),
Context.getApplicableDiagnostic(
diag::err_odr_tag_type_inconsistent))
- << Context.ToCtx.getTypeDeclType(D2);
+ << Context.ToCtx.getTypeDeclType(D2)
+ << (&Context.FromCtx != &Context.ToCtx);
Context.Diag2(Base2->getBeginLoc(), diag::note_odr_base)
<< Base2->getType() << Base2->getSourceRange();
Context.Diag1(Base1->getBeginLoc(), diag::note_odr_base)
@@ -1770,7 +1774,8 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
Context.Diag2(D2->getLocation(),
Context.getApplicableDiagnostic(
diag::err_odr_tag_type_inconsistent))
- << Context.ToCtx.getTypeDeclType(D2);
+ << Context.ToCtx.getTypeDeclType(D2)
+ << (&Context.FromCtx != &Context.ToCtx);
Context.Diag2(Base2->getBeginLoc(), diag::note_odr_virtual_base)
<< Base2->isVirtual() << Base2->getSourceRange();
Context.Diag1(Base1->getBeginLoc(), diag::note_odr_base)
@@ -1791,7 +1796,8 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
Context.Diag2(D2->getLocation(),
Context.getApplicableDiagnostic(
diag::err_odr_tag_type_inconsistent))
- << Context.ToCtx.getTypeDeclType(D2CXX);
+ << Context.ToCtx.getTypeDeclType(D2CXX)
+ << (&Context.FromCtx != &Context.ToCtx);
Context.Diag1((*Friend1)->getFriendLoc(), diag::note_odr_friend);
Context.Diag2(D2->getLocation(), diag::note_odr_missing_friend);
}
@@ -1803,7 +1809,8 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
Context.Diag2(D2->getLocation(),
Context.getApplicableDiagnostic(
diag::err_odr_tag_type_inconsistent))
- << Context.ToCtx.getTypeDeclType(D2CXX);
+ << Context.ToCtx.getTypeDeclType(D2CXX)
+ << (&Context.FromCtx != &Context.ToCtx);
Context.Diag1((*Friend1)->getFriendLoc(), diag::note_odr_friend);
Context.Diag2((*Friend2)->getFriendLoc(), diag::note_odr_friend);
}
@@ -1816,7 +1823,8 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
Context.Diag2(D2->getLocation(),
Context.getApplicableDiagnostic(
diag::err_odr_tag_type_inconsistent))
- << Context.ToCtx.getTypeDeclType(D2);
+ << Context.ToCtx.getTypeDeclType(D2)
+ << (&Context.FromCtx != &Context.ToCtx);
Context.Diag2((*Friend2)->getFriendLoc(), diag::note_odr_friend);
Context.Diag1(D1->getLocation(), diag::note_odr_missing_friend);
}
@@ -1827,7 +1835,8 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
Context.Diag2(D2->getLocation(),
Context.getApplicableDiagnostic(
diag::err_odr_tag_type_inconsistent))
- << Context.ToCtx.getTypeDeclType(D2);
+ << Context.ToCtx.getTypeDeclType(D2)
+ << (&Context.FromCtx != &Context.ToCtx);
const CXXBaseSpecifier *Base1 = D1CXX->bases_begin();
Context.Diag1(Base1->getBeginLoc(), diag::note_odr_base)
<< Base1->getType() << Base1->getSourceRange();
@@ -1849,7 +1858,8 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
Context.Diag2(D2->getLocation(),
Context.getApplicableDiagnostic(
diag::err_odr_tag_type_inconsistent))
- << Context.ToCtx.getTypeDeclType(D2);
+ << Context.ToCtx.getTypeDeclType(D2)
+ << (&Context.FromCtx != &Context.ToCtx);
Context.Diag1(Field1->getLocation(), diag::note_odr_field)
<< Field1->getDeclName() << Field1->getType();
Context.Diag2(D2->getLocation(), diag::note_odr_missing_field);
@@ -1865,7 +1875,8 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
if (Context.Complain) {
Context.Diag2(D2->getLocation(), Context.getApplicableDiagnostic(
diag::err_odr_tag_type_inconsistent))
- << Context.ToCtx.getTypeDeclType(D2);
+ << Context.ToCtx.getTypeDeclType(D2)
+ << (&Context.FromCtx != &Context.ToCtx);
Context.Diag2(Field2->getLocation(), diag::note_odr_field)
<< Field2->getDeclName() << Field2->getType();
Context.Diag1(D1->getLocation(), diag::note_odr_missing_field);
@@ -1920,7 +1931,8 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
Context.Diag2(D2->getLocation(),
Context.getApplicableDiagnostic(
diag::err_odr_tag_type_inconsistent))
- << Context.ToCtx.getTypeDeclType(D2);
+ << Context.ToCtx.getTypeDeclType(D2)
+ << (&Context.FromCtx != &Context.ToCtx);
Context.Diag1(EC1->getLocation(), diag::note_odr_enumerator)
<< EC1->getDeclName() << toString(EC1->getInitVal(), 10);
Context.Diag2(D2->getLocation(), diag::note_odr_missing_enumerator);
@@ -1936,7 +1948,8 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
Context.Diag2(D2->getLocation(),
Context.getApplicableDiagnostic(
diag::err_odr_tag_type_inconsistent))
- << Context.ToCtx.getTypeDeclType(D2);
+ << Context.ToCtx.getTypeDeclType(D2)
+ << (&Context.FromCtx != &Context.ToCtx);
Context.Diag2(EC2->getLocation(), diag::note_odr_enumerator)
<< EC2->getDeclName() << toString(EC2->getInitVal(), 10);
Context.Diag1(EC1->getLocation(), diag::note_odr_enumerator)
@@ -1950,7 +1963,8 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
if (Context.Complain) {
Context.Diag2(D2->getLocation(), Context.getApplicableDiagnostic(
diag::err_odr_tag_type_inconsistent))
- << Context.ToCtx.getTypeDeclType(D2);
+ << Context.ToCtx.getTypeDeclType(D2)
+ << (&Context.FromCtx != &Context.ToCtx);
Context.Diag2(EC2->getLocation(), diag::note_odr_enumerator)
<< EC2->getDeclName() << toString(EC2->getInitVal(), 10);
Context.Diag1(D1->getLocation(), diag::note_odr_missing_enumerator);
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index fdfba48ab4b1c..888dc68af1350 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -17950,7 +17950,8 @@ Sema::ActOnTag(Scope *S, unsigned TagSpec, TagUseKind TUK, SourceLocation KWLoc,
// ensure the decl passes the structural compatibility check in
// C11 6.2.7/1 (or 6.1.2.6/1 in C89).
NamedDecl *Hidden = nullptr;
- if (SkipBody && !hasVisibleDefinition(Def, &Hidden)) {
+ if (SkipBody &&
+ (!hasVisibleDefinition(Def, &Hidden) || getLangOpts().C23)) {
// There is a definition of this tag, but it is not visible. We
// explicitly make use of C++'s one definition rule here, and
// assume that this definition is identical to the hidden one
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index 39717386b0d08..25d348dbc153e 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -9089,8 +9089,8 @@ bool Sema::hasStructuralCompatLayout(Decl *D, Decl *Suggested) {
// FIXME: Add a specific mode for C11 6.2.7/1 in StructuralEquivalenceContext
// and isolate from other C++ specific checks.
StructuralEquivalenceContext Ctx(
- D->getASTContext(), Suggested->getASTContext(), NonEquivalentDecls,
- StructuralEquivalenceKind::Default,
+ getLangOpts(), D->getASTContext(), Suggested->getASTContext(),
+ NonEquivalentDecls, StructuralEquivalenceKind::Default,
false /*StrictTypeSpelling*/, true /*Complain*/,
true /*ErrorOnTagTypeMismatch*/);
return Ctx.IsEquivalent(D, Suggested);
diff --git a/clang/lib/Serialization/ASTReader.cpp b/clang/lib/Serialization/ASTReader.cpp
index e53a30c08a759..4f7dbe0a3f90c 100644
--- a/clang/lib/Serialization/ASTReader.cpp
+++ b/clang/lib/Serialization/ASTReader.cpp
@@ -10295,7 +10295,7 @@ void ASTReader::finishPendingActions() {
PendingObjCExtensionIvarRedeclarations.back().second;
StructuralEquivalenceContext::NonEquivalentDeclSet NonEquivalentDecls;
StructuralEquivalenceContext Ctx(
- ExtensionsPair.first->getASTContext(),
+ ContextObj->getLangOpts(), ExtensionsPair.first->getASTContext(),
ExtensionsPair.second->getASTContext(), NonEquivalentDecls,
StructuralEquivalenceKind::Default, /*StrictTypeSpelling =*/false,
/*Complain =*/false,
diff --git a/clang/lib/Serialization/ASTReaderDecl.cpp b/clang/lib/Serialization/ASTReaderDecl.cpp
index c3341e00bacef..471e0d25782cc 100644
--- a/clang/lib/Serialization/ASTReaderDecl.cpp
+++ b/clang/lib/Serialization/ASTReaderDecl.cpp
@@ -4567,8 +4567,9 @@ namespace {
Reader.getOwningModuleFile(Cat)) {
StructuralEquivalenceContext::NonEquivalentDeclSet NonEquivalentDecls;
StructuralEquivalenceContext Ctx(
- Cat->getASTContext(), Existing->getASTContext(),
- NonEquivalentDecls, StructuralEquivalenceKind::Default,
+ Reader.getContext().getLangOpts(), Cat->getASTContext(),
+ Existing->getASTContext(), NonEquivalentDecls,
+ StructuralEquivalenceKind::Default,
/*StrictTypeSpelling =*/false,
/*Complain =*/false,
/*ErrorOnTagTypeMismatch =*/true);
diff --git a/clang/test/C/C23/n3037.c b/clang/test/C/C23/n3037.c
index 34822c362255e..af3e035edc307 100644
--- a/clang/test/C/C23/n3037.c
+++ b/clang/test/C/C23/n3037.c
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -fsyntax-only -std=c23 -verify=both %s
+// RUN: %clang_cc1 -fsyntax-only -std=c23 -verify=both,c23 %s
// RUN: %clang_cc1 -fsyntax-only -std=c17 -verify=both,c17 %s
/* WG14 N3037:
@@ -41,3 +41,66 @@ void func3(void) {
struct S { int x; }; // c17-error {{redefinition of 'S'}}
struct T { struct S s; }; // c17-error {{redefinition of 'T'}}
}
+
+struct food { int (*p)[3]; }; // c23-note {{field 'p' has type 'int (*)[3]' here}} \
+ c17-note {{previous definition is here}}
+struct food { int (*p)[]; }; // c23-error {{type 'struct food' has incompatible definitions}} \
+ c23-note {{field 'p' has type 'int (*)[]' here}} \
+ c17-error {{redefinition of 'food'}}
+union bard { int x; float y; }; // c23-note {{field has name 'x' here}} \
+ c17-note {{previous definition is here}}
+union bard { int z; float y; }; // c23-error {{type 'union bard' has incompatible definitions}} \
+ c23-note {{field has name 'z' here}} \
+ c17-error {{redefinition of 'bard'}}
+union purr { int x; float y; }; // c23-note {{field has name 'x' here}} \
+ c17-note {{previous definition is here}}
+union purr { float y; int x; }; // c23-error {{type 'union purr' has incompatible definitions}} \
+ c23-note {{field has name 'y' here}} \
+ c17-error {{redefinition of 'purr'}}
+
+// FIXME: this should have a structural equivalence error in C23.
+struct [[gnu::packed]] attr_test { // c17-note {{previous definition is here}}
+ int x;
+};
+
+struct attr_test { // c17-error {{redefinition of 'attr_test'}}
+ int x;
+};
+
+// FIXME: this should have a structural equivalence error in C23.
+struct field_attr_test { // c17-note {{previous definition is here}}
+ int x;
+ [[gnu::packed]] int y;
+};
+
+struct field_attr_test { // c17-error {{redefinition of 'field_attr_test'}}
+ int x;
+ int y;
+};
+
+// Show that equivalent field types are not an issue.
+typedef int typedef_of_type_int;
+struct equivalent_field_types { // c17-note {{previous definition is here}}
+ int x;
+};
+
+struct equivalent_field_types { // c17-error {{redefinition of 'equivalent_field_types'}}
+ typedef_of_type_int x;
+};
+
+struct quals_matter { // c17-note {{previous definition is here}}
+ int x; // c23-note {{field 'x' has type 'int' here}}
+};
+
+struct quals_matter { // c17-error {{redefinition of 'quals_matter'}} \
+ c23-error {{type 'struct quals_matter' has incompatible definitions}}
+ const int x; // c23-note {{field 'x' has type 'const int' here}}
+};
+
+struct qual_order_does_not_matter { // c17-note {{previous definition is here}}
+ const volatile int x;
+};
+
+struct qual_order_does_not_matter { // c17-error {{redefinition of 'qual_order_does_not_matter'}}
+ volatile const int x;
+};
diff --git a/clang/unittests/AST/StructuralEquivalenceTest.cpp b/clang/unittests/AST/StructuralEquivalenceTest.cpp
index 7cf52df9b14d0..41e32acb833f3 100644
--- a/clang/unittests/AST/StructuralEquivalenceTest.cpp
+++ b/clang/unittests/AST/StructuralEquivalenceTest.cpp
@@ -137,13 +137,15 @@ struct StructuralEquivalenceTest : ::testing::Test {
StructuralEquivalenceContext::NonEquivalentDeclSet NonEquivalentDecls01;
StructuralEquivalenceContext::NonEquivalentDeclSet NonEquivalentDecls10;
StructuralEquivalenceContext Ctx01(
- D0->getASTContext(), D1->getASTContext(), NonEquivalentDecls01,
- StructuralEquivalenceKind::Default, /*StrictTypeSpelling=*/false,
+ D0->getLangOpts(), D0->getASTContext(), D1->getASTContext(),
+ NonEquivalentDecls01, StructuralEquivalenceKind::Default,
+ /*StrictTypeSpelling=*/false,
/*Complain=*/false, /*ErrorOnTagTypeMismatch=*/false,
IgnoreTemplateParmDepth);
StructuralEquivalenceContext Ctx10(
- D1->getASTContext(), D0->getASTContext(), NonEquivalentDecls10,
- StructuralEquivalenceKind::Default, /*StrictTypeSpelling=*/false,
+ D1->getLangOpts(), D1->getASTContext(), D0->getASTContext(),
+ NonEquivalentDecls10, StructuralEquivalenceKind::Default,
+ /*StrictTypeSpelling=*/false,
/*Complain=*/false, /*ErrorOnTagTypeMismatch=*/false,
IgnoreTemplateParmDepth);
bool Eq01 = Ctx01.IsEquivalent(D0, D1);
@@ -156,11 +158,11 @@ struct StructuralEquivalenceTest : ::testing::Test {
StructuralEquivalenceContext::NonEquivalentDeclSet NonEquivalentDecls01;
StructuralEquivalenceContext::NonEquivalentDeclSet NonEquivalentDecls10;
StructuralEquivalenceContext Ctx01(
- *S0.Context, *S1.Context, NonEquivalentDecls01,
- StructuralEquivalenceKind::Default, false, false);
+ S0.Context->getLangOpts(), *S0.Context, *S1.Context,
+ NonEquivalentDecls01, StructuralEquivalenceKind::Default, false, false);
StructuralEquivalenceContext Ctx10(
- *S1.Context, *S0.Context, NonEquivalentDecls10,
- StructuralEquivalenceKind::Default, false, false);
+ S1.Context->getLangOpts(), *S1.Context, *S0.Context,
+ NonEquivalentDecls10, StructuralEquivalenceKind::Default, false, false);
bool Eq01 = Ctx01.IsEquivalent(S0.S, S1.S);
bool Eq10 = Ctx10.IsEquivalent(S1.S, S0.S);
EXPECT_EQ(Eq01, Eq10);
@@ -1826,8 +1828,9 @@ TEST_F(StructuralEquivalenceCacheTest, SimpleNonEq) {
Lang_CXX03);
StructuralEquivalenceContext Ctx(
- get<0>(TU)->getASTContext(), get<1>(TU)->getASTContext(),
- NonEquivalentDecls, StructuralEquivalenceKind::Default, false, false);
+ get<0>(TU)->getASTContext().getLangOpts(), get<0>(TU)->getASTContext(),
+ get<1>(TU)->getASTContext(), NonEquivalentDecls,
+ StructuralEquivalenceKind::Default, false, false);
auto X = findDeclPair<FunctionDecl>(TU, functionDecl(hasName("x")));
EXPECT_FALSE(Ctx.IsEquivalent(X.first, X.second));
@@ -1849,8 +1852,9 @@ TEST_F(StructuralEquivalenceCacheTest, ReturnStmtNonEq) {
Lang_CXX03);
StructuralEquivalenceContext Ctx(
- get<0>(TU)->getASTContext(), get<1>(TU)->getASTContext(),
- NonEquivalentDecls, StructuralEquivalenceKind::Default, false, false);
+ get<0>(TU)->getASTContext().getLangOpts(), get<0>(TU)->getASTContext(),
+ get<1>(TU)->getASTContext(), NonEquivalentDecls,
+ StructuralEquivalenceKind::Default, false, false);
auto X = findDeclPair<FunctionDecl>(TU, functionDecl(hasName("x")));
EXPECT_FALSE(Ctx.IsEquivalent(X.first->getBody(), X.second->getBody()));
@@ -1868,8 +1872,9 @@ TEST_F(StructuralEquivalenceCacheTest, VarDeclNoEq) {
Lang_CXX03);
StructuralEquivalenceContext Ctx(
- get<0>(TU)->getASTContext(), get<1>(TU)->getASTContext(),
- NonEquivalentDecls, StructuralEquivalenceKind::Default, false, false);
+ get<0>(TU)->getASTContext().getLangOpts(), get<0>(TU)->getASTContext(),
+ get<1>(TU)->getASTContext(), NonEquivalentDecls,
+ StructuralEquivalenceKind::Default, false, false);
auto Var = findDeclPair<VarDecl>(TU, varDecl());
EXPECT_FALSE(Ctx.IsEquivalent(Var.first, Var.second));
@@ -1886,8 +1891,9 @@ TEST_F(StructuralEquivalenceCacheTest, VarDeclWithDifferentStorageClassNoEq) {
Lang_CXX03);
StructuralEquivalenceContext Ctx(
- get<0>(TU)->getASTContext(), get<1>(TU)->getASTContext(),
- NonEquivalentDecls, StructuralEquivalenceKind::Default, false, false);
+ get<0>(TU)->getASTContext().getLangOpts(), get<0>(TU)->getASTContext(),
+ get<1>(TU)->getASTContext(), NonEquivalentDecls,
+ StructuralEquivalenceKind::Default, false, false);
auto Var = findDeclPair<VarDecl>(TU, varDecl());
EXPECT_FALSE(Ctx.IsEquivalent(Var.first, Var.second));
@@ -1913,8 +1919,9 @@ TEST_F(StructuralEquivalenceCacheTest,
Lang_CXX03);
StructuralEquivalenceContext Ctx(
- get<0>(TU)->getASTContext(), get<1>(TU)->getASTContext(),
- NonEquivalentDecls, StructuralEquivalenceKind::Default, false, false);
+ get<0>(TU)->getASTContext().getLangOpts(), get<0>(TU)->getASTContext(),
+ get<1>(TU)->getASTContext(), NonEquivalentDecls,
+ StructuralEquivalenceKind::Default, false, false);
auto NTTP = findDeclPair<NonTypeTemplateParmDecl>(
TU, nonTypeTemplateParmDecl(hasName("T")));
@@ -1932,8 +1939,9 @@ TEST_F(StructuralEquivalenceCacheTest, VarDeclWithInitNoEq) {
Lang_CXX03);
StructuralEquivalenceContext Ctx(
- get<0>(TU)->getASTContext(), get<1>(TU)->getASTContext(),
- NonEquivalentDecls, StructuralEquivalenceKind::Default, false, false);
+ get<0>(TU)->getASTContext().getLangOpts(), get<0>(TU)->getASTContext(),
+ get<1>(TU)->getASTContext(), NonEquivalentDecls,
+ StructuralEquivalenceKind::Default, false, false);
auto Var = findDeclPair<VarDecl>(TU, varDecl());
EXPECT_FALSE(Ctx.IsEquivalent(Var.first, Var.second));
@@ -1964,8 +1972,9 @@ TEST_F(StructuralEquivalenceCacheTest, SpecialNonEq) {
Lang_CXX03);
StructuralEquivalenceContext Ctx(
- get<0>(TU)->getASTContext(), get<1>(TU)->getASTContext(),
- NonEquivalentDecls, StructuralEquivalenceKind::Default, false, false);
+ get<0>(TU)->getASTContext().getLangOpts(), get<0>(TU)->getASTContext(),
+ get<1>(TU)->getASTContext(), NonEquivalentDecls,
+ StructuralEquivalenceKind::Default, false, false);
auto C = findDeclPair<CXXRecordDecl>(
TU, cxxRecordDecl(hasName("C"), unless(isImplicit())));
@@ -2003,8 +2012,9 @@ TEST_F(StructuralEquivalenceCacheTest, Cycle) {
Lang_CXX03);
StructuralEquivalenceContext Ctx(
- get<0>(TU)->getASTContext(), get<1>(TU)->getASTContext(),
- NonEquivalentDecls, StructuralEquivalenceKind::Default, false, false);
+ get<0>(TU)->getASTContext().getLangOpts(), get<0>(TU)->getASTContext(),
+ get<1>(TU)->getASTContext(), NonEquivalentDecls,
+ StructuralEquivalenceKind::Default, false, false);
auto C = findDeclPair<CXXRecordDecl>(
TU, cxxRecordDecl(hasName("C"), unless(isImplicit())));
@@ -2066,9 +2076,9 @@ TEST_F(StructuralEquivalenceCacheTest, TemplateParmDepth) {
ASSERT_EQ(D1->getTemplateDepth(), 0u);
StructuralEquivalenceContext Ctx_NoIgnoreTemplateParmDepth(
- get<0>(TU)->getASTContext(), get<1>(TU)->getASTContext(),
- NonEquivalentDecls, StructuralEquivalenceKind::Default, false, false,
- false, false);
+ get<0>(TU)->getASTContext().getLangOpts(), get<0>(TU)->getASTContext(),
+ get<1>(TU)->getASTContext(), NonEquivalentDecls,
+ StructuralEquivalenceKind::Default, false, false, false, false);
EXPECT_FALSE(Ctx_NoIgnoreTemplateParmDepth.IsEquivalent(D0, D1));
@@ -2080,9 +2090,9 @@ TEST_F(StructuralEquivalenceCacheTest, TemplateParmDepth) {
EXPECT_FALSE(isInNonEqCache(std::make_pair(NonEqDecl0, NonEqDecl1), true));
StructuralEquivalenceContext Ctx_IgnoreTemplateParmDepth(
- get<0>(TU)->getASTContext(), get<1>(TU)->getASTContext(),
- NonEquivalentDecls, StructuralEquivalenceKind::Default, false, false,
- false, true);
+ get<0>(TU)->getASTContext().getLangOpts(), get<0>(TU)->getASTContext(),
+ get<1>(TU)->getASTContext(), NonEquivalentDecls,
+ StructuralEquivalenceKind::Default, false, false, false, true);
EXPECT_TRUE(Ctx_IgnoreTemplateParmDepth.IsEquivalent(D0, D1));
>From 0485d2603cf4715da89bf597a7e0e0d493faf4c2 Mon Sep 17 00:00:00 2001
From: Aaron Ballman <aaron at aaronballman.com>
Date: Mon, 24 Mar 2025 10:11:31 -0400
Subject: [PATCH 03/20] Handle enumerations
---
clang/include/clang/AST/ASTContext.h | 2 +-
clang/include/clang/Parse/Parser.h | 3 +-
clang/include/clang/Sema/Sema.h | 3 +-
clang/lib/AST/ASTContext.cpp | 68 ++-----------------
clang/lib/AST/ASTStructuralEquivalence.cpp | 78 +++++++++++++++++-----
clang/lib/Parse/ParseDecl.cpp | 7 +-
clang/lib/Sema/SemaDecl.cpp | 5 +-
clang/test/C/C23/n3037.c | 34 ++++++++++
8 files changed, 113 insertions(+), 87 deletions(-)
diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h
index f4a475b193884..2e94b475209bf 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -3127,7 +3127,7 @@ class ASTContext : public RefCountedBase<ASTContext> {
QualType mergeTransparentUnionType(QualType, QualType,
bool OfBlockPointer=false,
bool Unqualified = false);
- QualType mergeRecordTypes(QualType, QualType);
+ QualType mergeTagTypes(QualType, QualType);
QualType mergeObjCGCQualifiers(QualType, QualType);
diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index fbe2865b1b7c1..f87f9bb93f076 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -2549,7 +2549,8 @@ class Parser : public CodeCompletionHandler {
void ParseEnumSpecifier(SourceLocation TagLoc, DeclSpec &DS,
const ParsedTemplateInfo &TemplateInfo,
AccessSpecifier AS, DeclSpecContext DSC);
- void ParseEnumBody(SourceLocation StartLoc, Decl *TagDecl);
+ void ParseEnumBody(SourceLocation StartLoc, Decl *TagDecl,
+ SkipBodyInfo *SkipBody = nullptr);
void ParseStructUnionBody(SourceLocation StartLoc, DeclSpec::TST TagType,
RecordDecl *TagDecl);
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index e215f07e2bf0a..e324bd07be24a 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -4078,7 +4078,8 @@ class Sema final : public SemaBase {
Decl *ActOnEnumConstant(Scope *S, Decl *EnumDecl, Decl *LastEnumConstant,
SourceLocation IdLoc, IdentifierInfo *Id,
const ParsedAttributesView &Attrs,
- SourceLocation EqualLoc, Expr *Val);
+ SourceLocation EqualLoc, Expr *Val,
+ SkipBodyInfo *SkipBody = nullptr);
void ActOnEnumBody(SourceLocation EnumLoc, SourceRange BraceRange,
Decl *EnumDecl, ArrayRef<Decl *> Elements, Scope *S,
const ParsedAttributesView &Attr);
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index df40d9819413b..98d9b05cac963 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -11433,75 +11433,20 @@ static QualType mergeEnumWithInteger(ASTContext &Context, const EnumType *ET,
return {};
}
-QualType ASTContext::mergeRecordTypes(QualType LHS, QualType RHS) {
- // C17 and earlier and C++ disallow two struct or union definitions within
- // the same TU from being compatible.
+QualType ASTContext::mergeTagTypes(QualType LHS, QualType RHS) {
+ // C17 and earlier and C++ disallow two tag definitions within the same TU
+ // from being compatible.
if (LangOpts.CPlusPlus || !LangOpts.C23)
return {};
+ // C23, on the other hand, requires the members to be "the same enough", so
+ // we use a structural equivalence check.
StructuralEquivalenceContext::NonEquivalentDeclSet NonEquivalentDecls;
StructuralEquivalenceContext Ctx(
getLangOpts(), *this, *this, NonEquivalentDecls,
StructuralEquivalenceKind::Default, false /*StrictTypeSpelling*/,
false /*Complain*/, true /*ErrorOnTagTypeMismatch*/);
return Ctx.IsEquivalent(LHS, RHS) ? LHS : QualType{};
-
- // C23, on the other hand, requires the members to be "the same enough".
- // C23 6.2.7p1:
- // ... Moreover, two complete structure, union, or enumerated types declared
- // with the same tag are compatible if members satisfy the following
- // requirements:
- // - there shall be a one-to-one correspondence between their members such
- // that each pair of corresponding members are declared with compatible
- // types;
- // - if one member of the pair is declared with an alignment specifier, the
- // other is declared with an equivalent alignment specifier;
- // - and, if one member of the pair is declared with a name, the other is
- // declared with the same name.
- // For two structures, corresponding members shall be declared in the same
- // order. For two unions declared in the same translation unit, corresponding
- // members shall be declared in the same order. For two structures or unions,
- // corresponding bit-fields shall have the same widths. ... For determining
- // type compatibility, anonymous structures and unions are considered a
- // regular member of the containing structure or union type, and the type of
- // an anonymous structure or union is considered compatible to the type of
- // another anonymous structure or union, respectively, if their members
- // fulfill the preceding requirements. ... Otherwise, the structure, union,
- // or enumerated types are incompatible.
-
- const RecordDecl *LHSRecord = LHS->getAsRecordDecl(),
- *RHSRecord = RHS->getAsRecordDecl();
-
- // Test for different tag kinds; that makes them incompatible.
- if (LHSRecord->getTagKind() != RHSRecord->getTagKind())
- return {};
-
- // Walk over both lists of members. It would be nice to use a zip iterator
- // for this, but we don't know whether the two records have the same number
- // of fields and we don't want to walk the list of fields more often than we
- // need to.
- auto LHSIter = LHSRecord->field_begin(), LHSEnd = LHSRecord->field_end(),
- RHSIter = RHSRecord->field_begin(), RHSEnd = RHSRecord->field_end();
- for (; LHSIter != LHSEnd && RHSIter != RHSEnd; ++LHSIter, ++RHSIter) {
- const FieldDecl *LField = *LHSIter;
- const FieldDecl *RField = *RHSIter;
-
- // Check that the field names are identical.
- if (LField->getIdentifier() != RField->getIdentifier())
- return {};
- // Check alignment specifiers.
- if (LField->getMaxAlignment() != RField->getMaxAlignment())
- return {};
- // Finally, check if the field types cannot be merged.
- if (mergeTypes(LField->getType(), RField->getType()).isNull())
- return {};
- }
- // Different number of fields between the two structures, so they're not
- // compatible.
- if (LHSIter != LHSEnd || RHSIter != RHSEnd)
- return {};
-
- return LHS;
}
QualType ASTContext::mergeTypes(QualType LHS, QualType RHS, bool OfBlockPointer,
@@ -11799,9 +11744,8 @@ QualType ASTContext::mergeTypes(QualType LHS, QualType RHS, bool OfBlockPointer,
return mergeFunctionTypes(LHS, RHS, OfBlockPointer, Unqualified,
/*AllowCXX=*/false, IsConditionalOperator);
case Type::Record:
- return mergeRecordTypes(LHS, RHS);
case Type::Enum:
- return {};
+ return mergeTagTypes(LHS, RHS);
case Type::Builtin:
// Only exactly equal builtin types are compatible, which is tested above.
return {};
diff --git a/clang/lib/AST/ASTStructuralEquivalence.cpp b/clang/lib/AST/ASTStructuralEquivalence.cpp
index bf8281fc9cba2..f727228e1c4c1 100644
--- a/clang/lib/AST/ASTStructuralEquivalence.cpp
+++ b/clang/lib/AST/ASTStructuralEquivalence.cpp
@@ -1635,6 +1635,28 @@ static bool NameIsStructurallyEquivalent(const TagDecl &D1, const TagDecl &D2) {
/// Determine structural equivalence of two records.
static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
RecordDecl *D1, RecordDecl *D2) {
+ // C23 6.2.7p1:
+ // ... Moreover, two complete structure, union, or enumerated types declared
+ // with the same tag are compatible if members satisfy the following
+ // requirements:
+ // - there shall be a one-to-one correspondence between their members such
+ // that each pair of corresponding members are declared with compatible
+ // types;
+ // - if one member of the pair is declared with an alignment specifier, the
+ // other is declared with an equivalent alignment specifier;
+ // - and, if one member of the pair is declared with a name, the other is
+ // declared with the same name.
+ // For two structures, corresponding members shall be declared in the same
+ // order. For two unions declared in the same translation unit, corresponding
+ // members shall be declared in the same order. For two structures or unions,
+ // corresponding bit-fields shall have the same widths. ... For determining
+ // type compatibility, anonymous structures and unions are considered a
+ // regular member of the containing structure or union type, and the type of
+ // an anonymous structure or union is considered compatible to the type of
+ // another anonymous structure or union, respectively, if their members
+ // fulfill the preceding requirements. ... Otherwise, the structure, union,
+ // or enumerated types are incompatible.
+
if (!NameIsStructurallyEquivalent(*D1, *D2)) {
return false;
}
@@ -1667,7 +1689,9 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
// If the records occur in different context (namespace), these should be
// different. This is specially important if the definition of one or both
- // records is missing.
+ // records is missing. In C23, different contexts do not make for a different
+ // structural type (a local struct definition can be a valid redefinition of
+ // a file scope struct definition).
if (!Context.LangOpts.C23 &&
!IsRecordContextStructurallyEquivalent(Context, D1, D2))
return false;
@@ -1916,16 +1940,35 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
// Compare the definitions of these two enums. If either or both are
// incomplete (i.e. forward declared), we assume that they are equivalent.
+ // In C23, the order of the enumerations does not matter, only the names and
+ // values do.
D1 = D1->getDefinition();
D2 = D2->getDefinition();
if (!D1 || !D2)
return true;
- EnumDecl::enumerator_iterator EC2 = D2->enumerator_begin(),
- EC2End = D2->enumerator_end();
- for (EnumDecl::enumerator_iterator EC1 = D1->enumerator_begin(),
- EC1End = D1->enumerator_end();
- EC1 != EC1End; ++EC1, ++EC2) {
+ llvm::SmallVector<const EnumConstantDecl *, 8> D1Enums, D2Enums;
+ auto CopyEnumerators =
+ [](auto &&Range, llvm::SmallVectorImpl<const EnumConstantDecl *> &Cont) {
+ for (const EnumConstantDecl *ECD : Range)
+ Cont.push_back(ECD);
+ };
+ CopyEnumerators(D1->enumerators(), D1Enums);
+ CopyEnumerators(D2->enumerators(), D2Enums);
+
+ // In C23 mode, the order of the enumerations does not matter, so sort them
+ // by name to get them both into a consistent ordering.
+ if (Context.LangOpts.C23) {
+ auto Sorter = [](const EnumConstantDecl *LHS, const EnumConstantDecl *RHS) {
+ return LHS->getName() < RHS->getName();
+ };
+ llvm::sort(D1Enums, Sorter);
+ llvm::sort(D2Enums, Sorter);
+ }
+
+ auto EC2 = D2Enums.begin(), EC2End = D2Enums.end();
+ for (auto EC1 = D1Enums.begin(), EC1End = D1Enums.end(); EC1 != EC1End;
+ ++EC1, ++EC2) {
if (EC2 == EC2End) {
if (Context.Complain) {
Context.Diag2(D2->getLocation(),
@@ -1933,27 +1976,28 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
diag::err_odr_tag_type_inconsistent))
<< Context.ToCtx.getTypeDeclType(D2)
<< (&Context.FromCtx != &Context.ToCtx);
- Context.Diag1(EC1->getLocation(), diag::note_odr_enumerator)
- << EC1->getDeclName() << toString(EC1->getInitVal(), 10);
+ Context.Diag1((*EC1)->getLocation(), diag::note_odr_enumerator)
+ << (*EC1)->getDeclName() << toString((*EC1)->getInitVal(), 10);
Context.Diag2(D2->getLocation(), diag::note_odr_missing_enumerator);
}
return false;
}
- llvm::APSInt Val1 = EC1->getInitVal();
- llvm::APSInt Val2 = EC2->getInitVal();
+ llvm::APSInt Val1 = (*EC1)->getInitVal();
+ llvm::APSInt Val2 = (*EC2)->getInitVal();
if (!llvm::APSInt::isSameValue(Val1, Val2) ||
- !IsStructurallyEquivalent(EC1->getIdentifier(), EC2->getIdentifier())) {
+ !IsStructurallyEquivalent((*EC1)->getIdentifier(),
+ (*EC2)->getIdentifier())) {
if (Context.Complain) {
Context.Diag2(D2->getLocation(),
Context.getApplicableDiagnostic(
diag::err_odr_tag_type_inconsistent))
<< Context.ToCtx.getTypeDeclType(D2)
<< (&Context.FromCtx != &Context.ToCtx);
- Context.Diag2(EC2->getLocation(), diag::note_odr_enumerator)
- << EC2->getDeclName() << toString(EC2->getInitVal(), 10);
- Context.Diag1(EC1->getLocation(), diag::note_odr_enumerator)
- << EC1->getDeclName() << toString(EC1->getInitVal(), 10);
+ Context.Diag2((*EC2)->getLocation(), diag::note_odr_enumerator)
+ << (*EC2)->getDeclName() << toString((*EC2)->getInitVal(), 10);
+ Context.Diag1((*EC1)->getLocation(), diag::note_odr_enumerator)
+ << (*EC1)->getDeclName() << toString((*EC1)->getInitVal(), 10);
}
return false;
}
@@ -1965,8 +2009,8 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
diag::err_odr_tag_type_inconsistent))
<< Context.ToCtx.getTypeDeclType(D2)
<< (&Context.FromCtx != &Context.ToCtx);
- Context.Diag2(EC2->getLocation(), diag::note_odr_enumerator)
- << EC2->getDeclName() << toString(EC2->getInitVal(), 10);
+ Context.Diag2((*EC2)->getLocation(), diag::note_odr_enumerator)
+ << (*EC2)->getDeclName() << toString((*EC2)->getInitVal(), 10);
Context.Diag1(D1->getLocation(), diag::note_odr_missing_enumerator);
}
return false;
diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index 9ca3e2b5756ca..1ea6eb3efe979 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -5653,7 +5653,7 @@ void Parser::ParseEnumSpecifier(SourceLocation StartLoc, DeclSpec &DS,
if (Tok.is(tok::l_brace) && TUK == TagUseKind::Definition) {
Decl *D = SkipBody.CheckSameAsPrevious ? SkipBody.New : TagDecl;
- ParseEnumBody(StartLoc, D);
+ ParseEnumBody(StartLoc, D, &SkipBody);
if (SkipBody.CheckSameAsPrevious &&
!Actions.ActOnDuplicateDefinition(getCurScope(), TagDecl, SkipBody)) {
DS.SetTypeSpecError();
@@ -5678,7 +5678,8 @@ void Parser::ParseEnumSpecifier(SourceLocation StartLoc, DeclSpec &DS,
/// enumeration-constant:
/// identifier
///
-void Parser::ParseEnumBody(SourceLocation StartLoc, Decl *EnumDecl) {
+void Parser::ParseEnumBody(SourceLocation StartLoc, Decl *EnumDecl,
+ SkipBodyInfo *SkipBody) {
// Enter the scope of the enum body and start the definition.
ParseScope EnumScope(this, Scope::DeclScope | Scope::EnumScope);
Actions.ActOnTagStartDefinition(getCurScope(), EnumDecl);
@@ -5736,7 +5737,7 @@ void Parser::ParseEnumBody(SourceLocation StartLoc, Decl *EnumDecl) {
// Install the enumerator constant into EnumDecl.
Decl *EnumConstDecl = Actions.ActOnEnumConstant(
getCurScope(), EnumDecl, LastEnumConstDecl, IdentLoc, Ident, attrs,
- EqualLoc, AssignedVal.get());
+ EqualLoc, AssignedVal.get(), SkipBody);
EnumAvailabilityDiags.back().done();
EnumConstantDecls.push_back(EnumConstDecl);
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 888dc68af1350..19855d7f6b8c0 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -19941,7 +19941,8 @@ SkipBodyInfo Sema::shouldSkipAnonEnumBody(Scope *S, IdentifierInfo *II,
Decl *Sema::ActOnEnumConstant(Scope *S, Decl *theEnumDecl, Decl *lastEnumConst,
SourceLocation IdLoc, IdentifierInfo *Id,
const ParsedAttributesView &Attrs,
- SourceLocation EqualLoc, Expr *Val) {
+ SourceLocation EqualLoc, Expr *Val,
+ SkipBodyInfo *SkipBody) {
EnumDecl *TheEnumDecl = cast<EnumDecl>(theEnumDecl);
EnumConstantDecl *LastEnumConst =
cast_or_null<EnumConstantDecl>(lastEnumConst);
@@ -19978,7 +19979,7 @@ Decl *Sema::ActOnEnumConstant(Scope *S, Decl *theEnumDecl, Decl *lastEnumConst,
if (!New)
return nullptr;
- if (PrevDecl) {
+ if (PrevDecl && (!SkipBody || !SkipBody->CheckSameAsPrevious)) {
if (!TheEnumDecl->isScoped() && isa<ValueDecl>(PrevDecl)) {
// Check for other kinds of shadowing not already handled.
CheckShadow(New, PrevDecl, R);
diff --git a/clang/test/C/C23/n3037.c b/clang/test/C/C23/n3037.c
index af3e035edc307..cc261d44d6b54 100644
--- a/clang/test/C/C23/n3037.c
+++ b/clang/test/C/C23/n3037.c
@@ -104,3 +104,37 @@ struct qual_order_does_not_matter { // c17-note {{previous definition is here}}
struct qual_order_does_not_matter { // c17-error {{redefinition of 'qual_order_does_not_matter'}}
volatile const int x;
};
+
+struct nested { // both-note {{previous definition is here}}
+ int x;
+ struct nested { // both-error {{nested redefinition of 'nested'}}
+ int x;
+ };
+};
+
+enum E { A }; // c17-note 2 {{previous definition is here}}
+enum E { A }; // c17-error {{redefinition of 'E'}} \
+ c17-error {{redefinition of enumerator 'A'}}
+
+enum Q { D = 1 }; // c17-note 2 {{previous definition is here}}
+enum Q { D = D }; // c17-error {{redefinition of 'Q'}} \
+ c17-error {{redefinition of enumerator 'D'}}
+
+// The order of the enumeration constants does not matter, only the values do.
+enum X { B = 1, C = 1 + 1 }; // c17-note 3 {{previous definition is here}}
+enum X { C = 2, B = 1 }; // c17-error {{redefinition of 'X'}} \
+ c17-error {{redefinition of enumerator 'C'}} \
+ c17-error {{redefinition of enumerator 'B'}}
+
+// Different enumeration constants.
+enum Y { YA = 1, YB = 2 }; // c23-note {{enumerator 'YB' with value 2 here}} \
+ c17-note 3 {{previous definition is here}}
+enum Y { YA = 1, YB = 3 }; // c23-error {{type 'enum Y' has incompatible definitions}} \
+ c23-note {{enumerator 'YB' with value 3 here}} \
+ c17-error {{redefinition of 'Y'}} \
+ c17-error {{redefinition of enumerator 'YA'}} \
+ c17-error {{redefinition of enumerator 'YB'}}
+
+// Different enumeration names, same named constants.
+enum Z1 { ZC = 1 }; // both-note {{previous definition is here}}
+enum Z2 { ZC = 1 }; // both-error {{redefinition of enumerator 'ZC'}}
>From 5d6c4f596e5eed07d23a8be8ef922b0020aec5d1 Mon Sep 17 00:00:00 2001
From: Aaron Ballman <aaron at aaronballman.com>
Date: Mon, 24 Mar 2025 15:19:14 -0400
Subject: [PATCH 04/20] Handle attributes, add a ton of tests
---
clang/docs/ReleaseNotes.rst | 4 +
.../include/clang/Basic/DiagnosticASTKinds.td | 6 +
clang/lib/AST/ASTStructuralEquivalence.cpp | 155 +++++++++++++--
clang/lib/Sema/SemaDecl.cpp | 2 +
clang/test/C/C23/n3037.c | 178 +++++++++++++++++-
5 files changed, 318 insertions(+), 27 deletions(-)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 97f6ea90d4705..07d29057be86c 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -152,6 +152,10 @@ C23 Feature Support
which clarified that a compound literal used within a function prototype is
treated as if the compound literal were within the body rather than at file
scope.
+- Implemented `WG14 N3037 <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3037.pdf>`_
+ which allows tag types to be redefined within the same translation unit so
+ long as both definitions are structurally equivalent (same tag types, same
+ tag names, same tag members, etc).
Non-comprehensive list of changes in this release
-------------------------------------------------
diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td
index daa379ca4162b..a5593ead29334 100644
--- a/clang/include/clang/Basic/DiagnosticASTKinds.td
+++ b/clang/include/clang/Basic/DiagnosticASTKinds.td
@@ -478,6 +478,12 @@ def warn_odr_function_type_inconsistent : Warning<
"external function %0 declared with incompatible types in different "
"translation units (%1 vs. %2)">,
InGroup<ODR>;
+def err_odr_attr_inconsistent : Error<
+ "attribute %0 is incompatible%select{| in different translation units}1">;
+def warn_odr_attr_inconsistent : Warning<
+ err_odr_attr_inconsistent.Summary>, InGroup<ODR>;
+def note_odr_attr : Note<"attribute %0 here">;
+def note_odr_attr_missing : Note<"no corresponding attribute here">;
def err_odr_tag_type_inconsistent
: Error<"type %0 has incompatible definitions%select{| in different "
"translation units}1">;
diff --git a/clang/lib/AST/ASTStructuralEquivalence.cpp b/clang/lib/AST/ASTStructuralEquivalence.cpp
index f727228e1c4c1..69ef09d0bc3fc 100644
--- a/clang/lib/AST/ASTStructuralEquivalence.cpp
+++ b/clang/lib/AST/ASTStructuralEquivalence.cpp
@@ -61,6 +61,7 @@
#include "clang/AST/ASTStructuralEquivalence.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/ASTDiagnostic.h"
+#include "clang/AST/Attr.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclBase.h"
#include "clang/AST/DeclCXX.h"
@@ -111,6 +112,8 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
NestedNameSpecifier *NNS1,
NestedNameSpecifier *NNS2);
+static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
+ const Attr *Attr1, const Attr *Attr2);
static bool IsStructurallyEquivalent(const IdentifierInfo *Name1,
const IdentifierInfo *Name2);
@@ -450,6 +453,107 @@ class StmtComparer {
};
} // namespace
+static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
+ const Attr *Attr1, const Attr *Attr2) {
+ // Two attributes are structurally equivalent if they are the same kind
+ // of attribute, spelled with the same spelling kind, and have the same
+ // arguments. This means that [[noreturn]] and __attribute__((noreturn)) are
+ // not structurally equivalent, nor are [[nodiscard("foo")]] and
+ // [[nodiscard("bar")]].
+ if (Attr1->getKind() != Attr2->getKind())
+ return false;
+
+ if (Attr1->getSyntax() != Attr2->getSyntax())
+ return false;
+
+ if (Attr1->getSpellingListIndex() != Attr2->getSpellingListIndex())
+ return false;
+
+ auto GetAttrName = [](const Attr *A) {
+ if (const IdentifierInfo *II = A->getAttrName())
+ return II->getName();
+ return StringRef{};
+ };
+
+ if (GetAttrName(Attr1) != GetAttrName(Attr2))
+ return false;
+
+ // FIXME: check the attribute arguments. Attr does not track the arguments on
+ // the base class, which makes this awkward. We may want to tablegen a
+ // comparison function for attributes? In the meantime, we're doing this the
+ // cheap way by pretty printing the attributes and checking they produce
+ // equivalent string representations.
+ std::string AttrStr1, AttrStr2;
+ PrintingPolicy DefaultPolicy(Context.LangOpts);
+ llvm::raw_string_ostream SS1(AttrStr1), SS2(AttrStr2);
+ Attr1->printPretty(SS1, DefaultPolicy);
+ Attr2->printPretty(SS2, DefaultPolicy);
+
+ return SS1.str() == SS2.str();
+}
+
+static bool CheckStructurallyEquivalentAttributes(
+ StructuralEquivalenceContext &Context, const Decl *D1, const Decl *D2,
+ unsigned ApplicableDiagnostic, const Decl *PrimaryDecl = nullptr) {
+ // Gather the attributes and sort them by name so that they're in equivalent
+ // orders. This means that __attribute__((foo, bar)) is equivalent to
+ // __attribute__((bar, foo)).
+ llvm::SmallVector<const Attr *, 2> Attrs1, Attrs2;
+ auto CopyAttrs = [](auto &&Range, llvm::SmallVectorImpl<const Attr *> &Cont) {
+ for (const Attr *A : Range)
+ Cont.push_back(A);
+ };
+ CopyAttrs(D1->attrs(), Attrs1);
+ CopyAttrs(D2->attrs(), Attrs2);
+
+ auto Sorter = [](const Attr *LHS, const Attr *RHS) {
+ const IdentifierInfo *II1 = LHS->getAttrName(), *II2 = RHS->getAttrName();
+ if (!II1 || !II2)
+ return II1 == II2;
+ return *II1 < *II2;
+ };
+ llvm::sort(Attrs1, Sorter);
+ llvm::sort(Attrs2, Sorter);
+
+ auto A2 = Attrs2.begin(), A2End = Attrs2.end();
+ const NamedDecl *DiagnoseDecl =
+ cast<NamedDecl>(PrimaryDecl ? PrimaryDecl : D2);
+ for (auto A1 = Attrs1.begin(), A1End = Attrs1.end(); A1 != A1End;
+ ++A1, ++A2) {
+ if (A2 == A2End) {
+ if (Context.Complain) {
+ Context.Diag2(DiagnoseDecl->getLocation(), ApplicableDiagnostic)
+ << DiagnoseDecl << (&Context.FromCtx != &Context.ToCtx);
+ Context.Diag1((*A1)->getLocation(), diag::note_odr_attr) << *A1;
+ Context.Diag2(D2->getLocation(), diag::note_odr_attr_missing);
+ }
+ return false;
+ }
+
+ if (!IsStructurallyEquivalent(Context, *A1, *A2)) {
+ if (Context.Complain) {
+ Context.Diag2(DiagnoseDecl->getLocation(), ApplicableDiagnostic)
+ << DiagnoseDecl << (&Context.FromCtx != &Context.ToCtx);
+ Context.Diag2((*A2)->getLocation(), diag::note_odr_attr) << *A2;
+ Context.Diag1((*A1)->getLocation(), diag::note_odr_attr) << *A1;
+ }
+ return false;
+ }
+ }
+
+ if (A2 != A2End) {
+ if (Context.Complain) {
+ Context.Diag2(DiagnoseDecl->getLocation(), ApplicableDiagnostic)
+ << DiagnoseDecl << (&Context.FromCtx != &Context.ToCtx);
+ Context.Diag1(D1->getLocation(), diag::note_odr_attr_missing);
+ Context.Diag1((*A2)->getLocation(), diag::note_odr_attr) << *A2;
+ }
+ return false;
+ }
+
+ return true;
+}
+
static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
const UnaryOperator *E1,
const CXXOperatorCallExpr *E2) {
@@ -1500,6 +1604,15 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
return IsStructurallyEquivalent(Context, Field1->getBitWidth(),
Field2->getBitWidth());
+ // In C23 mode, check for structural equivalence of attributes on the fields.
+ // FIXME: Should this happen in C++ as well?
+ if (Context.LangOpts.C23 &&
+ !CheckStructurallyEquivalentAttributes(
+ Context, Field1, Field2,
+ Context.getApplicableDiagnostic(diag::err_odr_tag_type_inconsistent),
+ Owner2))
+ return false;
+
return true;
}
@@ -1687,6 +1800,14 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
}
}
+ // In C23 mode, check for structural equivalence of attributes on the record
+ // itself. FIXME: Should this happen in C++ as well?
+ if (Context.LangOpts.C23 &&
+ !CheckStructurallyEquivalentAttributes(
+ Context, D1, D2,
+ Context.getApplicableDiagnostic(diag::err_odr_tag_type_inconsistent)))
+ return false;
+
// If the records occur in different context (namespace), these should be
// different. This is specially important if the definition of one or both
// records is missing. In C23, different contexts do not make for a different
@@ -1911,26 +2032,6 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
return true;
}
-static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
- EnumConstantDecl *D1,
- EnumConstantDecl *D2) {
- const llvm::APSInt &FromVal = D1->getInitVal();
- const llvm::APSInt &ToVal = D2->getInitVal();
- if (FromVal.isSigned() != ToVal.isSigned())
- return false;
- if (FromVal.getBitWidth() != ToVal.getBitWidth())
- return false;
- if (FromVal != ToVal)
- return false;
-
- if (!IsStructurallyEquivalent(D1->getIdentifier(), D2->getIdentifier()))
- return false;
-
- // Init expressions are the most expensive check, so do them last.
- return IsStructurallyEquivalent(Context, D1->getInitExpr(),
- D2->getInitExpr());
-}
-
/// Determine structural equivalence of two enums.
static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
EnumDecl *D1, EnumDecl *D2) {
@@ -1947,6 +2048,12 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
if (!D1 || !D2)
return true;
+ if (Context.LangOpts.C23 &&
+ !CheckStructurallyEquivalentAttributes(
+ Context, D1, D2,
+ Context.getApplicableDiagnostic(diag::err_odr_tag_type_inconsistent)))
+ return false;
+
llvm::SmallVector<const EnumConstantDecl *, 8> D1Enums, D2Enums;
auto CopyEnumerators =
[](auto &&Range, llvm::SmallVectorImpl<const EnumConstantDecl *> &Cont) {
@@ -2001,6 +2108,12 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
}
return false;
}
+ if (Context.LangOpts.C23 && !CheckStructurallyEquivalentAttributes(
+ Context, *EC1, *EC2,
+ Context.getApplicableDiagnostic(
+ diag::err_odr_tag_type_inconsistent),
+ D2))
+ return false;
}
if (EC2 != EC2End) {
@@ -2449,6 +2562,8 @@ unsigned StructuralEquivalenceContext::getApplicableDiagnostic(
return ErrorDiagnostic;
switch (ErrorDiagnostic) {
+ case diag::err_odr_attr_inconsistent:
+ return diag::warn_odr_attr_inconsistent;
case diag::err_odr_variable_type_inconsistent:
return diag::warn_odr_variable_type_inconsistent;
case diag::err_odr_variable_multiple_def:
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 19855d7f6b8c0..212c520c91c4e 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -17964,6 +17964,8 @@ Sema::ActOnTag(Scope *S, unsigned TagSpec, TagUseKind TUK, SourceLocation KWLoc,
SkipBody->CheckSameAsPrevious = true;
SkipBody->New = createTagFromNewDecl();
SkipBody->Previous = Def;
+
+ ProcessDeclAttributeList(S, SkipBody->New, Attrs);
return Def;
} else {
SkipBody->ShouldSkip = true;
diff --git a/clang/test/C/C23/n3037.c b/clang/test/C/C23/n3037.c
index cc261d44d6b54..19cc57c1c6297 100644
--- a/clang/test/C/C23/n3037.c
+++ b/clang/test/C/C23/n3037.c
@@ -58,23 +58,123 @@ union purr { float y; int x; }; // c23-error {{type 'union purr' has incompatibl
c23-note {{field has name 'y' here}} \
c17-error {{redefinition of 'purr'}}
-// FIXME: this should have a structural equivalence error in C23.
-struct [[gnu::packed]] attr_test { // c17-note {{previous definition is here}}
+// Different attributes on the tag type are an error.
+struct [[gnu::packed]] attr_test { // c17-note {{previous definition is here}} \
+ c23-note {{attribute 'packed' here}}
int x;
};
-struct attr_test { // c17-error {{redefinition of 'attr_test'}}
+struct attr_test { // c17-error {{redefinition of 'attr_test'}} \
+ c23-error {{type 'attr_test' has incompatible definitions}} \
+ c23-note {{no corresponding attribute here}}
int x;
};
-// FIXME: this should have a structural equivalence error in C23.
-struct field_attr_test { // c17-note {{previous definition is here}}
+struct attr_test_2 { // c17-note {{previous definition is here}} \
+ c23-note {{no corresponding attribute here}}
int x;
- [[gnu::packed]] int y;
};
-struct field_attr_test { // c17-error {{redefinition of 'field_attr_test'}}
+struct [[gnu::packed]] attr_test_2 { // c17-error {{redefinition of 'attr_test_2'}} \
+ c23-error {{type 'attr_test_2' has incompatible definitions}} \
+ c23-note {{attribute 'packed' here}}
int x;
+};
+
+struct [[deprecated]] attr_test_3 { // c17-note {{previous definition is here}} \
+ c23-note {{attribute 'deprecated' here}}
+ int x;
+};
+
+struct [[gnu::packed]] attr_test_3 { // c17-error {{redefinition of 'attr_test_3'}} \
+ c23-error {{type 'attr_test_3' has incompatible definitions}} \
+ c23-note {{attribute 'packed' here}}
+ int x;
+};
+
+// Same attribute works fine!
+struct [[deprecated]] attr_test_4 { // c17-note {{previous definition is here}}
+ int x;
+};
+
+struct [[deprecated]] attr_test_4 { // c17-error {{redefinition of 'attr_test_4'}}
+ int x;
+};
+
+// Same attribute with the same arguments is also fine.
+struct [[deprecated("testing")]] attr_test_5 { // c17-note {{previous definition is here}}
+ int x;
+};
+
+struct [[deprecated("testing")]] attr_test_5 { // c17-error {{redefinition of 'attr_test_5'}}
+ int x;
+};
+
+// Same attribute with different arguments is not allowed. FIXME: it would be
+// nicer to explain that the arguments are different, but attribute argument
+// handling is already challenging.
+struct [[deprecated("testing")]] attr_test_6 { // c17-note {{previous definition is here}} \
+ c23-note {{attribute 'deprecated' here}}
+ int x;
+};
+
+struct [[deprecated("oh no!")]] attr_test_6 { // c17-error {{redefinition of 'attr_test_6'}} \
+ c23-error {{type 'attr_test_6' has incompatible definitions}} \
+ c23-note {{attribute 'deprecated' here}}
+ int x;
+};
+
+// Different attributes on the fields make them incompatible.
+struct field_attr_test_1 { // c17-note {{previous definition is here}}
+ int x;
+ [[gnu::packed]] int y; // c23-note {{attribute 'packed' here}}
+};
+
+struct field_attr_test_1 { // c17-error {{redefinition of 'field_attr_test_1'}} \
+ c23-error {{type 'field_attr_test_1' has incompatible definitions}}
+ int x;
+ int y; // c23-note {{no corresponding attribute here}}
+};
+
+struct field_attr_test_2 { // c17-note {{previous definition is here}}
+ [[gnu::packed]] int x; // c23-note {{attribute 'packed' here}}
+ int y;
+};
+
+struct field_attr_test_2 { // c17-error {{redefinition of 'field_attr_test_2'}} \
+ c23-error {{type 'field_attr_test_2' has incompatible definitions}}
+ int x; // c23-note {{no corresponding attribute here}}
+ int y;
+};
+
+struct field_attr_test_3 { // c17-note {{previous definition is here}}
+ [[gnu::packed]] int x;
+ int y;
+};
+
+struct field_attr_test_3 { // c17-error {{redefinition of 'field_attr_test_3'}}
+ int x [[gnu::packed]];
+ int y;
+};
+
+struct field_attr_test_4 { // c17-note {{previous definition is here}}
+ [[gnu::packed]] int x; // c23-note {{attribute 'packed' here}}
+ int y;
+};
+
+struct field_attr_test_4 { // c17-error {{redefinition of 'field_attr_test_4'}} \
+ c23-error {{type 'field_attr_test_4' has incompatible definitions}}
+ [[deprecated]] int x; // c23-note {{attribute 'deprecated' here}}
+ int y;
+};
+
+struct field_attr_test_5 { // c17-note {{previous definition is here}}
+ [[deprecated("testing")]] int x;
+ int y;
+};
+
+struct field_attr_test_5 { // c17-error {{redefinition of 'field_attr_test_5'}}
+ [[deprecated("testing")]] int x;
int y;
};
@@ -138,3 +238,67 @@ enum Y { YA = 1, YB = 3 }; // c23-error {{type 'enum Y' has incompatible definit
// Different enumeration names, same named constants.
enum Z1 { ZC = 1 }; // both-note {{previous definition is here}}
enum Z2 { ZC = 1 }; // both-error {{redefinition of enumerator 'ZC'}}
+
+// Test attributes on the enumeration and enumerators.
+enum [[deprecated]] enum_attr_test_1 { // c17-note {{previous definition is here}}
+ EAT1 [[deprecated]] // c17-note {{previous definition is here}}
+};
+
+enum [[deprecated]] enum_attr_test_1 { // c17-error {{redefinition of 'enum_attr_test_1'}}
+ EAT1 [[deprecated]] // c17-error {{redefinition of enumerator 'EAT1'}}
+};
+
+enum [[deprecated]] enum_attr_test_2 { // c17-note {{previous definition is here}} \
+ c23-note {{attribute 'deprecated' here}}
+ EAT2 // c17-note {{previous definition is here}}
+};
+
+enum enum_attr_test_2 { // c17-error {{redefinition of 'enum_attr_test_2'}} \
+ c23-error {{type 'enum_attr_test_2' has incompatible definition}} \
+ c23-note {{no corresponding attribute here}}
+ EAT2 // c17-error {{redefinition of enumerator 'EAT2'}}
+};
+
+enum enum_attr_test_3 { // c17-note {{previous definition is here}} \
+ c23-note {{no corresponding attribute here}}
+ EAT3 // c17-note {{previous definition is here}}
+};
+
+enum [[deprecated]] enum_attr_test_3 { // c17-error {{redefinition of 'enum_attr_test_3'}} \
+ c23-error {{type 'enum_attr_test_3' has incompatible definition}} \
+ c23-note {{attribute 'deprecated' here}}
+ EAT3 // c17-error {{redefinition of enumerator 'EAT3'}}
+};
+
+enum [[clang::flag_enum]] enum_attr_test_4 { // c17-note {{previous definition is here}} \
+ c23-note {{attribute 'flag_enum' here}}
+ EAT4 // c17-note {{previous definition is here}}
+};
+
+enum [[deprecated]] enum_attr_test_4 { // c17-error {{redefinition of 'enum_attr_test_4'}} \
+ c23-error {{type 'enum_attr_test_4' has incompatible definition}} \
+ c23-note {{attribute 'deprecated' here}}
+ EAT4 // c17-error {{redefinition of enumerator 'EAT4'}}
+};
+
+enum enum_attr_test_5 { // c17-note {{previous definition is here}}
+ EAT5 [[deprecated]] // c17-note {{previous definition is here}} \
+ c23-note {{attribute 'deprecated' here}}
+};
+
+enum enum_attr_test_5 { // c17-error {{redefinition of 'enum_attr_test_5'}} \
+ c23-error {{type 'enum_attr_test_5' has incompatible definition}}
+ EAT5 // c17-error {{redefinition of enumerator 'EAT5'}} \
+ c23-note {{no corresponding attribute here}}
+};
+
+enum enum_attr_test_6 { // c17-note {{previous definition is here}}
+ EAT6 // c17-note {{previous definition is here}} \
+ c23-note {{no corresponding attribute here}}
+};
+
+enum enum_attr_test_6 { // c17-error {{redefinition of 'enum_attr_test_6'}} \
+ c23-error {{type 'enum_attr_test_6' has incompatible definition}}
+ EAT6 [[deprecated]] // c17-error {{redefinition of enumerator 'EAT6'}} \
+ c23-note {{attribute 'deprecated' here}}
+};
>From 8cebad548739ce7f7f3691023036261947c95750 Mon Sep 17 00:00:00 2001
From: Aaron Ballman <aaron at aaronballman.com>
Date: Mon, 24 Mar 2025 15:19:51 -0400
Subject: [PATCH 05/20] Update website
---
clang/www/c_status.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang/www/c_status.html b/clang/www/c_status.html
index c9e2eda4304f3..82087b262ea6e 100644
--- a/clang/www/c_status.html
+++ b/clang/www/c_status.html
@@ -904,7 +904,7 @@ <h2 id="c2x">C23 implementation status</h2>
<tr>
<td>Improved tag compatibility</td>
<td><a href="https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3037.pdf">N3037</a></td>
- <td class="none" align="center">No</td>
+ <td class="unreleased" align="center">Clang 21</td>
</tr>
<tr>
<td>#embed</td>
>From 8c89a774a170fdcae6fbbea34c4db1c753620726 Mon Sep 17 00:00:00 2001
From: Aaron Ballman <aaron at aaronballman.com>
Date: Mon, 24 Mar 2025 17:05:13 -0400
Subject: [PATCH 06/20] Fix failing tests
---
clang/lib/AST/ASTStructuralEquivalence.cpp | 4 ++--
clang/test/C/drs/dr1xx.c | 24 +++++++++++-----------
2 files changed, 14 insertions(+), 14 deletions(-)
diff --git a/clang/lib/AST/ASTStructuralEquivalence.cpp b/clang/lib/AST/ASTStructuralEquivalence.cpp
index 69ef09d0bc3fc..739079d906b36 100644
--- a/clang/lib/AST/ASTStructuralEquivalence.cpp
+++ b/clang/lib/AST/ASTStructuralEquivalence.cpp
@@ -1843,11 +1843,11 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
// Compare the definitions of these two records. If either or both are
// incomplete (i.e. it is a forward decl), we assume that they are
- // equivalent.
+ // equivalent. except in C23 mode.
D1 = D1->getDefinition();
D2 = D2->getDefinition();
if (!D1 || !D2)
- return true;
+ return !Context.LangOpts.C23;
// If any of the records has external storage and we do a minimal check (or
// AST import) we assume they are equivalent. (If we didn't have this
diff --git a/clang/test/C/drs/dr1xx.c b/clang/test/C/drs/dr1xx.c
index 47538e44428c3..c4103b943cd75 100644
--- a/clang/test/C/drs/dr1xx.c
+++ b/clang/test/C/drs/dr1xx.c
@@ -1,8 +1,8 @@
-/* RUN: %clang_cc1 -std=c89 -fsyntax-only -verify=expected,c89only -pedantic -Wno-c11-extensions %s
- RUN: %clang_cc1 -std=c99 -fsyntax-only -verify=expected,c99untilc2x -pedantic -Wno-c11-extensions %s
- RUN: %clang_cc1 -std=c11 -fsyntax-only -verify=expected,c99untilc2x -pedantic %s
- RUN: %clang_cc1 -std=c17 -fsyntax-only -verify=expected,c99untilc2x -pedantic %s
- RUN: %clang_cc1 -std=c2x -fsyntax-only -verify=expected,c2xandup -pedantic %s
+/* RUN: %clang_cc1 -std=c89 -fsyntax-only -verify=expected,c89only,untilc23 -pedantic -Wno-c11-extensions %s
+ RUN: %clang_cc1 -std=c99 -fsyntax-only -verify=expected,c99untilc2x,untilc23 -pedantic -Wno-c11-extensions %s
+ RUN: %clang_cc1 -std=c11 -fsyntax-only -verify=expected,c99untilc2x,untilc23 -pedantic %s
+ RUN: %clang_cc1 -std=c17 -fsyntax-only -verify=expected,c99untilc2x,untilc23 -pedantic %s
+ RUN: %clang_cc1 -std=c23 -fsyntax-only -verify=expected,c2xandup -pedantic %s
*/
/* The following are DRs which do not require tests to demonstrate
@@ -87,15 +87,15 @@ void dr101_caller(void) {
* Tag redeclaration constraints
*/
void dr102(void) {
- struct S { int member; }; /* expected-note {{previous definition is here}} */
- struct S { int member; }; /* expected-error {{redefinition of 'S'}} */
+ struct S { int member; }; /* untilc23-note {{previous definition is here}} */
+ struct S { int member; }; /* untilc23-error {{redefinition of 'S'}} */
- union U { int member; }; /* expected-note {{previous definition is here}} */
- union U { int member; }; /* expected-error {{redefinition of 'U'}} */
+ union U { int member; }; /* untilc23-note {{previous definition is here}} */
+ union U { int member; }; /* untilc23-error {{redefinition of 'U'}} */
- enum E { member }; /* expected-note 2{{previous definition is here}} */
- enum E { member }; /* expected-error {{redefinition of 'E'}}
- expected-error {{redefinition of enumerator 'member'}} */
+ enum E { member }; /* untilc23-note 2{{previous definition is here}} */
+ enum E { member }; /* untilc23-error {{redefinition of 'E'}}
+ untilc23-error {{redefinition of enumerator 'member'}} */
}
/* WG14 DR103: yes
>From e44a16221330e24a488b95caef3e05c0a9e749d6 Mon Sep 17 00:00:00 2001
From: Aaron Ballman <aaron at aaronballman.com>
Date: Mon, 24 Mar 2025 17:19:44 -0400
Subject: [PATCH 07/20] Add additional test cases
and simplify the implementation somewhat.
---
clang/lib/AST/ASTStructuralEquivalence.cpp | 39 ++++++++++----------
clang/test/C/C23/n3037.c | 42 ++++++++++++++++++++++
2 files changed, 60 insertions(+), 21 deletions(-)
diff --git a/clang/lib/AST/ASTStructuralEquivalence.cpp b/clang/lib/AST/ASTStructuralEquivalence.cpp
index 739079d906b36..c566447ebc809 100644
--- a/clang/lib/AST/ASTStructuralEquivalence.cpp
+++ b/clang/lib/AST/ASTStructuralEquivalence.cpp
@@ -492,9 +492,10 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
return SS1.str() == SS2.str();
}
-static bool CheckStructurallyEquivalentAttributes(
- StructuralEquivalenceContext &Context, const Decl *D1, const Decl *D2,
- unsigned ApplicableDiagnostic, const Decl *PrimaryDecl = nullptr) {
+static bool
+CheckStructurallyEquivalentAttributes(StructuralEquivalenceContext &Context,
+ const Decl *D1, const Decl *D2,
+ const Decl *PrimaryDecl = nullptr) {
// Gather the attributes and sort them by name so that they're in equivalent
// orders. This means that __attribute__((foo, bar)) is equivalent to
// __attribute__((bar, foo)).
@@ -522,7 +523,9 @@ static bool CheckStructurallyEquivalentAttributes(
++A1, ++A2) {
if (A2 == A2End) {
if (Context.Complain) {
- Context.Diag2(DiagnoseDecl->getLocation(), ApplicableDiagnostic)
+ Context.Diag2(DiagnoseDecl->getLocation(),
+ Context.getApplicableDiagnostic(
+ diag::err_odr_tag_type_inconsistent))
<< DiagnoseDecl << (&Context.FromCtx != &Context.ToCtx);
Context.Diag1((*A1)->getLocation(), diag::note_odr_attr) << *A1;
Context.Diag2(D2->getLocation(), diag::note_odr_attr_missing);
@@ -532,7 +535,9 @@ static bool CheckStructurallyEquivalentAttributes(
if (!IsStructurallyEquivalent(Context, *A1, *A2)) {
if (Context.Complain) {
- Context.Diag2(DiagnoseDecl->getLocation(), ApplicableDiagnostic)
+ Context.Diag2(DiagnoseDecl->getLocation(),
+ Context.getApplicableDiagnostic(
+ diag::err_odr_tag_type_inconsistent))
<< DiagnoseDecl << (&Context.FromCtx != &Context.ToCtx);
Context.Diag2((*A2)->getLocation(), diag::note_odr_attr) << *A2;
Context.Diag1((*A1)->getLocation(), diag::note_odr_attr) << *A1;
@@ -543,7 +548,9 @@ static bool CheckStructurallyEquivalentAttributes(
if (A2 != A2End) {
if (Context.Complain) {
- Context.Diag2(DiagnoseDecl->getLocation(), ApplicableDiagnostic)
+ Context.Diag2(
+ DiagnoseDecl->getLocation(),
+ Context.getApplicableDiagnostic(diag::err_odr_tag_type_inconsistent))
<< DiagnoseDecl << (&Context.FromCtx != &Context.ToCtx);
Context.Diag1(D1->getLocation(), diag::note_odr_attr_missing);
Context.Diag1((*A2)->getLocation(), diag::note_odr_attr) << *A2;
@@ -1607,10 +1614,7 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
// In C23 mode, check for structural equivalence of attributes on the fields.
// FIXME: Should this happen in C++ as well?
if (Context.LangOpts.C23 &&
- !CheckStructurallyEquivalentAttributes(
- Context, Field1, Field2,
- Context.getApplicableDiagnostic(diag::err_odr_tag_type_inconsistent),
- Owner2))
+ !CheckStructurallyEquivalentAttributes(Context, Field1, Field2, Owner2))
return false;
return true;
@@ -1803,9 +1807,7 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
// In C23 mode, check for structural equivalence of attributes on the record
// itself. FIXME: Should this happen in C++ as well?
if (Context.LangOpts.C23 &&
- !CheckStructurallyEquivalentAttributes(
- Context, D1, D2,
- Context.getApplicableDiagnostic(diag::err_odr_tag_type_inconsistent)))
+ !CheckStructurallyEquivalentAttributes(Context, D1, D2))
return false;
// If the records occur in different context (namespace), these should be
@@ -2049,9 +2051,7 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
return true;
if (Context.LangOpts.C23 &&
- !CheckStructurallyEquivalentAttributes(
- Context, D1, D2,
- Context.getApplicableDiagnostic(diag::err_odr_tag_type_inconsistent)))
+ !CheckStructurallyEquivalentAttributes(Context, D1, D2))
return false;
llvm::SmallVector<const EnumConstantDecl *, 8> D1Enums, D2Enums;
@@ -2108,11 +2108,8 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
}
return false;
}
- if (Context.LangOpts.C23 && !CheckStructurallyEquivalentAttributes(
- Context, *EC1, *EC2,
- Context.getApplicableDiagnostic(
- diag::err_odr_tag_type_inconsistent),
- D2))
+ if (Context.LangOpts.C23 &&
+ !CheckStructurallyEquivalentAttributes(Context, *EC1, *EC2, D2))
return false;
}
diff --git a/clang/test/C/C23/n3037.c b/clang/test/C/C23/n3037.c
index 19cc57c1c6297..c7edb7bc1fa3e 100644
--- a/clang/test/C/C23/n3037.c
+++ b/clang/test/C/C23/n3037.c
@@ -178,6 +178,48 @@ struct field_attr_test_5 { // c17-error {{redefinition of 'field_attr_test_5'}}
int y;
};
+// Show that attribute order does not matter.
+struct [[deprecated("testing"), gnu::packed]] field_attr_test_6 { // c17-note {{previous definition is here}}
+ int x;
+ int y;
+};
+
+struct [[gnu::packed, deprecated("testing")]] field_attr_test_6 { // c17-error {{redefinition of 'field_attr_test_6'}}
+ int x;
+ int y;
+};
+
+// Show that attribute syntax does matter.
+// FIXME: more clearly identify the syntax as the problem.
+struct [[gnu::packed]] field_attr_test_7 { // c17-note {{previous definition is here}} \
+ c23-note {{attribute 'packed' here}}
+ int x;
+ int y;
+};
+
+struct __attribute__((packed)) field_attr_test_7 { // c17-error {{redefinition of 'field_attr_test_7'}} \
+ c23-error {{type 'field_attr_test_7' has incompatible definitions}} \
+ c23-note {{attribute 'packed' here}}
+ int x;
+ int y;
+};
+
+// Show that attribute *spelling* matters. These two attributes share the same
+// implementation in the AST, but the spelling still matters.
+struct [[nodiscard]] field_attr_test_8 { // c17-note {{previous definition is here}} \
+ c23-note {{attribute 'nodiscard' here}}
+ int x;
+ int y;
+};
+
+struct [[gnu::warn_unused_result]] field_attr_test_8 { // c17-error {{redefinition of 'field_attr_test_8'}} \
+ c23-error {{type 'field_attr_test_8' has incompatible definitions}} \
+ c23-note {{attribute 'warn_unused_result' here}}
+ int x;
+ int y;
+};
+
+
// Show that equivalent field types are not an issue.
typedef int typedef_of_type_int;
struct equivalent_field_types { // c17-note {{previous definition is here}}
>From 980a9f7af4690389b953cf83f6012e91f60dad24 Mon Sep 17 00:00:00 2001
From: Aaron Ballman <aaron at aaronballman.com>
Date: Mon, 24 Mar 2025 17:37:02 -0400
Subject: [PATCH 08/20] Add codegen test
---
clang/test/C/C23/n3037_1.c | 48 ++++++++++++++++++++++++++++++++++++++
1 file changed, 48 insertions(+)
create mode 100644 clang/test/C/C23/n3037_1.c
diff --git a/clang/test/C/C23/n3037_1.c b/clang/test/C/C23/n3037_1.c
new file mode 100644
index 0000000000000..cbbeee75a5b96
--- /dev/null
+++ b/clang/test/C/C23/n3037_1.c
@@ -0,0 +1,48 @@
+// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --version 5
+// RUN: %clang_cc1 -std=c23 -emit-llvm -o - %s | FileCheck %s
+
+// This tests the codegen behavior of redefined tag types to ensure the
+// generated code looks reasonable.
+
+enum E {
+ One = 1,
+ Zero = 0,
+ Two = 2
+};
+
+struct S {
+ int x;
+ int y;
+};
+
+struct S func(struct S s, enum E e);
+
+struct S {
+ int x, y;
+} what();
+
+// CHECK-LABEL: define dso_local i64 @func(
+// CHECK-SAME: i64 [[S_COERCE:%.*]], i32 noundef [[E:%.*]]) #[[ATTR0:[0-9]+]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[RETVAL:%.*]] = alloca [[STRUCT_S:%.*]], align 4
+// CHECK-NEXT: [[S:%.*]] = alloca [[STRUCT_S]], align 4
+// CHECK-NEXT: [[E_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT: store i64 [[S_COERCE]], ptr [[S]], align 4
+// CHECK-NEXT: store i32 [[E]], ptr [[E_ADDR]], align 4
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[E_ADDR]], align 4
+// CHECK-NEXT: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_S]], ptr [[S]], i32 0, i32 0
+// CHECK-NEXT: store i32 [[TMP0]], ptr [[X]], align 4
+// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[RETVAL]], ptr align 4 [[S]], i64 8, i1 false)
+// CHECK-NEXT: [[TMP1:%.*]] = load i64, ptr [[RETVAL]], align 4
+// CHECK-NEXT: ret i64 [[TMP1]]
+//
+struct S func(struct S s, enum E e) {
+ s.x = (int)e;
+ return s;
+}
+
+enum E {
+ Zero,
+ One,
+ Two
+};
>From 255e4b865a34e399f3878a63d0c123dc0d97ab27 Mon Sep 17 00:00:00 2001
From: Aaron Ballman <aaron at aaronballman.com>
Date: Mon, 24 Mar 2025 17:47:24 -0400
Subject: [PATCH 09/20] Add an enum test with a fixed underlying type
---
clang/test/C/C23/n3037.c | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/clang/test/C/C23/n3037.c b/clang/test/C/C23/n3037.c
index c7edb7bc1fa3e..254b4bfd931d7 100644
--- a/clang/test/C/C23/n3037.c
+++ b/clang/test/C/C23/n3037.c
@@ -344,3 +344,10 @@ enum enum_attr_test_6 { // c17-error {{redefinition of 'enum_attr_test_6'}} \
EAT6 [[deprecated]] // c17-error {{redefinition of enumerator 'EAT6'}} \
c23-note {{attribute 'deprecated' here}}
};
+
+// You cannot declare one with a fixed underlying type and the other without a
+// fixed underlying type, or a different underlying type. However, it's worth
+// showing that the underlying type doesn't change the redefinition behavior.
+enum fixed_test_1 : int { FT1 }; // c17-note 2 {{previous definition is here}}
+enum fixed_test_1 : int { FT1 }; // c17-error {{redefinition of 'fixed_test_1'}} \
+ c17-error {{redefinition of enumerator 'FT1'}}
>From 2a57c85d883182c61206e620b5f4cd4ce2fdadb2 Mon Sep 17 00:00:00 2001
From: Aaron Ballman <aaron at aaronballman.com>
Date: Tue, 25 Mar 2025 08:21:21 -0400
Subject: [PATCH 10/20] Properly diagnose bit-fields
---
.../include/clang/Basic/DiagnosticASTKinds.td | 2 +
clang/lib/AST/ASTStructuralEquivalence.cpp | 37 +++++++++--
clang/test/C/C23/n3037.c | 64 +++++++++++++++++++
3 files changed, 96 insertions(+), 7 deletions(-)
diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td
index a5593ead29334..f5e22196867d9 100644
--- a/clang/include/clang/Basic/DiagnosticASTKinds.td
+++ b/clang/include/clang/Basic/DiagnosticASTKinds.td
@@ -494,6 +494,8 @@ def warn_odr_tag_type_inconsistent
def note_odr_tag_kind_here: Note<
"%0 is a %select{struct|interface|union|class|enum}1 here">;
def note_odr_field : Note<"field %0 has type %1 here">;
+def note_odr_field_bit_width : Note<"bit-field %0 has bit-width %1 here">;
+def note_odr_field_not_bit_field : Note<"field %0 is not a bit-field">;
def note_odr_field_name : Note<"field has name %0 here">;
def note_odr_missing_field : Note<"no corresponding field here">;
def note_odr_base : Note<"class has base type %0">;
diff --git a/clang/lib/AST/ASTStructuralEquivalence.cpp b/clang/lib/AST/ASTStructuralEquivalence.cpp
index c566447ebc809..f9a566c8d5d12 100644
--- a/clang/lib/AST/ASTStructuralEquivalence.cpp
+++ b/clang/lib/AST/ASTStructuralEquivalence.cpp
@@ -1565,6 +1565,12 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
QualType Owner2Type) {
const auto *Owner2 = cast<Decl>(Field2->getDeclContext());
+ // In C23 mode, check for structural equivalence of attributes on the fields.
+ // FIXME: Should this happen in C++ as well?
+ if (Context.LangOpts.C23 &&
+ !CheckStructurallyEquivalentAttributes(Context, Field1, Field2, Owner2))
+ return false;
+
// For anonymous structs/unions, match up the anonymous struct/union type
// declarations directly, so that we don't go off searching for anonymous
// types
@@ -1607,15 +1613,32 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
return false;
}
- if (Field1->isBitField())
- return IsStructurallyEquivalent(Context, Field1->getBitWidth(),
- Field2->getBitWidth());
+ if ((Field1->isBitField() || Field2->isBitField()) &&
+ !IsStructurallyEquivalent(Context, Field1->getBitWidth(),
+ Field2->getBitWidth())) {
+ auto DiagNote = [&](const FieldDecl *FD) {
+ if (FD->isBitField()) {
+ std::string Str;
+ llvm::raw_string_ostream OS(Str);
+ PrintingPolicy Policy(Context.LangOpts);
+ FD->getBitWidth()->printPretty(OS, nullptr, Policy);
- // In C23 mode, check for structural equivalence of attributes on the fields.
- // FIXME: Should this happen in C++ as well?
- if (Context.LangOpts.C23 &&
- !CheckStructurallyEquivalentAttributes(Context, Field1, Field2, Owner2))
+ Context.Diag2(FD->getLocation(), diag::note_odr_field_bit_width)
+ << FD->getDeclName() << OS.str();
+ } else {
+ Context.Diag2(FD->getLocation(), diag::note_odr_field_not_bit_field)
+ << FD->getDeclName();
+ }
+ };
+
+ Context.Diag2(
+ Owner2->getLocation(),
+ Context.getApplicableDiagnostic(diag::err_odr_tag_type_inconsistent))
+ << Owner2Type << (&Context.FromCtx != &Context.ToCtx);
+ DiagNote(Field2);
+ DiagNote(Field1);
return false;
+ }
return true;
}
diff --git a/clang/test/C/C23/n3037.c b/clang/test/C/C23/n3037.c
index 254b4bfd931d7..f67c6fb61284e 100644
--- a/clang/test/C/C23/n3037.c
+++ b/clang/test/C/C23/n3037.c
@@ -254,6 +254,70 @@ struct nested { // both-note {{previous definition is here}}
};
};
+// Show that bit-field order does matter, including anonymous bit-fields.
+struct bit_field_1 { // c17-note 2 {{previous definition is here}}
+ int a : 1;
+ int : 0; // c23-note {{field has name '' here}}
+ int b : 1;
+};
+
+struct bit_field_1 { // c17-error {{redefinition of 'bit_field_1'}}
+ int a : 1;
+ int : 0;
+ int b : 1;
+};
+
+struct bit_field_1 { // c17-error {{redefinition of 'bit_field_1'}} \
+ c23-error {{type 'struct bit_field_1' has incompatible definitions}}
+ int a : 1;
+ int b : 1; // c23-note {{field has name 'b' here}}
+};
+
+struct bit_field_2 { // c17-note {{previous definition is here}}
+ int a : 1;
+ int b : 1; // c23-note {{bit-field 'b' has bit-width 1 here}}
+};
+
+struct bit_field_2 { // c17-error {{redefinition of 'bit_field_2'}} \
+ c23-error {{type 'struct bit_field_2' has incompatible definitions}}
+ int a : 1;
+ int b : 2; // c23-note {{bit-field 'b' has bit-width 2 here}}
+};
+
+// Test a bit-field with an attribute.
+struct bit_field_3 { // c17-note {{previous definition is here}}
+ int a : 1;
+ int b : 1; // c23-note {{no corresponding attribute here}}
+};
+
+struct bit_field_3 { // c17-error {{redefinition of 'bit_field_3'}} \
+ c23-error {{type 'bit_field_3' has incompatible definitions}}
+ int a : 1;
+ [[deprecated]] int b : 1; // c23-note {{attribute 'deprecated' here}}
+};
+
+struct bit_field_4 { // c17-note {{previous definition is here}}
+ int a : 1;
+ int b : 1; // c23-note {{bit-field 'b' has bit-width 1 here}}
+};
+
+struct bit_field_4 { // c17-error {{redefinition of 'bit_field_4'}} \
+ c23-error {{type 'struct bit_field_4' has incompatible definitions}}
+ int a : 1;
+ int b; // c23-note {{field 'b' is not a bit-field}}
+};
+
+struct bit_field_5 { // c17-note {{previous definition is here}}
+ int a : 1;
+ int b; // c23-note {{field 'b' is not a bit-field}}
+};
+
+struct bit_field_5 { // c17-error {{redefinition of 'bit_field_5'}} \
+ c23-error {{type 'struct bit_field_5' has incompatible definitions}}
+ int a : 1;
+ int b : 1; // c23-note {{bit-field 'b' has bit-width 1 here}}
+};
+
enum E { A }; // c17-note 2 {{previous definition is here}}
enum E { A }; // c17-error {{redefinition of 'E'}} \
c17-error {{redefinition of enumerator 'A'}}
>From ea24cf3d1a927838dfbbc36b0d7db69145bde3b1 Mon Sep 17 00:00:00 2001
From: Aaron Ballman <aaron at aaronballman.com>
Date: Tue, 25 Mar 2025 08:26:36 -0400
Subject: [PATCH 11/20] Improve diagnostic consistency
---
clang/lib/AST/ASTStructuralEquivalence.cpp | 12 +++++----
clang/test/C/C23/n3037.c | 30 +++++++++++-----------
2 files changed, 22 insertions(+), 20 deletions(-)
diff --git a/clang/lib/AST/ASTStructuralEquivalence.cpp b/clang/lib/AST/ASTStructuralEquivalence.cpp
index f9a566c8d5d12..c0e0ecba72a22 100644
--- a/clang/lib/AST/ASTStructuralEquivalence.cpp
+++ b/clang/lib/AST/ASTStructuralEquivalence.cpp
@@ -517,8 +517,7 @@ CheckStructurallyEquivalentAttributes(StructuralEquivalenceContext &Context,
llvm::sort(Attrs2, Sorter);
auto A2 = Attrs2.begin(), A2End = Attrs2.end();
- const NamedDecl *DiagnoseDecl =
- cast<NamedDecl>(PrimaryDecl ? PrimaryDecl : D2);
+ const auto *DiagnoseDecl = cast<TypeDecl>(PrimaryDecl ? PrimaryDecl : D2);
for (auto A1 = Attrs1.begin(), A1End = Attrs1.end(); A1 != A1End;
++A1, ++A2) {
if (A2 == A2End) {
@@ -526,7 +525,8 @@ CheckStructurallyEquivalentAttributes(StructuralEquivalenceContext &Context,
Context.Diag2(DiagnoseDecl->getLocation(),
Context.getApplicableDiagnostic(
diag::err_odr_tag_type_inconsistent))
- << DiagnoseDecl << (&Context.FromCtx != &Context.ToCtx);
+ << Context.ToCtx.getTypeDeclType(DiagnoseDecl)
+ << (&Context.FromCtx != &Context.ToCtx);
Context.Diag1((*A1)->getLocation(), diag::note_odr_attr) << *A1;
Context.Diag2(D2->getLocation(), diag::note_odr_attr_missing);
}
@@ -538,7 +538,8 @@ CheckStructurallyEquivalentAttributes(StructuralEquivalenceContext &Context,
Context.Diag2(DiagnoseDecl->getLocation(),
Context.getApplicableDiagnostic(
diag::err_odr_tag_type_inconsistent))
- << DiagnoseDecl << (&Context.FromCtx != &Context.ToCtx);
+ << Context.ToCtx.getTypeDeclType(DiagnoseDecl)
+ << (&Context.FromCtx != &Context.ToCtx);
Context.Diag2((*A2)->getLocation(), diag::note_odr_attr) << *A2;
Context.Diag1((*A1)->getLocation(), diag::note_odr_attr) << *A1;
}
@@ -551,7 +552,8 @@ CheckStructurallyEquivalentAttributes(StructuralEquivalenceContext &Context,
Context.Diag2(
DiagnoseDecl->getLocation(),
Context.getApplicableDiagnostic(diag::err_odr_tag_type_inconsistent))
- << DiagnoseDecl << (&Context.FromCtx != &Context.ToCtx);
+ << Context.ToCtx.getTypeDeclType(DiagnoseDecl)
+ << (&Context.FromCtx != &Context.ToCtx);
Context.Diag1(D1->getLocation(), diag::note_odr_attr_missing);
Context.Diag1((*A2)->getLocation(), diag::note_odr_attr) << *A2;
}
diff --git a/clang/test/C/C23/n3037.c b/clang/test/C/C23/n3037.c
index f67c6fb61284e..46b706eb7725d 100644
--- a/clang/test/C/C23/n3037.c
+++ b/clang/test/C/C23/n3037.c
@@ -65,7 +65,7 @@ struct [[gnu::packed]] attr_test { // c17-note {{previous definition is here}} \
};
struct attr_test { // c17-error {{redefinition of 'attr_test'}} \
- c23-error {{type 'attr_test' has incompatible definitions}} \
+ c23-error {{type 'struct attr_test' has incompatible definitions}} \
c23-note {{no corresponding attribute here}}
int x;
};
@@ -76,7 +76,7 @@ struct attr_test_2 { // c17-note {{previous definition is here}} \
};
struct [[gnu::packed]] attr_test_2 { // c17-error {{redefinition of 'attr_test_2'}} \
- c23-error {{type 'attr_test_2' has incompatible definitions}} \
+ c23-error {{type 'struct attr_test_2' has incompatible definitions}} \
c23-note {{attribute 'packed' here}}
int x;
};
@@ -87,7 +87,7 @@ struct [[deprecated]] attr_test_3 { // c17-note {{previous definition is here}}
};
struct [[gnu::packed]] attr_test_3 { // c17-error {{redefinition of 'attr_test_3'}} \
- c23-error {{type 'attr_test_3' has incompatible definitions}} \
+ c23-error {{type 'struct attr_test_3' has incompatible definitions}} \
c23-note {{attribute 'packed' here}}
int x;
};
@@ -119,7 +119,7 @@ struct [[deprecated("testing")]] attr_test_6 { // c17-note {{previous definition
};
struct [[deprecated("oh no!")]] attr_test_6 { // c17-error {{redefinition of 'attr_test_6'}} \
- c23-error {{type 'attr_test_6' has incompatible definitions}} \
+ c23-error {{type 'struct attr_test_6' has incompatible definitions}} \
c23-note {{attribute 'deprecated' here}}
int x;
};
@@ -131,7 +131,7 @@ struct field_attr_test_1 { // c17-note {{previous definition is here}}
};
struct field_attr_test_1 { // c17-error {{redefinition of 'field_attr_test_1'}} \
- c23-error {{type 'field_attr_test_1' has incompatible definitions}}
+ c23-error {{type 'struct field_attr_test_1' has incompatible definitions}}
int x;
int y; // c23-note {{no corresponding attribute here}}
};
@@ -142,7 +142,7 @@ struct field_attr_test_2 { // c17-note {{previous definition is here}}
};
struct field_attr_test_2 { // c17-error {{redefinition of 'field_attr_test_2'}} \
- c23-error {{type 'field_attr_test_2' has incompatible definitions}}
+ c23-error {{type 'struct field_attr_test_2' has incompatible definitions}}
int x; // c23-note {{no corresponding attribute here}}
int y;
};
@@ -163,7 +163,7 @@ struct field_attr_test_4 { // c17-note {{previous definition is here}}
};
struct field_attr_test_4 { // c17-error {{redefinition of 'field_attr_test_4'}} \
- c23-error {{type 'field_attr_test_4' has incompatible definitions}}
+ c23-error {{type 'struct field_attr_test_4' has incompatible definitions}}
[[deprecated]] int x; // c23-note {{attribute 'deprecated' here}}
int y;
};
@@ -198,7 +198,7 @@ struct [[gnu::packed]] field_attr_test_7 { // c17-note {{previous definition is
};
struct __attribute__((packed)) field_attr_test_7 { // c17-error {{redefinition of 'field_attr_test_7'}} \
- c23-error {{type 'field_attr_test_7' has incompatible definitions}} \
+ c23-error {{type 'struct field_attr_test_7' has incompatible definitions}} \
c23-note {{attribute 'packed' here}}
int x;
int y;
@@ -213,7 +213,7 @@ struct [[nodiscard]] field_attr_test_8 { // c17-note {{previous definition is he
};
struct [[gnu::warn_unused_result]] field_attr_test_8 { // c17-error {{redefinition of 'field_attr_test_8'}} \
- c23-error {{type 'field_attr_test_8' has incompatible definitions}} \
+ c23-error {{type 'struct field_attr_test_8' has incompatible definitions}} \
c23-note {{attribute 'warn_unused_result' here}}
int x;
int y;
@@ -291,7 +291,7 @@ struct bit_field_3 { // c17-note {{previous definition is here}}
};
struct bit_field_3 { // c17-error {{redefinition of 'bit_field_3'}} \
- c23-error {{type 'bit_field_3' has incompatible definitions}}
+ c23-error {{type 'struct bit_field_3' has incompatible definitions}}
int a : 1;
[[deprecated]] int b : 1; // c23-note {{attribute 'deprecated' here}}
};
@@ -360,7 +360,7 @@ enum [[deprecated]] enum_attr_test_2 { // c17-note {{previous definition is here
};
enum enum_attr_test_2 { // c17-error {{redefinition of 'enum_attr_test_2'}} \
- c23-error {{type 'enum_attr_test_2' has incompatible definition}} \
+ c23-error {{type 'enum enum_attr_test_2' has incompatible definition}} \
c23-note {{no corresponding attribute here}}
EAT2 // c17-error {{redefinition of enumerator 'EAT2'}}
};
@@ -371,7 +371,7 @@ enum enum_attr_test_3 { // c17-note {{previous definition is here}} \
};
enum [[deprecated]] enum_attr_test_3 { // c17-error {{redefinition of 'enum_attr_test_3'}} \
- c23-error {{type 'enum_attr_test_3' has incompatible definition}} \
+ c23-error {{type 'enum enum_attr_test_3' has incompatible definition}} \
c23-note {{attribute 'deprecated' here}}
EAT3 // c17-error {{redefinition of enumerator 'EAT3'}}
};
@@ -382,7 +382,7 @@ enum [[clang::flag_enum]] enum_attr_test_4 { // c17-note {{previous definition i
};
enum [[deprecated]] enum_attr_test_4 { // c17-error {{redefinition of 'enum_attr_test_4'}} \
- c23-error {{type 'enum_attr_test_4' has incompatible definition}} \
+ c23-error {{type 'enum enum_attr_test_4' has incompatible definition}} \
c23-note {{attribute 'deprecated' here}}
EAT4 // c17-error {{redefinition of enumerator 'EAT4'}}
};
@@ -393,7 +393,7 @@ enum enum_attr_test_5 { // c17-note {{previous definition is here}}
};
enum enum_attr_test_5 { // c17-error {{redefinition of 'enum_attr_test_5'}} \
- c23-error {{type 'enum_attr_test_5' has incompatible definition}}
+ c23-error {{type 'enum enum_attr_test_5' has incompatible definition}}
EAT5 // c17-error {{redefinition of enumerator 'EAT5'}} \
c23-note {{no corresponding attribute here}}
};
@@ -404,7 +404,7 @@ enum enum_attr_test_6 { // c17-note {{previous definition is here}}
};
enum enum_attr_test_6 { // c17-error {{redefinition of 'enum_attr_test_6'}} \
- c23-error {{type 'enum_attr_test_6' has incompatible definition}}
+ c23-error {{type 'enum enum_attr_test_6' has incompatible definition}}
EAT6 [[deprecated]] // c17-error {{redefinition of enumerator 'EAT6'}} \
c23-note {{attribute 'deprecated' here}}
};
>From 350666b0e765f4aa976ec0d8219da969ce3f9386 Mon Sep 17 00:00:00 2001
From: Aaron Ballman <aaron at aaronballman.com>
Date: Tue, 25 Mar 2025 10:09:37 -0400
Subject: [PATCH 12/20] Fix failing tests
---
clang/lib/AST/ASTStructuralEquivalence.cpp | 45 ++++++++++++----------
clang/test/ASTMerge/struct/test.c | 12 +++++-
2 files changed, 36 insertions(+), 21 deletions(-)
diff --git a/clang/lib/AST/ASTStructuralEquivalence.cpp b/clang/lib/AST/ASTStructuralEquivalence.cpp
index c0e0ecba72a22..c3a0980edc1ef 100644
--- a/clang/lib/AST/ASTStructuralEquivalence.cpp
+++ b/clang/lib/AST/ASTStructuralEquivalence.cpp
@@ -1618,27 +1618,32 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
if ((Field1->isBitField() || Field2->isBitField()) &&
!IsStructurallyEquivalent(Context, Field1->getBitWidth(),
Field2->getBitWidth())) {
- auto DiagNote = [&](const FieldDecl *FD) {
- if (FD->isBitField()) {
- std::string Str;
- llvm::raw_string_ostream OS(Str);
- PrintingPolicy Policy(Context.LangOpts);
- FD->getBitWidth()->printPretty(OS, nullptr, Policy);
-
- Context.Diag2(FD->getLocation(), diag::note_odr_field_bit_width)
- << FD->getDeclName() << OS.str();
- } else {
- Context.Diag2(FD->getLocation(), diag::note_odr_field_not_bit_field)
- << FD->getDeclName();
- }
- };
+ if (Context.Complain) {
+ auto DiagNote = [&](const FieldDecl *FD,
+ DiagnosticBuilder (
+ StructuralEquivalenceContext::*Diag)(
+ SourceLocation, unsigned)) {
+ if (FD->isBitField()) {
+ std::string Str;
+ llvm::raw_string_ostream OS(Str);
+ PrintingPolicy Policy(Context.LangOpts);
+ FD->getBitWidth()->printPretty(OS, nullptr, Policy);
+
+ (Context.*Diag)(FD->getLocation(), diag::note_odr_field_bit_width)
+ << FD->getDeclName() << OS.str();
+ } else {
+ (Context.*Diag)(FD->getLocation(), diag::note_odr_field_not_bit_field)
+ << FD->getDeclName();
+ }
+ };
- Context.Diag2(
- Owner2->getLocation(),
- Context.getApplicableDiagnostic(diag::err_odr_tag_type_inconsistent))
- << Owner2Type << (&Context.FromCtx != &Context.ToCtx);
- DiagNote(Field2);
- DiagNote(Field1);
+ Context.Diag2(
+ Owner2->getLocation(),
+ Context.getApplicableDiagnostic(diag::err_odr_tag_type_inconsistent))
+ << Owner2Type << (&Context.FromCtx != &Context.ToCtx);
+ DiagNote(Field2, &StructuralEquivalenceContext::Diag2);
+ DiagNote(Field1, &StructuralEquivalenceContext::Diag1);
+ }
return false;
}
diff --git a/clang/test/ASTMerge/struct/test.c b/clang/test/ASTMerge/struct/test.c
index 4dfa74737eed8..10ea753b142bd 100644
--- a/clang/test/ASTMerge/struct/test.c
+++ b/clang/test/ASTMerge/struct/test.c
@@ -21,6 +21,16 @@
// CHECK: struct1.c:27:8: note: no corresponding field here
// CHECK: struct2.c:24:31: warning: external variable 'x4' declared with incompatible types in different translation units ('struct S4' vs. 'struct S4')
// CHECK: struct1.c:27:22: note: declared here with type 'struct S4'
+// CHECK: struct1.c:33:8: warning: type 'struct S6' has incompatible definitions in different translation units
+// CHECK: struct1.c:33:33: note: bit-field 'j' has bit-width 8 here
+// CHECK: struct2.c:30:33: note: field 'j' is not a bit-field
+// CHECK: struct2.c:30:38: warning: external variable 'x6' declared with incompatible types in different translation units ('struct S6' vs. 'struct S6')
+// CHECK: struct1.c:33:42: note: declared here with type 'struct S6'
+// CHECK: struct1.c:36:8: warning: type 'struct S7' has incompatible definitions in different translation units
+// CHECK: struct1.c:36:33: note: bit-field 'j' has bit-width 8 here
+// CHECK: struct2.c:33:33: note: bit-field 'j' has bit-width 16 here
+// CHECK: struct2.c:33:43: warning: external variable 'x7' declared with incompatible types in different translation units ('struct S7' vs. 'struct S7')
+// CHECK: struct1.c:36:42: note: declared here with type 'struct S7'
// CHECK: struct1.c:56:10: warning: type 'struct DeeperError' has incompatible definitions in different translation units
// CHECK: struct1.c:56:35: note: field 'f' has type 'int' here
// CHECK: struct2.c:53:37: note: field 'f' has type 'float' here
@@ -42,4 +52,4 @@
// CHECK: struct2.c:129:9: note: field 'S' has type 'struct (unnamed struct at [[PATH_TO_INPUTS]]struct2.c:127:7)' here
// CHECK: struct2.c:138:3: warning: external variable 'x16' declared with incompatible types in different translation units ('struct DeepUnnamedError' vs. 'struct DeepUnnamedError')
// CHECK: struct1.c:141:3: note: declared here with type 'struct DeepUnnamedError'
-// CHECK: 17 warnings generated
+// CHECK: 20 warnings generated
>From eac0f5c01b68dfc8271e7eb62d0597f3c12ed8f8 Mon Sep 17 00:00:00 2001
From: Aaron Ballman <aaron at aaronballman.com>
Date: Tue, 25 Mar 2025 10:18:59 -0400
Subject: [PATCH 13/20] Add another test, crank up diagnostic levels
---
clang/test/C/C23/n3037.c | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/clang/test/C/C23/n3037.c b/clang/test/C/C23/n3037.c
index 46b706eb7725d..84f04d85d9c73 100644
--- a/clang/test/C/C23/n3037.c
+++ b/clang/test/C/C23/n3037.c
@@ -1,5 +1,5 @@
-// RUN: %clang_cc1 -fsyntax-only -std=c23 -verify=both,c23 %s
-// RUN: %clang_cc1 -fsyntax-only -std=c17 -verify=both,c17 %s
+// RUN: %clang_cc1 -fsyntax-only -std=c23 -pedantic -Wall -Wno-comment -verify=both,c23 %s
+// RUN: %clang_cc1 -fsyntax-only -std=c17 -pedantic -Wall -Wno-comment -Wno-c23-extensions -verify=both,c17 %s
/* WG14 N3037:
* Improved tag compatibility
@@ -13,7 +13,7 @@ struct foo { int a; } p;
void baz(struct foo f); // c17-note {{passing argument to parameter 'f' here}}
void bar(void) {
- struct foo { int a; } q;
+ struct foo { int a; } q = {};
baz(q); // c17-error {{passing 'struct foo' to parameter of incompatible type 'struct foo'}}
}
@@ -415,3 +415,7 @@ enum enum_attr_test_6 { // c17-error {{redefinition of 'enum_attr_test_6'}} \
enum fixed_test_1 : int { FT1 }; // c17-note 2 {{previous definition is here}}
enum fixed_test_1 : int { FT1 }; // c17-error {{redefinition of 'fixed_test_1'}} \
c17-error {{redefinition of enumerator 'FT1'}}
+
+enum fixed_test_2 : int { FT2 }; // c17-note 2 {{previous definition is here}}
+enum fixed_test_2 : typedef_of_type_int { FT2 }; // c17-error {{redefinition of 'fixed_test_2'}} \
+ c17-error {{redefinition of enumerator 'FT2'}}
>From 9b3b5b09d8c39f1b6664a34ff96e5e0e6b20ff0c Mon Sep 17 00:00:00 2001
From: Aaron Ballman <aaron at aaronballman.com>
Date: Tue, 25 Mar 2025 10:39:23 -0400
Subject: [PATCH 14/20] Even more test cases
---
clang/test/C/C23/n3037.c | 35 +++++++++++++++++++++++++++++++++++
1 file changed, 35 insertions(+)
diff --git a/clang/test/C/C23/n3037.c b/clang/test/C/C23/n3037.c
index 84f04d85d9c73..a6eafe3634ef8 100644
--- a/clang/test/C/C23/n3037.c
+++ b/clang/test/C/C23/n3037.c
@@ -419,3 +419,38 @@ enum fixed_test_1 : int { FT1 }; // c17-error {{redefinition of 'fixed_test_1'}}
enum fixed_test_2 : int { FT2 }; // c17-note 2 {{previous definition is here}}
enum fixed_test_2 : typedef_of_type_int { FT2 }; // c17-error {{redefinition of 'fixed_test_2'}} \
c17-error {{redefinition of enumerator 'FT2'}}
+
+// Test more bizarre situations in terms of where the type is declared. This
+// has always been allowed.
+struct declared_funny_1 { int x; }
+declared_funny_func(struct declared_funny_1 { int x; } arg) { // both-warning {{declaration of 'struct declared_funny_1' will not be visible outside of this function}}
+ return declared_funny_func((__typeof__(arg)){ 0 });
+}
+
+// However, this is new.
+struct Outer {
+ struct Inner { // c17-note {{previous definition is here}}
+ int x;
+ } i;
+
+ enum InnerEnum { // c17-note {{previous definition is here}}
+ IE1 // c17-note {{previous definition is here}}
+ } j;
+};
+
+struct Inner { // c17-error {{redefinition of 'Inner'}}
+ int x;
+};
+
+enum InnerEnum { // c17-error {{redefinition of 'InnerEnum'}}
+ IE1 // c17-error {{redefinition of enumerator 'IE1'}}
+};
+
+void hidden(void) {
+ struct hidden_struct { int x; };
+}
+
+struct hidden_struct { // This is fine because the previous declaration is not visible.
+ int y;
+ int z;
+};
>From 1543f7561314abc5d5dbe0834d8bc2792b1c16f8 Mon Sep 17 00:00:00 2001
From: Aaron Ballman <aaron at aaronballman.com>
Date: Tue, 25 Mar 2025 11:25:01 -0400
Subject: [PATCH 15/20] Add additional test coverage
---
clang/test/C/C23/n3037.c | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/clang/test/C/C23/n3037.c b/clang/test/C/C23/n3037.c
index a6eafe3634ef8..32966bc0b2000 100644
--- a/clang/test/C/C23/n3037.c
+++ b/clang/test/C/C23/n3037.c
@@ -454,3 +454,19 @@ struct hidden_struct { // This is fine because the previous declaration is not v
int y;
int z;
};
+
+struct array { int y; int x[]; }; // c17-note {{previous definition is here}} \
+ c23-note {{field 'x' has type 'int[]' here}}
+struct array { int y; int x[0]; }; // c17-error {{redefinition of 'array'}} \
+ c23-error {{type 'struct array' has incompatible definitions}} \
+ c23-note {{field 'x' has type 'int[0]' here}} \
+ both-warning {{zero size arrays are an extension}}
+
+struct alignment { // c17-note {{previous definition is here}}
+ _Alignas(int) int x; // c23-note {{attribute '_Alignas' here}}
+};
+
+struct alignment { // c17-error {{redefinition of 'alignment'}} \
+ c23-error {{type 'struct alignment' has incompatible definition}}
+ int x; // c23-note {{no corresponding attribute here}}
+};
>From 557d7268f982a54a75d9bfcd880395c3dcf87393 Mon Sep 17 00:00:00 2001
From: Aaron Ballman <aaron at aaronballman.com>
Date: Tue, 25 Mar 2025 12:25:07 -0400
Subject: [PATCH 16/20] Restore code that was still used, just not in an
obvious way
---
clang/lib/AST/ASTStructuralEquivalence.cpp | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/clang/lib/AST/ASTStructuralEquivalence.cpp b/clang/lib/AST/ASTStructuralEquivalence.cpp
index c3a0980edc1ef..3f0bd8d8f5a8b 100644
--- a/clang/lib/AST/ASTStructuralEquivalence.cpp
+++ b/clang/lib/AST/ASTStructuralEquivalence.cpp
@@ -2064,6 +2064,26 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
return true;
}
+static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
+ EnumConstantDecl *D1,
+ EnumConstantDecl *D2) {
+ const llvm::APSInt &FromVal = D1->getInitVal();
+ const llvm::APSInt &ToVal = D2->getInitVal();
+ if (FromVal.isSigned() != ToVal.isSigned())
+ return false;
+ if (FromVal.getBitWidth() != ToVal.getBitWidth())
+ return false;
+ if (FromVal != ToVal)
+ return false;
+
+ if (!IsStructurallyEquivalent(D1->getIdentifier(), D2->getIdentifier()))
+ return false;
+
+ // Init expressions are the most expensive check, so do them last.
+ return IsStructurallyEquivalent(Context, D1->getInitExpr(),
+ D2->getInitExpr());
+}
+
/// Determine structural equivalence of two enums.
static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
EnumDecl *D1, EnumDecl *D2) {
>From f37909e83943292d3d5ec74325ea73000594d316 Mon Sep 17 00:00:00 2001
From: Aaron Ballman <aaron at aaronballman.com>
Date: Tue, 25 Mar 2025 15:01:51 -0400
Subject: [PATCH 17/20] Change formatting based on review feedback
---
clang/lib/AST/ASTContext.cpp | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index 98d9b05cac963..bd50a16841200 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -11444,8 +11444,8 @@ QualType ASTContext::mergeTagTypes(QualType LHS, QualType RHS) {
StructuralEquivalenceContext::NonEquivalentDeclSet NonEquivalentDecls;
StructuralEquivalenceContext Ctx(
getLangOpts(), *this, *this, NonEquivalentDecls,
- StructuralEquivalenceKind::Default, false /*StrictTypeSpelling*/,
- false /*Complain*/, true /*ErrorOnTagTypeMismatch*/);
+ StructuralEquivalenceKind::Default, /*StrictTypeSpelling*/ false,
+ /*Complain*/ false, /*ErrorOnTagTypeMismatch*/ true);
return Ctx.IsEquivalent(LHS, RHS) ? LHS : QualType{};
}
>From ffe77b567bba5d8f1ea4b7059c78bd4157df7a78 Mon Sep 17 00:00:00 2001
From: Aaron Ballman <aaron at aaronballman.com>
Date: Wed, 26 Mar 2025 08:06:27 -0400
Subject: [PATCH 18/20] Fix comments; NFC
---
clang/lib/AST/ASTContext.cpp | 4 +-
clang/lib/Sema/SemaType.cpp | 4 +-
clang/lib/Serialization/ASTReaderDecl.cpp | 6 +--
.../AST/StructuralEquivalenceTest.cpp | 48 ++++++++++++-------
4 files changed, 39 insertions(+), 23 deletions(-)
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index bd50a16841200..00b8bf4ae513a 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -11444,8 +11444,8 @@ QualType ASTContext::mergeTagTypes(QualType LHS, QualType RHS) {
StructuralEquivalenceContext::NonEquivalentDeclSet NonEquivalentDecls;
StructuralEquivalenceContext Ctx(
getLangOpts(), *this, *this, NonEquivalentDecls,
- StructuralEquivalenceKind::Default, /*StrictTypeSpelling*/ false,
- /*Complain*/ false, /*ErrorOnTagTypeMismatch*/ true);
+ StructuralEquivalenceKind::Default, /*StrictTypeSpelling=*/ false,
+ /*Complain=*/ false, /*ErrorOnTagTypeMismatch=*/ true);
return Ctx.IsEquivalent(LHS, RHS) ? LHS : QualType{};
}
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index 25d348dbc153e..8ad0376a693cd 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -9091,8 +9091,8 @@ bool Sema::hasStructuralCompatLayout(Decl *D, Decl *Suggested) {
StructuralEquivalenceContext Ctx(
getLangOpts(), D->getASTContext(), Suggested->getASTContext(),
NonEquivalentDecls, StructuralEquivalenceKind::Default,
- false /*StrictTypeSpelling*/, true /*Complain*/,
- true /*ErrorOnTagTypeMismatch*/);
+ /*StrictTypeSpelling=*/false, /*Complain=*/true,
+ /*ErrorOnTagTypeMismatch=*/true);
return Ctx.IsEquivalent(D, Suggested);
}
diff --git a/clang/lib/Serialization/ASTReaderDecl.cpp b/clang/lib/Serialization/ASTReaderDecl.cpp
index 471e0d25782cc..51acb3df05266 100644
--- a/clang/lib/Serialization/ASTReaderDecl.cpp
+++ b/clang/lib/Serialization/ASTReaderDecl.cpp
@@ -4570,9 +4570,9 @@ namespace {
Reader.getContext().getLangOpts(), Cat->getASTContext(),
Existing->getASTContext(), NonEquivalentDecls,
StructuralEquivalenceKind::Default,
- /*StrictTypeSpelling =*/false,
- /*Complain =*/false,
- /*ErrorOnTagTypeMismatch =*/true);
+ /*StrictTypeSpelling=*/false,
+ /*Complain=*/false,
+ /*ErrorOnTagTypeMismatch=*/true);
if (!Ctx.IsEquivalent(Cat, Existing)) {
// Warn only if the categories with the same name are different.
Reader.Diag(Cat->getLocation(), diag::warn_dup_category_def)
diff --git a/clang/unittests/AST/StructuralEquivalenceTest.cpp b/clang/unittests/AST/StructuralEquivalenceTest.cpp
index 41e32acb833f3..ef82afaf3f8dc 100644
--- a/clang/unittests/AST/StructuralEquivalenceTest.cpp
+++ b/clang/unittests/AST/StructuralEquivalenceTest.cpp
@@ -157,12 +157,16 @@ struct StructuralEquivalenceTest : ::testing::Test {
bool testStructuralMatch(StmtWithASTContext S0, StmtWithASTContext S1) {
StructuralEquivalenceContext::NonEquivalentDeclSet NonEquivalentDecls01;
StructuralEquivalenceContext::NonEquivalentDeclSet NonEquivalentDecls10;
- StructuralEquivalenceContext Ctx01(
- S0.Context->getLangOpts(), *S0.Context, *S1.Context,
- NonEquivalentDecls01, StructuralEquivalenceKind::Default, false, false);
- StructuralEquivalenceContext Ctx10(
- S1.Context->getLangOpts(), *S1.Context, *S0.Context,
- NonEquivalentDecls10, StructuralEquivalenceKind::Default, false, false);
+ StructuralEquivalenceContext Ctx01(S0.Context->getLangOpts(), *S0.Context,
+ *S1.Context, NonEquivalentDecls01,
+ StructuralEquivalenceKind::Default,
+ /*StrictTypeSpelling=*/false,
+ /*Complain=*/false);
+ StructuralEquivalenceContext Ctx10(S1.Context->getLangOpts(), *S1.Context,
+ *S0.Context, NonEquivalentDecls10,
+ StructuralEquivalenceKind::Default,
+ /*StrictTypeSpelling=*/false,
+ /*Complain=*/false);
bool Eq01 = Ctx01.IsEquivalent(S0.S, S1.S);
bool Eq10 = Ctx10.IsEquivalent(S1.S, S0.S);
EXPECT_EQ(Eq01, Eq10);
@@ -1830,7 +1834,8 @@ TEST_F(StructuralEquivalenceCacheTest, SimpleNonEq) {
StructuralEquivalenceContext Ctx(
get<0>(TU)->getASTContext().getLangOpts(), get<0>(TU)->getASTContext(),
get<1>(TU)->getASTContext(), NonEquivalentDecls,
- StructuralEquivalenceKind::Default, false, false);
+ StructuralEquivalenceKind::Default, /*StrictTypeSpelling=*/false,
+ /*Complain=*/false);
auto X = findDeclPair<FunctionDecl>(TU, functionDecl(hasName("x")));
EXPECT_FALSE(Ctx.IsEquivalent(X.first, X.second));
@@ -1854,7 +1859,8 @@ TEST_F(StructuralEquivalenceCacheTest, ReturnStmtNonEq) {
StructuralEquivalenceContext Ctx(
get<0>(TU)->getASTContext().getLangOpts(), get<0>(TU)->getASTContext(),
get<1>(TU)->getASTContext(), NonEquivalentDecls,
- StructuralEquivalenceKind::Default, false, false);
+ StructuralEquivalenceKind::Default, /*StrictTypeSpelling=*/false,
+ /*Complain=*/false);
auto X = findDeclPair<FunctionDecl>(TU, functionDecl(hasName("x")));
EXPECT_FALSE(Ctx.IsEquivalent(X.first->getBody(), X.second->getBody()));
@@ -1874,7 +1880,8 @@ TEST_F(StructuralEquivalenceCacheTest, VarDeclNoEq) {
StructuralEquivalenceContext Ctx(
get<0>(TU)->getASTContext().getLangOpts(), get<0>(TU)->getASTContext(),
get<1>(TU)->getASTContext(), NonEquivalentDecls,
- StructuralEquivalenceKind::Default, false, false);
+ StructuralEquivalenceKind::Default, /*StrictTypeSpelling=*/false,
+ /*Complain=*/false);
auto Var = findDeclPair<VarDecl>(TU, varDecl());
EXPECT_FALSE(Ctx.IsEquivalent(Var.first, Var.second));
@@ -1893,7 +1900,8 @@ TEST_F(StructuralEquivalenceCacheTest, VarDeclWithDifferentStorageClassNoEq) {
StructuralEquivalenceContext Ctx(
get<0>(TU)->getASTContext().getLangOpts(), get<0>(TU)->getASTContext(),
get<1>(TU)->getASTContext(), NonEquivalentDecls,
- StructuralEquivalenceKind::Default, false, false);
+ StructuralEquivalenceKind::Default, /*StrictTypeSpelling=*/false,
+ /*Complain=*/false);
auto Var = findDeclPair<VarDecl>(TU, varDecl());
EXPECT_FALSE(Ctx.IsEquivalent(Var.first, Var.second));
@@ -1921,7 +1929,8 @@ TEST_F(StructuralEquivalenceCacheTest,
StructuralEquivalenceContext Ctx(
get<0>(TU)->getASTContext().getLangOpts(), get<0>(TU)->getASTContext(),
get<1>(TU)->getASTContext(), NonEquivalentDecls,
- StructuralEquivalenceKind::Default, false, false);
+ StructuralEquivalenceKind::Default, /*StrictTypeSpelling=*/false,
+ /*Complain=*/false);
auto NTTP = findDeclPair<NonTypeTemplateParmDecl>(
TU, nonTypeTemplateParmDecl(hasName("T")));
@@ -1941,7 +1950,8 @@ TEST_F(StructuralEquivalenceCacheTest, VarDeclWithInitNoEq) {
StructuralEquivalenceContext Ctx(
get<0>(TU)->getASTContext().getLangOpts(), get<0>(TU)->getASTContext(),
get<1>(TU)->getASTContext(), NonEquivalentDecls,
- StructuralEquivalenceKind::Default, false, false);
+ StructuralEquivalenceKind::Default, /*StrictTypeSpelling=*/false,
+ /*Complain=*/false);
auto Var = findDeclPair<VarDecl>(TU, varDecl());
EXPECT_FALSE(Ctx.IsEquivalent(Var.first, Var.second));
@@ -1974,7 +1984,8 @@ TEST_F(StructuralEquivalenceCacheTest, SpecialNonEq) {
StructuralEquivalenceContext Ctx(
get<0>(TU)->getASTContext().getLangOpts(), get<0>(TU)->getASTContext(),
get<1>(TU)->getASTContext(), NonEquivalentDecls,
- StructuralEquivalenceKind::Default, false, false);
+ StructuralEquivalenceKind::Default, /*StrictTypeSpelling=*/false,
+ /*Complain=*/false);
auto C = findDeclPair<CXXRecordDecl>(
TU, cxxRecordDecl(hasName("C"), unless(isImplicit())));
@@ -2014,7 +2025,8 @@ TEST_F(StructuralEquivalenceCacheTest, Cycle) {
StructuralEquivalenceContext Ctx(
get<0>(TU)->getASTContext().getLangOpts(), get<0>(TU)->getASTContext(),
get<1>(TU)->getASTContext(), NonEquivalentDecls,
- StructuralEquivalenceKind::Default, false, false);
+ StructuralEquivalenceKind::Default, /*StrictTypeSpelling=*/false,
+ /*Complain=*/false);
auto C = findDeclPair<CXXRecordDecl>(
TU, cxxRecordDecl(hasName("C"), unless(isImplicit())));
@@ -2078,7 +2090,9 @@ TEST_F(StructuralEquivalenceCacheTest, TemplateParmDepth) {
StructuralEquivalenceContext Ctx_NoIgnoreTemplateParmDepth(
get<0>(TU)->getASTContext().getLangOpts(), get<0>(TU)->getASTContext(),
get<1>(TU)->getASTContext(), NonEquivalentDecls,
- StructuralEquivalenceKind::Default, false, false, false, false);
+ StructuralEquivalenceKind::Default, /*StrictTypeSpelling=*/false,
+ /*Complain=*/false, /*ErrorOnTagTypeMismatch=*/false,
+ /*IgnoreTemplateParmDepth=*/false);
EXPECT_FALSE(Ctx_NoIgnoreTemplateParmDepth.IsEquivalent(D0, D1));
@@ -2092,7 +2106,9 @@ TEST_F(StructuralEquivalenceCacheTest, TemplateParmDepth) {
StructuralEquivalenceContext Ctx_IgnoreTemplateParmDepth(
get<0>(TU)->getASTContext().getLangOpts(), get<0>(TU)->getASTContext(),
get<1>(TU)->getASTContext(), NonEquivalentDecls,
- StructuralEquivalenceKind::Default, false, false, false, true);
+ StructuralEquivalenceKind::Default, /*StrictTypeSpelling=*/false,
+ /*Complain=*/false, /*ErrorOnTagTypeMismatch=*/false,
+ /*IgnoreTemplateParmDepth=*/true);
EXPECT_TRUE(Ctx_IgnoreTemplateParmDepth.IsEquivalent(D0, D1));
>From d6b390faa8e5253c82dff8fce9fd2af917c477be Mon Sep 17 00:00:00 2001
From: Aaron Ballman <aaron at aaronballman.com>
Date: Wed, 26 Mar 2025 08:19:04 -0400
Subject: [PATCH 19/20] Remove unused code
---
clang/include/clang/Basic/DiagnosticASTKinds.td | 4 ----
clang/lib/AST/ASTStructuralEquivalence.cpp | 2 --
2 files changed, 6 deletions(-)
diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td
index f5e22196867d9..44deb4ef66825 100644
--- a/clang/include/clang/Basic/DiagnosticASTKinds.td
+++ b/clang/include/clang/Basic/DiagnosticASTKinds.td
@@ -478,10 +478,6 @@ def warn_odr_function_type_inconsistent : Warning<
"external function %0 declared with incompatible types in different "
"translation units (%1 vs. %2)">,
InGroup<ODR>;
-def err_odr_attr_inconsistent : Error<
- "attribute %0 is incompatible%select{| in different translation units}1">;
-def warn_odr_attr_inconsistent : Warning<
- err_odr_attr_inconsistent.Summary>, InGroup<ODR>;
def note_odr_attr : Note<"attribute %0 here">;
def note_odr_attr_missing : Note<"no corresponding attribute here">;
def err_odr_tag_type_inconsistent
diff --git a/clang/lib/AST/ASTStructuralEquivalence.cpp b/clang/lib/AST/ASTStructuralEquivalence.cpp
index 3f0bd8d8f5a8b..d9d9772efad04 100644
--- a/clang/lib/AST/ASTStructuralEquivalence.cpp
+++ b/clang/lib/AST/ASTStructuralEquivalence.cpp
@@ -2609,8 +2609,6 @@ unsigned StructuralEquivalenceContext::getApplicableDiagnostic(
return ErrorDiagnostic;
switch (ErrorDiagnostic) {
- case diag::err_odr_attr_inconsistent:
- return diag::warn_odr_attr_inconsistent;
case diag::err_odr_variable_type_inconsistent:
return diag::warn_odr_variable_type_inconsistent;
case diag::err_odr_variable_multiple_def:
>From 2bde3a9c6eb5e23b0e9274122bd6f9a50ffa3bc4 Mon Sep 17 00:00:00 2001
From: Aaron Ballman <aaron at aaronballman.com>
Date: Wed, 26 Mar 2025 08:30:05 -0400
Subject: [PATCH 20/20] Fix bit-width checking and add a test case
---
clang/lib/AST/ASTStructuralEquivalence.cpp | 16 +++++++++-------
clang/test/C/C23/n3037.c | 13 +++++++++++++
2 files changed, 22 insertions(+), 7 deletions(-)
diff --git a/clang/lib/AST/ASTStructuralEquivalence.cpp b/clang/lib/AST/ASTStructuralEquivalence.cpp
index d9d9772efad04..dca9d88ab441d 100644
--- a/clang/lib/AST/ASTStructuralEquivalence.cpp
+++ b/clang/lib/AST/ASTStructuralEquivalence.cpp
@@ -1618,19 +1618,21 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
if ((Field1->isBitField() || Field2->isBitField()) &&
!IsStructurallyEquivalent(Context, Field1->getBitWidth(),
Field2->getBitWidth())) {
- if (Context.Complain) {
+ // Two bit-fields can be structurally unequivalent but still be okay for
+ // the purposes of C where they simply need to have the same values, not
+ // the same token sequences.
+ bool Diagnose = true;
+ if (Context.LangOpts.C23 && Field1->isBitField() && Field2->isBitField())
+ Diagnose = Field1->getBitWidthValue() != Field2->getBitWidthValue();
+
+ if (Diagnose && Context.Complain) {
auto DiagNote = [&](const FieldDecl *FD,
DiagnosticBuilder (
StructuralEquivalenceContext::*Diag)(
SourceLocation, unsigned)) {
if (FD->isBitField()) {
- std::string Str;
- llvm::raw_string_ostream OS(Str);
- PrintingPolicy Policy(Context.LangOpts);
- FD->getBitWidth()->printPretty(OS, nullptr, Policy);
-
(Context.*Diag)(FD->getLocation(), diag::note_odr_field_bit_width)
- << FD->getDeclName() << OS.str();
+ << FD->getDeclName() << FD->getBitWidthValue();
} else {
(Context.*Diag)(FD->getLocation(), diag::note_odr_field_not_bit_field)
<< FD->getDeclName();
diff --git a/clang/test/C/C23/n3037.c b/clang/test/C/C23/n3037.c
index 32966bc0b2000..3c6cc4d13b75a 100644
--- a/clang/test/C/C23/n3037.c
+++ b/clang/test/C/C23/n3037.c
@@ -318,6 +318,14 @@ struct bit_field_5 { // c17-error {{redefinition of 'bit_field_5'}} \
int b : 1; // c23-note {{bit-field 'b' has bit-width 1 here}}
};
+struct bit_field_6 { // c17-note {{previous definition is here}}
+ int a : 2;
+};
+
+struct bit_field_6 { // c17-error {{redefinition of 'bit_field_6'}}
+ int a : 1 + 1;
+};
+
enum E { A }; // c17-note 2 {{previous definition is here}}
enum E { A }; // c17-error {{redefinition of 'E'}} \
c17-error {{redefinition of enumerator 'A'}}
@@ -462,6 +470,11 @@ struct array { int y; int x[0]; }; // c17-error {{redefinition of 'array'}} \
c23-note {{field 'x' has type 'int[0]' here}} \
both-warning {{zero size arrays are an extension}}
+// So long as the bounds are the same value, everything is fine. They do not
+// have to be token equivalent.
+struct array_2 { int y; int x[3]; }; // c17-note {{previous definition is here}}
+struct array_2 { int y; int x[1 + 1 + 1]; }; // c17-error {{redefinition of 'array_2'}}
+
struct alignment { // c17-note {{previous definition is here}}
_Alignas(int) int x; // c23-note {{attribute '_Alignas' here}}
};
More information about the cfe-commits
mailing list