brintos

brintos / llvm-project-archived public Read only

0
0
Text · 19.3 KiB · c970449 Raw
516 lines · cpp
1//===--- DefineInline.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#include "AST.h"10#include "FindTarget.h"11#include "Selection.h"12#include "SourceCode.h"13#include "XRefs.h"14#include "refactor/Tweak.h"15#include "support/Logger.h"16#include "clang/AST/ASTContext.h"17#include "clang/AST/ASTTypeTraits.h"18#include "clang/AST/Decl.h"19#include "clang/AST/DeclBase.h"20#include "clang/AST/DeclCXX.h"21#include "clang/AST/DeclTemplate.h"22#include "clang/AST/NestedNameSpecifier.h"23#include "clang/AST/Stmt.h"24#include "clang/Basic/LangOptions.h"25#include "clang/Basic/SourceLocation.h"26#include "clang/Basic/SourceManager.h"27#include "clang/Basic/TokenKinds.h"28#include "clang/Lex/Lexer.h"29#include "clang/Lex/Token.h"30#include "clang/Sema/Lookup.h"31#include "clang/Sema/Sema.h"32#include "clang/Tooling/Core/Replacement.h"33#include "llvm/ADT/DenseMap.h"34#include "llvm/ADT/DenseSet.h"35#include "llvm/ADT/SmallVector.h"36#include "llvm/ADT/StringRef.h"37#include "llvm/Support/Casting.h"38#include "llvm/Support/Error.h"39#include "llvm/Support/raw_ostream.h"40#include <cstddef>41#include <optional>42#include <set>43#include <string>44#include <unordered_map>45#include <utility>46#include <vector>47 48namespace clang {49namespace clangd {50namespace {51 52// Returns semicolon location for the given FD. Since AST doesn't contain that53// information, searches for a semicolon by lexing from end of function decl54// while skipping comments.55std::optional<SourceLocation> getSemicolonForDecl(const FunctionDecl *FD) {56  const SourceManager &SM = FD->getASTContext().getSourceManager();57  const LangOptions &LangOpts = FD->getASTContext().getLangOpts();58 59  SourceLocation CurLoc = FD->getEndLoc();60  auto NextTok = Lexer::findNextToken(CurLoc, SM, LangOpts);61  if (!NextTok || !NextTok->is(tok::semi))62    return std::nullopt;63  return NextTok->getLocation();64}65 66// Deduces the FunctionDecl from a selection. Requires either the function body67// or the function decl to be selected. Returns null if none of the above68// criteria is met.69const FunctionDecl *getSelectedFunction(const SelectionTree::Node *SelNode) {70  const DynTypedNode &AstNode = SelNode->ASTNode;71  if (const FunctionDecl *FD = AstNode.get<FunctionDecl>())72    return FD;73  if (AstNode.get<CompoundStmt>() &&74      SelNode->Selected == SelectionTree::Complete) {75    if (const SelectionTree::Node *P = SelNode->Parent)76      return P->ASTNode.get<FunctionDecl>();77  }78  return nullptr;79}80 81// Checks the decls mentioned in Source are visible in the context of Target.82// Achieves that by checking declarations occur before target location in83// translation unit or declared in the same class.84bool checkDeclsAreVisible(const llvm::DenseSet<const Decl *> &DeclRefs,85                          const FunctionDecl *Target, const SourceManager &SM) {86  SourceLocation TargetLoc = Target->getLocation();87  // To be used in visibility check below, decls in a class are visible88  // independent of order.89  const RecordDecl *Class = nullptr;90  if (const auto *MD = llvm::dyn_cast<CXXMethodDecl>(Target))91    Class = MD->getParent();92 93  for (const auto *DR : DeclRefs) {94    // Use canonical decl, since having one decl before target is enough.95    const Decl *D = DR->getCanonicalDecl();96    if (D == Target)97      continue;98    SourceLocation DeclLoc = D->getLocation();99 100    // FIXME: Allow declarations from different files with include insertion.101    if (!SM.isWrittenInSameFile(DeclLoc, TargetLoc))102      return false;103 104    // If declaration is before target, then it is visible.105    if (SM.isBeforeInTranslationUnit(DeclLoc, TargetLoc))106      continue;107 108    // Otherwise they need to be in same class109    if (!Class)110      return false;111    const RecordDecl *Parent = nullptr;112    if (const auto *MD = llvm::dyn_cast<CXXMethodDecl>(D))113      Parent = MD->getParent();114    else if (const auto *FD = llvm::dyn_cast<FieldDecl>(D))115      Parent = FD->getParent();116    if (Parent != Class)117      return false;118  }119  return true;120}121 122// Rewrites body of FD by re-spelling all of the names to make sure they are123// still valid in context of Target.124llvm::Expected<std::string> qualifyAllDecls(const FunctionDecl *FD,125                                            const FunctionDecl *Target,126                                            const HeuristicResolver *Resolver) {127  // There are three types of spellings that needs to be qualified in a function128  // body:129  // - Types:       Foo                 -> ns::Foo130  // - DeclRefExpr: ns2::foo()          -> ns1::ns2::foo();131  // - UsingDecls:132  //    using ns2::foo      -> using ns1::ns2::foo133  //    using namespace ns2 -> using namespace ns1::ns2134  //    using ns3 = ns2     -> using ns3 = ns1::ns2135  //136  // Go over all references inside a function body to generate replacements that137  // will qualify those. So that body can be moved into an arbitrary file.138  // We perform the qualification by qualifying the first type/decl in a139  // (un)qualified name. e.g:140  //    namespace a { namespace b { class Bar{}; void foo(); } }141  //    b::Bar x; -> a::b::Bar x;142  //    foo(); -> a::b::foo();143 144  auto *TargetContext = Target->getLexicalDeclContext();145  const SourceManager &SM = FD->getASTContext().getSourceManager();146 147  tooling::Replacements Replacements;148  bool HadErrors = false;149  findExplicitReferences(150      FD->getBody(),151      [&](ReferenceLoc Ref) {152        // Since we want to qualify only the first qualifier, skip names with a153        // qualifier.154        if (Ref.Qualifier)155          return;156        // There might be no decl in dependent contexts, there's nothing much we157        // can do in such cases.158        if (Ref.Targets.empty())159          return;160        // Do not qualify names introduced by macro expansions.161        if (Ref.NameLoc.isMacroID())162          return;163 164        for (const NamedDecl *ND : Ref.Targets) {165          if (ND->getDeclContext() != Ref.Targets.front()->getDeclContext()) {166            elog("define inline: Targets from multiple contexts: {0}, {1}",167                 printQualifiedName(*Ref.Targets.front()),168                 printQualifiedName(*ND));169            HadErrors = true;170            return;171          }172        }173        // All Targets are in the same scope, so we can safely chose first one.174        const NamedDecl *ND = Ref.Targets.front();175        // Skip anything from a non-namespace scope, these can be:176        // - Function or Method scopes, which means decl is local and doesn't177        // need178        //   qualification.179        // - From Class/Struct/Union scope, which again doesn't need any180        // qualifiers,181        //   rather the left side of it requires qualification, like:182        //   namespace a { class Bar { public: static int x; } }183        //   void foo() { Bar::x; }184        //                ~~~~~ -> we need to qualify Bar not x.185        if (!ND->getDeclContext()->isNamespace())186          return;187 188        const std::string Qualifier = getQualification(189            FD->getASTContext(), TargetContext, Target->getBeginLoc(), ND);190        if (auto Err = Replacements.add(191                tooling::Replacement(SM, Ref.NameLoc, 0, Qualifier))) {192          HadErrors = true;193          elog("define inline: Failed to add quals: {0}", std::move(Err));194        }195      },196      Resolver);197 198  if (HadErrors)199    return error(200        "define inline: Failed to compute qualifiers. See logs for details.");201 202  // Get new begin and end positions for the qualified body.203  auto OrigBodyRange = toHalfOpenFileRange(204      SM, FD->getASTContext().getLangOpts(), FD->getBody()->getSourceRange());205  if (!OrigBodyRange)206    return error("Couldn't get range func body.");207 208  unsigned BodyBegin = SM.getFileOffset(OrigBodyRange->getBegin());209  unsigned BodyEnd = Replacements.getShiftedCodePosition(210      SM.getFileOffset(OrigBodyRange->getEnd()));211 212  // Trim the result to function body.213  auto QualifiedFunc = tooling::applyAllReplacements(214      SM.getBufferData(SM.getFileID(OrigBodyRange->getBegin())), Replacements);215  if (!QualifiedFunc)216    return QualifiedFunc.takeError();217  return QualifiedFunc->substr(BodyBegin, BodyEnd - BodyBegin + 1);218}219 220/// Generates Replacements for changing template and function parameter names in221/// \p Dest to be the same as in \p Source.222llvm::Expected<tooling::Replacements>223renameParameters(const FunctionDecl *Dest, const FunctionDecl *Source,224                 const HeuristicResolver *Resolver) {225  llvm::DenseMap<const Decl *, std::string> ParamToNewName;226  llvm::DenseMap<const NamedDecl *, std::vector<SourceLocation>> RefLocs;227  auto HandleParam = [&](const NamedDecl *DestParam,228                         const NamedDecl *SourceParam) {229    // No need to rename if parameters already have the same name.230    if (DestParam->getName() == SourceParam->getName())231      return;232    std::string NewName;233    // Unnamed parameters won't be visited in findExplicitReferences. So add234    // them here.235    if (DestParam->getName().empty()) {236      RefLocs[DestParam].push_back(DestParam->getLocation());237      // If decl is unnamed in destination we pad the new name to avoid gluing238      // with previous token, e.g. foo(int^) shouldn't turn into foo(intx).239      NewName = " ";240    }241    NewName.append(std::string(SourceParam->getName()));242    ParamToNewName[DestParam->getCanonicalDecl()] = std::move(NewName);243  };244 245  // Populate mapping for template parameters.246  auto *DestTempl = Dest->getDescribedFunctionTemplate();247  auto *SourceTempl = Source->getDescribedFunctionTemplate();248  assert(bool(DestTempl) == bool(SourceTempl));249  if (DestTempl) {250    const auto *DestTPL = DestTempl->getTemplateParameters();251    const auto *SourceTPL = SourceTempl->getTemplateParameters();252    assert(DestTPL->size() == SourceTPL->size());253 254    for (size_t I = 0, EP = DestTPL->size(); I != EP; ++I)255      HandleParam(DestTPL->getParam(I), SourceTPL->getParam(I));256  }257 258  // Populate mapping for function params.259  assert(Dest->param_size() == Source->param_size());260  for (size_t I = 0, E = Dest->param_size(); I != E; ++I)261    HandleParam(Dest->getParamDecl(I), Source->getParamDecl(I));262 263  const SourceManager &SM = Dest->getASTContext().getSourceManager();264  const LangOptions &LangOpts = Dest->getASTContext().getLangOpts();265  // Collect other references in function signature, i.e parameter types and266  // default arguments.267  findExplicitReferences(268      // Use function template in case of templated functions to visit template269      // parameters.270      DestTempl ? llvm::dyn_cast<Decl>(DestTempl) : llvm::dyn_cast<Decl>(Dest),271      [&](ReferenceLoc Ref) {272        if (Ref.Targets.size() != 1)273          return;274        const auto *Target =275            llvm::cast<NamedDecl>(Ref.Targets.front()->getCanonicalDecl());276        auto It = ParamToNewName.find(Target);277        if (It == ParamToNewName.end())278          return;279        RefLocs[Target].push_back(Ref.NameLoc);280      },281      Resolver);282 283  // Now try to generate edits for all the refs.284  tooling::Replacements Replacements;285  for (auto &Entry : RefLocs) {286    const auto *OldDecl = Entry.first;287    llvm::StringRef OldName = OldDecl->getName();288    llvm::StringRef NewName = ParamToNewName[OldDecl];289    for (SourceLocation RefLoc : Entry.second) {290      CharSourceRange ReplaceRange;291      // In case of unnamed parameters, we have an empty char range, whereas we292      // have a tokenrange at RefLoc with named parameters.293      if (OldName.empty())294        ReplaceRange = CharSourceRange::getCharRange(RefLoc, RefLoc);295      else296        ReplaceRange = CharSourceRange::getTokenRange(RefLoc, RefLoc);297      // If occurrence is coming from a macro expansion, try to get back to the298      // file range.299      if (RefLoc.isMacroID()) {300        ReplaceRange = Lexer::makeFileCharRange(ReplaceRange, SM, LangOpts);301        // Bail out if we need to replace macro bodies.302        if (ReplaceRange.isInvalid()) {303          auto Err = error("Cant rename parameter inside macro body.");304          elog("define inline: {0}", Err);305          return std::move(Err);306        }307      }308 309      if (auto Err = Replacements.add(310              tooling::Replacement(SM, ReplaceRange, NewName))) {311        elog("define inline: Couldn't replace parameter name for {0} to {1}: "312             "{2}",313             OldName, NewName, Err);314        return std::move(Err);315      }316    }317  }318  return Replacements;319}320 321// Returns the canonical declaration for the given FunctionDecl. This will322// usually be the first declaration in current translation unit with the323// exception of template specialization.324// For those we return first declaration different than the canonical one.325// Because canonical declaration points to template decl instead of326// specialization.327const FunctionDecl *findTarget(const FunctionDecl *FD) {328  auto *CanonDecl = FD->getCanonicalDecl();329  if (!FD->isFunctionTemplateSpecialization() || CanonDecl == FD)330    return CanonDecl;331  // For specializations CanonicalDecl is the TemplatedDecl, which is not the332  // target we want to inline into. Instead we traverse previous decls to find333  // the first forward decl for this specialization.334  auto *PrevDecl = FD;335  while (PrevDecl->getPreviousDecl() != CanonDecl) {336    PrevDecl = PrevDecl->getPreviousDecl();337    assert(PrevDecl && "Found specialization without template decl");338  }339  return PrevDecl;340}341 342// Returns the beginning location for a FunctionDecl. Returns location of343// template keyword for templated functions.344SourceLocation getBeginLoc(const FunctionDecl *FD) {345  // Include template parameter list.346  if (auto *FTD = FD->getDescribedFunctionTemplate())347    return FTD->getBeginLoc();348  return FD->getBeginLoc();349}350 351std::optional<tooling::Replacement>352addInlineIfInHeader(const FunctionDecl *FD) {353  // This includes inline functions and constexpr functions.354  if (FD->isInlined() || llvm::isa<CXXMethodDecl>(FD))355    return std::nullopt;356  // Primary template doesn't need inline.357  if (FD->isTemplated() && !FD->isFunctionTemplateSpecialization())358    return std::nullopt;359 360  const SourceManager &SM = FD->getASTContext().getSourceManager();361  llvm::StringRef FileName = SM.getFilename(FD->getLocation());362 363  // If it is not a header we don't need to mark function as "inline".364  if (!isHeaderFile(FileName, FD->getASTContext().getLangOpts()))365    return std::nullopt;366 367  return tooling::Replacement(SM, FD->getInnerLocStart(), 0, "inline ");368}369 370/// Moves definition of a function/method to its declaration location.371/// Before:372/// a.h:373///   void foo();374///375/// a.cc:376///   void foo() { return; }377///378/// ------------------------379/// After:380/// a.h:381///   void foo() { return; }382///383/// a.cc:384///385class DefineInline : public Tweak {386public:387  const char *id() const final;388 389  llvm::StringLiteral kind() const override {390    return CodeAction::REFACTOR_KIND;391  }392  std::string title() const override {393    return "Move function body to declaration";394  }395 396  // Returns true when selection is on a function definition that does not397  // make use of any internal symbols.398  bool prepare(const Selection &Sel) override {399    const SelectionTree::Node *SelNode = Sel.ASTSelection.commonAncestor();400    if (!SelNode)401      return false;402    Source = getSelectedFunction(SelNode);403    if (!Source || !Source->hasBody())404      return false;405    // Only the last level of template parameter locations are not kept in AST,406    // so if we are inlining a method that is in a templated class, there is no407    // way to verify template parameter names. Therefore we bail out.408    if (auto *MD = llvm::dyn_cast<CXXMethodDecl>(Source)) {409      if (MD->getParent()->isTemplated())410        return false;411    }412    // If function body starts or ends inside a macro, we refuse to move it into413    // declaration location.414    if (Source->getBody()->getBeginLoc().isMacroID() ||415        Source->getBody()->getEndLoc().isMacroID())416      return false;417 418    Target = findTarget(Source);419    if (Target == Source) {420      // The only declaration is Source. No other declaration to move function421      // body.422      // FIXME: If we are in an implementation file, figure out a suitable423      // location to put declaration. Possibly using other declarations in the424      // AST.425      return false;426    }427 428    // Check if the decls referenced in function body are visible in the429    // declaration location.430    if (!checkDeclsAreVisible(getNonLocalDeclRefs(*Sel.AST, Source), Target,431                              Sel.AST->getSourceManager()))432      return false;433 434    return true;435  }436 437  Expected<Effect> apply(const Selection &Sel) override {438    const auto &AST = Sel.AST->getASTContext();439    const auto &SM = AST.getSourceManager();440 441    auto Semicolon = getSemicolonForDecl(Target);442    if (!Semicolon)443      return error("Couldn't find semicolon for target declaration.");444 445    auto AddInlineIfNecessary = addInlineIfInHeader(Target);446    auto ParamReplacements =447        renameParameters(Target, Source, Sel.AST->getHeuristicResolver());448    if (!ParamReplacements)449      return ParamReplacements.takeError();450 451    auto QualifiedBody =452        qualifyAllDecls(Source, Target, Sel.AST->getHeuristicResolver());453    if (!QualifiedBody)454      return QualifiedBody.takeError();455 456    const tooling::Replacement SemicolonToFuncBody(SM, *Semicolon, 1,457                                                   *QualifiedBody);458    tooling::Replacements TargetFileReplacements(SemicolonToFuncBody);459    TargetFileReplacements = TargetFileReplacements.merge(*ParamReplacements);460    if (AddInlineIfNecessary) {461      if (auto Err = TargetFileReplacements.add(*AddInlineIfNecessary))462        return std::move(Err);463    }464 465    auto DefRange = toHalfOpenFileRange(466        SM, AST.getLangOpts(),467        SM.getExpansionRange(CharSourceRange::getCharRange(getBeginLoc(Source),468                                                           Source->getEndLoc()))469            .getAsRange());470    if (!DefRange)471      return error("Couldn't get range for the source.");472    unsigned int SourceLen = SM.getFileOffset(DefRange->getEnd()) -473                             SM.getFileOffset(DefRange->getBegin());474    const tooling::Replacement DeleteFuncBody(SM, DefRange->getBegin(),475                                              SourceLen, "");476 477    llvm::SmallVector<std::pair<std::string, Edit>> Edits;478    // Edit for Target.479    auto FE = Effect::fileEdit(SM, SM.getFileID(*Semicolon),480                               std::move(TargetFileReplacements));481    if (!FE)482      return FE.takeError();483    Edits.push_back(std::move(*FE));484 485    // Edit for Source.486    if (!SM.isWrittenInSameFile(DefRange->getBegin(),487                                SM.getExpansionLoc(Target->getBeginLoc()))) {488      // Generate a new edit if the Source and Target are in different files.489      auto FE = Effect::fileEdit(SM, SM.getFileID(Sel.Cursor),490                                 tooling::Replacements(DeleteFuncBody));491      if (!FE)492        return FE.takeError();493      Edits.push_back(std::move(*FE));494    } else {495      // Merge with previous edit if they are in the same file.496      if (auto Err = Edits.front().second.Replacements.add(DeleteFuncBody))497        return std::move(Err);498    }499 500    Effect E;501    for (auto &Pair : Edits)502      E.ApplyEdits.try_emplace(std::move(Pair.first), std::move(Pair.second));503    return E;504  }505 506private:507  const FunctionDecl *Source = nullptr;508  const FunctionDecl *Target = nullptr;509};510 511REGISTER_TWEAK(DefineInline)512 513} // namespace514} // namespace clangd515} // namespace clang516