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