clang-tools 22.0.0git
DefineOutline.cpp
Go to the documentation of this file.
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-exception
6//
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 body
45// or the function decl to be selected. Returns null if none of the above
46// criteria is met.
47// FIXME: This is shared with define inline, move them to a common header once
48// 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 empty
72// 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 until
86 // 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 reach
95 // 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 of
102 // 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 newline
152 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 range
171// `FromRange`. Removes matching instances of given token preceeding the
172// 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 a
212// different location, qualifies return value and function name to achieve that.
213// Contains function signature, except defaulted parameter arguments, body and
214// template parameters if applicable. No need to qualify parameters, as they are
215// 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, then
229 // qualifies those to be valid in TargetContext.
231 FD,
232 [&](ReferenceLoc Ref) {
233 // It is enough to qualify the first qualifier, so skip references with
234 // a qualifier. Also we can't do much if there are no targets or name is
235 // 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 to
268 // constructor/destructors, it only provides references to type names inside
269 // them.
270 // this works for constructors, but doesn't work for destructor as type name
271 // 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 in
282 // 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 update
297 // 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 always
374// contains function body. In addition to that it might contain constructor
375// 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. Therefore
381 // 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.h
414/// void foo() { return; }
415/// a.cc
416/// #include "a.h"
417///
418/// ----------------
419///
420/// After:
421/// a.h
422/// void foo();
423/// a.cc
424/// #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 {
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 their
448 // 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 the
467 // 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 file
484 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 in
493 // the prepare, since performing disk IO on each prepare request might be
494 // 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 it
513 // 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 else
624 log("getDefinitionOfAdjacentDecl: {0}", Loc.takeError());
625 }
626 });
627 if (!CandidateLoc)
628 return;
629
630 // If our definition is constrained to the same file, ignore
631 // definitions that are not located there.
632 // If our definition is not constrained to the same file, but
633 // our anchor definition is in the same file, then we also put our
634 // definition there, because that appears to be the user preference.
635 // Exception: If the existing definition is a template, then the
636 // 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 position
675 // of the name. Therefore, we heuristically try to locate the last token
676 // 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 the
692 // 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 else
707 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 else
752 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 to
762 // 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} // namespace
798} // namespace clangd
799} // namespace clang
#define REGISTER_TWEAK(Subclass)
Definition Tweak.h:129
An interface base for small context-sensitive refactoring actions.
Definition Tweak.h:46
static URI createFile(llvm::StringRef AbsolutePath)
This creates a file:// URI for AbsolutePath. The path must be absolute.
Definition URI.cpp:237
std::string toString() const
Returns a string URI with all components percent-encoded.
Definition URI.cpp:160
FIXME: Skip testing on windows temporarily due to the different escaping code mode.
Definition AST.cpp:45
llvm::Expected< Location > indexToLSPLocation(const SymbolLocation &Loc, llvm::StringRef TUPath)
Helper function for deriving an LSP Location from an index SymbolLocation.
SymbolID getSymbolID(const Decl *D)
Gets the symbol ID for a declaration. Returned SymbolID might be null.
Definition AST.cpp:354
std::optional< SourceRange > toHalfOpenFileRange(const SourceManager &SM, const LangOptions &LangOpts, SourceRange R)
Turns a token range into a half-open range and checks its correctness.
std::string getQualification(ASTContext &Context, const DeclContext *DestContext, SourceLocation InsertionPoint, const NamedDecl *ND)
Gets the nested name specifier necessary for spelling ND in DestContext, at InsertionPoint.
Definition AST.cpp:698
void findExplicitReferences(const Stmt *S, llvm::function_ref< void(ReferenceLoc)> Out, const HeuristicResolver *Resolver)
Recursively traverse S and report all references explicitly written in the code.
llvm::Error error(std::error_code EC, const char *Fmt, Ts &&... Vals)
Definition Logger.h:79
Position sourceLocToPosition(const SourceManager &SM, SourceLocation Loc)
Turn a SourceLocation into a [line, column] pair.
void log(const char *Fmt, Ts &&... Vals)
Definition Logger.h:67
EligibleRegion getEligiblePoints(llvm::StringRef Code, llvm::StringRef FullyQualifiedName, const LangOptions &LangOpts)
Returns most eligible region to insert a definition for FullyQualifiedName in the Code.
llvm::Expected< size_t > positionToOffset(llvm::StringRef Code, Position P, bool AllowColumnsBeyondLineLength)
Turn a [line, column] pair into an offset in Code.
std::optional< Path > getCorrespondingHeaderOrSource(PathRef OriginalFile, llvm::IntrusiveRefCntPtr< llvm::vfs::FileSystem > VFS)
Given a header file, returns the best matching source file, and vice visa.
std::string getNamespaceAtPosition(StringRef Code, const Position &Pos, const LangOptions &LangOpts)
std::string printQualifiedName(const NamedDecl &ND)
Returns the qualified name of ND.
Definition AST.cpp:207
void elog(const char *Fmt, Ts &&... Vals)
Definition Logger.h:61
bool isHeaderFile(llvm::StringRef FileName, std::optional< LangOptions > LangOpts)
Infers whether this is a header from the FileName and LangOpts (if presents).
cppcoreguidelines::ProBoundsAvoidUncheckedContainerAccessCheck P
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Simplified description of a clang AST node.
Definition Protocol.h:2026
static const llvm::StringLiteral REFACTOR_KIND
Definition Protocol.h:1085
Represents a symbol occurrence in the source file.
Definition Ref.h:88
Information about a reference written in the source code, independent of the actual AST node that thi...
Definition FindTarget.h:128
Input to prepare and apply tweaks.
Definition Tweak.h:49