xref: /llvm-project/clang-tools-extra/clangd/refactor/tweaks/PopulateSwitch.cpp (revision cd6022916bff1d6fab007b554810b631549ba43c)
1018066d9STadeo Kondrak //===--- PopulateSwitch.cpp --------------------------------------*- C++-*-===//
2018066d9STadeo Kondrak //
3018066d9STadeo Kondrak // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4018066d9STadeo Kondrak // See https://llvm.org/LICENSE.txt for license information.
5018066d9STadeo Kondrak // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6018066d9STadeo Kondrak //
7018066d9STadeo Kondrak //===----------------------------------------------------------------------===//
8018066d9STadeo Kondrak //
9018066d9STadeo Kondrak // Tweak that populates an empty switch statement of an enumeration type with
10018066d9STadeo Kondrak // all of the enumerators of that type.
11018066d9STadeo Kondrak //
12018066d9STadeo Kondrak // Before:
13018066d9STadeo Kondrak //   enum Color { RED, GREEN, BLUE };
14018066d9STadeo Kondrak //
15018066d9STadeo Kondrak //   void f(Color color) {
16018066d9STadeo Kondrak //     switch (color) {}
17018066d9STadeo Kondrak //   }
18018066d9STadeo Kondrak //
19018066d9STadeo Kondrak // After:
20018066d9STadeo Kondrak //   enum Color { RED, GREEN, BLUE };
21018066d9STadeo Kondrak //
22018066d9STadeo Kondrak //   void f(Color color) {
23018066d9STadeo Kondrak //     switch (color) {
24018066d9STadeo Kondrak //     case RED:
25018066d9STadeo Kondrak //     case GREEN:
26018066d9STadeo Kondrak //     case BLUE:
27018066d9STadeo Kondrak //       break;
28018066d9STadeo Kondrak //     }
29018066d9STadeo Kondrak //   }
30018066d9STadeo Kondrak //
31018066d9STadeo Kondrak //===----------------------------------------------------------------------===//
32018066d9STadeo Kondrak 
33018066d9STadeo Kondrak #include "AST.h"
34018066d9STadeo Kondrak #include "Selection.h"
35018066d9STadeo Kondrak #include "refactor/Tweak.h"
36018066d9STadeo Kondrak #include "clang/AST/Decl.h"
37018066d9STadeo Kondrak #include "clang/AST/Stmt.h"
38018066d9STadeo Kondrak #include "clang/AST/Type.h"
39018066d9STadeo Kondrak #include "clang/Basic/SourceLocation.h"
40018066d9STadeo Kondrak #include "clang/Basic/SourceManager.h"
41018066d9STadeo Kondrak #include "clang/Tooling/Core/Replacement.h"
425918ef8bSNathan James #include "llvm/ADT/MapVector.h"
435918ef8bSNathan James #include "llvm/ADT/STLExtras.h"
444fb303f3STadeo Kondrak #include <cassert>
45018066d9STadeo Kondrak #include <string>
46018066d9STadeo Kondrak 
47018066d9STadeo Kondrak namespace clang {
48018066d9STadeo Kondrak namespace clangd {
49018066d9STadeo Kondrak namespace {
50018066d9STadeo Kondrak class PopulateSwitch : public Tweak {
51018066d9STadeo Kondrak   const char *id() const override;
52018066d9STadeo Kondrak   bool prepare(const Selection &Sel) override;
53018066d9STadeo Kondrak   Expected<Effect> apply(const Selection &Sel) override;
title() const54018066d9STadeo Kondrak   std::string title() const override { return "Populate switch"; }
kind() const5517747d2eSSam McCall   llvm::StringLiteral kind() const override {
5657ac47d7SSam McCall     return CodeAction::QUICKFIX_KIND;
5717747d2eSSam McCall   }
58018066d9STadeo Kondrak 
59018066d9STadeo Kondrak private:
605918ef8bSNathan James   class ExpectedCase {
615918ef8bSNathan James   public:
ExpectedCase(const EnumConstantDecl * Decl)625918ef8bSNathan James     ExpectedCase(const EnumConstantDecl *Decl) : Data(Decl, false) {}
isCovered() const635918ef8bSNathan James     bool isCovered() const { return Data.getInt(); }
setCovered(bool Val=true)645918ef8bSNathan James     void setCovered(bool Val = true) { Data.setInt(Val); }
getEnumConstant() const655918ef8bSNathan James     const EnumConstantDecl *getEnumConstant() const {
665918ef8bSNathan James       return Data.getPointer();
675918ef8bSNathan James     }
685918ef8bSNathan James 
695918ef8bSNathan James   private:
705918ef8bSNathan James     llvm::PointerIntPair<const EnumConstantDecl *, 1, bool> Data;
715918ef8bSNathan James   };
725918ef8bSNathan James 
73018066d9STadeo Kondrak   const DeclContext *DeclCtx = nullptr;
74018066d9STadeo Kondrak   const SwitchStmt *Switch = nullptr;
75018066d9STadeo Kondrak   const CompoundStmt *Body = nullptr;
764fb303f3STadeo Kondrak   const EnumType *EnumT = nullptr;
77018066d9STadeo Kondrak   const EnumDecl *EnumD = nullptr;
785918ef8bSNathan James   // Maps the Enum values to the EnumConstantDecl and a bool signifying if its
795918ef8bSNathan James   // covered in the switch.
805918ef8bSNathan James   llvm::MapVector<llvm::APSInt, ExpectedCase> ExpectedCases;
81018066d9STadeo Kondrak };
82018066d9STadeo Kondrak 
REGISTER_TWEAK(PopulateSwitch)83018066d9STadeo Kondrak REGISTER_TWEAK(PopulateSwitch)
84018066d9STadeo Kondrak 
85018066d9STadeo Kondrak bool PopulateSwitch::prepare(const Selection &Sel) {
86018066d9STadeo Kondrak   const SelectionTree::Node *CA = Sel.ASTSelection.commonAncestor();
87018066d9STadeo Kondrak   if (!CA)
88018066d9STadeo Kondrak     return false;
89018066d9STadeo Kondrak 
90c5c3cdb9SSam McCall   // Support targeting
91c5c3cdb9SSam McCall   //  - the switch statement itself (keyword, parens)
92c5c3cdb9SSam McCall   //  - the whole expression (possibly wrapped in implicit casts)
93c5c3cdb9SSam McCall   //  - the outer body (typically CompoundStmt)
94c5c3cdb9SSam McCall   // Selections *within* the expression or body don't trigger.
95c5c3cdb9SSam McCall   // direct child (the
96c5c3cdb9SSam McCall   Switch = CA->ASTNode.get<SwitchStmt>();
97c5c3cdb9SSam McCall   if (!Switch) {
98c5c3cdb9SSam McCall     if (const SelectionTree::Node *Parent = CA->outerImplicit().Parent)
99c5c3cdb9SSam McCall       Switch = Parent->ASTNode.get<SwitchStmt>();
100018066d9STadeo Kondrak     if (!Switch)
101018066d9STadeo Kondrak       return false;
102c5c3cdb9SSam McCall   }
103c5c3cdb9SSam McCall   // Body need not be a CompoundStmt! But that's all we support editing.
104c5c3cdb9SSam McCall   Body = llvm::dyn_cast_or_null<CompoundStmt>(Switch->getBody());
105018066d9STadeo Kondrak   if (!Body)
106018066d9STadeo Kondrak     return false;
107c5c3cdb9SSam McCall   DeclCtx = &CA->getDeclContext();
108018066d9STadeo Kondrak 
109c5c3cdb9SSam McCall   // Examine the condition of the switch statement to see if it's an enum.
110018066d9STadeo Kondrak   const Expr *Cond = Switch->getCond();
111018066d9STadeo Kondrak   if (!Cond)
112018066d9STadeo Kondrak     return false;
113018066d9STadeo Kondrak   // Ignore implicit casts, since enums implicitly cast to integer types.
114018066d9STadeo Kondrak   Cond = Cond->IgnoreParenImpCasts();
115a90d57b6SDavid Goldman   // Get the canonical type to handle typedefs.
116a90d57b6SDavid Goldman   EnumT = Cond->getType().getCanonicalType()->getAsAdjusted<EnumType>();
117018066d9STadeo Kondrak   if (!EnumT)
118018066d9STadeo Kondrak     return false;
119018066d9STadeo Kondrak   EnumD = EnumT->getDecl();
120f6970503SAdam Czachorowski   if (!EnumD || EnumD->isDependentType())
121018066d9STadeo Kondrak     return false;
122018066d9STadeo Kondrak 
123c5c3cdb9SSam McCall   // Finally, check which cases exist and which are covered.
1245918ef8bSNathan James   // We trigger if there are any values in the enum that aren't covered by the
1255918ef8bSNathan James   // switch.
1264fb303f3STadeo Kondrak 
1275918ef8bSNathan James   ASTContext &Ctx = Sel.AST->getASTContext();
1285918ef8bSNathan James 
1295918ef8bSNathan James   unsigned EnumIntWidth = Ctx.getIntWidth(QualType(EnumT, 0));
1305918ef8bSNathan James   bool EnumIsSigned = EnumT->isSignedIntegerOrEnumerationType();
1315918ef8bSNathan James 
1325918ef8bSNathan James   auto Normalize = [&](llvm::APSInt Val) {
1335918ef8bSNathan James     Val = Val.extOrTrunc(EnumIntWidth);
1345918ef8bSNathan James     Val.setIsSigned(EnumIsSigned);
1355918ef8bSNathan James     return Val;
1365918ef8bSNathan James   };
1375918ef8bSNathan James 
1385918ef8bSNathan James   for (auto *EnumConstant : EnumD->enumerators()) {
1395918ef8bSNathan James     ExpectedCases.insert(
1405918ef8bSNathan James         std::make_pair(Normalize(EnumConstant->getInitVal()), EnumConstant));
1415918ef8bSNathan James   }
1425918ef8bSNathan James 
1435918ef8bSNathan James   for (const SwitchCase *CaseList = Switch->getSwitchCaseList(); CaseList;
1445918ef8bSNathan James        CaseList = CaseList->getNextSwitchCase()) {
1454fb303f3STadeo Kondrak     // Default likely intends to cover cases we'd insert.
1464fb303f3STadeo Kondrak     if (isa<DefaultStmt>(CaseList))
147018066d9STadeo Kondrak       return false;
148018066d9STadeo Kondrak 
1494fb303f3STadeo Kondrak     const CaseStmt *CS = cast<CaseStmt>(CaseList);
1505918ef8bSNathan James 
1515918ef8bSNathan James     // GNU range cases are rare, we don't support them.
1524fb303f3STadeo Kondrak     if (CS->caseStmtIsGNURange())
1534fb303f3STadeo Kondrak       return false;
1544fb303f3STadeo Kondrak 
155a90d57b6SDavid Goldman     // Support for direct references to enum constants. This is required to
156a90d57b6SDavid Goldman     // support C and ObjC which don't contain values in their ConstantExprs.
157a90d57b6SDavid Goldman     // The general way to get the value of a case is EvaluateAsRValue, but we'd
158a90d57b6SDavid Goldman     // rather not deal with that in case the AST is broken.
159a90d57b6SDavid Goldman     if (auto *DRE = dyn_cast<DeclRefExpr>(CS->getLHS()->IgnoreParenCasts())) {
160a90d57b6SDavid Goldman       if (auto *Enumerator = dyn_cast<EnumConstantDecl>(DRE->getDecl())) {
161a90d57b6SDavid Goldman         auto Iter = ExpectedCases.find(Normalize(Enumerator->getInitVal()));
162a90d57b6SDavid Goldman         if (Iter != ExpectedCases.end())
163a90d57b6SDavid Goldman           Iter->second.setCovered();
164a90d57b6SDavid Goldman         continue;
165a90d57b6SDavid Goldman       }
166a90d57b6SDavid Goldman     }
167a90d57b6SDavid Goldman 
168a90d57b6SDavid Goldman     // ConstantExprs with values are expected for C++, otherwise the storage
169a90d57b6SDavid Goldman     // kind will be None.
170a90d57b6SDavid Goldman 
1714fb303f3STadeo Kondrak     // Case expression is not a constant expression or is value-dependent,
1724fb303f3STadeo Kondrak     // so we may not be able to work out which cases are covered.
1734fb303f3STadeo Kondrak     const ConstantExpr *CE = dyn_cast<ConstantExpr>(CS->getLHS());
1744fb303f3STadeo Kondrak     if (!CE || CE->isValueDependent())
1754fb303f3STadeo Kondrak       return false;
1765918ef8bSNathan James 
177a90d57b6SDavid Goldman     // We need a stored value in order to continue; currently both C and ObjC
178a90d57b6SDavid Goldman     // enums won't have one.
179*cd602291SVlad Serebrennikov     if (CE->getResultStorageKind() == ConstantResultStorageKind::None)
1805918ef8bSNathan James       return false;
1815918ef8bSNathan James     auto Iter = ExpectedCases.find(Normalize(CE->getResultAsAPSInt()));
1825918ef8bSNathan James     if (Iter != ExpectedCases.end())
1835918ef8bSNathan James       Iter->second.setCovered();
1844fb303f3STadeo Kondrak   }
1854fb303f3STadeo Kondrak 
1865918ef8bSNathan James   return !llvm::all_of(ExpectedCases,
1875918ef8bSNathan James                        [](auto &Pair) { return Pair.second.isCovered(); });
188018066d9STadeo Kondrak }
189018066d9STadeo Kondrak 
apply(const Selection & Sel)190018066d9STadeo Kondrak Expected<Tweak::Effect> PopulateSwitch::apply(const Selection &Sel) {
1914fb303f3STadeo Kondrak   ASTContext &Ctx = Sel.AST->getASTContext();
1924fb303f3STadeo Kondrak 
193018066d9STadeo Kondrak   SourceLocation Loc = Body->getRBracLoc();
1944fb303f3STadeo Kondrak   ASTContext &DeclASTCtx = DeclCtx->getParentASTContext();
195018066d9STadeo Kondrak 
1965918ef8bSNathan James   llvm::SmallString<256> Text;
1975918ef8bSNathan James   for (auto &EnumConstant : ExpectedCases) {
1985918ef8bSNathan James     // Skip any enum constants already covered
1995918ef8bSNathan James     if (EnumConstant.second.isCovered())
2004fb303f3STadeo Kondrak       continue;
2014fb303f3STadeo Kondrak 
2025918ef8bSNathan James     Text.append({"case ", getQualification(DeclASTCtx, DeclCtx, Loc, EnumD)});
2035918ef8bSNathan James     if (EnumD->isScoped())
2045918ef8bSNathan James       Text.append({EnumD->getName(), "::"});
2055918ef8bSNathan James     Text.append({EnumConstant.second.getEnumConstant()->getName(), ":"});
206018066d9STadeo Kondrak   }
2074fb303f3STadeo Kondrak 
2084fb303f3STadeo Kondrak   assert(!Text.empty() && "No enumerators to insert!");
209018066d9STadeo Kondrak   Text += "break;";
210018066d9STadeo Kondrak 
2114fb303f3STadeo Kondrak   const SourceManager &SM = Ctx.getSourceManager();
212018066d9STadeo Kondrak   return Effect::mainFileEdit(
213018066d9STadeo Kondrak       SM, tooling::Replacements(tooling::Replacement(SM, Loc, 0, Text)));
214018066d9STadeo Kondrak }
215018066d9STadeo Kondrak } // namespace
216018066d9STadeo Kondrak } // namespace clangd
217018066d9STadeo Kondrak } // namespace clang
218