//===--- 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 #include namespace clang { namespace clangd { namespace { class PopulateSwitch : public Tweak { const char *id() const override; bool prepare(const Selection &Sel) override; Expected 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 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 ExpectedCases; }; REGISTER_TWEAK(PopulateSwitch) bool PopulateSwitch::prepare(const Selection &Sel) { const SelectionTree::Node *CA = Sel.ASTSelection.commonAncestor(); if (!CA) return false; const Stmt *CAStmt = CA->ASTNode.get(); if (!CAStmt) return false; // Go up a level if we see a compound statement. // switch (value) {} // ^^ if (isa(CAStmt)) { CA = CA->Parent; if (!CA) return false; CAStmt = CA->ASTNode.get(); if (!CAStmt) return false; } DeclCtx = &CA->getDeclContext(); Switch = dyn_cast(CAStmt); if (!Switch) return false; Body = dyn_cast(Switch->getBody()); if (!Body) return false; const Expr *Cond = Switch->getCond(); if (!Cond) return false; // Ignore implicit casts, since enums implicitly cast to integer types. Cond = Cond->IgnoreParenImpCasts(); EnumT = Cond->getType()->getAsAdjusted(); if (!EnumT) return false; EnumD = EnumT->getDecl(); if (!EnumD || EnumD->isDependentType()) return false; // 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(CaseList)) return false; const CaseStmt *CS = cast(CaseList); // GNU range cases are rare, we don't support them. if (CS->caseStmtIsGNURange()) return false; // 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(CS->getLHS()); if (!CE || CE->isValueDependent()) return false; // Unsure if this case could ever come up, but prevents an unreachable // executing in getResultAsAPSInt. 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 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