clang-tools 20.0.0git
PopulateSwitch.cpp
Go to the documentation of this file.
1//===--- PopulateSwitch.cpp --------------------------------------*- C++-*-===//
2//
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
6//
7//===----------------------------------------------------------------------===//
8//
9// Tweak that populates an empty switch statement of an enumeration type with
10// all of the enumerators of that type.
11//
12// Before:
13// enum Color { RED, GREEN, BLUE };
14//
15// void f(Color color) {
16// switch (color) {}
17// }
18//
19// After:
20// enum Color { RED, GREEN, BLUE };
21//
22// void f(Color color) {
23// switch (color) {
24// case RED:
25// case GREEN:
26// case BLUE:
27// break;
28// }
29// }
30//
31//===----------------------------------------------------------------------===//
32
33#include "AST.h"
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"
44#include <cassert>
45#include <string>
46
47namespace clang {
48namespace clangd {
49namespace {
50class 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 {
57 }
58
59private:
60 class ExpectedCase {
61 public:
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();
67 }
68
69 private:
70 llvm::PointerIntPair<const EnumConstantDecl *, 1, bool> Data;
71 };
72
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;
81};
82
83REGISTER_TWEAK(PopulateSwitch)
84
85bool PopulateSwitch::prepare(const Selection &Sel) {
86 const SelectionTree::Node *CA = Sel.ASTSelection.commonAncestor();
87 if (!CA)
88 return false;
89
90 // Support targeting
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.
95 // direct child (the
96 Switch = CA->ASTNode.get<SwitchStmt>();
97 if (!Switch) {
98 if (const SelectionTree::Node *Parent = CA->outerImplicit().Parent)
99 Switch = Parent->ASTNode.get<SwitchStmt>();
100 if (!Switch)
101 return false;
102 }
103 // Body need not be a CompoundStmt! But that's all we support editing.
104 Body = llvm::dyn_cast_or_null<CompoundStmt>(Switch->getBody());
105 if (!Body)
106 return false;
107 DeclCtx = &CA->getDeclContext();
108
109 // Examine the condition of the switch statement to see if it's an enum.
110 const Expr *Cond = Switch->getCond();
111 if (!Cond)
112 return false;
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>();
117 if (!EnumT)
118 return false;
119 EnumD = EnumT->getDecl();
120 if (!EnumD || EnumD->isDependentType())
121 return false;
122
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
125 // switch.
126
127 ASTContext &Ctx = Sel.AST->getASTContext();
128
129 unsigned EnumIntWidth = Ctx.getIntWidth(QualType(EnumT, 0));
130 bool EnumIsSigned = EnumT->isSignedIntegerOrEnumerationType();
131
132 auto Normalize = [&](llvm::APSInt Val) {
133 Val = Val.extOrTrunc(EnumIntWidth);
134 Val.setIsSigned(EnumIsSigned);
135 return Val;
136 };
137
138 for (auto *EnumConstant : EnumD->enumerators()) {
139 ExpectedCases.insert(
140 std::make_pair(Normalize(EnumConstant->getInitVal()), EnumConstant));
141 }
142
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))
147 return false;
148
149 const CaseStmt *CS = cast<CaseStmt>(CaseList);
150
151 // GNU range cases are rare, we don't support them.
152 if (CS->caseStmtIsGNURange())
153 return false;
154
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();
164 continue;
165 }
166 }
167
168 // ConstantExprs with values are expected for C++, otherwise the storage
169 // kind will be None.
170
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())
175 return false;
176
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() == ConstantResultStorageKind::None)
180 return false;
181 auto Iter = ExpectedCases.find(Normalize(CE->getResultAsAPSInt()));
182 if (Iter != ExpectedCases.end())
183 Iter->second.setCovered();
184 }
185
186 return !llvm::all_of(ExpectedCases,
187 [](auto &Pair) { return Pair.second.isCovered(); });
188}
189
190Expected<Tweak::Effect> PopulateSwitch::apply(const Selection &Sel) {
191 ASTContext &Ctx = Sel.AST->getASTContext();
192
193 SourceLocation Loc = Body->getRBracLoc();
194 ASTContext &DeclASTCtx = DeclCtx->getParentASTContext();
195
196 llvm::SmallString<256> Text;
197 for (auto &EnumConstant : ExpectedCases) {
198 // Skip any enum constants already covered
199 if (EnumConstant.second.isCovered())
200 continue;
201
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(), ":"});
206 }
207
208 assert(!Text.empty() && "No enumerators to insert!");
209 Text += "break;";
210
211 const SourceManager &SM = Ctx.getSourceManager();
212 return Effect::mainFileEdit(
213 SM, tooling::Replacements(tooling::Replacement(SM, Loc, 0, Text)));
214}
215} // namespace
216} // namespace clangd
217} // namespace clang
const FunctionDecl * Decl
CaptureExpr CE
const Node * Parent
SourceLocation Loc
#define REGISTER_TWEAK(Subclass)
Definition: Tweak.h:129
std::string getQualification(ASTContext &Context, const DeclContext *DestContext, SourceLocation InsertionPoint, const NamedDecl *ND)
Gets the nested name specifier necessary for spelling ND in DestContext, at InsertionPoint.
Definition: AST.cpp:660
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
static const llvm::StringLiteral QUICKFIX_KIND
Definition: Protocol.h:1070