brintos

brintos / llvm-project-archived public Read only

0
0
Text · 7.0 KiB · 769d73f Raw
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