brintos

brintos / llvm-project-archived public Read only

0
0
Text · 29.4 KiB · 45e7ade Raw
800 lines · cpp
1//===--- DefineOutline.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 "FindSymbols.h"11#include "FindTarget.h"12#include "HeaderSourceSwitch.h"13#include "ParsedAST.h"14#include "Selection.h"15#include "SourceCode.h"16#include "refactor/Tweak.h"17#include "support/Logger.h"18#include "support/Path.h"19#include "clang/AST/ASTTypeTraits.h"20#include "clang/AST/Attr.h"21#include "clang/AST/Decl.h"22#include "clang/AST/DeclBase.h"23#include "clang/AST/DeclCXX.h"24#include "clang/AST/DeclTemplate.h"25#include "clang/AST/Stmt.h"26#include "clang/Basic/SourceLocation.h"27#include "clang/Basic/SourceManager.h"28#include "clang/Basic/TokenKinds.h"29#include "clang/Tooling/Core/Replacement.h"30#include "clang/Tooling/Syntax/Tokens.h"31#include "llvm/ADT/STLExtras.h"32#include "llvm/ADT/StringRef.h"33#include "llvm/Support/Casting.h"34#include "llvm/Support/Error.h"35#include <cstddef>36#include <optional>37#include <string>38#include <tuple>39 40namespace clang {41namespace clangd {42namespace {43 44// Deduces the FunctionDecl from a selection. Requires either the function body45// or the function decl to be selected. Returns null if none of the above46// criteria is met.47// FIXME: This is shared with define inline, move them to a common header once48// we have a place for such.49const FunctionDecl *getSelectedFunction(const SelectionTree::Node *SelNode) {50  if (!SelNode)51    return nullptr;52  const DynTypedNode &AstNode = SelNode->ASTNode;53  if (const FunctionDecl *FD = AstNode.get<FunctionDecl>())54    return FD;55  if (AstNode.get<CompoundStmt>() &&56      SelNode->Selected == SelectionTree::Complete) {57    if (const SelectionTree::Node *P = SelNode->Parent)58      return P->ASTNode.get<FunctionDecl>();59  }60  return nullptr;61}62 63std::optional<Path> getSourceFile(llvm::StringRef FileName,64                                  const Tweak::Selection &Sel) {65  assert(Sel.FS);66  if (auto Source = getCorrespondingHeaderOrSource(FileName, Sel.FS))67    return *Source;68  return getCorrespondingHeaderOrSource(FileName, *Sel.AST, Sel.Index);69}70 71// Synthesize a DeclContext for TargetNS from CurContext. TargetNS must be empty72// for global namespace, and endwith "::" otherwise.73// Returns std::nullopt if TargetNS is not a prefix of CurContext.74std::optional<const DeclContext *>75findContextForNS(llvm::StringRef TargetNS, const DeclContext *CurContext) {76  assert(TargetNS.empty() || TargetNS.ends_with("::"));77  // Skip any non-namespace contexts, e.g. TagDecls, functions/methods.78  CurContext = CurContext->getEnclosingNamespaceContext();79  // If TargetNS is empty, it means global ns, which is translation unit.80  if (TargetNS.empty()) {81    while (!CurContext->isTranslationUnit())82      CurContext = CurContext->getParent();83    return CurContext;84  }85  // Otherwise we need to drop any trailing namespaces from CurContext until86  // we reach TargetNS.87  std::string TargetContextNS =88      CurContext->isNamespace()89          ? llvm::cast<NamespaceDecl>(CurContext)->getQualifiedNameAsString()90          : "";91  TargetContextNS.append("::");92 93  llvm::StringRef CurrentContextNS(TargetContextNS);94  // If TargetNS is not a prefix of CurrentContext, there's no way to reach95  // it.96  if (!CurrentContextNS.starts_with(TargetNS))97    return std::nullopt;98 99  while (CurrentContextNS != TargetNS) {100    CurContext = CurContext->getParent();101    // These colons always exists since TargetNS is a prefix of102    // CurrentContextNS, it ends with "::" and they are not equal.103    CurrentContextNS = CurrentContextNS.take_front(104        CurrentContextNS.drop_back(2).rfind("::") + 2);105  }106  return CurContext;107}108 109// Returns source code for FD after applying Replacements.110// FIXME: Make the function take a parameter to return only the function body,111// afterwards it can be shared with define-inline code action.112llvm::Expected<std::string>113getFunctionSourceAfterReplacements(const FunctionDecl *FD,114                                   const tooling::Replacements &Replacements,115                                   bool TargetFileIsHeader) {116  const auto &SM = FD->getASTContext().getSourceManager();117  auto OrigFuncRange = toHalfOpenFileRange(118      SM, FD->getASTContext().getLangOpts(), FD->getSourceRange());119  if (!OrigFuncRange)120    return error("Couldn't get range for function.");121 122  // Get new begin and end positions for the qualified function definition.123  unsigned FuncBegin = SM.getFileOffset(OrigFuncRange->getBegin());124  unsigned FuncEnd = Replacements.getShiftedCodePosition(125      SM.getFileOffset(OrigFuncRange->getEnd()));126 127  // Trim the result to function definition.128  auto QualifiedFunc = tooling::applyAllReplacements(129      SM.getBufferData(SM.getMainFileID()), Replacements);130  if (!QualifiedFunc)131    return QualifiedFunc.takeError();132 133  auto Source = QualifiedFunc->substr(FuncBegin, FuncEnd - FuncBegin + 1);134  std::string TemplatePrefix;135  auto AddToTemplatePrefixIfApplicable = [&](const Decl *D) {136    const TemplateParameterList *Params = D->getDescribedTemplateParams();137    if (!Params)138      return;139    for (Decl *P : *Params) {140      if (auto *TTP = dyn_cast<TemplateTypeParmDecl>(P))141        TTP->removeDefaultArgument();142      else if (auto *NTTP = dyn_cast<NonTypeTemplateParmDecl>(P))143        NTTP->removeDefaultArgument();144      else if (auto *TTPD = dyn_cast<TemplateTemplateParmDecl>(P))145        TTPD->removeDefaultArgument();146    }147    std::string S;148    llvm::raw_string_ostream Stream(S);149    Params->print(Stream, FD->getASTContext());150    if (!S.empty())151      *S.rbegin() = '\n'; // Replace space with newline152    TemplatePrefix.insert(0, S);153  };154  AddToTemplatePrefixIfApplicable(FD);155  if (auto *MD = llvm::dyn_cast<CXXMethodDecl>(FD)) {156    for (const CXXRecordDecl *Parent = MD->getParent(); Parent;157         Parent =158             llvm::dyn_cast_or_null<const CXXRecordDecl>(Parent->getParent())) {159      AddToTemplatePrefixIfApplicable(Parent);160    }161  }162 163  if (TargetFileIsHeader)164    Source.insert(0, "inline ");165  if (!TemplatePrefix.empty())166    Source.insert(0, TemplatePrefix);167  return Source;168}169 170// Returns replacements to delete tokens with kind `Kind` in the range171// `FromRange`. Removes matching instances of given token preceeding the172// function defition.173llvm::Expected<tooling::Replacements>174deleteTokensWithKind(const syntax::TokenBuffer &TokBuf, tok::TokenKind Kind,175                     SourceRange FromRange) {176  tooling::Replacements DelKeywordCleanups;177  llvm::Error Errors = llvm::Error::success();178  bool FoundAny = false;179  for (const auto &Tok : TokBuf.expandedTokens(FromRange)) {180    if (Tok.kind() != Kind)181      continue;182    FoundAny = true;183    auto Spelling = TokBuf.spelledForExpanded(llvm::ArrayRef(Tok));184    if (!Spelling) {185      Errors = llvm::joinErrors(186          std::move(Errors),187          error("define outline: couldn't remove `{0}` keyword.",188                tok::getKeywordSpelling(Kind)));189      break;190    }191    auto &SM = TokBuf.sourceManager();192    CharSourceRange DelRange =193        syntax::Token::range(SM, Spelling->front(), Spelling->back())194            .toCharRange(SM);195    if (auto Err =196            DelKeywordCleanups.add(tooling::Replacement(SM, DelRange, "")))197      Errors = llvm::joinErrors(std::move(Errors), std::move(Err));198  }199  if (!FoundAny) {200    Errors = llvm::joinErrors(201        std::move(Errors),202        error("define outline: couldn't find `{0}` keyword to remove.",203              tok::getKeywordSpelling(Kind)));204  }205 206  if (Errors)207    return std::move(Errors);208  return DelKeywordCleanups;209}210 211// Creates a modified version of function definition that can be inserted at a212// different location, qualifies return value and function name to achieve that.213// Contains function signature, except defaulted parameter arguments, body and214// template parameters if applicable. No need to qualify parameters, as they are215// looked up in the context containing the function/method.216// FIXME: Drop attributes in function signature.217llvm::Expected<std::string>218getFunctionSourceCode(const FunctionDecl *FD, const DeclContext *TargetContext,219                      const syntax::TokenBuffer &TokBuf,220                      const HeuristicResolver *Resolver,221                      bool TargetFileIsHeader) {222  auto &AST = FD->getASTContext();223  auto &SM = AST.getSourceManager();224 225  llvm::Error Errors = llvm::Error::success();226  tooling::Replacements DeclarationCleanups;227 228  // Finds the first unqualified name in function return type and name, then229  // qualifies those to be valid in TargetContext.230  findExplicitReferences(231      FD,232      [&](ReferenceLoc Ref) {233        // It is enough to qualify the first qualifier, so skip references with234        // a qualifier. Also we can't do much if there are no targets or name is235        // inside a macro body.236        if (Ref.Qualifier || Ref.Targets.empty() || Ref.NameLoc.isMacroID())237          return;238        // Only qualify return type and function name.239        if (Ref.NameLoc != FD->getReturnTypeSourceRange().getBegin() &&240            Ref.NameLoc != FD->getLocation())241          return;242 243        for (const NamedDecl *ND : Ref.Targets) {244          if (ND->getKind() == Decl::TemplateTypeParm)245            return;246          if (ND->getDeclContext() != Ref.Targets.front()->getDeclContext()) {247            elog("Targets from multiple contexts: {0}, {1}",248                 printQualifiedName(*Ref.Targets.front()),249                 printQualifiedName(*ND));250            return;251          }252        }253        const NamedDecl *ND = Ref.Targets.front();254        std::string Qualifier =255            getQualification(AST, TargetContext,256                             SM.getLocForStartOfFile(SM.getMainFileID()), ND);257        if (ND->getDeclContext()->isDependentContext() &&258            llvm::isa<TypeDecl>(ND)) {259          Qualifier.insert(0, "typename ");260        }261        if (auto Err = DeclarationCleanups.add(262                tooling::Replacement(SM, Ref.NameLoc, 0, Qualifier)))263          Errors = llvm::joinErrors(std::move(Errors), std::move(Err));264      },265      Resolver);266 267  // findExplicitReferences doesn't provide references to268  // constructor/destructors, it only provides references to type names inside269  // them.270  // this works for constructors, but doesn't work for destructor as type name271  // doesn't cover leading `~`, so handle it specially.272  if (const auto *Destructor = llvm::dyn_cast<CXXDestructorDecl>(FD)) {273    if (auto Err = DeclarationCleanups.add(tooling::Replacement(274            SM, Destructor->getLocation(), 0,275            getQualification(AST, TargetContext,276                             SM.getLocForStartOfFile(SM.getMainFileID()),277                             Destructor))))278      Errors = llvm::joinErrors(std::move(Errors), std::move(Err));279  }280 281  // Get rid of default arguments, since they should not be specified in282  // out-of-line definition.283  for (const auto *PVD : FD->parameters()) {284    if (!PVD->hasDefaultArg())285      continue;286    // Deletion range spans the initializer, usually excluding the `=`.287    auto DelRange = CharSourceRange::getTokenRange(PVD->getDefaultArgRange());288    // Get all tokens before the default argument.289    auto Tokens = TokBuf.expandedTokens(PVD->getSourceRange())290                      .take_while([&SM, &DelRange](const syntax::Token &Tok) {291                        return SM.isBeforeInTranslationUnit(292                            Tok.location(), DelRange.getBegin());293                      });294    if (TokBuf.expandedTokens(DelRange.getAsRange()).front().kind() !=295        tok::equal) {296      // Find the last `=` if it isn't included in the initializer, and update297      // the DelRange to include it.298      auto Tok =299          llvm::find_if(llvm::reverse(Tokens), [](const syntax::Token &Tok) {300            return Tok.kind() == tok::equal;301          });302      assert(Tok != Tokens.rend());303      DelRange.setBegin(Tok->location());304    }305    if (auto Err =306            DeclarationCleanups.add(tooling::Replacement(SM, DelRange, "")))307      Errors = llvm::joinErrors(std::move(Errors), std::move(Err));308  }309 310  auto DelAttr = [&](const Attr *A) {311    if (!A)312      return;313    auto AttrTokens =314        TokBuf.spelledForExpanded(TokBuf.expandedTokens(A->getRange()));315    assert(A->getLocation().isValid());316    if (!AttrTokens || AttrTokens->empty()) {317      Errors = llvm::joinErrors(318          std::move(Errors), error("define outline: Can't move out of line as "319                                   "function has a macro `{0}` specifier.",320                                   A->getSpelling()));321      return;322    }323    CharSourceRange DelRange =324        syntax::Token::range(SM, AttrTokens->front(), AttrTokens->back())325            .toCharRange(SM);326    if (auto Err =327            DeclarationCleanups.add(tooling::Replacement(SM, DelRange, "")))328      Errors = llvm::joinErrors(std::move(Errors), std::move(Err));329  };330 331  DelAttr(FD->getAttr<OverrideAttr>());332  DelAttr(FD->getAttr<FinalAttr>());333 334  auto DelKeyword = [&](tok::TokenKind Kind, SourceRange FromRange) {335    auto DelKeywords = deleteTokensWithKind(TokBuf, Kind, FromRange);336    if (!DelKeywords) {337      Errors = llvm::joinErrors(std::move(Errors), DelKeywords.takeError());338      return;339    }340    DeclarationCleanups = DeclarationCleanups.merge(*DelKeywords);341  };342 343  if (FD->isInlineSpecified())344    DelKeyword(tok::kw_inline, {FD->getBeginLoc(), FD->getLocation()});345  if (const auto *MD = dyn_cast<CXXMethodDecl>(FD)) {346    if (MD->isVirtualAsWritten())347      DelKeyword(tok::kw_virtual, {FD->getBeginLoc(), FD->getLocation()});348    if (MD->isStatic())349      DelKeyword(tok::kw_static, {FD->getBeginLoc(), FD->getLocation()});350  }351  if (const auto *CD = dyn_cast<CXXConstructorDecl>(FD)) {352    if (CD->isExplicit())353      DelKeyword(tok::kw_explicit, {FD->getBeginLoc(), FD->getLocation()});354  }355 356  if (Errors)357    return std::move(Errors);358  return getFunctionSourceAfterReplacements(FD, DeclarationCleanups,359                                            TargetFileIsHeader);360}361 362struct InsertionPoint {363  const DeclContext *EnclosingNamespace = nullptr;364  size_t Offset;365};366 367enum class RelativeInsertPos { Before, After };368struct InsertionAnchor {369  Location Loc;370  RelativeInsertPos RelInsertPos = RelativeInsertPos::Before;371};372 373// Returns the range that should be deleted from declaration, which always374// contains function body. In addition to that it might contain constructor375// initializers.376SourceRange getDeletionRange(const FunctionDecl *FD,377                             const syntax::TokenBuffer &TokBuf) {378  auto DeletionRange = FD->getBody()->getSourceRange();379  if (auto *CD = llvm::dyn_cast<CXXConstructorDecl>(FD)) {380    // AST doesn't contain the location for ":" in ctor initializers. Therefore381    // we find it by finding the first ":" before the first ctor initializer.382    SourceLocation InitStart;383    // Find the first initializer.384    for (const auto *CInit : CD->inits()) {385      // SourceOrder is -1 for implicit initializers.386      if (CInit->getSourceOrder() != 0)387        continue;388      InitStart = CInit->getSourceLocation();389      break;390    }391    if (InitStart.isValid()) {392      auto Toks = TokBuf.expandedTokens(CD->getSourceRange());393      // Drop any tokens after the initializer.394      Toks = Toks.take_while([&TokBuf, &InitStart](const syntax::Token &Tok) {395        return TokBuf.sourceManager().isBeforeInTranslationUnit(Tok.location(),396                                                                InitStart);397      });398      // Look for the first colon.399      auto Tok =400          llvm::find_if(llvm::reverse(Toks), [](const syntax::Token &Tok) {401            return Tok.kind() == tok::colon;402          });403      assert(Tok != Toks.rend());404      DeletionRange.setBegin(Tok->location());405    }406  }407  return DeletionRange;408}409 410/// Moves definition of a function/method to an appropriate implementation file.411///412/// Before:413/// a.h414///   void foo() { return; }415/// a.cc416///   #include "a.h"417///418/// ----------------419///420/// After:421/// a.h422///   void foo();423/// a.cc424///   #include "a.h"425///   void foo() { return; }426class DefineOutline : public Tweak {427public:428  const char *id() const override;429 430  bool hidden() const override { return false; }431  llvm::StringLiteral kind() const override {432    return CodeAction::REFACTOR_KIND;433  }434  std::string title() const override {435    return "Move function body to out-of-line";436  }437 438  bool prepare(const Selection &Sel) override {439    SameFile = !isHeaderFile(Sel.AST->tuPath(), Sel.AST->getLangOpts());440    Source = getSelectedFunction(Sel.ASTSelection.commonAncestor());441 442    // Bail out if the selection is not a in-line function definition.443    if (!Source || !Source->doesThisDeclarationHaveABody() ||444        Source->isOutOfLine())445      return false;446 447    // Bail out if this is a function template specialization, as their448    // definitions need to be visible in all including translation units.449    if (Source->getTemplateSpecializationInfo())450      return false;451 452    auto *MD = llvm::dyn_cast<CXXMethodDecl>(Source);453    if (!MD) {454      if (Source->getDescribedFunctionTemplate())455        return false;456      // Can't outline free-standing functions in the same file.457      return !SameFile;458    }459 460    for (const CXXRecordDecl *Parent = MD->getParent(); Parent;461         Parent =462             llvm::dyn_cast_or_null<const CXXRecordDecl>(Parent->getParent())) {463      if (const TemplateParameterList *Params =464              Parent->getDescribedTemplateParams()) {465 466        // Class template member functions must be defined in the467        // same file.468        SameFile = true;469 470        // Bail out if the template parameter is unnamed.471        for (NamedDecl *P : *Params) {472          if (!P->getIdentifier())473            return false;474        }475      }476    }477 478    // Function templates must be defined in the same file.479    if (MD->getDescribedTemplate())480      SameFile = true;481 482    // The refactoring is meaningless for unnamed classes and namespaces,483    // unless we're outlining in the same file484    for (const DeclContext *DC = MD->getParent(); DC; DC = DC->getParent()) {485      if (auto *ND = llvm::dyn_cast<NamedDecl>(DC)) {486        if (ND->getDeclName().isEmpty() &&487            (!SameFile || !llvm::dyn_cast<NamespaceDecl>(ND)))488          return false;489      }490    }491 492    // Note that we don't check whether an implementation file exists or not in493    // the prepare, since performing disk IO on each prepare request might be494    // expensive.495    return true;496  }497 498  Expected<Effect> apply(const Selection &Sel) override {499    const SourceManager &SM = Sel.AST->getSourceManager();500    std::optional<Path> CCFile;501    auto Anchor = getDefinitionOfAdjacentDecl(Sel);502    if (Anchor) {503      CCFile = Anchor->Loc.uri.file();504    } else {505      CCFile = SameFile ? Sel.AST->tuPath().str()506                        : getSourceFile(Sel.AST->tuPath(), Sel);507    }508    if (!CCFile)509      return error("Couldn't find a suitable implementation file.");510    assert(Sel.FS && "FS Must be set in apply");511    auto Buffer = Sel.FS->getBufferForFile(*CCFile);512    // FIXME: Maybe we should consider creating the implementation file if it513    // doesn't exist?514    if (!Buffer)515      return llvm::errorCodeToError(Buffer.getError());516 517    auto Contents = Buffer->get()->getBuffer();518    SourceManagerForFile SMFF(*CCFile, Contents);519 520    std::optional<Position> InsertionPos;521    if (Anchor) {522      if (auto P = getInsertionPointFromExistingDefinition(523              SMFF, **Buffer, Anchor->Loc, Anchor->RelInsertPos, Sel.AST)) {524        InsertionPos = *P;525      }526    }527 528    std::optional<std::size_t> Offset;529    const DeclContext *EnclosingNamespace = nullptr;530    std::string EnclosingNamespaceName;531 532    if (InsertionPos) {533      EnclosingNamespaceName = getNamespaceAtPosition(Contents, *InsertionPos,534                                                      Sel.AST->getLangOpts());535    } else if (SameFile) {536      auto P = getInsertionPointInMainFile(Sel.AST);537      if (!P)538        return P.takeError();539      Offset = P->Offset;540      EnclosingNamespace = P->EnclosingNamespace;541    } else {542      auto Region = getEligiblePoints(543          Contents, Source->getQualifiedNameAsString(), Sel.AST->getLangOpts());544      assert(!Region.EligiblePoints.empty());545      EnclosingNamespaceName = Region.EnclosingNamespace;546      InsertionPos = Region.EligiblePoints.back();547    }548 549    if (InsertionPos) {550      auto O = positionToOffset(Contents, *InsertionPos);551      if (!O)552        return O.takeError();553      Offset = *O;554      auto TargetContext =555          findContextForNS(EnclosingNamespaceName, Source->getDeclContext());556      if (!TargetContext)557        return error("define outline: couldn't find a context for target");558      EnclosingNamespace = *TargetContext;559    }560 561    assert(Offset);562    assert(EnclosingNamespace);563 564    auto FuncDef = getFunctionSourceCode(565        Source, EnclosingNamespace, Sel.AST->getTokens(),566        Sel.AST->getHeuristicResolver(),567        SameFile && isHeaderFile(Sel.AST->tuPath(), Sel.AST->getLangOpts()));568    if (!FuncDef)569      return FuncDef.takeError();570 571    const tooling::Replacement InsertFunctionDef(*CCFile, *Offset, 0, *FuncDef);572    auto Effect = Effect::mainFileEdit(573        SMFF.get(), tooling::Replacements(InsertFunctionDef));574    if (!Effect)575      return Effect.takeError();576 577    tooling::Replacements HeaderUpdates(tooling::Replacement(578        Sel.AST->getSourceManager(),579        CharSourceRange::getTokenRange(*toHalfOpenFileRange(580            SM, Sel.AST->getLangOpts(),581            getDeletionRange(Source, Sel.AST->getTokens()))),582        ";"));583 584    if (Source->isInlineSpecified()) {585      auto DelInline =586          deleteTokensWithKind(Sel.AST->getTokens(), tok::kw_inline,587                               {Source->getBeginLoc(), Source->getLocation()});588      if (!DelInline)589        return DelInline.takeError();590      HeaderUpdates = HeaderUpdates.merge(*DelInline);591    }592 593    if (SameFile) {594      tooling::Replacements &R = Effect->ApplyEdits[*CCFile].Replacements;595      R = R.merge(HeaderUpdates);596    } else {597      auto HeaderFE = Effect::fileEdit(SM, SM.getMainFileID(), HeaderUpdates);598      if (!HeaderFE)599        return HeaderFE.takeError();600      Effect->ApplyEdits.try_emplace(HeaderFE->first,601                                     std::move(HeaderFE->second));602    }603    return std::move(*Effect);604  }605 606  std::optional<InsertionAnchor>607  getDefinitionOfAdjacentDecl(const Selection &Sel) {608    if (!Sel.Index)609      return {};610    std::optional<Location> Anchor;611    std::string TuURI = URI::createFile(Sel.AST->tuPath()).toString();612    auto CheckCandidate = [&](Decl *Candidate) {613      assert(Candidate != Source);614      if (auto Func = llvm::dyn_cast_or_null<FunctionDecl>(Candidate);615          !Func || Func->isThisDeclarationADefinition()) {616        return;617      }618      std::optional<Location> CandidateLoc;619      Sel.Index->lookup({{getSymbolID(Candidate)}}, [&](const Symbol &S) {620        if (S.Definition) {621          if (auto Loc = indexToLSPLocation(S.Definition, Sel.AST->tuPath()))622            CandidateLoc = *Loc;623          else624            log("getDefinitionOfAdjacentDecl: {0}", Loc.takeError());625        }626      });627      if (!CandidateLoc)628        return;629 630      // If our definition is constrained to the same file, ignore631      // definitions that are not located there.632      // If our definition is not constrained to the same file, but633      // our anchor definition is in the same file, then we also put our634      // definition there, because that appears to be the user preference.635      // Exception: If the existing definition is a template, then the636      // location is likely due to technical necessity rather than preference,637      // so ignore that definition.638      bool CandidateSameFile = TuURI == CandidateLoc->uri.uri();639      if (SameFile && !CandidateSameFile)640        return;641      if (!SameFile && CandidateSameFile) {642        if (Candidate->isTemplateDecl())643          return;644        SameFile = true;645      }646      Anchor = *CandidateLoc;647    };648 649    // Try to find adjacent function declarations.650    // Determine the closest one by alternatingly going "up" and "down"651    // from our function in increasing steps.652    const DeclContext *ParentContext = Source->getParent();653    const auto SourceIt = llvm::find_if(654        ParentContext->decls(), [this](const Decl *D) { return D == Source; });655    if (SourceIt == ParentContext->decls_end())656      return {};657    const int Preceding = std::distance(ParentContext->decls_begin(), SourceIt);658    const int Following =659        std::distance(SourceIt, ParentContext->decls_end()) - 1;660    for (int Offset = 1; Offset <= Preceding || Offset <= Following; ++Offset) {661      if (Offset <= Preceding)662        CheckCandidate(663            *std::next(ParentContext->decls_begin(), Preceding - Offset));664      if (Anchor)665        return InsertionAnchor{*Anchor, RelativeInsertPos::After};666      if (Offset <= Following)667        CheckCandidate(*std::next(SourceIt, Offset));668      if (Anchor)669        return InsertionAnchor{*Anchor, RelativeInsertPos::Before};670    }671    return {};672  }673 674  // We don't know the actual start or end of the definition, only the position675  // of the name. Therefore, we heuristically try to locate the last token676  // before or in this function, respectively. Adapt as required by user code.677  std::optional<Position> getInsertionPointFromExistingDefinition(678      SourceManagerForFile &SMFF, const llvm::MemoryBuffer &Buffer,679      const Location &Loc, RelativeInsertPos RelInsertPos, ParsedAST *AST) {680    auto StartOffset = positionToOffset(Buffer.getBuffer(), Loc.range.start);681    if (!StartOffset)682      return {};683    SourceLocation InsertionLoc;684    SourceManager &SM = SMFF.get();685 686    auto InsertBefore = [&] {687      // Go backwards until we encounter one of the following:688      //   - An opening brace (of a namespace).689      //   - A closing brace (of a function definition).690      //   - A semicolon (of a declaration).691      // If no such token was found, then the first token in the file starts the692      // definition.693      auto Tokens = syntax::tokenize(694          syntax::FileRange(SM.getMainFileID(), 0, *StartOffset), SM,695          AST->getLangOpts());696      if (Tokens.empty())697        return;698      for (auto I = std::rbegin(Tokens);699           InsertionLoc.isInvalid() && I != std::rend(Tokens); ++I) {700        switch (I->kind()) {701        case tok::l_brace:702        case tok::r_brace:703        case tok::semi:704          if (I != std::rbegin(Tokens))705            InsertionLoc = std::prev(I)->location();706          else707            InsertionLoc = I->endLocation();708          break;709        default:710          break;711        }712      }713      if (InsertionLoc.isInvalid())714        InsertionLoc = Tokens.front().location();715    };716 717    if (RelInsertPos == RelativeInsertPos::Before) {718      InsertBefore();719    } else {720      // Skip over one top-level pair of parentheses (for the parameter list)721      // and one pair of curly braces (for the code block).722      // If that fails, insert before the function instead.723      auto Tokens =724          syntax::tokenize(syntax::FileRange(SM.getMainFileID(), *StartOffset,725                                             Buffer.getBuffer().size()),726                           SM, AST->getLangOpts());727      bool SkippedParams = false;728      int OpenParens = 0;729      int OpenBraces = 0;730      std::optional<syntax::Token> Tok;731      for (const auto &T : Tokens) {732        tok::TokenKind StartKind = SkippedParams ? tok::l_brace : tok::l_paren;733        tok::TokenKind EndKind = SkippedParams ? tok::r_brace : tok::r_paren;734        int &Count = SkippedParams ? OpenBraces : OpenParens;735        if (T.kind() == StartKind) {736          ++Count;737        } else if (T.kind() == EndKind) {738          if (--Count == 0) {739            if (SkippedParams) {740              Tok = T;741              break;742            }743            SkippedParams = true;744          } else if (Count < 0) {745            break;746          }747        }748      }749      if (Tok)750        InsertionLoc = Tok->endLocation();751      else752        InsertBefore();753    }754 755    if (!InsertionLoc.isValid())756      return {};757    return sourceLocToPosition(SM, InsertionLoc);758  }759 760  // Returns the most natural insertion point in this file.761  // This is a fallback for when we failed to find an existing definition to762  // place the new one next to. It only considers namespace proximity.763  llvm::Expected<InsertionPoint> getInsertionPointInMainFile(ParsedAST *AST) {764    // If the definition goes to the same file and there is a namespace,765    // we should (and, in the case of anonymous namespaces, need to)766    // put the definition into the original namespace block.767    auto *Klass = Source->getDeclContext()->getOuterLexicalRecordContext();768    if (!Klass)769      return error("moving to same file not supported for free functions");770    const SourceLocation EndLoc = Klass->getBraceRange().getEnd();771    const auto &TokBuf = AST->getTokens();772    auto Tokens = TokBuf.expandedTokens();773    auto It = llvm::lower_bound(774        Tokens, EndLoc, [](const syntax::Token &Tok, SourceLocation EndLoc) {775          return Tok.location() < EndLoc;776        });777    while (It != Tokens.end()) {778      if (It->kind() != tok::semi) {779        ++It;780        continue;781      }782      unsigned Offset =783          AST->getSourceManager().getDecomposedLoc(It->endLocation()).second;784      return InsertionPoint{Klass->getEnclosingNamespaceContext(), Offset};785    }786    return error(787        "failed to determine insertion location: no end of class found");788  }789 790private:791  const FunctionDecl *Source = nullptr;792  bool SameFile = false;793};794 795REGISTER_TWEAK(DefineOutline)796 797} // namespace798} // namespace clangd799} // namespace clang800