Skip to content

Commit b1d0605

Browse files
authored
[Clang] adjust caret placement for the suggested attribute location for enum class (#168092)
Fixes #163224 --- This patch addresses the issue by correcting the caret insertion location for attributes incorrectly positioned before an enum. The location is now derived from the associated `EnumDecl`: for named enums, the attribute is placed before the identifier, while for anonymous enum definitions, it is placed before the opening brace, with a fallback to the semicolon when no brace is present. For example: ```cpp [[nodiscard]] enum class E1 {}; ``` is now suggested as: ```cpp enum class [[nodiscard]] E1 {}; ```
1 parent be3204a commit b1d0605

File tree

5 files changed

+84
-26
lines changed

5 files changed

+84
-26
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,8 @@ Improvements to Clang's diagnostics
448448
- A new warning ``-Wenum-compare-typo`` has been added to detect potential erroneous
449449
comparison operators when mixed with bitwise operators in enum value initializers.
450450
This can be locally disabled by explicitly casting the initializer value.
451+
- Clang now provides correct caret placement when attributes appear before
452+
`enum class` (#GH163224).
451453

452454
- A new warning ``-Wshadow-header`` has been added to detect when a header file
453455
is found in multiple search directories (excluding system paths).

clang/include/clang/AST/Decl.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4040,6 +4040,11 @@ class EnumDecl : public TagDecl {
40404040
/// and can be accessed with the provided accessors.
40414041
unsigned ODRHash;
40424042

4043+
/// Source range covering the enum key:
4044+
/// - 'enum' (unscoped)
4045+
/// - 'enum class|struct' (scoped)
4046+
SourceRange EnumKeyRange;
4047+
40434048
EnumDecl(ASTContext &C, DeclContext *DC, SourceLocation StartLoc,
40444049
SourceLocation IdLoc, IdentifierInfo *Id, EnumDecl *PrevDecl,
40454050
bool Scoped, bool ScopedUsingClassTag, bool Fixed);
@@ -4077,6 +4082,10 @@ class EnumDecl : public TagDecl {
40774082
/// Microsoft-style enumeration with a fixed underlying type.
40784083
void setFixed(bool Fixed = true) { EnumDeclBits.IsFixed = Fixed; }
40794084

4085+
SourceRange getEnumKeyRange() const { return EnumKeyRange; }
4086+
4087+
void setEnumKeyRange(SourceRange Range) { EnumKeyRange = Range; }
4088+
40804089
private:
40814090
/// True if a valid hash is stored in ODRHash.
40824091
bool hasODRHash() const { return EnumDeclBits.HasODRHash; }

clang/lib/Parse/Parser.cpp

Lines changed: 18 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1100,30 +1100,25 @@ Parser::DeclGroupPtrTy Parser::ParseDeclOrFunctionDefInternal(
11001100
// C99 6.7.2.3p6: Handle "struct-or-union identifier;", "enum { X };"
11011101
// declaration-specifiers init-declarator-list[opt] ';'
11021102
if (Tok.is(tok::semi)) {
1103-
auto LengthOfTSTToken = [](DeclSpec::TST TKind) {
1104-
assert(DeclSpec::isDeclRep(TKind));
1105-
switch(TKind) {
1106-
case DeclSpec::TST_class:
1107-
return 5;
1108-
case DeclSpec::TST_struct:
1109-
return 6;
1110-
case DeclSpec::TST_union:
1111-
return 5;
1112-
case DeclSpec::TST_enum:
1113-
return 4;
1114-
case DeclSpec::TST_interface:
1115-
return 9;
1116-
default:
1117-
llvm_unreachable("we only expect to get the length of the class/struct/union/enum");
1103+
// Suggest correct location to fix '[[attrib]] struct' to 'struct
1104+
// [[attrib]]'
1105+
SourceLocation CorrectLocationForAttributes{};
1106+
TypeSpecifierType TKind = DS.getTypeSpecType();
1107+
if (DeclSpec::isDeclRep(TKind)) {
1108+
if (TKind == DeclSpec::TST_enum) {
1109+
if (const auto *ED = dyn_cast_or_null<EnumDecl>(DS.getRepAsDecl())) {
1110+
CorrectLocationForAttributes =
1111+
PP.getLocForEndOfToken(ED->getEnumKeyRange().getEnd());
1112+
}
11181113
}
1119-
1120-
};
1121-
// Suggest correct location to fix '[[attrib]] struct' to 'struct [[attrib]]'
1122-
SourceLocation CorrectLocationForAttributes =
1123-
DeclSpec::isDeclRep(DS.getTypeSpecType())
1124-
? DS.getTypeSpecTypeLoc().getLocWithOffset(
1125-
LengthOfTSTToken(DS.getTypeSpecType()))
1126-
: SourceLocation();
1114+
if (CorrectLocationForAttributes.isInvalid()) {
1115+
const auto &Policy = Actions.getASTContext().getPrintingPolicy();
1116+
unsigned Offset =
1117+
StringRef(DeclSpec::getSpecifierName(TKind, Policy)).size();
1118+
CorrectLocationForAttributes =
1119+
DS.getTypeSpecTypeLoc().getLocWithOffset(Offset);
1120+
}
1121+
}
11271122
ProhibitAttributes(Attrs, CorrectLocationForAttributes);
11281123
ConsumeToken();
11291124
RecordDecl *AnonRecord = nullptr;

clang/lib/Sema/SemaDecl.cpp

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18484,17 +18484,21 @@ Sema::ActOnTag(Scope *S, unsigned TagSpec, TagUseKind TUK, SourceLocation KWLoc,
1848418484
cast_or_null<EnumDecl>(PrevDecl), ScopedEnum,
1848518485
ScopedEnumUsesClassTag, IsFixed);
1848618486

18487+
EnumDecl *ED = cast<EnumDecl>(New);
18488+
ED->setEnumKeyRange(SourceRange(
18489+
KWLoc, ScopedEnumKWLoc.isValid() ? ScopedEnumKWLoc : KWLoc));
18490+
1848718491
if (isStdAlignValT && (!StdAlignValT || getStdAlignValT()->isImplicit()))
1848818492
StdAlignValT = cast<EnumDecl>(New);
1848918493

1849018494
// If this is an undefined enum, warn.
1849118495
if (TUK != TagUseKind::Definition && !Invalid) {
1849218496
TagDecl *Def;
18493-
if (IsFixed && cast<EnumDecl>(New)->isFixed()) {
18497+
if (IsFixed && ED->isFixed()) {
1849418498
// C++0x: 7.2p2: opaque-enum-declaration.
1849518499
// Conflicts are diagnosed above. Do nothing.
18496-
}
18497-
else if (PrevDecl && (Def = cast<EnumDecl>(PrevDecl)->getDefinition())) {
18500+
} else if (PrevDecl &&
18501+
(Def = cast<EnumDecl>(PrevDecl)->getDefinition())) {
1849818502
Diag(Loc, diag::ext_forward_ref_enum_def)
1849918503
<< New;
1850018504
Diag(Def->getLocation(), diag::note_previous_definition);
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// RUN: %clang_cc1 -fsyntax-only -verify %s
2+
// RUN: not %clang_cc1 -fsyntax-only -fdiagnostics-parseable-fixits -fno-diagnostics-show-line-numbers %s 2>&1 | FileCheck %s -strict-whitespace
3+
4+
[[nodiscard]] enum class E1 { };
5+
// expected-error@-1 {{misplaced attributes; expected attributes here}}
6+
// CHECK: {{^}}{{\[\[}}nodiscard]] enum class E1 { };
7+
// CHECK: {{^}}~~~~~~~~~~~~~ ^
8+
// CHECK: fix-it:"{{.*}}":{[[@LINE-4]]:1-[[@LINE-4]]:15}:""
9+
// CHECK: fix-it:"{{.*}}":{[[@LINE-5]]:25-[[@LINE-5]]:25}:"{{\[\[}}nodiscard]]"
10+
11+
[[nodiscard]] enum struct E2 { };
12+
// expected-error@-1 {{misplaced attributes; expected attributes here}}
13+
// CHECK: {{^}}{{\[\[}}nodiscard]] enum struct E2 { };
14+
// CHECK: {{^}}~~~~~~~~~~~~~ ^
15+
// CHECK: fix-it:"{{.*}}":{[[@LINE-4]]:1-[[@LINE-4]]:15}:""
16+
// CHECK: fix-it:"{{.*}}":{[[@LINE-5]]:26-[[@LINE-5]]:26}:"{{\[\[}}nodiscard]]"
17+
18+
[[nodiscard]] enum class E3 { };
19+
// expected-error@-1 {{misplaced attributes; expected attributes here}}
20+
// CHECK: {{^}}{{\[\[}}nodiscard]] enum class E3 { };
21+
// CHECK: {{^}}~~~~~~~~~~~~~ ^
22+
// CHECK: fix-it:"{{.*}}":{[[@LINE-4]]:1-[[@LINE-4]]:15}:""
23+
// CHECK: fix-it:"{{.*}}":{[[@LINE-5]]:34-[[@LINE-5]]:34}:"{{\[\[}}nodiscard]]"
24+
25+
[[nodiscard]] enum /*comment*/ class E4 { };
26+
// expected-error@-1 {{misplaced attributes; expected attributes here}}
27+
// CHECK: {{^}}{{\[\[}}nodiscard]] enum /*comment*/ class E4 { };
28+
// CHECK: {{^}}~~~~~~~~~~~~~ ^
29+
// CHECK: fix-it:"{{.*}}":{[[@LINE-4]]:1-[[@LINE-4]]:15}:""
30+
// CHECK: fix-it:"{{.*}}":{[[@LINE-5]]:38-[[@LINE-5]]:38}:"{{\[\[}}nodiscard]]"
31+
32+
[[nodiscard]] enum { A = 0 };
33+
// expected-error@-1 {{misplaced attributes; expected attributes here}}
34+
// CHECK: {{^}}{{\[\[}}nodiscard]] enum { A = 0 };
35+
// CHECK: {{^}}~~~~~~~~~~~~~ ^
36+
// CHECK: fix-it:"{{.*}}":{[[@LINE-4]]:1-[[@LINE-4]]:15}:""
37+
// CHECK: fix-it:"{{.*}}":{[[@LINE-5]]:19-[[@LINE-5]]:19}:"{{\[\[}}nodiscard]]"
38+
39+
namespace NS {
40+
enum class E5;
41+
}
42+
43+
[[nodiscard]] enum class NS::E5 { };
44+
// expected-error@-1 {{misplaced attributes; expected attributes here}}
45+
// CHECK: {{^}}{{\[\[}}nodiscard]] enum class NS::E5 { };
46+
// CHECK: {{^}}~~~~~~~~~~~~~ ^
47+
// CHECK: fix-it:"{{.*}}":{[[@LINE-4]]:1-[[@LINE-4]]:15}:""
48+
// CHECK: fix-it:"{{.*}}":{[[@LINE-5]]:25-[[@LINE-5]]:25}:"{{\[\[}}nodiscard]]"

0 commit comments

Comments
 (0)