218 lines · cpp
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-exception6//7//===----------------------------------------------------------------------===//8//9// Tweak that populates an empty switch statement of an enumeration type with10// 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 47namespace clang {48namespace clangd {49namespace {50class PopulateSwitch : public Tweak {51 const char *id() const override;52 bool prepare(const Selection &Sel) override;53 Expected<Effect> apply(const Selection &Sel) override;54 std::string title() const override { return "Populate switch"; }55 llvm::StringLiteral kind() const override {56 return CodeAction::QUICKFIX_KIND;57 }58 59private:60 class ExpectedCase {61 public:62 ExpectedCase(const EnumConstantDecl *Decl) : Data(Decl, false) {}63 bool isCovered() const { return Data.getInt(); }64 void setCovered(bool Val = true) { Data.setInt(Val); }65 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 its79 // covered in the switch.80 llvm::MapVector<llvm::APSInt, ExpectedCase> ExpectedCases;81};82 83REGISTER_TWEAK(PopulateSwitch)84 85bool PopulateSwitch::prepare(const Selection &Sel) {86 const SelectionTree::Node *CA = Sel.ASTSelection.commonAncestor();87 if (!CA)88 return false;89 90 // Support targeting91 // - 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()->getAsCanonical<EnumType>();117 if (!EnumT)118 return false;119 EnumD = EnumT->getDecl()->getDefinitionOrSelf();120 if (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 the125 // 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 to156 // 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'd158 // 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 storage169 // 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 ObjC178 // 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 190Expected<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 covered199 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} // namespace216} // namespace clangd217} // namespace clang218