Skip to content

[C23] More improved type compatibility for enumerations #150946

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jul 29, 2025

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 added a commit to AaronBallman/llvm-project that referenced this pull request Jul 29, 2025
The structural equivalence checker was not paying attention to whether
enumerations had compatible fixed underlying types or not.

Fixes llvm#150594
@AaronBallman
Copy link
Collaborator Author

Manual backport happened here: #151199

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

Fixes llvm#150594
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