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