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