[clang] [C23] Implement WG14 N3037 (PR #132939)

Aaron Ballman via cfe-commits cfe-commits at lists.llvm.org
Thu Apr 3 04:25:11 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/26] [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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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}}
 };

>From d4fb30bc765588cafc25b1dbc4a9ddbef88e5a9a Mon Sep 17 00:00:00 2001
From: Aaron Ballman <aaron at aaronballman.com>
Date: Wed, 26 Mar 2025 08:38:01 -0400
Subject: [PATCH 21/26] Use back_inserter; NFC

---
 clang/lib/AST/ASTStructuralEquivalence.cpp | 8 ++------
 1 file changed, 2 insertions(+), 6 deletions(-)

diff --git a/clang/lib/AST/ASTStructuralEquivalence.cpp b/clang/lib/AST/ASTStructuralEquivalence.cpp
index dca9d88ab441d..f25353cb59f4b 100644
--- a/clang/lib/AST/ASTStructuralEquivalence.cpp
+++ b/clang/lib/AST/ASTStructuralEquivalence.cpp
@@ -500,12 +500,8 @@ CheckStructurallyEquivalentAttributes(StructuralEquivalenceContext &Context,
   // 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);
+  llvm::copy(D1->attrs(), std::back_inserter(Attrs1));
+  llvm::copy(D2->attrs(), std::back_inserter(Attrs2));
 
   auto Sorter = [](const Attr *LHS, const Attr *RHS) {
     const IdentifierInfo *II1 = LHS->getAttrName(), *II2 = RHS->getAttrName();

>From 1fa6761b92191f2a73550f1bfc20476721362a95 Mon Sep 17 00:00:00 2001
From: Aaron Ballman <aaron at aaronballman.com>
Date: Wed, 26 Mar 2025 08:38:35 -0400
Subject: [PATCH 22/26] Fix formatting; NFC

---
 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 00b8bf4ae513a..8517c118cee24 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{};
 }
 

>From 13ce82d976fcea8a19e0dafc8316f60c89f3573c Mon Sep 17 00:00:00 2001
From: Aaron Ballman <aaron at aaronballman.com>
Date: Thu, 27 Mar 2025 13:24:28 -0400
Subject: [PATCH 23/26] Update test case to say this is implemented in Clang 21

---
 clang/test/C/C23/n3037.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang/test/C/C23/n3037.c b/clang/test/C/C23/n3037.c
index 3c6cc4d13b75a..610a23e69af79 100644
--- a/clang/test/C/C23/n3037.c
+++ b/clang/test/C/C23/n3037.c
@@ -1,7 +1,7 @@
 // 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:
+/* WG14 N3037: Clang 21
  * Improved tag compatibility
  *
  * Identical tag types have always been compatible across TU boundaries. This

>From 730e758652d9e8b8e0958cc5aa91abb987b5048a Mon Sep 17 00:00:00 2001
From: Aaron Ballman <aaron at aaronballman.com>
Date: Thu, 27 Mar 2025 14:09:29 -0400
Subject: [PATCH 24/26] Add an explicit triple to the codegen test

---
 clang/test/C/C23/n3037_1.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang/test/C/C23/n3037_1.c b/clang/test/C/C23/n3037_1.c
index cbbeee75a5b96..d5f6895883a5f 100644
--- a/clang/test/C/C23/n3037_1.c
+++ b/clang/test/C/C23/n3037_1.c
@@ -1,5 +1,5 @@
 // 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
+// RUN: %clang_cc1 -std=c23 -triple x86_64-unknown-unknown -emit-llvm -o - %s | FileCheck %s
 
 // This tests the codegen behavior of redefined tag types to ensure the
 // generated code looks reasonable.

>From f71107f5b3e9011dc589b93d4eb63affffb80f12 Mon Sep 17 00:00:00 2001
From: Aaron Ballman <aaron at aaronballman.com>
Date: Tue, 1 Apr 2025 15:09:27 -0400
Subject: [PATCH 25/26] Rework attribute handling based on review feedback

If either tag type has an attribute or has a member with an attribute,
including keywords that are implemented via an attribute like _Alignas,
then the types are treated as incompatible. A warning which defaults to
an error is emitted; if the user downgrades the error back to a warning
or disables the warning entirely, then it's undefined behavior and the
user gets what they get.

Eventually we will implement tablegen logic so attributes can be
handled generically via information encoded in Attr.td.
---
 .../include/clang/Basic/DiagnosticASTKinds.td |   7 +-
 clang/lib/AST/ASTStructuralEquivalence.cpp    | 127 +++---------
 clang/test/C/C23/n3037.c                      | 192 ++++--------------
 3 files changed, 67 insertions(+), 259 deletions(-)

diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td
index 44deb4ef66825..6d193ceafee40 100644
--- a/clang/include/clang/Basic/DiagnosticASTKinds.td
+++ b/clang/include/clang/Basic/DiagnosticASTKinds.td
@@ -478,8 +478,11 @@ def warn_odr_function_type_inconsistent : Warning<
   "external function %0 declared with incompatible types in different "
   "translation units (%1 vs. %2)">,
   InGroup<ODR>;
-def note_odr_attr : Note<"attribute %0 here">;
-def note_odr_attr_missing : Note<"no corresponding attribute here">;
+def warn_odr_tag_type_with_attributes : Warning<
+  "type %0 has %select{an attribute|a member with an attribute}1 which "
+  "currently causes the types to be treated as though they are incompatible">,
+  InGroup<ODR>, DefaultError;
+def note_odr_attr_here : Note<"attribute %0 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 b66f92c847b50..ec43537da89a4 100644
--- a/clang/lib/AST/ASTStructuralEquivalence.cpp
+++ b/clang/lib/AST/ASTStructuralEquivalence.cpp
@@ -453,109 +453,38 @@ 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,
                                       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;
-  llvm::copy(D1->attrs(), std::back_inserter(Attrs1));
-  llvm::copy(D2->attrs(), std::back_inserter(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 auto *DiagnoseDecl = cast<TypeDecl>(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(),
-                      Context.getApplicableDiagnostic(
-                          diag::err_odr_tag_type_inconsistent))
-            << 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);
-      }
-      return false;
-    }
-
-    if (!IsStructurallyEquivalent(Context, *A1, *A2)) {
-      if (Context.Complain) {
-        Context.Diag2(DiagnoseDecl->getLocation(),
-                      Context.getApplicableDiagnostic(
-                          diag::err_odr_tag_type_inconsistent))
-            << 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;
-      }
-      return false;
-    }
-  }
-
-  if (A2 != A2End) {
-    if (Context.Complain) {
-      Context.Diag2(
-          DiagnoseDecl->getLocation(),
-          Context.getApplicableDiagnostic(diag::err_odr_tag_type_inconsistent))
-          << 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;
-    }
-    return false;
-  }
-
+  // If either declaration has an attribute on it, we treat the declarations
+  // as not being structurally equivalent.
+  // FIXME: this should be handled on a case-by-case basis via tablegen in
+  // Attr.td. There are multiple cases to consider: one declation with the
+  // attribute, another without it; different attribute syntax|spellings for
+  // the same semantic attribute, differences in attribute arguments, order
+  // in which attributes are applied, how to merge attributes if the types are
+  // structurally equivalent, etc.
+  const Attr *D1Attr = nullptr, *D2Attr = nullptr;
+  if (D1->hasAttrs())
+    D1Attr = *D1->getAttrs().begin();
+  if (D2->hasAttrs())
+    D2Attr = *D2->getAttrs().begin();
+  if (D1Attr || D2Attr) {
+    const auto *DiagnoseDecl = cast<TypeDecl>(PrimaryDecl ? PrimaryDecl : D2);
+    Context.Diag2(DiagnoseDecl->getLocation(),
+                  diag::warn_odr_tag_type_with_attributes)
+        << Context.ToCtx.getTypeDeclType(DiagnoseDecl)
+        << (PrimaryDecl != nullptr);
+    if (D1Attr)
+      Context.Diag1(D1Attr->getLoc(), diag::note_odr_attr_here) << D1Attr;
+    if (D2Attr)
+      Context.Diag1(D2Attr->getLoc(), diag::note_odr_attr_here) << D2Attr;
+  }
+
+  // The above diagnostic is a warning which defaults to an error. If treated
+  // as a warning, we'll go ahead and allow any attribute differences to be
+  // undefined behavior and the user gets what they get in terms of behavior.
   return true;
 }
 
diff --git a/clang/test/C/C23/n3037.c b/clang/test/C/C23/n3037.c
index 610a23e69af79..0f70d38583eb1 100644
--- a/clang/test/C/C23/n3037.c
+++ b/clang/test/C/C23/n3037.c
@@ -58,82 +58,49 @@ 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'}}
 
-// Different attributes on the tag type are an error.
+// The presence of an attribute makes two types not compatible.
 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'}} \
-                      c23-error {{type 'struct attr_test' has incompatible definitions}} \
-                      c23-note {{no corresponding attribute here}}
+                      c23-error {{type 'struct attr_test' has an attribute which currently causes the types to be treated as though they are incompatible}}
   int x;
 };
 
-struct attr_test_2 { // c17-note {{previous definition is here}} \
-                        c23-note {{no corresponding attribute here}}
+struct attr_test_2 { // c17-note {{previous definition is here}}
   int x;
 };
 
 struct [[gnu::packed]] attr_test_2 { // c17-error {{redefinition of 'attr_test_2'}} \
-                                        c23-error {{type 'struct attr_test_2' has incompatible definitions}} \
+                                        c23-error {{type 'struct attr_test_2' has an attribute which currently causes the types to be treated as though they are incompatible}} \
                                         c23-note {{attribute 'packed' here}}
   int x;
 };
 
-struct [[deprecated]] attr_test_3 { // c17-note {{previous definition is here}} \
-                                       c23-note {{attribute 'deprecated' here}}
+// This includes the same attribute on both types.
+struct [[gnu::packed]] attr_test_3 { // c17-note {{previous definition is here}} \
+                                       c23-note {{attribute 'packed' here}}
   int x;
 };
 
 struct [[gnu::packed]] attr_test_3 { // c17-error {{redefinition of 'attr_test_3'}} \
-                                        c23-error {{type 'struct attr_test_3' has incompatible definitions}} \
+                                        c23-error {{type 'struct attr_test_3' has an attribute which currently causes the types to be treated as though they are incompatible}} \
                                         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 'struct attr_test_6' has incompatible definitions}} \
-                                                 c23-note {{attribute 'deprecated' here}}
-  int x;
-};
-
-// Different attributes on the fields make them incompatible.
+// Everything which applies to the tag itself also applies to fields.
 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 'struct field_attr_test_1' has incompatible definitions}}
+                              c23-error {{type 'struct field_attr_test_1' has a member with an attribute which currently causes the types to be treated as though they are incompatible}}
   int x;
-  int y; // c23-note {{no corresponding attribute here}}
+  int y;
 };
 
 struct field_attr_test_2 { // c17-note {{previous definition is here}}
@@ -142,84 +109,22 @@ 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 'struct 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 'struct 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;
-};
-
-// 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}}
+                              c23-error {{type 'struct field_attr_test_2' has a member with an attribute which currently causes the types to be treated as though they are incompatible}}
   int x;
   int y;
 };
 
-struct __attribute__((packed)) field_attr_test_7 { // c17-error {{redefinition of 'field_attr_test_7'}} \
-                                                      c23-error {{type 'struct 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;
+struct field_attr_test_3 { // c17-note {{previous definition is here}}
+  [[gnu::packed]] int x;   // c23-note {{attribute 'packed' here}}
   int y;
 };
 
-struct [[gnu::warn_unused_result]] field_attr_test_8 { // c17-error {{redefinition of 'field_attr_test_8'}} \
-                                                          c23-error {{type 'struct field_attr_test_8' has incompatible definitions}} \
-                                                          c23-note {{attribute 'warn_unused_result' here}}
-  int x;
+struct field_attr_test_3 { // c17-error {{redefinition of 'field_attr_test_3'}} \
+                              c23-error {{type 'struct field_attr_test_3' has a member with an attribute which currently causes the types to be treated as though they are incompatible}}
+  int x [[gnu::packed]];   // c23-note {{attribute 'packed' here}}
   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}}
@@ -287,11 +192,11 @@ struct bit_field_2 { // c17-error {{redefinition of 'bit_field_2'}} \
 // 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}}
+  int b : 1;
 };
 
 struct bit_field_3 { // c17-error {{redefinition of 'bit_field_3'}} \
-                        c23-error {{type 'struct bit_field_3' has incompatible definitions}}
+                        c23-error {{type 'struct bit_field_3' has a member with an attribute which currently causes the types to be treated as though they are incompatible}}
   int a : 1;
   [[deprecated]] int b : 1; // c23-note {{attribute 'deprecated' here}}
 };
@@ -354,12 +259,18 @@ 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-note {{previous definition is here}} \
+                                          c23-note {{attribute 'deprecated' here}}
+  EAT1 [[deprecated]] // c17-note {{previous definition is here}} \
+                         c23-note {{attribute 'deprecated' 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_1 { // c17-error {{redefinition of 'enum_attr_test_1'}} \
+                                          c23-error {{type 'enum enum_attr_test_1' has an attribute which currently causes the types to be treated as though they are incompatible}} \
+                                          c23-error {{type 'enum enum_attr_test_1' has a member with an attribute which currently causes the types to be treated as though they are incompatible}} \
+                                          c23-note {{attribute 'deprecated' here}}
+  EAT1 [[deprecated]] // c17-error {{redefinition of enumerator 'EAT1'}} \
+                         c23-note {{attribute 'deprecated' here}}
 };
 
 enum [[deprecated]] enum_attr_test_2 { // c17-note {{previous definition is here}} \
@@ -368,55 +279,20 @@ 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 enum_attr_test_2' has incompatible definition}} \
-                           c23-note {{no corresponding attribute here}}
+                           c23-error {{type 'enum enum_attr_test_2' has an attribute which currently causes the types to be treated as though they are incompatible}}
   EAT2 // c17-error {{redefinition of enumerator 'EAT2'}}
 };
 
-enum enum_attr_test_3 { // c17-note {{previous definition is here}} \
-                           c23-note {{no corresponding attribute here}}
+enum enum_attr_test_3 { // c17-note {{previous definition is 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 enum_attr_test_3' has incompatible definition}} \
+                                          c23-error {{type 'enum enum_attr_test_3' has an attribute which currently causes the types to be treated as though they are incompatible}} \
                                           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 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 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 enum_attr_test_6' has incompatible definition}}
-  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.
@@ -480,6 +356,6 @@ struct alignment { // c17-note {{previous definition is here}}
 };
 
 struct alignment { // c17-error {{redefinition of 'alignment'}} \
-                      c23-error {{type 'struct alignment' has incompatible definition}}
-  int x;           // c23-note {{no corresponding attribute here}}
+                      c23-error {{type 'struct alignment' has a member with an attribute which currently causes the types to be treated as though they are incompatible}}
+  int x;
 };

>From 23c58b573bf9a1b68f0fc82ceb5de4ab10fe13ba Mon Sep 17 00:00:00 2001
From: Aaron Ballman <aaron at aaronballman.com>
Date: Tue, 1 Apr 2025 15:15:34 -0400
Subject: [PATCH 26/26] Add test codegen test for attribute handling

---
 clang/test/C/C23/n3037_1.c | 42 +++++++++++++++++++++++++++++++++++++-
 1 file changed, 41 insertions(+), 1 deletion(-)

diff --git a/clang/test/C/C23/n3037_1.c b/clang/test/C/C23/n3037_1.c
index d5f6895883a5f..e494eaf5828aa 100644
--- a/clang/test/C/C23/n3037_1.c
+++ b/clang/test/C/C23/n3037_1.c
@@ -1,5 +1,5 @@
 // NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --version 5
-// RUN: %clang_cc1 -std=c23 -triple x86_64-unknown-unknown -emit-llvm -o - %s | FileCheck %s
+// RUN: %clang_cc1 -std=c23 -Wno-error=odr -triple x86_64-unknown-unknown -emit-llvm -o - %s | FileCheck %s
 
 // This tests the codegen behavior of redefined tag types to ensure the
 // generated code looks reasonable.
@@ -46,3 +46,43 @@ enum E {
   One,
   Two
 };
+
+// Ensure that ignoring the incompatibility due to attributes does not cause a
+// crash. Note, this is undefined behavior in Clang until we implement
+// attribute structural compatibility logic, so this is not intended to verify
+// any particular behavior beyond "don't crash."
+struct T {
+  _Alignas(double) int x;
+};
+
+// CHECK-LABEL: define dso_local i32 @foo(
+// CHECK-SAME: i32 [[T_COERCE:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT:  [[ENTRY:.*:]]
+// CHECK-NEXT:    [[T:%.*]] = alloca [[STRUCT_T:%.*]], align 8
+// CHECK-NEXT:    [[COERCE_DIVE:%.*]] = getelementptr inbounds nuw [[STRUCT_T]], ptr [[T]], i32 0, i32 0
+// CHECK-NEXT:    store i32 [[T_COERCE]], ptr [[COERCE_DIVE]], align 8
+// CHECK-NEXT:    [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_T]], ptr [[T]], i32 0, i32 0
+// CHECK-NEXT:    [[TMP0:%.*]] = load i32, ptr [[X]], align 8
+// CHECK-NEXT:    ret i32 [[TMP0]]
+//
+int foo(struct T t) {
+  return t.x;
+}
+
+struct T {
+  int x;
+};
+
+// CHECK-LABEL: define dso_local i32 @bar(
+// CHECK-SAME: i32 [[T_COERCE:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT:  [[ENTRY:.*:]]
+// CHECK-NEXT:    [[T:%.*]] = alloca [[STRUCT_T:%.*]], align 8
+// CHECK-NEXT:    [[COERCE_DIVE:%.*]] = getelementptr inbounds nuw [[STRUCT_T]], ptr [[T]], i32 0, i32 0
+// CHECK-NEXT:    store i32 [[T_COERCE]], ptr [[COERCE_DIVE]], align 8
+// CHECK-NEXT:    [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_T]], ptr [[T]], i32 0, i32 0
+// CHECK-NEXT:    [[TMP0:%.*]] = load i32, ptr [[X]], align 8
+// CHECK-NEXT:    ret i32 [[TMP0]]
+//
+int bar(struct T t) {
+  return t.x;
+}



More information about the cfe-commits mailing list