Skip to content

Commit a2246ee

Browse files
authored
[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
1 parent 587ba75 commit a2246ee

File tree

9 files changed

+140
-4
lines changed

9 files changed

+140
-4
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,15 @@ C23 Feature Support
312312
`WG14 N2975 <https://open-std.org/JTC1/SC22/WG14/www/docs/n2975.pdf>`_
313313
- Fixed a bug with handling the type operand form of ``typeof`` when it is used
314314
to specify a fixed underlying type for an enumeration. #GH146351
315+
- Fixed a rejects-valid bug where Clang would reject an enumeration with an
316+
``_Atomic`` underlying type. The underlying type is the non-atomic,
317+
unqualified version of the specified type. Due to the perhaps surprising lack
318+
of atomic behavior, this is diagnosed under
319+
``-Wunderlying-atomic-qualifier-ignored``, which defaults to an error. This
320+
can be downgraded with ``-Wno-underlying-atomic-qualifier-ignored`` or
321+
``-Wno-error=underlying-atomic-qualifier-ignored``. Clang now also diagnoses
322+
cv-qualifiers as being ignored, but that warning does not default to an error.
323+
It can be controlled by ``-Wunderlying-cv-qualifier-ignore``. (#GH147736)
315324

316325
C11 Feature Support
317326
^^^^^^^^^^^^^^^^^^^

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9365,6 +9365,16 @@ def warn_atomic_implicit_seq_cst : Warning<
93659365
InGroup<DiagGroup<"atomic-implicit-seq-cst">>, DefaultIgnore;
93669366
def err_atomic_unsupported : Error<
93679367
"atomic types are not supported in '%0'">;
9368+
def warn_cv_stripped_in_enum : Warning<
9369+
"%enum_select<CVQualList>{"
9370+
"%Both{'const' and 'volatile' qualifiers}|"
9371+
"%Const{'const' qualifier}|"
9372+
"%Volatile{'volatile' qualifier}}0 in enumeration underlying type ignored">,
9373+
InGroup<DiagGroup<"underlying-cv-qualifier-ignored">>;
9374+
def warn_atomic_stripped_in_enum : Warning<
9375+
"'_Atomic' qualifier ignored; operations involving the enumeration type will "
9376+
"be non-atomic">,
9377+
InGroup<DiagGroup<"underlying-atomic-qualifier-ignored">>, DefaultError;
93689378

93699379
def err_overflow_builtin_must_be_int : Error<
93709380
"operand argument to %select{overflow builtin|checked integer operation}0 "

clang/lib/Sema/SemaDecl.cpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17155,6 +17155,30 @@ bool Sema::CheckEnumUnderlyingType(TypeSourceInfo *TI) {
1715517155
if (T->isDependentType())
1715617156
return false;
1715717157

17158+
// C++0x 7.2p2: The type-specifier-seq of an enum-base shall name an
17159+
// integral type; any cv-qualification is ignored.
17160+
// C23 6.7.3.3p5: The underlying type of the enumeration is the unqualified,
17161+
// non-atomic version of the type specified by the type specifiers in the
17162+
// specifier qualifier list.
17163+
// Because of how odd C's rule is, we'll let the user know that operations
17164+
// involving the enumeration type will be non-atomic.
17165+
if (T->isAtomicType())
17166+
Diag(UnderlyingLoc, diag::warn_atomic_stripped_in_enum);
17167+
17168+
Qualifiers Q = T.getQualifiers();
17169+
std::optional<unsigned> QualSelect;
17170+
if (Q.hasConst() && Q.hasVolatile())
17171+
QualSelect = diag::CVQualList::Both;
17172+
else if (Q.hasConst())
17173+
QualSelect = diag::CVQualList::Const;
17174+
else if (Q.hasVolatile())
17175+
QualSelect = diag::CVQualList::Volatile;
17176+
17177+
if (QualSelect)
17178+
Diag(UnderlyingLoc, diag::warn_cv_stripped_in_enum) << *QualSelect;
17179+
17180+
T = T.getAtomicUnqualifiedType();
17181+
1715817182
// This doesn't use 'isIntegralType' despite the error message mentioning
1715917183
// integral type because isIntegralType would also allow enum types in C.
1716017184
if (const BuiltinType *BT = T->getAs<BuiltinType>())
@@ -17551,6 +17575,9 @@ Sema::ActOnTag(Scope *S, unsigned TagSpec, TagUseKind TUK, SourceLocation KWLoc,
1755117575
} else if (UnderlyingType.get()) {
1755217576
// C++0x 7.2p2: The type-specifier-seq of an enum-base shall name an
1755317577
// integral type; any cv-qualification is ignored.
17578+
// C23 6.7.3.3p5: The underlying type of the enumeration is the
17579+
// unqualified, non-atomic version of the type specified by the type
17580+
// specifiers in the specifier qualifier list.
1755417581
TypeSourceInfo *TI = nullptr;
1755517582
GetTypeFromParser(UnderlyingType.get(), &TI);
1755617583
EnumUnderlying = TI;
@@ -17563,6 +17590,18 @@ Sema::ActOnTag(Scope *S, unsigned TagSpec, TagUseKind TUK, SourceLocation KWLoc,
1756317590
UPPC_FixedUnderlyingType))
1756417591
EnumUnderlying = Context.IntTy.getTypePtr();
1756517592

17593+
// If the underlying type is atomic, we need to adjust the type before
17594+
// continuing. This only happens in the case we stored a TypeSourceInfo
17595+
// into EnumUnderlying because the other cases are error recovery up to
17596+
// this point. But because it's not possible to gin up a TypeSourceInfo
17597+
// for a non-atomic type from an atomic one, we'll store into the Type
17598+
// field instead. FIXME: it would be nice to have an easy way to get a
17599+
// derived TypeSourceInfo which strips qualifiers including the weird
17600+
// ones like _Atomic where it forms a different type.
17601+
if (TypeSourceInfo *TI = dyn_cast<TypeSourceInfo *>(EnumUnderlying);
17602+
TI && TI->getType()->isAtomicType())
17603+
EnumUnderlying = TI->getType().getAtomicUnqualifiedType().getTypePtr();
17604+
1756617605
} else if (Context.getTargetInfo().getTriple().isWindowsMSVCEnvironment()) {
1756717606
// For MSVC ABI compatibility, unfixed enums must use an underlying type
1756817607
// of 'int'. However, if this is an unfixed forward declaration, don't set

clang/lib/Sema/SemaTemplateInstantiateDecl.cpp

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2022,8 +2022,17 @@ Decl *TemplateDeclInstantiator::VisitEnumDecl(EnumDecl *D) {
20222022
DeclarationName());
20232023
if (!NewTI || SemaRef.CheckEnumUnderlyingType(NewTI))
20242024
Enum->setIntegerType(SemaRef.Context.IntTy);
2025-
else
2026-
Enum->setIntegerTypeSourceInfo(NewTI);
2025+
else {
2026+
// If the underlying type is atomic, we need to adjust the type before
2027+
// continuing. See C23 6.7.3.3p5 and Sema::ActOnTag(). FIXME: same as
2028+
// within ActOnTag(), it would be nice to have an easy way to get a
2029+
// derived TypeSourceInfo which strips qualifiers including the weird
2030+
// ones like _Atomic where it forms a different type.
2031+
if (NewTI->getType()->isAtomicType())
2032+
Enum->setIntegerType(NewTI->getType().getAtomicUnqualifiedType());
2033+
else
2034+
Enum->setIntegerTypeSourceInfo(NewTI);
2035+
}
20272036

20282037
// C++23 [conv.prom]p4
20292038
// if integral promotion can be applied to its underlying type, a prvalue

clang/test/C/C23/n3030.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,19 @@ enum e : short f = 0; // expected-error {{non-defining declaration of enumeratio
9191
enum g : short { yyy } h = yyy;
9292

9393
enum ee2 : typeof ((enum ee3 : short { A })0, (short)0);
94+
95+
enum not_actually_atomic : _Atomic(short) { // expected-error {{'_Atomic' qualifier ignored; operations involving the enumeration type will be non-atomic}}
96+
Surprise
97+
};
98+
99+
enum not_actually_const : const int { // expected-warning {{'const' qualifier in enumeration underlying type ignored}}
100+
SurpriseAgain
101+
};
102+
103+
enum not_actually_volatile : volatile int { // expected-warning {{'volatile' qualifier in enumeration underlying type ignored}}
104+
SurpriseOnceMore
105+
};
106+
107+
enum not_acually_const_or_volatile : const volatile int { // expected-warning {{'const' and 'volatile' qualifiers in enumeration underlying type ignored}}
108+
WhyTheSurprise
109+
};

clang/test/C/C23/n3030_1.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// RUN: %clang_cc1 -std=c23 -Wno-underlying-atomic-qualifier-ignored -ast-dump %s | FileCheck %s
2+
3+
// The underlying type is the unqualified, non-atomic version of the type
4+
// specified.
5+
enum const_enum : const short { ConstE };
6+
// CHECK: EnumDecl {{.*}} const_enum 'short'
7+
8+
// These were previously being diagnosed as invalid underlying types. They
9+
// are valid; the _Atomic is stripped from the underlying type.
10+
enum atomic_enum1 : _Atomic(int) { AtomicE1 };
11+
// CHECK: EnumDecl {{.*}} atomic_enum1 'int'
12+
enum atomic_enum2 : _Atomic long long { AtomicE2 };
13+
// CHECK: EnumDecl {{.*}} atomic_enum2 'long long'
Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
// RUN: %clang_cc1 -std=c++11 -verify %s
22

3-
// expected-no-diagnostics
4-
enum class E : int const volatile { };
3+
enum class E : int const volatile { }; // expected-warning {{'const' and 'volatile' qualifiers in enumeration underlying type ignored}}
54
using T = __underlying_type(E);
65
using T = int;

clang/test/CodeGen/enum3.c

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --version 5
2+
// RUN: %clang_cc1 -triple x86_64-unknown-unknown -Wno-error=underlying-atomic-qualifier-ignored -std=c23 %s -emit-llvm -o - | FileCheck %s
3+
4+
// Ensure that an "atomic" underlying type has no actual atomic semantics
5+
// because the qualifier is stripped.
6+
7+
enum E : _Atomic(int) {
8+
Foo
9+
};
10+
11+
// CHECK-LABEL: define {{.*}} void @test(
12+
// CHECK-SAME: i32 noundef [[E:%.*]]) #[[ATTR0:[0-9]+]] {
13+
// CHECK-NEXT: [[ENTRY:.*:]]
14+
// CHECK-NEXT: [[E_ADDR:%.*]] = alloca i32
15+
// CHECK-NEXT: [[X:%.*]] = alloca i32
16+
// CHECK-NEXT: store i32 [[E]], ptr [[E_ADDR]]
17+
// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[E_ADDR]]
18+
// CHECK-NEXT: store i32 [[TMP0]], ptr [[X]]
19+
// CHECK-NEXT: store i32 0, ptr [[E_ADDR]]
20+
// CHECK-NEXT: ret void
21+
//
22+
void test(enum E e) {
23+
int x = e;
24+
e = Foo;
25+
}
26+

clang/test/SemaCXX/enum-scoped.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,3 +349,18 @@ enum class B;
349349
A a;
350350
B b{a}; // expected-error {{cannot initialize}}
351351
}
352+
353+
namespace GH147736 {
354+
template <typename Ty>
355+
struct S {
356+
enum OhBoy : Ty { // expected-error 2 {{'_Atomic' qualifier ignored; operations involving the enumeration type will be non-atomic}}
357+
Unimportant
358+
} e;
359+
};
360+
361+
// Okay, was previously rejected. The underlying type is int.
362+
S<_Atomic(int)> s; // expected-warning {{'_Atomic' is a C11 extension}}
363+
// expected-note@-1 {{in instantiation of template class 'GH147736::S<_Atomic(int)>' requested here}}
364+
static_assert(__is_same(__underlying_type(S<_Atomic(long long)>::OhBoy), long long), ""); // expected-warning {{'_Atomic' is a C11 extension}}
365+
// expected-note@-1 {{in instantiation of template class 'GH147736::S<_Atomic(long long)>' requested here}}
366+
}

0 commit comments

Comments
 (0)