clang-tools 23.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 (auto ReturnTypeRange = FD->getReturnTypeSourceRange();
240 Ref.NameLoc != FD->getLocation() &&
241 (ReturnTypeRange.isInvalid() ||
242 SM.isBeforeInTranslationUnit(Ref.NameLoc,
243 ReturnTypeRange.getBegin()) ||
244 SM.isBeforeInTranslationUnit(ReturnTypeRange.getEnd(),
245 Ref.NameLoc)))
246 return;
247
248 for (const NamedDecl *ND : Ref.Targets) {
249 if (ND->getKind() == Decl::TemplateTypeParm)
250 return;
251 if (ND->getDeclContext() != Ref.Targets.front()->getDeclContext()) {
252 elog("Targets from multiple contexts: {0}, {1}",
253 printQualifiedName(*Ref.Targets.front()),
254 printQualifiedName(*ND));
255 return;
256 }
257 }
258 const NamedDecl *ND = Ref.Targets.front();
259 std::string Qualifier =
260 getQualification(AST, TargetContext,
261 SM.getLocForStartOfFile(SM.getMainFileID()), ND);
262 if (ND->getDeclContext()->isDependentContext() &&
263 llvm::isa<TypeDecl>(ND)) {
264 Qualifier.insert(0, "typename ");
265 }
266 if (auto Err = DeclarationCleanups.add(
267 tooling::Replacement(SM, Ref.NameLoc, 0, Qualifier)))
268 Errors = llvm::joinErrors(std::move(Errors), std::move(Err));
269 },
270 Resolver);
271
272 // findExplicitReferences doesn't provide references to
273 // constructor/destructors, it only provides references to type names inside
274 // them.
275 // this works for constructors, but doesn't work for destructor as type name
276 // doesn't cover leading `~`, so handle it specially.
277 if (const auto *Destructor = llvm::dyn_cast<CXXDestructorDecl>(FD)) {
278 if (auto Err = DeclarationCleanups.add(tooling::Replacement(
279 SM, Destructor->getLocation(), 0,
280 getQualification(AST, TargetContext,
281 SM.getLocForStartOfFile(SM.getMainFileID()),
282 Destructor))))
283 Errors = llvm::joinErrors(std::move(Errors), std::move(Err));
284 }
285
286 // Get rid of default arguments, since they should not be specified in
287 // out-of-line definition.
288 for (const auto *PVD : FD->parameters()) {
289 if (!PVD->hasDefaultArg())
290 continue;
291 // Deletion range spans the initializer, usually excluding the `=`.
292 auto DelRange = CharSourceRange::getTokenRange(PVD->getDefaultArgRange());
293 // Get all tokens before the default argument.
294 auto Tokens = TokBuf.expandedTokens(PVD->getSourceRange())
295 .take_while([&SM, &DelRange](const syntax::Token &Tok) {
296 return SM.isBeforeInTranslationUnit(
297 Tok.location(), DelRange.getBegin());
298 });
299 if (TokBuf.expandedTokens(DelRange.getAsRange()).front().kind() !=
300 tok::equal) {
301 // Find the last `=` if it isn't included in the initializer, and update
302 // the DelRange to include it.
303 auto Tok =
304 llvm::find_if(llvm::reverse(Tokens), [](const syntax::Token &Tok) {
305 return Tok.kind() == tok::equal;
306 });
307 assert(Tok != Tokens.rend());
308 DelRange.setBegin(Tok->location());
309 }
310 if (auto Err =
311 DeclarationCleanups.add(tooling::Replacement(SM, DelRange, "")))
312 Errors = llvm::joinErrors(std::move(Errors), std::move(Err));
313 }
314
315 auto DelAttr = [&](const Attr *A) {
316 if (!A)
317 return;
318 auto AttrTokens =
319 TokBuf.spelledForExpanded(TokBuf.expandedTokens(A->getRange()));
320 assert(A->getLocation().isValid());
321 if (!AttrTokens || AttrTokens->empty()) {
322 Errors = llvm::joinErrors(
323 std::move(Errors), error("define outline: Can't move out of line as "
324 "function has a macro `{0}` specifier.",
325 A->getSpelling()));
326 return;
327 }
328 CharSourceRange DelRange =
329 syntax::Token::range(SM, AttrTokens->front(), AttrTokens->back())
330 .toCharRange(SM);
331 if (auto Err =
332 DeclarationCleanups.add(tooling::Replacement(SM, DelRange, "")))
333 Errors = llvm::joinErrors(std::move(Errors), std::move(Err));
334 };
335
336 DelAttr(FD->getAttr<OverrideAttr>());
337 DelAttr(FD->getAttr<FinalAttr>());
338
339 auto DelKeyword = [&](tok::TokenKind Kind, SourceRange FromRange) {
340 auto DelKeywords = deleteTokensWithKind(TokBuf, Kind, FromRange);
341 if (!DelKeywords) {
342 Errors = llvm::joinErrors(std::move(Errors), DelKeywords.takeError());
343 return;
344 }
345 DeclarationCleanups = DeclarationCleanups.merge(*DelKeywords);
346 };
347
348 if (FD->isInlineSpecified())
349 DelKeyword(tok::kw_inline, {FD->getBeginLoc(), FD->getLocation()});
350 if (const auto *MD = dyn_cast<CXXMethodDecl>(FD)) {
351 if (MD->isVirtualAsWritten())
352 DelKeyword(tok::kw_virtual, {FD->getBeginLoc(), FD->getLocation()});
353 if (MD->isStatic())
354 DelKeyword(tok::kw_static, {FD->getBeginLoc(), FD->getLocation()});
355 }
356 if (const auto *CD = dyn_cast<CXXConstructorDecl>(FD)) {
357 if (CD->isExplicit())
358 DelKeyword(tok::kw_explicit, {FD->getBeginLoc(), FD->getLocation()});
359 }
360
361 if (Errors)
362 return std::move(Errors);
363 return getFunctionSourceAfterReplacements(FD, DeclarationCleanups,
364 TargetFileIsHeader);
365}
366
367struct InsertionPoint {
368 const DeclContext *EnclosingNamespace = nullptr;
369 size_t Offset;
370};
371
372enum class RelativeInsertPos { Before, After };
373struct InsertionAnchor {
374 Location Loc;
375 RelativeInsertPos RelInsertPos = RelativeInsertPos::Before;
376};
377
378// Returns the range that should be deleted from declaration, which always
379// contains function body. In addition to that it might contain constructor
380// initializers.
381SourceRange getDeletionRange(const FunctionDecl *FD,
382 const syntax::TokenBuffer &TokBuf) {
383 auto DeletionRange = FD->getBody()->getSourceRange();
384 if (auto *CD = llvm::dyn_cast<CXXConstructorDecl>(FD)) {
385 // AST doesn't contain the location for ":" in ctor initializers. Therefore
386 // we find it by finding the first ":" before the first ctor initializer.
387 SourceLocation InitStart;
388 // Find the first initializer.
389 for (const auto *CInit : CD->inits()) {
390 // SourceOrder is -1 for implicit initializers.
391 if (CInit->getSourceOrder() != 0)
392 continue;
393 InitStart = CInit->getSourceLocation();
394 break;
395 }
396 if (InitStart.isValid()) {
397 auto Toks = TokBuf.expandedTokens(CD->getSourceRange());
398 // Drop any tokens after the initializer.
399 Toks = Toks.take_while([&TokBuf, &InitStart](const syntax::Token &Tok) {
400 return TokBuf.sourceManager().isBeforeInTranslationUnit(Tok.location(),
401 InitStart);
402 });
403 // Look for the first colon.
404 auto Tok =
405 llvm::find_if(llvm::reverse(Toks), [](const syntax::Token &Tok) {
406 return Tok.kind() == tok::colon;
407 });
408 assert(Tok != Toks.rend());
409 DeletionRange.setBegin(Tok->location());
410 }
411 }
412 return DeletionRange;
413}
414
415/// Moves definition of a function/method to an appropriate implementation file.
416///
417/// Before:
418/// a.h
419/// void foo() { return; }
420/// a.cc
421/// #include "a.h"
422///
423/// ----------------
424///
425/// After:
426/// a.h
427/// void foo();
428/// a.cc
429/// #include "a.h"
430/// void foo() { return; }
431class DefineOutline : public Tweak {
432public:
433 const char *id() const override;
434
435 bool hidden() const override { return false; }
436 llvm::StringLiteral kind() const override {
438 }
439 std::string title() const override {
440 return "Move function body to out-of-line";
441 }
442
443 bool prepare(const Selection &Sel) override {
444 SameFile = !isHeaderFile(Sel.AST->tuPath(), Sel.AST->getLangOpts());
445 Source = getSelectedFunction(Sel.ASTSelection.commonAncestor());
446
447 // Bail out if the selection is not a in-line function definition.
448 if (!Source || !Source->doesThisDeclarationHaveABody() ||
449 Source->isOutOfLine())
450 return false;
451
452 // Bail out if this is a function template specialization, as their
453 // definitions need to be visible in all including translation units.
454 if (Source->getTemplateSpecializationInfo())
455 return false;
456
457 auto *MD = llvm::dyn_cast<CXXMethodDecl>(Source);
458 if (!MD) {
459 if (Source->getDescribedFunctionTemplate())
460 return false;
461 // Can't outline free-standing functions in the same file.
462 return !SameFile;
463 }
464
465 for (const CXXRecordDecl *Parent = MD->getParent(); Parent;
466 Parent =
467 llvm::dyn_cast_or_null<const CXXRecordDecl>(Parent->getParent())) {
468 if (const TemplateParameterList *Params =
469 Parent->getDescribedTemplateParams()) {
470
471 // Class template member functions must be defined in the
472 // same file.
473 SameFile = true;
474
475 // Bail out if the template parameter is unnamed.
476 for (NamedDecl *P : *Params) {
477 if (!P->getIdentifier())
478 return false;
479 }
480 }
481 }
482
483 // Function templates must be defined in the same file.
484 if (MD->getDescribedTemplate())
485 SameFile = true;
486
487 // The refactoring is meaningless for unnamed classes and namespaces,
488 // unless we're outlining in the same file
489 for (const DeclContext *DC = MD->getParent(); DC; DC = DC->getParent()) {
490 if (auto *ND = llvm::dyn_cast<NamedDecl>(DC)) {
491 if (ND->getDeclName().isEmpty() &&
492 (!SameFile || !llvm::dyn_cast<NamespaceDecl>(ND)))
493 return false;
494 }
495 }
496
497 // Note that we don't check whether an implementation file exists or not in
498 // the prepare, since performing disk IO on each prepare request might be
499 // expensive.
500 return true;
501 }
502
503 Expected<Effect> apply(const Selection &Sel) override {
504 const SourceManager &SM = Sel.AST->getSourceManager();
505 std::optional<Path> CCFile;
506 auto Anchor = getDefinitionOfAdjacentDecl(Sel);
507 if (Anchor) {
508 CCFile = Anchor->Loc.uri.file();
509 } else {
510 CCFile = SameFile ? Sel.AST->tuPath().str()
511 : getSourceFile(Sel.AST->tuPath(), Sel);
512 }
513 if (!CCFile)
514 return error("Couldn't find a suitable implementation file.");
515 assert(Sel.FS && "FS Must be set in apply");
516 auto Buffer = Sel.FS->getBufferForFile(*CCFile);
517 // FIXME: Maybe we should consider creating the implementation file if it
518 // doesn't exist?
519 if (!Buffer)
520 return llvm::errorCodeToError(Buffer.getError());
521
522 auto Contents = Buffer->get()->getBuffer();
523 SourceManagerForFile SMFF(*CCFile, Contents);
524
525 std::optional<Position> InsertionPos;
526 if (Anchor) {
527 if (auto P = getInsertionPointFromExistingDefinition(
528 SMFF, **Buffer, Anchor->Loc, Anchor->RelInsertPos, Sel.AST)) {
529 InsertionPos = *P;
530 }
531 }
532
533 std::optional<std::size_t> Offset;
534 const DeclContext *EnclosingNamespace = nullptr;
535 std::string EnclosingNamespaceName;
536
537 if (InsertionPos) {
538 EnclosingNamespaceName = getNamespaceAtPosition(Contents, *InsertionPos,
539 Sel.AST->getLangOpts());
540 } else if (SameFile) {
541 auto P = getInsertionPointInMainFile(Sel.AST);
542 if (!P)
543 return P.takeError();
544 Offset = P->Offset;
545 EnclosingNamespace = P->EnclosingNamespace;
546 } else {
547 auto Region = getEligiblePoints(
548 Contents, Source->getQualifiedNameAsString(), Sel.AST->getLangOpts());
549 assert(!Region.EligiblePoints.empty());
550 EnclosingNamespaceName = Region.EnclosingNamespace;
551 InsertionPos = Region.EligiblePoints.back();
552 }
553
554 if (InsertionPos) {
555 auto O = positionToOffset(Contents, *InsertionPos);
556 if (!O)
557 return O.takeError();
558 Offset = *O;
559 auto TargetContext =
560 findContextForNS(EnclosingNamespaceName, Source->getDeclContext());
561 if (!TargetContext)
562 return error("define outline: couldn't find a context for target");
563 EnclosingNamespace = *TargetContext;
564 }
565
566 assert(Offset);
567 assert(EnclosingNamespace);
568
569 auto FuncDef = getFunctionSourceCode(
570 Source, EnclosingNamespace, Sel.AST->getTokens(),
571 Sel.AST->getHeuristicResolver(),
572 SameFile && isHeaderFile(Sel.AST->tuPath(), Sel.AST->getLangOpts()));
573 if (!FuncDef)
574 return FuncDef.takeError();
575
576 const tooling::Replacement InsertFunctionDef(*CCFile, *Offset, 0, *FuncDef);
577 auto Effect = Effect::mainFileEdit(
578 SMFF.get(), tooling::Replacements(InsertFunctionDef));
579 if (!Effect)
580 return Effect.takeError();
581
582 tooling::Replacements HeaderUpdates(tooling::Replacement(
583 Sel.AST->getSourceManager(),
584 CharSourceRange::getTokenRange(*toHalfOpenFileRange(
585 SM, Sel.AST->getLangOpts(),
586 getDeletionRange(Source, Sel.AST->getTokens()))),
587 ";"));
588
589 if (Source->isInlineSpecified()) {
590 auto DelInline =
591 deleteTokensWithKind(Sel.AST->getTokens(), tok::kw_inline,
592 {Source->getBeginLoc(), Source->getLocation()});
593 if (!DelInline)
594 return DelInline.takeError();
595 HeaderUpdates = HeaderUpdates.merge(*DelInline);
596 }
597
598 if (SameFile) {
599 tooling::Replacements &R = Effect->ApplyEdits[*CCFile].Replacements;
600 R = R.merge(HeaderUpdates);
601 } else {
602 auto HeaderFE = Effect::fileEdit(SM, SM.getMainFileID(), HeaderUpdates);
603 if (!HeaderFE)
604 return HeaderFE.takeError();
605 Effect->ApplyEdits.try_emplace(HeaderFE->first,
606 std::move(HeaderFE->second));
607 }
608 return std::move(*Effect);
609 }
610
611 std::optional<InsertionAnchor>
612 getDefinitionOfAdjacentDecl(const Selection &Sel) {
613 if (!Sel.Index)
614 return {};
615 std::optional<Location> Anchor;
616 std::string TuURI = URI::createFile(Sel.AST->tuPath()).toString();
617 auto CheckCandidate = [&](Decl *Candidate) {
618 assert(Candidate != Source);
619 if (auto Func = llvm::dyn_cast_or_null<FunctionDecl>(Candidate);
620 !Func || Func->isThisDeclarationADefinition()) {
621 return;
622 }
623 std::optional<Location> CandidateLoc;
624 Sel.Index->lookup({{getSymbolID(Candidate)}}, [&](const Symbol &S) {
625 if (S.Definition) {
626 if (auto Loc = indexToLSPLocation(S.Definition, Sel.AST->tuPath()))
627 CandidateLoc = *Loc;
628 else
629 log("getDefinitionOfAdjacentDecl: {0}", Loc.takeError());
630 }
631 });
632 if (!CandidateLoc)
633 return;
634
635 // If our definition is constrained to the same file, ignore
636 // definitions that are not located there.
637 // If our definition is not constrained to the same file, but
638 // our anchor definition is in the same file, then we also put our
639 // definition there, because that appears to be the user preference.
640 // Exception: If the existing definition is a template, then the
641 // location is likely due to technical necessity rather than preference,
642 // so ignore that definition.
643 bool CandidateSameFile = TuURI == CandidateLoc->uri.uri();
644 if (SameFile && !CandidateSameFile)
645 return;
646 if (!SameFile && CandidateSameFile) {
647 if (Candidate->isTemplateDecl())
648 return;
649 SameFile = true;
650 }
651 Anchor = *CandidateLoc;
652 };
653
654 // Try to find adjacent function declarations.
655 // Determine the closest one by alternatingly going "up" and "down"
656 // from our function in increasing steps.
657 const DeclContext *ParentContext = Source->getParent();
658 const auto SourceIt = llvm::find_if(
659 ParentContext->decls(), [this](const Decl *D) { return D == Source; });
660 if (SourceIt == ParentContext->decls_end())
661 return {};
662 const int Preceding = std::distance(ParentContext->decls_begin(), SourceIt);
663 const int Following =
664 std::distance(SourceIt, ParentContext->decls_end()) - 1;
665 for (int Offset = 1; Offset <= Preceding || Offset <= Following; ++Offset) {
666 if (Offset <= Preceding)
667 CheckCandidate(
668 *std::next(ParentContext->decls_begin(), Preceding - Offset));
669 if (Anchor)
670 return InsertionAnchor{*Anchor, RelativeInsertPos::After};
671 if (Offset <= Following)
672 CheckCandidate(*std::next(SourceIt, Offset));
673 if (Anchor)
674 return InsertionAnchor{*Anchor, RelativeInsertPos::Before};
675 }
676 return {};
677 }
678
679 // Helper function to skip over a matching pair of tokens (e.g., parentheses
680 // or braces). Returns an iterator to the matching closing token, or the end
681 // iterator if no matching pair is found.
682 template <typename Iterator>
683 Iterator skipTokenPair(Iterator Begin, Iterator End, tok::TokenKind StartKind,
684 tok::TokenKind EndKind) {
685 int Count = 0;
686 for (auto It = Begin; It != End; ++It) {
687 if (It->kind() == StartKind) {
688 ++Count;
689 } else if (It->kind() == EndKind) {
690 if (--Count == 0)
691 return It;
692 if (Count < 0)
693 // We encountered a closing token without a matching opening token.
694 return End;
695 }
696 }
697 return End;
698 }
699
700 // We don't know the actual start or end of the definition, only the position
701 // of the name. Therefore, we heuristically try to locate the last token
702 // before or in this function, respectively. Adapt as required by user code.
703 std::optional<Position> getInsertionPointFromExistingDefinition(
704 SourceManagerForFile &SMFF, const llvm::MemoryBuffer &Buffer,
705 const Location &Loc, RelativeInsertPos RelInsertPos, ParsedAST *AST) {
706 auto StartOffset = positionToOffset(Buffer.getBuffer(), Loc.range.start);
707 if (!StartOffset)
708 return {};
709 SourceLocation InsertionLoc;
710 SourceManager &SM = SMFF.get();
711
712 auto InsertBefore = [&] {
713 // Go backwards until we encounter one of the following:
714 // - An opening brace (of a namespace).
715 // - A closing brace (of a function definition).
716 // - A semicolon (of a declaration).
717 // If no such token was found, then the first token in the file starts the
718 // definition.
719 auto Tokens = syntax::tokenize(
720 syntax::FileRange(SM.getMainFileID(), 0, *StartOffset), SM,
721 AST->getLangOpts());
722 if (Tokens.empty())
723 return;
724 for (auto I = std::rbegin(Tokens);
725 InsertionLoc.isInvalid() && I != std::rend(Tokens); ++I) {
726 switch (I->kind()) {
727 case tok::l_brace:
728 case tok::r_brace:
729 case tok::semi:
730 if (I != std::rbegin(Tokens))
731 InsertionLoc = std::prev(I)->location();
732 else
733 InsertionLoc = I->endLocation();
734 break;
735 default:
736 break;
737 }
738 }
739 if (InsertionLoc.isInvalid())
740 InsertionLoc = Tokens.front().location();
741 };
742
743 if (RelInsertPos == RelativeInsertPos::Before) {
744 InsertBefore();
745 } else {
746 // Skip over one top-level pair of parentheses (for the parameter list)
747 // and one pair of curly braces (for the code block), or `= default`.
748 // If that fails, insert before the function instead.
749 auto Tokens =
750 syntax::tokenize(syntax::FileRange(SM.getMainFileID(), *StartOffset,
751 Buffer.getBuffer().size()),
752 SM, AST->getLangOpts());
753 std::optional<syntax::Token> Tok;
754
755 // Skip parameter list (parentheses)
756 auto ClosingParen = skipTokenPair(Tokens.begin(), Tokens.end(),
757 tok::l_paren, tok::r_paren);
758 if (ClosingParen != Tokens.end()) {
759 // After the closing paren, check for `= default`
760 auto AfterParams = std::next(ClosingParen);
761 if (AfterParams != Tokens.end() && AfterParams->kind() == tok::equal) {
762 auto NextIt = std::next(AfterParams);
763 if (NextIt != Tokens.end() && NextIt->kind() == tok::kw_default) {
764 // Find the semicolon after `default`.
765 auto SemiIt = std::next(NextIt);
766 if (SemiIt != Tokens.end() && SemiIt->kind() == tok::semi) {
767 Tok = *SemiIt;
768 }
769 }
770 }
771
772 // If not `= default`, skip function body (braces)
773 if (!Tok && AfterParams != Tokens.end()) {
774 auto ClosingBrace = skipTokenPair(AfterParams, Tokens.end(),
775 tok::l_brace, tok::r_brace);
776 if (ClosingBrace != Tokens.end()) {
777 Tok = *ClosingBrace;
778 }
779 }
780 }
781
782 if (Tok)
783 InsertionLoc = Tok->endLocation();
784 else
785 InsertBefore();
786 }
787
788 if (!InsertionLoc.isValid())
789 return {};
790 return sourceLocToPosition(SM, InsertionLoc);
791 }
792
793 // Returns the most natural insertion point in this file.
794 // This is a fallback for when we failed to find an existing definition to
795 // place the new one next to. It only considers namespace proximity.
796 llvm::Expected<InsertionPoint> getInsertionPointInMainFile(ParsedAST *AST) {
797 // If the definition goes to the same file and there is a namespace,
798 // we should (and, in the case of anonymous namespaces, need to)
799 // put the definition into the original namespace block.
800 auto *Klass = Source->getDeclContext()->getOuterLexicalRecordContext();
801 if (!Klass)
802 return error("moving to same file not supported for free functions");
803 const SourceLocation EndLoc = Klass->getBraceRange().getEnd();
804 const auto &TokBuf = AST->getTokens();
805 auto Tokens = TokBuf.expandedTokens();
806 auto It = llvm::lower_bound(
807 Tokens, EndLoc, [](const syntax::Token &Tok, SourceLocation EndLoc) {
808 return Tok.location() < EndLoc;
809 });
810 while (It != Tokens.end()) {
811 if (It->kind() != tok::semi) {
812 ++It;
813 continue;
814 }
815 unsigned Offset =
816 AST->getSourceManager().getDecomposedLoc(It->endLocation()).second;
817 return InsertionPoint{Klass->getEnclosingNamespaceContext(), Offset};
818 }
819 return error(
820 "failed to determine insertion location: no end of class found");
821 }
822
823private:
824 const FunctionDecl *Source = nullptr;
825 bool SameFile = false;
826};
827
828REGISTER_TWEAK(DefineOutline)
829
830} // namespace
831} // namespace clangd
832} // 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:44
llvm::Expected< Location > indexToLSPLocation(const SymbolLocation &Loc, llvm::StringRef TUPath)
Ensure we have enough bits to represent all SymbolTag values.
SymbolID getSymbolID(const Decl *D)
Gets the symbol ID for a declaration. Returned SymbolID might be null.
Definition AST.cpp:353
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:697
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:206
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:2058
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