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

Aaron Ballman via cfe-commits cfe-commits at lists.llvm.org
Tue Mar 25 08:28:32 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/15] [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/15] 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/15] 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/15] 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/15] 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/15] 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/15] 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/15] 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/15] 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/15] 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/15] 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/15] 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/15] 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/15] 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/15] 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}}
+};



More information about the cfe-commits mailing list