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