clang-tools  14.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 "support/Logger.h"
37 #include "clang/AST/Decl.h"
38 #include "clang/AST/Stmt.h"
39 #include "clang/AST/Type.h"
40 #include "clang/Basic/SourceLocation.h"
41 #include "clang/Basic/SourceManager.h"
42 #include "clang/Tooling/Core/Replacement.h"
43 #include "llvm/ADT/MapVector.h"
44 #include "llvm/ADT/STLExtras.h"
45 #include <cassert>
46 #include <string>
47 
48 namespace clang {
49 namespace clangd {
50 namespace {
51 class PopulateSwitch : public Tweak {
52  const char *id() const override;
53  bool prepare(const Selection &Sel) override;
54  Expected<Effect> apply(const Selection &Sel) override;
55  std::string title() const override { return "Populate switch"; }
56  llvm::StringLiteral kind() const override {
58  }
59 
60 private:
61  class ExpectedCase {
62  public:
63  ExpectedCase(const EnumConstantDecl *Decl) : Data(Decl, false) {}
64  bool isCovered() const { return Data.getInt(); }
65  void setCovered(bool Val = true) { Data.setInt(Val); }
66  const EnumConstantDecl *getEnumConstant() const {
67  return Data.getPointer();
68  }
69 
70  private:
71  llvm::PointerIntPair<const EnumConstantDecl *, 1, bool> Data;
72  };
73 
74  const DeclContext *DeclCtx = nullptr;
75  const SwitchStmt *Switch = nullptr;
76  const CompoundStmt *Body = nullptr;
77  const EnumType *EnumT = nullptr;
78  const EnumDecl *EnumD = nullptr;
79  // Maps the Enum values to the EnumConstantDecl and a bool signifying if its
80  // covered in the switch.
81  llvm::MapVector<llvm::APSInt, ExpectedCase> ExpectedCases;
82 };
83 
84 REGISTER_TWEAK(PopulateSwitch)
85 
86 bool PopulateSwitch::prepare(const Selection &Sel) {
87  const SelectionTree::Node *CA = Sel.ASTSelection.commonAncestor();
88  if (!CA)
89  return false;
90 
91  // Support targeting
92  // - the switch statement itself (keyword, parens)
93  // - the whole expression (possibly wrapped in implicit casts)
94  // - the outer body (typically CompoundStmt)
95  // Selections *within* the expression or body don't trigger.
96  // direct child (the
97  Switch = CA->ASTNode.get<SwitchStmt>();
98  if (!Switch) {
99  if (const SelectionTree::Node *Parent = CA->outerImplicit().Parent)
100  Switch = Parent->ASTNode.get<SwitchStmt>();
101  if (!Switch)
102  return false;
103  }
104  // Body need not be a CompoundStmt! But that's all we support editing.
105  Body = llvm::dyn_cast_or_null<CompoundStmt>(Switch->getBody());
106  if (!Body)
107  return false;
108  DeclCtx = &CA->getDeclContext();
109 
110  // Examine the condition of the switch statement to see if it's an enum.
111  const Expr *Cond = Switch->getCond();
112  if (!Cond)
113  return false;
114  // Ignore implicit casts, since enums implicitly cast to integer types.
115  Cond = Cond->IgnoreParenImpCasts();
116  // Get the canonical type to handle typedefs.
117  EnumT = Cond->getType().getCanonicalType()->getAsAdjusted<EnumType>();
118  if (!EnumT)
119  return false;
120  EnumD = EnumT->getDecl();
121  if (!EnumD || EnumD->isDependentType())
122  return false;
123 
124  // Finally, check which cases exist and which are covered.
125  // We trigger if there are any values in the enum that aren't covered by the
126  // switch.
127 
128  ASTContext &Ctx = Sel.AST->getASTContext();
129 
130  unsigned EnumIntWidth = Ctx.getIntWidth(QualType(EnumT, 0));
131  bool EnumIsSigned = EnumT->isSignedIntegerOrEnumerationType();
132 
133  auto Normalize = [&](llvm::APSInt Val) {
134  Val = Val.extOrTrunc(EnumIntWidth);
135  Val.setIsSigned(EnumIsSigned);
136  return Val;
137  };
138 
139  for (auto *EnumConstant : EnumD->enumerators()) {
140  ExpectedCases.insert(
141  std::make_pair(Normalize(EnumConstant->getInitVal()), EnumConstant));
142  }
143 
144  for (const SwitchCase *CaseList = Switch->getSwitchCaseList(); CaseList;
145  CaseList = CaseList->getNextSwitchCase()) {
146  // Default likely intends to cover cases we'd insert.
147  if (isa<DefaultStmt>(CaseList))
148  return false;
149 
150  const CaseStmt *CS = cast<CaseStmt>(CaseList);
151 
152  // GNU range cases are rare, we don't support them.
153  if (CS->caseStmtIsGNURange())
154  return false;
155 
156  // Support for direct references to enum constants. This is required to
157  // support C and ObjC which don't contain values in their ConstantExprs.
158  // The general way to get the value of a case is EvaluateAsRValue, but we'd
159  // rather not deal with that in case the AST is broken.
160  if (auto *DRE = dyn_cast<DeclRefExpr>(CS->getLHS()->IgnoreParenCasts())) {
161  if (auto *Enumerator = dyn_cast<EnumConstantDecl>(DRE->getDecl())) {
162  auto Iter = ExpectedCases.find(Normalize(Enumerator->getInitVal()));
163  if (Iter != ExpectedCases.end())
164  Iter->second.setCovered();
165  continue;
166  }
167  }
168 
169  // ConstantExprs with values are expected for C++, otherwise the storage
170  // kind will be None.
171 
172  // Case expression is not a constant expression or is value-dependent,
173  // so we may not be able to work out which cases are covered.
174  const ConstantExpr *CE = dyn_cast<ConstantExpr>(CS->getLHS());
175  if (!CE || CE->isValueDependent())
176  return false;
177 
178  // We need a stored value in order to continue; currently both C and ObjC
179  // enums won't have one.
180  if (CE->getResultStorageKind() == ConstantExpr::RSK_None)
181  return false;
182  auto Iter = ExpectedCases.find(Normalize(CE->getResultAsAPSInt()));
183  if (Iter != ExpectedCases.end())
184  Iter->second.setCovered();
185  }
186 
187  return !llvm::all_of(ExpectedCases,
188  [](auto &Pair) { return Pair.second.isCovered(); });
189 }
190 
191 Expected<Tweak::Effect> PopulateSwitch::apply(const Selection &Sel) {
192  ASTContext &Ctx = Sel.AST->getASTContext();
193 
194  SourceLocation Loc = Body->getRBracLoc();
195  ASTContext &DeclASTCtx = DeclCtx->getParentASTContext();
196 
197  llvm::SmallString<256> Text;
198  for (auto &EnumConstant : ExpectedCases) {
199  // Skip any enum constants already covered
200  if (EnumConstant.second.isCovered())
201  continue;
202 
203  Text.append({"case ", getQualification(DeclASTCtx, DeclCtx, Loc, EnumD)});
204  if (EnumD->isScoped())
205  Text.append({EnumD->getName(), "::"});
206  Text.append({EnumConstant.second.getEnumConstant()->getName(), ":"});
207  }
208 
209  assert(!Text.empty() && "No enumerators to insert!");
210  Text += "break;";
211 
212  const SourceManager &SM = Ctx.getSourceManager();
213  return Effect::mainFileEdit(
214  SM, tooling::Replacements(tooling::Replacement(SM, Loc, 0, Text)));
215 }
216 } // namespace
217 } // namespace clangd
218 } // namespace clang
Loc
SourceLocation Loc
Definition: KernelNameRestrictionCheck.cpp:45
Selection.h
Text
std::string Text
Definition: HTMLGenerator.cpp:80
Ctx
Context Ctx
Definition: TUScheduler.cpp:454
Decl
const FunctionDecl * Decl
Definition: AvoidBindCheck.cpp:100
Tweak.h
Logger.h
Parent
const Node * Parent
Definition: ExtractFunction.cpp:152
clang::clangd::getQualification
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:510
CE
CaptureExpr CE
Definition: AvoidBindCheck.cpp:67
clang
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Definition: ApplyReplacements.h:27
SM
const SourceManager & SM
Definition: IncludeCleaner.cpp:108
clang::clangd::HighlightingKind::EnumConstant
@ EnumConstant
REGISTER_TWEAK
#define REGISTER_TWEAK(Subclass)
Definition: Tweak.h:132
AST.h
clang::clangd::CodeAction::QUICKFIX_KIND
const static llvm::StringLiteral QUICKFIX_KIND
Definition: Protocol.h:979