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 "FindTarget.h" |
11 | #include "HeaderSourceSwitch.h" |
12 | #include "ParsedAST.h" |
13 | #include "Selection.h" |
14 | #include "SourceCode.h" |
15 | #include "refactor/Tweak.h" |
16 | #include "support/Logger.h" |
17 | #include "support/Path.h" |
18 | #include "clang/AST/ASTTypeTraits.h" |
19 | #include "clang/AST/Attr.h" |
20 | #include "clang/AST/Decl.h" |
21 | #include "clang/AST/DeclBase.h" |
22 | #include "clang/AST/DeclCXX.h" |
23 | #include "clang/AST/DeclTemplate.h" |
24 | #include "clang/AST/Stmt.h" |
25 | #include "clang/Basic/SourceLocation.h" |
26 | #include "clang/Basic/SourceManager.h" |
27 | #include "clang/Basic/TokenKinds.h" |
28 | #include "clang/Tooling/Core/Replacement.h" |
29 | #include "clang/Tooling/Syntax/Tokens.h" |
30 | #include "llvm/ADT/STLExtras.h" |
31 | #include "llvm/ADT/StringRef.h" |
32 | #include "llvm/Support/Casting.h" |
33 | #include "llvm/Support/Error.h" |
34 | #include <cstddef> |
35 | #include <optional> |
36 | #include <string> |
37 | |
38 | namespace clang { |
39 | namespace clangd { |
40 | namespace { |
41 | |
42 | // Deduces the FunctionDecl from a selection. Requires either the function body |
43 | // or the function decl to be selected. Returns null if none of the above |
44 | // criteria is met. |
45 | // FIXME: This is shared with define inline, move them to a common header once |
46 | // we have a place for such. |
47 | const FunctionDecl *getSelectedFunction(const SelectionTree::Node *SelNode) { |
48 | if (!SelNode) |
49 | return nullptr; |
50 | const DynTypedNode &AstNode = SelNode->ASTNode; |
51 | if (const FunctionDecl *FD = AstNode.get<FunctionDecl>()) |
52 | return FD; |
53 | if (AstNode.get<CompoundStmt>() && |
54 | SelNode->Selected == SelectionTree::Complete) { |
55 | if (const SelectionTree::Node *P = SelNode->Parent) |
56 | return P->ASTNode.get<FunctionDecl>(); |
57 | } |
58 | return nullptr; |
59 | } |
60 | |
61 | std::optional<Path> getSourceFile(llvm::StringRef FileName, |
62 | const Tweak::Selection &Sel) { |
63 | assert(Sel.FS); |
64 | if (auto Source = getCorrespondingHeaderOrSource(OriginalFile: FileName, VFS: Sel.FS)) |
65 | return *Source; |
66 | return getCorrespondingHeaderOrSource(OriginalFile: FileName, AST&: *Sel.AST, Index: Sel.Index); |
67 | } |
68 | |
69 | // Synthesize a DeclContext for TargetNS from CurContext. TargetNS must be empty |
70 | // for global namespace, and endwith "::" otherwise. |
71 | // Returns std::nullopt if TargetNS is not a prefix of CurContext. |
72 | std::optional<const DeclContext *> |
73 | findContextForNS(llvm::StringRef TargetNS, const DeclContext *CurContext) { |
74 | assert(TargetNS.empty() || TargetNS.ends_with("::" )); |
75 | // Skip any non-namespace contexts, e.g. TagDecls, functions/methods. |
76 | CurContext = CurContext->getEnclosingNamespaceContext(); |
77 | // If TargetNS is empty, it means global ns, which is translation unit. |
78 | if (TargetNS.empty()) { |
79 | while (!CurContext->isTranslationUnit()) |
80 | CurContext = CurContext->getParent(); |
81 | return CurContext; |
82 | } |
83 | // Otherwise we need to drop any trailing namespaces from CurContext until |
84 | // we reach TargetNS. |
85 | std::string TargetContextNS = |
86 | CurContext->isNamespace() |
87 | ? llvm::cast<NamespaceDecl>(Val: CurContext)->getQualifiedNameAsString() |
88 | : "" ; |
89 | TargetContextNS.append(s: "::" ); |
90 | |
91 | llvm::StringRef CurrentContextNS(TargetContextNS); |
92 | // If TargetNS is not a prefix of CurrentContext, there's no way to reach |
93 | // it. |
94 | if (!CurrentContextNS.starts_with(Prefix: TargetNS)) |
95 | return std::nullopt; |
96 | |
97 | while (CurrentContextNS != TargetNS) { |
98 | CurContext = CurContext->getParent(); |
99 | // These colons always exists since TargetNS is a prefix of |
100 | // CurrentContextNS, it ends with "::" and they are not equal. |
101 | CurrentContextNS = CurrentContextNS.take_front( |
102 | N: CurrentContextNS.drop_back(N: 2).rfind(Str: "::" ) + 2); |
103 | } |
104 | return CurContext; |
105 | } |
106 | |
107 | // Returns source code for FD after applying Replacements. |
108 | // FIXME: Make the function take a parameter to return only the function body, |
109 | // afterwards it can be shared with define-inline code action. |
110 | llvm::Expected<std::string> |
111 | getFunctionSourceAfterReplacements(const FunctionDecl *FD, |
112 | const tooling::Replacements &Replacements, |
113 | bool ) { |
114 | const auto &SM = FD->getASTContext().getSourceManager(); |
115 | auto OrigFuncRange = toHalfOpenFileRange( |
116 | SM, FD->getASTContext().getLangOpts(), FD->getSourceRange()); |
117 | if (!OrigFuncRange) |
118 | return error(Fmt: "Couldn't get range for function." ); |
119 | |
120 | // Get new begin and end positions for the qualified function definition. |
121 | unsigned FuncBegin = SM.getFileOffset(OrigFuncRange->getBegin()); |
122 | unsigned FuncEnd = Replacements.getShiftedCodePosition( |
123 | Position: SM.getFileOffset(OrigFuncRange->getEnd())); |
124 | |
125 | // Trim the result to function definition. |
126 | auto QualifiedFunc = tooling::applyAllReplacements( |
127 | SM.getBufferData(SM.getMainFileID()), Replacements); |
128 | if (!QualifiedFunc) |
129 | return QualifiedFunc.takeError(); |
130 | |
131 | auto Source = QualifiedFunc->substr(FuncBegin, FuncEnd - FuncBegin + 1); |
132 | std::string TemplatePrefix; |
133 | auto AddToTemplatePrefixIfApplicable = [&](const Decl *D) { |
134 | const TemplateParameterList *Params = D->getDescribedTemplateParams(); |
135 | if (!Params) |
136 | return; |
137 | for (Decl *P : *Params) { |
138 | if (auto *TTP = dyn_cast<TemplateTypeParmDecl>(P)) |
139 | TTP->removeDefaultArgument(); |
140 | else if (auto *NTTP = dyn_cast<NonTypeTemplateParmDecl>(P)) |
141 | NTTP->removeDefaultArgument(); |
142 | else if (auto *TTPD = dyn_cast<TemplateTemplateParmDecl>(P)) |
143 | TTPD->removeDefaultArgument(); |
144 | } |
145 | std::string S; |
146 | llvm::raw_string_ostream Stream(S); |
147 | Params->print(Stream, FD->getASTContext()); |
148 | if (!S.empty()) |
149 | *S.rbegin() = '\n'; // Replace space with newline |
150 | TemplatePrefix.insert(pos1: 0, str: S); |
151 | }; |
152 | AddToTemplatePrefixIfApplicable(FD); |
153 | if (auto *MD = llvm::dyn_cast<CXXMethodDecl>(Val: FD)) { |
154 | for (const CXXRecordDecl *Parent = MD->getParent(); Parent; |
155 | Parent = |
156 | llvm::dyn_cast_or_null<const CXXRecordDecl>(Parent->getParent())) { |
157 | AddToTemplatePrefixIfApplicable(Parent); |
158 | } |
159 | } |
160 | |
161 | if (TargetFileIsHeader) |
162 | Source.insert(0, "inline " ); |
163 | if (!TemplatePrefix.empty()) |
164 | Source.insert(0, TemplatePrefix); |
165 | return Source; |
166 | } |
167 | |
168 | // Returns replacements to delete tokens with kind `Kind` in the range |
169 | // `FromRange`. Removes matching instances of given token preceeding the |
170 | // function defition. |
171 | llvm::Expected<tooling::Replacements> |
172 | deleteTokensWithKind(const syntax::TokenBuffer &TokBuf, tok::TokenKind Kind, |
173 | SourceRange FromRange) { |
174 | tooling::Replacements DelKeywordCleanups; |
175 | llvm::Error Errors = llvm::Error::success(); |
176 | bool FoundAny = false; |
177 | for (const auto &Tok : TokBuf.expandedTokens(R: FromRange)) { |
178 | if (Tok.kind() != Kind) |
179 | continue; |
180 | FoundAny = true; |
181 | auto Spelling = TokBuf.spelledForExpanded(Expanded: llvm::ArrayRef(Tok)); |
182 | if (!Spelling) { |
183 | Errors = llvm::joinErrors( |
184 | E1: std::move(Errors), |
185 | E2: error(Fmt: "define outline: couldn't remove `{0}` keyword." , |
186 | Vals: tok::getKeywordSpelling(Kind))); |
187 | break; |
188 | } |
189 | auto &SM = TokBuf.sourceManager(); |
190 | CharSourceRange DelRange = |
191 | syntax::Token::range(SM, First: Spelling->front(), Last: Spelling->back()) |
192 | .toCharRange(SM); |
193 | if (auto Err = |
194 | DelKeywordCleanups.add(R: tooling::Replacement(SM, DelRange, "" ))) |
195 | Errors = llvm::joinErrors(E1: std::move(Errors), E2: std::move(Err)); |
196 | } |
197 | if (!FoundAny) { |
198 | Errors = llvm::joinErrors( |
199 | E1: std::move(Errors), |
200 | E2: error(Fmt: "define outline: couldn't find `{0}` keyword to remove." , |
201 | Vals: tok::getKeywordSpelling(Kind))); |
202 | } |
203 | |
204 | if (Errors) |
205 | return std::move(Errors); |
206 | return DelKeywordCleanups; |
207 | } |
208 | |
209 | // Creates a modified version of function definition that can be inserted at a |
210 | // different location, qualifies return value and function name to achieve that. |
211 | // Contains function signature, except defaulted parameter arguments, body and |
212 | // template parameters if applicable. No need to qualify parameters, as they are |
213 | // looked up in the context containing the function/method. |
214 | // FIXME: Drop attributes in function signature. |
215 | llvm::Expected<std::string> |
216 | getFunctionSourceCode(const FunctionDecl *FD, const DeclContext *TargetContext, |
217 | const syntax::TokenBuffer &TokBuf, |
218 | const HeuristicResolver *Resolver, |
219 | bool ) { |
220 | auto &AST = FD->getASTContext(); |
221 | auto &SM = AST.getSourceManager(); |
222 | |
223 | llvm::Error Errors = llvm::Error::success(); |
224 | tooling::Replacements DeclarationCleanups; |
225 | |
226 | // Finds the first unqualified name in function return type and name, then |
227 | // qualifies those to be valid in TargetContext. |
228 | findExplicitReferences( |
229 | FD, |
230 | [&](ReferenceLoc Ref) { |
231 | // It is enough to qualify the first qualifier, so skip references with |
232 | // a qualifier. Also we can't do much if there are no targets or name is |
233 | // inside a macro body. |
234 | if (Ref.Qualifier || Ref.Targets.empty() || Ref.NameLoc.isMacroID()) |
235 | return; |
236 | // Only qualify return type and function name. |
237 | if (Ref.NameLoc != FD->getReturnTypeSourceRange().getBegin() && |
238 | Ref.NameLoc != FD->getLocation()) |
239 | return; |
240 | |
241 | for (const NamedDecl *ND : Ref.Targets) { |
242 | if (ND->getKind() == Decl::TemplateTypeParm) |
243 | return; |
244 | if (ND->getDeclContext() != Ref.Targets.front()->getDeclContext()) { |
245 | elog(Fmt: "Targets from multiple contexts: {0}, {1}" , |
246 | Vals: printQualifiedName(ND: *Ref.Targets.front()), |
247 | Vals: printQualifiedName(ND: *ND)); |
248 | return; |
249 | } |
250 | } |
251 | const NamedDecl *ND = Ref.Targets.front(); |
252 | std::string Qualifier = |
253 | getQualification(AST, TargetContext, |
254 | SM.getLocForStartOfFile(SM.getMainFileID()), ND); |
255 | if (ND->getDeclContext()->isDependentContext() && |
256 | llvm::isa<TypeDecl>(Val: ND)) { |
257 | Qualifier.insert(pos: 0, s: "typename " ); |
258 | } |
259 | if (auto Err = DeclarationCleanups.add( |
260 | tooling::Replacement(SM, Ref.NameLoc, 0, Qualifier))) |
261 | Errors = llvm::joinErrors(E1: std::move(Errors), E2: std::move(Err)); |
262 | }, |
263 | Resolver); |
264 | |
265 | // findExplicitReferences doesn't provide references to |
266 | // constructor/destructors, it only provides references to type names inside |
267 | // them. |
268 | // this works for constructors, but doesn't work for destructor as type name |
269 | // doesn't cover leading `~`, so handle it specially. |
270 | if (const auto *Destructor = llvm::dyn_cast<CXXDestructorDecl>(Val: FD)) { |
271 | if (auto Err = DeclarationCleanups.add(tooling::Replacement( |
272 | SM, Destructor->getLocation(), 0, |
273 | getQualification(AST, TargetContext, |
274 | SM.getLocForStartOfFile(SM.getMainFileID()), |
275 | Destructor)))) |
276 | Errors = llvm::joinErrors(E1: std::move(Errors), E2: std::move(Err)); |
277 | } |
278 | |
279 | // Get rid of default arguments, since they should not be specified in |
280 | // out-of-line definition. |
281 | for (const auto *PVD : FD->parameters()) { |
282 | if (!PVD->hasDefaultArg()) |
283 | continue; |
284 | // Deletion range spans the initializer, usually excluding the `=`. |
285 | auto DelRange = CharSourceRange::getTokenRange(R: PVD->getDefaultArgRange()); |
286 | // Get all tokens before the default argument. |
287 | auto Tokens = TokBuf.expandedTokens(R: PVD->getSourceRange()) |
288 | .take_while(Pred: [&SM, &DelRange](const syntax::Token &Tok) { |
289 | return SM.isBeforeInTranslationUnit( |
290 | Tok.location(), DelRange.getBegin()); |
291 | }); |
292 | if (TokBuf.expandedTokens(R: DelRange.getAsRange()).front().kind() != |
293 | tok::equal) { |
294 | // Find the last `=` if it isn't included in the initializer, and update |
295 | // the DelRange to include it. |
296 | auto Tok = |
297 | llvm::find_if(Range: llvm::reverse(C&: Tokens), P: [](const syntax::Token &Tok) { |
298 | return Tok.kind() == tok::equal; |
299 | }); |
300 | assert(Tok != Tokens.rend()); |
301 | DelRange.setBegin(Tok->location()); |
302 | } |
303 | if (auto Err = |
304 | DeclarationCleanups.add(tooling::Replacement(SM, DelRange, "" ))) |
305 | Errors = llvm::joinErrors(E1: std::move(Errors), E2: std::move(Err)); |
306 | } |
307 | |
308 | auto DelAttr = [&](const Attr *A) { |
309 | if (!A) |
310 | return; |
311 | auto AttrTokens = |
312 | TokBuf.spelledForExpanded(Expanded: TokBuf.expandedTokens(R: A->getRange())); |
313 | assert(A->getLocation().isValid()); |
314 | if (!AttrTokens || AttrTokens->empty()) { |
315 | Errors = llvm::joinErrors( |
316 | E1: std::move(Errors), E2: error(Fmt: "define outline: Can't move out of line as " |
317 | "function has a macro `{0}` specifier." , |
318 | Vals: A->getSpelling())); |
319 | return; |
320 | } |
321 | CharSourceRange DelRange = |
322 | syntax::Token::range(SM, AttrTokens->front(), AttrTokens->back()) |
323 | .toCharRange(SM); |
324 | if (auto Err = |
325 | DeclarationCleanups.add(tooling::Replacement(SM, DelRange, "" ))) |
326 | Errors = llvm::joinErrors(E1: std::move(Errors), E2: std::move(Err)); |
327 | }; |
328 | |
329 | DelAttr(FD->getAttr<OverrideAttr>()); |
330 | DelAttr(FD->getAttr<FinalAttr>()); |
331 | |
332 | auto DelKeyword = [&](tok::TokenKind Kind, SourceRange FromRange) { |
333 | auto DelKeywords = deleteTokensWithKind(TokBuf, Kind, FromRange); |
334 | if (!DelKeywords) { |
335 | Errors = llvm::joinErrors(E1: std::move(Errors), E2: DelKeywords.takeError()); |
336 | return; |
337 | } |
338 | DeclarationCleanups = DeclarationCleanups.merge(Replaces: *DelKeywords); |
339 | }; |
340 | |
341 | if (FD->isInlineSpecified()) |
342 | DelKeyword(tok::kw_inline, {FD->getBeginLoc(), FD->getLocation()}); |
343 | if (const auto *MD = dyn_cast<CXXMethodDecl>(Val: FD)) { |
344 | if (MD->isVirtualAsWritten()) |
345 | DelKeyword(tok::kw_virtual, {FD->getBeginLoc(), FD->getLocation()}); |
346 | if (MD->isStatic()) |
347 | DelKeyword(tok::kw_static, {FD->getBeginLoc(), FD->getLocation()}); |
348 | } |
349 | if (const auto *CD = dyn_cast<CXXConstructorDecl>(Val: FD)) { |
350 | if (CD->isExplicit()) |
351 | DelKeyword(tok::kw_explicit, {FD->getBeginLoc(), FD->getLocation()}); |
352 | } |
353 | |
354 | if (Errors) |
355 | return std::move(Errors); |
356 | return getFunctionSourceAfterReplacements(FD, Replacements: DeclarationCleanups, |
357 | TargetFileIsHeader); |
358 | } |
359 | |
360 | struct InsertionPoint { |
361 | const DeclContext *EnclosingNamespace = nullptr; |
362 | size_t Offset; |
363 | }; |
364 | |
365 | // Returns the range that should be deleted from declaration, which always |
366 | // contains function body. In addition to that it might contain constructor |
367 | // initializers. |
368 | SourceRange getDeletionRange(const FunctionDecl *FD, |
369 | const syntax::TokenBuffer &TokBuf) { |
370 | auto DeletionRange = FD->getBody()->getSourceRange(); |
371 | if (auto *CD = llvm::dyn_cast<CXXConstructorDecl>(Val: FD)) { |
372 | // AST doesn't contain the location for ":" in ctor initializers. Therefore |
373 | // we find it by finding the first ":" before the first ctor initializer. |
374 | SourceLocation InitStart; |
375 | // Find the first initializer. |
376 | for (const auto *CInit : CD->inits()) { |
377 | // SourceOrder is -1 for implicit initializers. |
378 | if (CInit->getSourceOrder() != 0) |
379 | continue; |
380 | InitStart = CInit->getSourceLocation(); |
381 | break; |
382 | } |
383 | if (InitStart.isValid()) { |
384 | auto Toks = TokBuf.expandedTokens(CD->getSourceRange()); |
385 | // Drop any tokens after the initializer. |
386 | Toks = Toks.take_while([&TokBuf, &InitStart](const syntax::Token &Tok) { |
387 | return TokBuf.sourceManager().isBeforeInTranslationUnit(LHS: Tok.location(), |
388 | RHS: InitStart); |
389 | }); |
390 | // Look for the first colon. |
391 | auto Tok = |
392 | llvm::find_if(llvm::reverse(Toks), [](const syntax::Token &Tok) { |
393 | return Tok.kind() == tok::colon; |
394 | }); |
395 | assert(Tok != Toks.rend()); |
396 | DeletionRange.setBegin(Tok->location()); |
397 | } |
398 | } |
399 | return DeletionRange; |
400 | } |
401 | |
402 | /// Moves definition of a function/method to an appropriate implementation file. |
403 | /// |
404 | /// Before: |
405 | /// a.h |
406 | /// void foo() { return; } |
407 | /// a.cc |
408 | /// #include "a.h" |
409 | /// |
410 | /// ---------------- |
411 | /// |
412 | /// After: |
413 | /// a.h |
414 | /// void foo(); |
415 | /// a.cc |
416 | /// #include "a.h" |
417 | /// void foo() { return; } |
418 | class DefineOutline : public Tweak { |
419 | public: |
420 | const char *id() const override; |
421 | |
422 | bool hidden() const override { return false; } |
423 | llvm::StringLiteral kind() const override { |
424 | return CodeAction::REFACTOR_KIND; |
425 | } |
426 | std::string title() const override { |
427 | return "Move function body to out-of-line" ; |
428 | } |
429 | |
430 | bool prepare(const Selection &Sel) override { |
431 | SameFile = !isHeaderFile(FileName: Sel.AST->tuPath(), LangOpts: Sel.AST->getLangOpts()); |
432 | Source = getSelectedFunction(SelNode: Sel.ASTSelection.commonAncestor()); |
433 | |
434 | // Bail out if the selection is not a in-line function definition. |
435 | if (!Source || !Source->doesThisDeclarationHaveABody() || |
436 | Source->isOutOfLine()) |
437 | return false; |
438 | |
439 | // Bail out if this is a function template specialization, as their |
440 | // definitions need to be visible in all including translation units. |
441 | if (Source->getTemplateSpecializationInfo()) |
442 | return false; |
443 | |
444 | auto *MD = llvm::dyn_cast<CXXMethodDecl>(Val: Source); |
445 | if (!MD) { |
446 | if (Source->getDescribedFunctionTemplate()) |
447 | return false; |
448 | // Can't outline free-standing functions in the same file. |
449 | return !SameFile; |
450 | } |
451 | |
452 | for (const CXXRecordDecl *Parent = MD->getParent(); Parent; |
453 | Parent = |
454 | llvm::dyn_cast_or_null<const CXXRecordDecl>(Parent->getParent())) { |
455 | if (const TemplateParameterList *Params = |
456 | Parent->getDescribedTemplateParams()) { |
457 | |
458 | // Class template member functions must be defined in the |
459 | // same file. |
460 | SameFile = true; |
461 | |
462 | // Bail out if the template parameter is unnamed. |
463 | for (NamedDecl *P : *Params) { |
464 | if (!P->getIdentifier()) |
465 | return false; |
466 | } |
467 | } |
468 | } |
469 | |
470 | // Function templates must be defined in the same file. |
471 | if (MD->getDescribedTemplate()) |
472 | SameFile = true; |
473 | |
474 | // The refactoring is meaningless for unnamed classes and namespaces, |
475 | // unless we're outlining in the same file |
476 | for (const DeclContext *DC = MD->getParent(); DC; DC = DC->getParent()) { |
477 | if (auto *ND = llvm::dyn_cast<NamedDecl>(DC)) { |
478 | if (ND->getDeclName().isEmpty() && |
479 | (!SameFile || !llvm::dyn_cast<NamespaceDecl>(ND))) |
480 | return false; |
481 | } |
482 | } |
483 | |
484 | // Note that we don't check whether an implementation file exists or not in |
485 | // the prepare, since performing disk IO on each prepare request might be |
486 | // expensive. |
487 | return true; |
488 | } |
489 | |
490 | Expected<Effect> apply(const Selection &Sel) override { |
491 | const SourceManager &SM = Sel.AST->getSourceManager(); |
492 | auto CCFile = SameFile ? Sel.AST->tuPath().str() |
493 | : getSourceFile(FileName: Sel.AST->tuPath(), Sel); |
494 | if (!CCFile) |
495 | return error(Fmt: "Couldn't find a suitable implementation file." ); |
496 | assert(Sel.FS && "FS Must be set in apply" ); |
497 | auto Buffer = Sel.FS->getBufferForFile(Name: *CCFile); |
498 | // FIXME: Maybe we should consider creating the implementation file if it |
499 | // doesn't exist? |
500 | if (!Buffer) |
501 | return llvm::errorCodeToError(EC: Buffer.getError()); |
502 | auto Contents = Buffer->get()->getBuffer(); |
503 | auto InsertionPoint = getInsertionPoint(Contents, Sel); |
504 | if (!InsertionPoint) |
505 | return InsertionPoint.takeError(); |
506 | |
507 | auto FuncDef = getFunctionSourceCode( |
508 | FD: Source, TargetContext: InsertionPoint->EnclosingNamespace, TokBuf: Sel.AST->getTokens(), |
509 | Resolver: Sel.AST->getHeuristicResolver(), |
510 | TargetFileIsHeader: SameFile && isHeaderFile(FileName: Sel.AST->tuPath(), LangOpts: Sel.AST->getLangOpts())); |
511 | if (!FuncDef) |
512 | return FuncDef.takeError(); |
513 | |
514 | SourceManagerForFile SMFF(*CCFile, Contents); |
515 | const tooling::Replacement InsertFunctionDef( |
516 | *CCFile, InsertionPoint->Offset, 0, *FuncDef); |
517 | auto Effect = Effect::mainFileEdit( |
518 | SM: SMFF.get(), Replacements: tooling::Replacements(InsertFunctionDef)); |
519 | if (!Effect) |
520 | return Effect.takeError(); |
521 | |
522 | tooling::Replacements (tooling::Replacement( |
523 | Sel.AST->getSourceManager(), |
524 | CharSourceRange::getTokenRange(R: *toHalfOpenFileRange( |
525 | Mgr: SM, LangOpts: Sel.AST->getLangOpts(), |
526 | R: getDeletionRange(FD: Source, TokBuf: Sel.AST->getTokens()))), |
527 | ";" )); |
528 | |
529 | if (Source->isInlineSpecified()) { |
530 | auto DelInline = |
531 | deleteTokensWithKind(Sel.AST->getTokens(), tok::kw_inline, |
532 | {Source->getBeginLoc(), Source->getLocation()}); |
533 | if (!DelInline) |
534 | return DelInline.takeError(); |
535 | HeaderUpdates = HeaderUpdates.merge(Replaces: *DelInline); |
536 | } |
537 | |
538 | if (SameFile) { |
539 | tooling::Replacements &R = Effect->ApplyEdits[*CCFile].Replacements; |
540 | R = R.merge(Replaces: HeaderUpdates); |
541 | } else { |
542 | auto = Effect::fileEdit(SM, FID: SM.getMainFileID(), Replacements: HeaderUpdates); |
543 | if (!HeaderFE) |
544 | return HeaderFE.takeError(); |
545 | Effect->ApplyEdits.try_emplace(Key: HeaderFE->first, |
546 | Args: std::move(HeaderFE->second)); |
547 | } |
548 | return std::move(*Effect); |
549 | } |
550 | |
551 | // Returns the most natural insertion point for \p QualifiedName in \p |
552 | // Contents. This currently cares about only the namespace proximity, but in |
553 | // feature it should also try to follow ordering of declarations. For example, |
554 | // if decls come in order `foo, bar, baz` then this function should return |
555 | // some point between foo and baz for inserting bar. |
556 | // FIXME: The selection can be made smarter by looking at the definition |
557 | // locations for adjacent decls to Source. Unfortunately pseudo parsing in |
558 | // getEligibleRegions only knows about namespace begin/end events so we |
559 | // can't match function start/end positions yet. |
560 | llvm::Expected<InsertionPoint> getInsertionPoint(llvm::StringRef Contents, |
561 | const Selection &Sel) { |
562 | // If the definition goes to the same file and there is a namespace, |
563 | // we should (and, in the case of anonymous namespaces, need to) |
564 | // put the definition into the original namespace block. |
565 | if (SameFile) { |
566 | auto *Klass = Source->getDeclContext()->getOuterLexicalRecordContext(); |
567 | if (!Klass) |
568 | return error(Fmt: "moving to same file not supported for free functions" ); |
569 | const SourceLocation EndLoc = Klass->getBraceRange().getEnd(); |
570 | const auto &TokBuf = Sel.AST->getTokens(); |
571 | auto Tokens = TokBuf.expandedTokens(); |
572 | auto It = llvm::lower_bound( |
573 | Range&: Tokens, Value: EndLoc, C: [](const syntax::Token &Tok, SourceLocation EndLoc) { |
574 | return Tok.location() < EndLoc; |
575 | }); |
576 | while (It != Tokens.end()) { |
577 | if (It->kind() != tok::semi) { |
578 | ++It; |
579 | continue; |
580 | } |
581 | unsigned Offset = Sel.AST->getSourceManager() |
582 | .getDecomposedLoc(Loc: It->endLocation()) |
583 | .second; |
584 | return InsertionPoint{Klass->getEnclosingNamespaceContext(), Offset}; |
585 | } |
586 | return error( |
587 | Fmt: "failed to determine insertion location: no end of class found" ); |
588 | } |
589 | |
590 | auto Region = getEligiblePoints( |
591 | Contents, Source->getQualifiedNameAsString(), Sel.AST->getLangOpts()); |
592 | |
593 | assert(!Region.EligiblePoints.empty()); |
594 | auto Offset = positionToOffset(Contents, Region.EligiblePoints.back()); |
595 | if (!Offset) |
596 | return Offset.takeError(); |
597 | |
598 | auto TargetContext = |
599 | findContextForNS(Region.EnclosingNamespace, Source->getDeclContext()); |
600 | if (!TargetContext) |
601 | return error(Fmt: "define outline: couldn't find a context for target" ); |
602 | |
603 | return InsertionPoint{*TargetContext, *Offset}; |
604 | } |
605 | |
606 | private: |
607 | const FunctionDecl *Source = nullptr; |
608 | bool SameFile = false; |
609 | }; |
610 | |
611 | REGISTER_TWEAK(DefineOutline) |
612 | |
613 | } // namespace |
614 | } // namespace clangd |
615 | } // namespace clang |
616 | |