1 //===--- PopulateSwitch.cpp --------------------------------------*- C++-*-===//
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7 //===----------------------------------------------------------------------===//
9 // Tweak that populates an empty switch statement of an enumeration type with
10 // all of the enumerators of that type.
13 // enum Color { RED, GREEN, BLUE };
15 // void f(Color color) {
20 // enum Color { RED, GREEN, BLUE };
22 // void f(Color color) {
31 //===----------------------------------------------------------------------===//
34 #include "Selection.h"
35 #include "refactor/Tweak.h"
36 #include "clang/AST/Decl.h"
37 #include "clang/AST/Stmt.h"
38 #include "clang/AST/Type.h"
39 #include "clang/Basic/SourceLocation.h"
40 #include "clang/Basic/SourceManager.h"
41 #include "clang/Tooling/Core/Replacement.h"
42 #include "llvm/ADT/MapVector.h"
43 #include "llvm/ADT/STLExtras.h"
50 class PopulateSwitch
: public Tweak
{
51 const char *id() const override
;
52 bool prepare(const Selection
&Sel
) override
;
53 Expected
<Effect
> apply(const Selection
&Sel
) override
;
54 std::string
title() const override
{ return "Populate switch"; }
55 llvm::StringLiteral
kind() const override
{
56 return CodeAction::QUICKFIX_KIND
;
62 ExpectedCase(const EnumConstantDecl
*Decl
) : Data(Decl
, false) {}
63 bool isCovered() const { return Data
.getInt(); }
64 void setCovered(bool Val
= true) { Data
.setInt(Val
); }
65 const EnumConstantDecl
*getEnumConstant() const {
66 return Data
.getPointer();
70 llvm::PointerIntPair
<const EnumConstantDecl
*, 1, bool> Data
;
73 const DeclContext
*DeclCtx
= nullptr;
74 const SwitchStmt
*Switch
= nullptr;
75 const CompoundStmt
*Body
= nullptr;
76 const EnumType
*EnumT
= nullptr;
77 const EnumDecl
*EnumD
= nullptr;
78 // Maps the Enum values to the EnumConstantDecl and a bool signifying if its
79 // covered in the switch.
80 llvm::MapVector
<llvm::APSInt
, ExpectedCase
> ExpectedCases
;
83 REGISTER_TWEAK(PopulateSwitch
)
85 bool PopulateSwitch::prepare(const Selection
&Sel
) {
86 const SelectionTree::Node
*CA
= Sel
.ASTSelection
.commonAncestor();
91 // - the switch statement itself (keyword, parens)
92 // - the whole expression (possibly wrapped in implicit casts)
93 // - the outer body (typically CompoundStmt)
94 // Selections *within* the expression or body don't trigger.
96 Switch
= CA
->ASTNode
.get
<SwitchStmt
>();
98 if (const SelectionTree::Node
*Parent
= CA
->outerImplicit().Parent
)
99 Switch
= Parent
->ASTNode
.get
<SwitchStmt
>();
103 // Body need not be a CompoundStmt! But that's all we support editing.
104 Body
= llvm::dyn_cast_or_null
<CompoundStmt
>(Switch
->getBody());
107 DeclCtx
= &CA
->getDeclContext();
109 // Examine the condition of the switch statement to see if it's an enum.
110 const Expr
*Cond
= Switch
->getCond();
113 // Ignore implicit casts, since enums implicitly cast to integer types.
114 Cond
= Cond
->IgnoreParenImpCasts();
115 // Get the canonical type to handle typedefs.
116 EnumT
= Cond
->getType().getCanonicalType()->getAsAdjusted
<EnumType
>();
119 EnumD
= EnumT
->getDecl();
120 if (!EnumD
|| EnumD
->isDependentType())
123 // Finally, check which cases exist and which are covered.
124 // We trigger if there are any values in the enum that aren't covered by the
127 ASTContext
&Ctx
= Sel
.AST
->getASTContext();
129 unsigned EnumIntWidth
= Ctx
.getIntWidth(QualType(EnumT
, 0));
130 bool EnumIsSigned
= EnumT
->isSignedIntegerOrEnumerationType();
132 auto Normalize
= [&](llvm::APSInt Val
) {
133 Val
= Val
.extOrTrunc(EnumIntWidth
);
134 Val
.setIsSigned(EnumIsSigned
);
138 for (auto *EnumConstant
: EnumD
->enumerators()) {
139 ExpectedCases
.insert(
140 std::make_pair(Normalize(EnumConstant
->getInitVal()), EnumConstant
));
143 for (const SwitchCase
*CaseList
= Switch
->getSwitchCaseList(); CaseList
;
144 CaseList
= CaseList
->getNextSwitchCase()) {
145 // Default likely intends to cover cases we'd insert.
146 if (isa
<DefaultStmt
>(CaseList
))
149 const CaseStmt
*CS
= cast
<CaseStmt
>(CaseList
);
151 // GNU range cases are rare, we don't support them.
152 if (CS
->caseStmtIsGNURange())
155 // Support for direct references to enum constants. This is required to
156 // support C and ObjC which don't contain values in their ConstantExprs.
157 // The general way to get the value of a case is EvaluateAsRValue, but we'd
158 // rather not deal with that in case the AST is broken.
159 if (auto *DRE
= dyn_cast
<DeclRefExpr
>(CS
->getLHS()->IgnoreParenCasts())) {
160 if (auto *Enumerator
= dyn_cast
<EnumConstantDecl
>(DRE
->getDecl())) {
161 auto Iter
= ExpectedCases
.find(Normalize(Enumerator
->getInitVal()));
162 if (Iter
!= ExpectedCases
.end())
163 Iter
->second
.setCovered();
168 // ConstantExprs with values are expected for C++, otherwise the storage
169 // kind will be None.
171 // Case expression is not a constant expression or is value-dependent,
172 // so we may not be able to work out which cases are covered.
173 const ConstantExpr
*CE
= dyn_cast
<ConstantExpr
>(CS
->getLHS());
174 if (!CE
|| CE
->isValueDependent())
177 // We need a stored value in order to continue; currently both C and ObjC
178 // enums won't have one.
179 if (CE
->getResultStorageKind() == ConstantExpr::RSK_None
)
181 auto Iter
= ExpectedCases
.find(Normalize(CE
->getResultAsAPSInt()));
182 if (Iter
!= ExpectedCases
.end())
183 Iter
->second
.setCovered();
186 return !llvm::all_of(ExpectedCases
,
187 [](auto &Pair
) { return Pair
.second
.isCovered(); });
190 Expected
<Tweak::Effect
> PopulateSwitch::apply(const Selection
&Sel
) {
191 ASTContext
&Ctx
= Sel
.AST
->getASTContext();
193 SourceLocation Loc
= Body
->getRBracLoc();
194 ASTContext
&DeclASTCtx
= DeclCtx
->getParentASTContext();
196 llvm::SmallString
<256> Text
;
197 for (auto &EnumConstant
: ExpectedCases
) {
198 // Skip any enum constants already covered
199 if (EnumConstant
.second
.isCovered())
202 Text
.append({"case ", getQualification(DeclASTCtx
, DeclCtx
, Loc
, EnumD
)});
203 if (EnumD
->isScoped())
204 Text
.append({EnumD
->getName(), "::"});
205 Text
.append({EnumConstant
.second
.getEnumConstant()->getName(), ":"});
208 assert(!Text
.empty() && "No enumerators to insert!");
211 const SourceManager
&SM
= Ctx
.getSourceManager();
212 return Effect::mainFileEdit(
213 SM
, tooling::Replacements(tooling::Replacement(SM
, Loc
, 0, Text
)));
216 } // namespace clangd