[clang] a2246ee - [C23] Accept an _Atomic underlying type (#147802)

via cfe-commits cfe-commits at lists.llvm.org
Fri Jul 11 04:28:07 PDT 2025


Author: Aaron Ballman
Date: 2025-07-11T07:28:03-04:00
New Revision: a2246eebcae47c5cb92c524ee96191edb358922d

URL: https://github.com/llvm/llvm-project/commit/a2246eebcae47c5cb92c524ee96191edb358922d
DIFF: https://github.com/llvm/llvm-project/commit/a2246eebcae47c5cb92c524ee96191edb358922d.diff

LOG: [C23] Accept an _Atomic underlying type (#147802)

The underlying type of an enumeration is the non-atomic, unqualified
version of the specified type. Clang was rejecting such enumerations,
with a hard error, but now has the ability to downgrade the error into a
warning. Additionally, we diagnose (as a warning) dropping other
qualifiers. _Atomic is special given that an atomic type need not have
the same size as its non-atomic counterpart, and that the C++ version
of <stdatomic.h> defines _Atomic to std::atomic for easing cross-
language atomic use and std::atomic is an invalid enum base in C++.
(Note: we expose _Atomic in C++ even without including
<stdatomic,h>.)

Fixes #147736

Added: 
    clang/test/C/C23/n3030_1.c
    clang/test/CodeGen/enum3.c

Modified: 
    clang/docs/ReleaseNotes.rst
    clang/include/clang/Basic/DiagnosticSemaKinds.td
    clang/lib/Sema/SemaDecl.cpp
    clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
    clang/test/C/C23/n3030.c
    clang/test/CXX/dcl.dcl/dcl.enum/p2.cpp
    clang/test/SemaCXX/enum-scoped.cpp

Removed: 
    


################################################################################
diff  --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 4535f8881e406..e81a3d4976cf8 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -312,6 +312,15 @@ C23 Feature Support
   `WG14 N2975 <https://open-std.org/JTC1/SC22/WG14/www/docs/n2975.pdf>`_
 - Fixed a bug with handling the type operand form of ``typeof`` when it is used
   to specify a fixed underlying type for an enumeration. #GH146351
+- Fixed a rejects-valid bug where Clang would reject an enumeration with an
+  ``_Atomic`` underlying type. The underlying type is the non-atomic,
+  unqualified version of the specified type. Due to the perhaps surprising lack
+  of atomic behavior, this is diagnosed under
+  ``-Wunderlying-atomic-qualifier-ignored``, which defaults to an error. This
+  can be downgraded with ``-Wno-underlying-atomic-qualifier-ignored`` or
+  ``-Wno-error=underlying-atomic-qualifier-ignored``. Clang now also diagnoses
+  cv-qualifiers as being ignored, but that warning does not default to an error.
+  It can be controlled by ``-Wunderlying-cv-qualifier-ignore``. (#GH147736)
 
 C11 Feature Support
 ^^^^^^^^^^^^^^^^^^^

diff  --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 09ba796b22765..3b8f396e37c48 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -9365,6 +9365,16 @@ def warn_atomic_implicit_seq_cst : Warning<
   InGroup<DiagGroup<"atomic-implicit-seq-cst">>, DefaultIgnore;
 def err_atomic_unsupported : Error<
   "atomic types are not supported in '%0'">;
+def warn_cv_stripped_in_enum : Warning<
+  "%enum_select<CVQualList>{"
+    "%Both{'const' and 'volatile' qualifiers}|"
+    "%Const{'const' qualifier}|"
+    "%Volatile{'volatile' qualifier}}0 in enumeration underlying type ignored">,
+  InGroup<DiagGroup<"underlying-cv-qualifier-ignored">>;
+def warn_atomic_stripped_in_enum : Warning<
+  "'_Atomic' qualifier ignored; operations involving the enumeration type will "
+  "be non-atomic">,
+  InGroup<DiagGroup<"underlying-atomic-qualifier-ignored">>, DefaultError;
 
 def err_overflow_builtin_must_be_int : Error<
   "operand argument to %select{overflow builtin|checked integer operation}0 "

diff  --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 11cbda412667f..d7234e269f645 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -17155,6 +17155,30 @@ bool Sema::CheckEnumUnderlyingType(TypeSourceInfo *TI) {
   if (T->isDependentType())
     return false;
 
+  // C++0x 7.2p2: The type-specifier-seq of an enum-base shall name an
+  // integral type; any cv-qualification is ignored.
+  // C23 6.7.3.3p5: The underlying type of the enumeration is the unqualified,
+  // non-atomic version of the type specified by the type specifiers in the
+  // specifier qualifier list.
+  // Because of how odd C's rule is, we'll let the user know that operations
+  // involving the enumeration type will be non-atomic.
+  if (T->isAtomicType())
+    Diag(UnderlyingLoc, diag::warn_atomic_stripped_in_enum);
+
+  Qualifiers Q = T.getQualifiers();
+  std::optional<unsigned> QualSelect;
+  if (Q.hasConst() && Q.hasVolatile())
+    QualSelect = diag::CVQualList::Both;
+  else if (Q.hasConst())
+    QualSelect = diag::CVQualList::Const;
+  else if (Q.hasVolatile())
+    QualSelect = diag::CVQualList::Volatile;
+
+  if (QualSelect)
+    Diag(UnderlyingLoc, diag::warn_cv_stripped_in_enum) << *QualSelect;
+
+  T = T.getAtomicUnqualifiedType();
+
   // This doesn't use 'isIntegralType' despite the error message mentioning
   // integral type because isIntegralType would also allow enum types in C.
   if (const BuiltinType *BT = T->getAs<BuiltinType>())
@@ -17551,6 +17575,9 @@ Sema::ActOnTag(Scope *S, unsigned TagSpec, TagUseKind TUK, SourceLocation KWLoc,
     } else if (UnderlyingType.get()) {
       // C++0x 7.2p2: The type-specifier-seq of an enum-base shall name an
       // integral type; any cv-qualification is ignored.
+      // C23 6.7.3.3p5: The underlying type of the enumeration is the
+      // unqualified, non-atomic version of the type specified by the type
+      // specifiers in the specifier qualifier list.
       TypeSourceInfo *TI = nullptr;
       GetTypeFromParser(UnderlyingType.get(), &TI);
       EnumUnderlying = TI;
@@ -17563,6 +17590,18 @@ Sema::ActOnTag(Scope *S, unsigned TagSpec, TagUseKind TUK, SourceLocation KWLoc,
                                           UPPC_FixedUnderlyingType))
         EnumUnderlying = Context.IntTy.getTypePtr();
 
+      // If the underlying type is atomic, we need to adjust the type before
+      // continuing. This only happens in the case we stored a TypeSourceInfo
+      // into EnumUnderlying because the other cases are error recovery up to
+      // this point. But because it's not possible to gin up a TypeSourceInfo
+      // for a non-atomic type from an atomic one, we'll store into the Type
+      // field instead. FIXME: it would be nice to have an easy way to get a
+      // derived TypeSourceInfo which strips qualifiers including the weird
+      // ones like _Atomic where it forms a 
diff erent type.
+      if (TypeSourceInfo *TI = dyn_cast<TypeSourceInfo *>(EnumUnderlying);
+          TI && TI->getType()->isAtomicType())
+        EnumUnderlying = TI->getType().getAtomicUnqualifiedType().getTypePtr();
+
     } else if (Context.getTargetInfo().getTriple().isWindowsMSVCEnvironment()) {
       // For MSVC ABI compatibility, unfixed enums must use an underlying type
       // of 'int'. However, if this is an unfixed forward declaration, don't set

diff  --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
index 70a4c159f9805..e2c3cdcd536bc 100644
--- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
@@ -2022,8 +2022,17 @@ Decl *TemplateDeclInstantiator::VisitEnumDecl(EnumDecl *D) {
                                                 DeclarationName());
       if (!NewTI || SemaRef.CheckEnumUnderlyingType(NewTI))
         Enum->setIntegerType(SemaRef.Context.IntTy);
-      else
-        Enum->setIntegerTypeSourceInfo(NewTI);
+      else {
+        // If the underlying type is atomic, we need to adjust the type before
+        // continuing. See C23 6.7.3.3p5 and Sema::ActOnTag(). FIXME: same as
+        // within ActOnTag(), it would be nice to have an easy way to get a
+        // derived TypeSourceInfo which strips qualifiers including the weird
+        // ones like _Atomic where it forms a 
diff erent type.
+        if (NewTI->getType()->isAtomicType())
+          Enum->setIntegerType(NewTI->getType().getAtomicUnqualifiedType());
+        else
+          Enum->setIntegerTypeSourceInfo(NewTI);
+      }
 
       // C++23 [conv.prom]p4
       // if integral promotion can be applied to its underlying type, a prvalue

diff  --git a/clang/test/C/C23/n3030.c b/clang/test/C/C23/n3030.c
index 17084bbb55f50..94ea7037edd11 100644
--- a/clang/test/C/C23/n3030.c
+++ b/clang/test/C/C23/n3030.c
@@ -91,3 +91,19 @@ enum e : short f = 0; // expected-error {{non-defining declaration of enumeratio
 enum g : short { yyy } h = yyy;
 
 enum ee2 : typeof ((enum ee3 : short { A })0, (short)0);
+
+enum not_actually_atomic : _Atomic(short) { // expected-error {{'_Atomic' qualifier ignored; operations involving the enumeration type will be non-atomic}}
+  Surprise
+};
+
+enum not_actually_const : const int { // expected-warning {{'const' qualifier in enumeration underlying type ignored}}
+  SurpriseAgain
+};
+
+enum not_actually_volatile : volatile int { // expected-warning {{'volatile' qualifier in enumeration underlying type ignored}}
+  SurpriseOnceMore
+};
+
+enum not_acually_const_or_volatile : const volatile int { // expected-warning {{'const' and 'volatile' qualifiers in enumeration underlying type ignored}}
+  WhyTheSurprise
+};

diff  --git a/clang/test/C/C23/n3030_1.c b/clang/test/C/C23/n3030_1.c
new file mode 100644
index 0000000000000..1afc9855767f0
--- /dev/null
+++ b/clang/test/C/C23/n3030_1.c
@@ -0,0 +1,13 @@
+// RUN: %clang_cc1 -std=c23 -Wno-underlying-atomic-qualifier-ignored -ast-dump %s | FileCheck %s
+
+// The underlying type is the unqualified, non-atomic version of the type
+// specified.
+enum const_enum : const short { ConstE };
+// CHECK: EnumDecl {{.*}} const_enum 'short'
+
+// These were previously being diagnosed as invalid underlying types. They
+// are valid; the _Atomic is stripped from the underlying type.
+enum atomic_enum1 : _Atomic(int) { AtomicE1 };
+// CHECK: EnumDecl {{.*}} atomic_enum1 'int'
+enum atomic_enum2 : _Atomic long long { AtomicE2 };
+// CHECK: EnumDecl {{.*}} atomic_enum2 'long long'

diff  --git a/clang/test/CXX/dcl.dcl/dcl.enum/p2.cpp b/clang/test/CXX/dcl.dcl/dcl.enum/p2.cpp
index de826d0570422..7b69358687a2f 100644
--- a/clang/test/CXX/dcl.dcl/dcl.enum/p2.cpp
+++ b/clang/test/CXX/dcl.dcl/dcl.enum/p2.cpp
@@ -1,6 +1,5 @@
 // RUN: %clang_cc1 -std=c++11 -verify %s
 
-// expected-no-diagnostics
-enum class E : int const volatile { };
+enum class E : int const volatile { }; // expected-warning {{'const' and 'volatile' qualifiers in enumeration underlying type ignored}}
 using T = __underlying_type(E);
 using T = int;

diff  --git a/clang/test/CodeGen/enum3.c b/clang/test/CodeGen/enum3.c
new file mode 100644
index 0000000000000..6878a0bbb94d0
--- /dev/null
+++ b/clang/test/CodeGen/enum3.c
@@ -0,0 +1,26 @@
+// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --version 5
+// RUN: %clang_cc1 -triple x86_64-unknown-unknown -Wno-error=underlying-atomic-qualifier-ignored -std=c23 %s -emit-llvm -o - | FileCheck %s
+
+// Ensure that an "atomic" underlying type has no actual atomic semantics
+// because the qualifier is stripped.
+
+enum E : _Atomic(int) {
+  Foo
+};
+
+// CHECK-LABEL: define {{.*}} void @test(
+// CHECK-SAME: i32 noundef [[E:%.*]]) #[[ATTR0:[0-9]+]] {
+// CHECK-NEXT:  [[ENTRY:.*:]]
+// CHECK-NEXT:    [[E_ADDR:%.*]] = alloca i32
+// CHECK-NEXT:    [[X:%.*]] = alloca i32
+// CHECK-NEXT:    store i32 [[E]], ptr [[E_ADDR]]
+// CHECK-NEXT:    [[TMP0:%.*]] = load i32, ptr [[E_ADDR]]
+// CHECK-NEXT:    store i32 [[TMP0]], ptr [[X]]
+// CHECK-NEXT:    store i32 0, ptr [[E_ADDR]]
+// CHECK-NEXT:    ret void
+//
+void test(enum E e) {
+  int x = e;
+  e = Foo;
+}
+

diff  --git a/clang/test/SemaCXX/enum-scoped.cpp b/clang/test/SemaCXX/enum-scoped.cpp
index d7b7923430aff..0ce47274979d9 100644
--- a/clang/test/SemaCXX/enum-scoped.cpp
+++ b/clang/test/SemaCXX/enum-scoped.cpp
@@ -349,3 +349,18 @@ enum class B;
 A a;
 B b{a}; // expected-error {{cannot initialize}}
 }
+
+namespace GH147736 {
+template <typename Ty>
+struct S {
+  enum OhBoy : Ty { // expected-error 2 {{'_Atomic' qualifier ignored; operations involving the enumeration type will be non-atomic}}
+    Unimportant
+  } e;
+};
+
+// Okay, was previously rejected. The underlying type is int.
+S<_Atomic(int)> s; // expected-warning {{'_Atomic' is a C11 extension}}
+                   // expected-note at -1 {{in instantiation of template class 'GH147736::S<_Atomic(int)>' requested here}}
+static_assert(__is_same(__underlying_type(S<_Atomic(long long)>::OhBoy), long long), ""); // expected-warning {{'_Atomic' is a C11 extension}}
+                                                                                          // expected-note at -1 {{in instantiation of template class 'GH147736::S<_Atomic(long long)>' requested here}}
+}


        


More information about the cfe-commits mailing list