Skip to content

Conversation

@AaronBallman
Copy link
Collaborator

The structural equivalence checker was not paying attention to whether enumerations had compatible fixed underlying types or not.

Fixes #150594

The structural equivalence checker was not paying attention to whether
enumerations had compatible fixed underlying types or not.

Fixes llvm#150594
@AaronBallman AaronBallman added this to the LLVM 21.x Release milestone Jul 28, 2025
@AaronBallman AaronBallman added clang Clang issues not falling into any other category c23 clang:frontend Language frontend issues, e.g. anything involving "Sema" accepts-invalid diverges-from:gcc Does the clang frontend diverge from gcc on this issue labels Jul 28, 2025
@github-project-automation github-project-automation bot moved this to Needs Triage in LLVM Release Status Jul 28, 2025
@llvmbot
Copy link
Member

llvmbot commented Jul 28, 2025

@llvm/pr-subscribers-clang

Author: Aaron Ballman (AaronBallman)

Changes

The structural equivalence checker was not paying attention to whether enumerations had compatible fixed underlying types or not.

Fixes #150594


Full diff: https://github.com/llvm/llvm-project/pull/150946.diff

3 Files Affected:

  • (modified) clang/include/clang/Basic/DiagnosticASTKinds.td (+8)
  • (modified) clang/lib/AST/ASTStructuralEquivalence.cpp (+38)
  • (modified) clang/test/C/C23/n3037.c (+38)
diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td
index 071a38f513911..46d04b031798c 100644
--- a/clang/include/clang/Basic/DiagnosticASTKinds.td
+++ b/clang/include/clang/Basic/DiagnosticASTKinds.td
@@ -511,6 +511,14 @@ def note_odr_number_of_bases : Note<
   "class has %0 base %plural{1:class|:classes}0">;
 def note_odr_enumerator : Note<"enumerator %0 with value %1 here">;
 def note_odr_missing_enumerator : Note<"no corresponding enumerator here">;
+def note_odr_incompatible_fixed_underlying_type : Note<
+  "enumeration %0 declared with incompatible fixed underlying types (%1 vs. "
+  "%2)">;
+def note_odr_fixed_underlying_type : Note<
+  "enumeration %0 has fixed underlying type here">;
+def note_odr_missing_fixed_underlying_type : Note<
+  "enumeration %0 missing fixed underlying type here">;
+
 def err_odr_field_type_inconsistent : Error<
   "field %0 declared with incompatible types in different "
   "translation units (%1 vs. %2)">;
diff --git a/clang/lib/AST/ASTStructuralEquivalence.cpp b/clang/lib/AST/ASTStructuralEquivalence.cpp
index 0f2762d5c0f14..178c34a0400ae 100644
--- a/clang/lib/AST/ASTStructuralEquivalence.cpp
+++ b/clang/lib/AST/ASTStructuralEquivalence.cpp
@@ -2071,6 +2071,44 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
       !CheckStructurallyEquivalentAttributes(Context, D1, D2))
     return false;
 
+  // In C23, if one enumeration has a fixed underlying type, the other shall
+  // have a compatible fixed underlying type (6.2.7).
+  if (Context.LangOpts.C23) {
+    if (D1->isFixed() != D2->isFixed()) {
+      if (Context.Complain) {
+        Context.Diag2(D2->getLocation(),
+                      Context.getApplicableDiagnostic(
+                          diag::err_odr_tag_type_inconsistent))
+            << Context.ToCtx.getTypeDeclType(D2)
+            << (&Context.FromCtx != &Context.ToCtx);
+        EnumDecl *Has = D1->isFixed() ? D1 : D2;
+        EnumDecl *Missing = D1->isFixed() ? D1 : D2;
+        Context.Diag1(Has->getLocation(), diag::note_odr_fixed_underlying_type)
+            << Has;
+        Context.Diag2(Missing->getLocation(),
+                      diag::note_odr_missing_fixed_underlying_type)
+            << Missing;
+      }
+      return false;
+    }
+    if (D1->isFixed()) {
+      assert(D2->isFixed() && "enums expected to have fixed underlying types");
+      if (!IsStructurallyEquivalent(Context, D1->getIntegerType(),
+                                    D2->getIntegerType())) {
+        if (Context.Complain) {
+          Context.Diag2(D2->getLocation(),
+                        Context.getApplicableDiagnostic(
+                            diag::err_odr_tag_type_inconsistent))
+              << Context.ToCtx.getTypeDeclType(D2)
+              << (&Context.FromCtx != &Context.ToCtx);
+          Context.Diag1(D2->getLocation(), diag::note_odr_enumerator)
+              << D2 << D2->getIntegerType() << D1->getIntegerType();
+        }
+        return false;
+      }
+    }
+  }
+
   llvm::SmallVector<const EnumConstantDecl *, 8> D1Enums, D2Enums;
   auto CopyEnumerators =
       [](auto &&Range, llvm::SmallVectorImpl<const EnumConstantDecl *> &Cont) {
diff --git a/clang/test/C/C23/n3037.c b/clang/test/C/C23/n3037.c
index ce6f4c4ea7acf..468b110f2bf08 100644
--- a/clang/test/C/C23/n3037.c
+++ b/clang/test/C/C23/n3037.c
@@ -401,3 +401,41 @@ _Static_assert(0 == _Generic(inner_anon_tagged.untagged, struct { int i; } : 1,
 // unions and structures are both RecordDecl objects, whereas EnumDecl is not).
 enum { E_Untagged1 } nontag_enum; // both-note {{previous definition is here}}
 _Static_assert(0 == _Generic(nontag_enum, enum { E_Untagged1 } : 1, default : 0)); // both-error {{redefinition of enumerator 'E_Untagged1'}}
+
+// Test that enumerations with mixed underlying types are properly handled.
+enum GH150594_E1 : int { GH150594_Val1 };
+enum GH150594_E2 : int { GH150594_Val2 };
+enum GH150594_E3 { GH150594_Val3 };
+enum GH150594_E4 : int { GH150594_Val4 };
+void GH150594(void) {
+  extern enum GH150594_E1 Fn1(void); // both-note {{previous declaration is here}}
+  extern enum GH150594_E2 Fn2(void); // c17-note {{previous declaration is here}}
+  extern enum GH150594_E3 Fn3(void); // both-note {{previous declaration is here}}
+  extern enum GH150594_E4 Fn4(void); // both-note {{previous declaration is here}}
+  enum GH150594_E1 { GH150594_Val1 };
+  enum GH150594_E2 : int { GH150594_Val2 };
+  enum GH150594_E3 : int { GH150594_Val3 };
+  enum GH150594_E4 : short { GH150594_Val4 };
+  extern enum GH150594_E1 Fn1(void); // both-error {{conflicting types for 'Fn1'}}
+  extern enum GH150594_E2 Fn2(void); // c17-error {{conflicting types for 'Fn2'}}
+  extern enum GH150594_E3 Fn3(void); // both-error {{conflicting types for 'Fn3'}}
+  extern enum GH150594_E4 Fn4(void); // both-error {{conflicting types for 'Fn4'}}
+
+  // Show that two declarations in the same scope give expected diagnostics.
+  enum E1 { e1 };       // both-note {{previous declaration is here}}
+  enum E1 : int { e1 }; // both-error {{enumeration previously declared with nonfixed underlying type}}
+
+  enum E2 : int { e2 }; // both-note {{previous declaration is here}}
+  enum E2 { e2 };       // both-error {{enumeration previously declared with fixed underlying type}}
+
+  enum E3 : int { e3 };   // both-note {{previous declaration is here}}
+  enum E3 : short { e3 }; // both-error {{enumeration redeclared with different underlying type 'short' (was 'int')}}
+
+  typedef short foo;
+  enum E4 : foo { e4 };   // c17-note 2 {{previous definition is here}}
+  enum E4 : short { e4 }; // c17-error {{redefinition of 'E4'}} \
+                             c17-error {{redefinition of enumerator 'e4'}}
+
+  enum E5 : foo { e5 }; // both-note {{previous declaration is here}}
+  enum E5 : int { e5 }; // both-error {{enumeration redeclared with different underlying type 'int' (was 'foo' (aka 'short'))}}
+}

@AaronBallman
Copy link
Collaborator Author

There's no release note because this is covered by the existing one about N3037. Assuming this patch is landed in time, it would be cherry-picked to the 21.x branch (which also has the release note). If it's not cherry-picked, then I'll add a release note to the main branch.

@bolshakov-a
Copy link
Contributor

Thanks!

@tru tru moved this from Needs Triage to Needs Backport PR in LLVM Release Status Jul 29, 2025
// RUN: %clang_cc1 -std=c23 -emit-pch -o %t.2.ast %S/Inputs/enum4.c
// RUN: not %clang_cc1 -std=c23 -ast-merge %t.1.ast -ast-merge %t.2.ast -fsyntax-only %s 2>&1 | FileCheck %s

// FIXME: this error should not happen!
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a fix for this FIXME; once these changes land, I'll post the PR for it so I can reuse this test for coverage.

@github-project-automation github-project-automation bot moved this from Needs Backport PR to Needs Merge in LLVM Release Status Jul 29, 2025
@AaronBallman AaronBallman merged commit d2361e4 into llvm:main Jul 29, 2025
9 checks passed
@github-project-automation github-project-automation bot moved this from Needs Merge to Done in LLVM Release Status Jul 29, 2025
@AaronBallman AaronBallman deleted the aballman-gh150594 branch July 29, 2025 17:30
@AaronBallman
Copy link
Collaborator Author

/cherry-pick d2361e4

@llvmbot
Copy link
Member

llvmbot commented Jul 29, 2025

Failed to cherry-pick: d2361e4

https://github.com/llvm/llvm-project/actions/runs/16603178065

Please manually backport the fix and push it to your github fork. Once this is done, please create a pull request

@AaronBallman
Copy link
Collaborator Author

Manual backport happened here: #151199

AaronBallman added a commit to AaronBallman/llvm-project that referenced this pull request Aug 5, 2025
The structural equivalence checker was not paying attention to whether
enumerations had compatible fixed underlying types or not.

Fixes llvm#150594
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

accepts-invalid c23 clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category diverges-from:gcc Does the clang frontend diverge from gcc on this issue release:cherry-pick-failed

Projects

Development

Successfully merging this pull request may close these issues.

[clang] Enums differing in underlying type are compatible

4 participants