xref: /llvm-project/clang-tools-extra/clangd/refactor/tweaks/PopulateSwitch.cpp (revision cd6022916bff1d6fab007b554810b631549ba43c)
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 
47 namespace clang {
48 namespace clangd {
49 namespace {
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;
title() const54   std::string title() const override { return "Populate switch"; }
kind() const55   llvm::StringLiteral kind() const override {
56     return CodeAction::QUICKFIX_KIND;
57   }
58 
59 private:
60   class ExpectedCase {
61   public:
ExpectedCase(const EnumConstantDecl * Decl)62     ExpectedCase(const EnumConstantDecl *Decl) : Data(Decl, false) {}
isCovered() const63     bool isCovered() const { return Data.getInt(); }
setCovered(bool Val=true)64     void setCovered(bool Val = true) { Data.setInt(Val); }
getEnumConstant() const65     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 
REGISTER_TWEAK(PopulateSwitch)83 REGISTER_TWEAK(PopulateSwitch)
84 
85 bool 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 
apply(const Selection & Sel)190 Expected<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
218