[clang-tools-extra] NFC: Fix trivial typo in documents and comments
[lldb.git] / clang-tools-extra / clangd / refactor / tweaks / DefineOutline.cpp
1 //===--- DefineOutline.cpp ---------------------------------------*- C++-*-===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8
9 #include "AST.h"
10 #include "FindTarget.h"
11 #include "HeaderSourceSwitch.h"
12 #include "Logger.h"
13 #include "ParsedAST.h"
14 #include "Path.h"
15 #include "Selection.h"
16 #include "SourceCode.h"
17 #include "refactor/Tweak.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/Driver/Types.h"
29 #include "clang/Format/Format.h"
30 #include "clang/Lex/Lexer.h"
31 #include "clang/Tooling/Core/Replacement.h"
32 #include "clang/Tooling/Syntax/Tokens.h"
33 #include "llvm/ADT/None.h"
34 #include "llvm/ADT/Optional.h"
35 #include "llvm/ADT/STLExtras.h"
36 #include "llvm/ADT/StringRef.h"
37 #include "llvm/Support/Casting.h"
38 #include "llvm/Support/Error.h"
39 #include <cstddef>
40 #include <string>
41
42 namespace clang {
43 namespace clangd {
44 namespace {
45
46 // Deduces the FunctionDecl from a selection. Requires either the function body
47 // or the function decl to be selected. Returns null if none of the above
48 // criteria is met.
49 // FIXME: This is shared with define inline, move them to a common header once
50 // we have a place for such.
51 const FunctionDecl *getSelectedFunction(const SelectionTree::Node *SelNode) {
52   if (!SelNode)
53     return nullptr;
54   const ast_type_traits::DynTypedNode &AstNode = SelNode->ASTNode;
55   if (const FunctionDecl *FD = AstNode.get<FunctionDecl>())
56     return FD;
57   if (AstNode.get<CompoundStmt>() &&
58       SelNode->Selected == SelectionTree::Complete) {
59     if (const SelectionTree::Node *P = SelNode->Parent)
60       return P->ASTNode.get<FunctionDecl>();
61   }
62   return nullptr;
63 }
64
65 llvm::Optional<Path> getSourceFile(llvm::StringRef FileName,
66                                    const Tweak::Selection &Sel) {
67   if (auto Source = getCorrespondingHeaderOrSource(
68           std::string(FileName),
69           &Sel.AST->getSourceManager().getFileManager().getVirtualFileSystem()))
70     return *Source;
71   return getCorrespondingHeaderOrSource(std::string(FileName), *Sel.AST,
72                                         Sel.Index);
73 }
74
75 // Synthesize a DeclContext for TargetNS from CurContext. TargetNS must be empty
76 // for global namespace, and endwith "::" otherwise.
77 // Returns None if TargetNS is not a prefix of CurContext.
78 llvm::Optional<const DeclContext *>
79 findContextForNS(llvm::StringRef TargetNS, const DeclContext *CurContext) {
80   assert(TargetNS.empty() || TargetNS.endswith("::"));
81   // Skip any non-namespace contexts, e.g. TagDecls, functions/methods.
82   CurContext = CurContext->getEnclosingNamespaceContext();
83   // If TargetNS is empty, it means global ns, which is translation unit.
84   if (TargetNS.empty()) {
85     while (!CurContext->isTranslationUnit())
86       CurContext = CurContext->getParent();
87     return CurContext;
88   }
89   // Otherwise we need to drop any trailing namespaces from CurContext until
90   // we reach TargetNS.
91   std::string TargetContextNS =
92       CurContext->isNamespace()
93           ? llvm::cast<NamespaceDecl>(CurContext)->getQualifiedNameAsString()
94           : "";
95   TargetContextNS.append("::");
96
97   llvm::StringRef CurrentContextNS(TargetContextNS);
98   // If TargetNS is not a prefix of CurrentContext, there's no way to reach
99   // it.
100   if (!CurrentContextNS.startswith(TargetNS))
101     return llvm::None;
102
103   while (CurrentContextNS != TargetNS) {
104     CurContext = CurContext->getParent();
105     // These colons always exists since TargetNS is a prefix of
106     // CurrentContextNS, it ends with "::" and they are not equal.
107     CurrentContextNS = CurrentContextNS.take_front(
108         CurrentContextNS.drop_back(2).rfind("::") + 2);
109   }
110   return CurContext;
111 }
112
113 // Returns source code for FD after applying Replacements.
114 // FIXME: Make the function take a parameter to return only the function body,
115 // afterwards it can be shared with define-inline code action.
116 llvm::Expected<std::string>
117 getFunctionSourceAfterReplacements(const FunctionDecl *FD,
118                                    const tooling::Replacements &Replacements) {
119   const auto &SM = FD->getASTContext().getSourceManager();
120   auto OrigFuncRange = toHalfOpenFileRange(
121       SM, FD->getASTContext().getLangOpts(), FD->getSourceRange());
122   if (!OrigFuncRange)
123     return llvm::createStringError(llvm::inconvertibleErrorCode(),
124                                    "Couldn't get range for function.");
125   // Include template parameter list.
126   if (auto *FTD = FD->getDescribedFunctionTemplate())
127     OrigFuncRange->setBegin(FTD->getBeginLoc());
128
129   // Get new begin and end positions for the qualified function definition.
130   unsigned FuncBegin = SM.getFileOffset(OrigFuncRange->getBegin());
131   unsigned FuncEnd = Replacements.getShiftedCodePosition(
132       SM.getFileOffset(OrigFuncRange->getEnd()));
133
134   // Trim the result to function definition.
135   auto QualifiedFunc = tooling::applyAllReplacements(
136       SM.getBufferData(SM.getMainFileID()), Replacements);
137   if (!QualifiedFunc)
138     return QualifiedFunc.takeError();
139   return QualifiedFunc->substr(FuncBegin, FuncEnd - FuncBegin + 1);
140 }
141
142 // Creates a modified version of function definition that can be inserted at a
143 // different location, qualifies return value and function name to achieve that.
144 // Contains function signature, except defaulted parameter arguments, body and
145 // template parameters if applicable. No need to qualify parameters, as they are
146 // looked up in the context containing the function/method.
147 // FIXME: Drop attributes in function signature.
148 llvm::Expected<std::string>
149 getFunctionSourceCode(const FunctionDecl *FD, llvm::StringRef TargetNamespace,
150                       const syntax::TokenBuffer &TokBuf) {
151   auto &AST = FD->getASTContext();
152   auto &SM = AST.getSourceManager();
153   auto TargetContext = findContextForNS(TargetNamespace, FD->getDeclContext());
154   if (!TargetContext)
155     return llvm::createStringError(
156         llvm::inconvertibleErrorCode(),
157         "define outline: couldn't find a context for target");
158
159   llvm::Error Errors = llvm::Error::success();
160   tooling::Replacements DeclarationCleanups;
161
162   // Finds the first unqualified name in function return type and name, then
163   // qualifies those to be valid in TargetContext.
164   findExplicitReferences(FD, [&](ReferenceLoc Ref) {
165     // It is enough to qualify the first qualifier, so skip references with a
166     // qualifier. Also we can't do much if there are no targets or name is
167     // inside a macro body.
168     if (Ref.Qualifier || Ref.Targets.empty() || Ref.NameLoc.isMacroID())
169       return;
170     // Only qualify return type and function name.
171     if (Ref.NameLoc != FD->getReturnTypeSourceRange().getBegin() &&
172         Ref.NameLoc != FD->getLocation())
173       return;
174
175     for (const NamedDecl *ND : Ref.Targets) {
176       if (ND->getDeclContext() != Ref.Targets.front()->getDeclContext()) {
177         elog("Targets from multiple contexts: {0}, {1}",
178              printQualifiedName(*Ref.Targets.front()), printQualifiedName(*ND));
179         return;
180       }
181     }
182     const NamedDecl *ND = Ref.Targets.front();
183     const std::string Qualifier = getQualification(
184         AST, *TargetContext, SM.getLocForStartOfFile(SM.getMainFileID()), ND);
185     if (auto Err = DeclarationCleanups.add(
186             tooling::Replacement(SM, Ref.NameLoc, 0, Qualifier)))
187       Errors = llvm::joinErrors(std::move(Errors), std::move(Err));
188   });
189
190   // Get rid of default arguments, since they should not be specified in
191   // out-of-line definition.
192   for (const auto *PVD : FD->parameters()) {
193     if (PVD->hasDefaultArg()) {
194       // Deletion range initially spans the initializer, excluding the `=`.
195       auto DelRange = CharSourceRange::getTokenRange(PVD->getDefaultArgRange());
196       // Get all tokens before the default argument.
197       auto Tokens = TokBuf.expandedTokens(PVD->getSourceRange())
198                         .take_while([&SM, &DelRange](const syntax::Token &Tok) {
199                           return SM.isBeforeInTranslationUnit(
200                               Tok.location(), DelRange.getBegin());
201                         });
202       // Find the last `=` before the default arg.
203       auto Tok =
204           llvm::find_if(llvm::reverse(Tokens), [](const syntax::Token &Tok) {
205             return Tok.kind() == tok::equal;
206           });
207       assert(Tok != Tokens.rend());
208       DelRange.setBegin(Tok->location());
209       if (auto Err =
210               DeclarationCleanups.add(tooling::Replacement(SM, DelRange, "")))
211         Errors = llvm::joinErrors(std::move(Errors), std::move(Err));
212     }
213   }
214
215   auto DelAttr = [&](const Attr *A) {
216     if (!A)
217       return;
218     auto AttrTokens =
219         TokBuf.spelledForExpanded(TokBuf.expandedTokens(A->getRange()));
220     assert(A->getLocation().isValid());
221     if (!AttrTokens || AttrTokens->empty()) {
222       Errors = llvm::joinErrors(
223           std::move(Errors),
224           llvm::createStringError(
225               llvm::inconvertibleErrorCode(),
226               llvm::StringRef("define outline: Can't move out of line as "
227                               "function has a macro `") +
228                   A->getSpelling() + "` specifier."));
229       return;
230     }
231     CharSourceRange DelRange =
232         syntax::Token::range(SM, AttrTokens->front(), AttrTokens->back())
233             .toCharRange(SM);
234     if (auto Err =
235             DeclarationCleanups.add(tooling::Replacement(SM, DelRange, "")))
236       Errors = llvm::joinErrors(std::move(Errors), std::move(Err));
237   };
238
239   DelAttr(FD->getAttr<OverrideAttr>());
240   DelAttr(FD->getAttr<FinalAttr>());
241
242   if (FD->isVirtualAsWritten()) {
243     SourceRange SpecRange{FD->getBeginLoc(), FD->getLocation()};
244     bool HasErrors = true;
245
246     // Clang allows duplicating virtual specifiers so check for multiple
247     // occurrences.
248     for (const auto &Tok : TokBuf.expandedTokens(SpecRange)) {
249       if (Tok.kind() != tok::kw_virtual)
250         continue;
251       auto Spelling = TokBuf.spelledForExpanded(llvm::makeArrayRef(Tok));
252       if (!Spelling) {
253         HasErrors = true;
254         break;
255       }
256       HasErrors = false;
257       CharSourceRange DelRange =
258           syntax::Token::range(SM, Spelling->front(), Spelling->back())
259               .toCharRange(SM);
260       if (auto Err =
261               DeclarationCleanups.add(tooling::Replacement(SM, DelRange, "")))
262         Errors = llvm::joinErrors(std::move(Errors), std::move(Err));
263     }
264     if (HasErrors) {
265       Errors = llvm::joinErrors(
266           std::move(Errors),
267           llvm::createStringError(llvm::inconvertibleErrorCode(),
268                                   "define outline: Can't move out of line as "
269                                   "function has a macro `virtual` specifier."));
270     }
271   }
272
273   if (Errors)
274     return std::move(Errors);
275   return getFunctionSourceAfterReplacements(FD, DeclarationCleanups);
276 }
277
278 struct InsertionPoint {
279   std::string EnclosingNamespace;
280   size_t Offset;
281 };
282 // Returns the most natural insertion point for \p QualifiedName in \p Contents.
283 // This currently cares about only the namespace proximity, but in feature it
284 // should also try to follow ordering of declarations. For example, if decls
285 // come in order `foo, bar, baz` then this function should return some point
286 // between foo and baz for inserting bar.
287 llvm::Expected<InsertionPoint> getInsertionPoint(llvm::StringRef Contents,
288                                                  llvm::StringRef QualifiedName,
289                                                  const LangOptions &LangOpts) {
290   auto Region = getEligiblePoints(Contents, QualifiedName, LangOpts);
291
292   assert(!Region.EligiblePoints.empty());
293   // FIXME: This selection can be made smarter by looking at the definition
294   // locations for adjacent decls to Source. Unfortunately pseudo parsing in
295   // getEligibleRegions only knows about namespace begin/end events so we
296   // can't match function start/end positions yet.
297   auto Offset = positionToOffset(Contents, Region.EligiblePoints.back());
298   if (!Offset)
299     return Offset.takeError();
300   return InsertionPoint{Region.EnclosingNamespace, *Offset};
301 }
302
303 // Returns the range that should be deleted from declaration, which always
304 // contains function body. In addition to that it might contain constructor
305 // initializers.
306 SourceRange getDeletionRange(const FunctionDecl *FD,
307                              const syntax::TokenBuffer &TokBuf) {
308   auto DeletionRange = FD->getBody()->getSourceRange();
309   if (auto *CD = llvm::dyn_cast<CXXConstructorDecl>(FD)) {
310     const auto &SM = TokBuf.sourceManager();
311     // AST doesn't contain the location for ":" in ctor initializers. Therefore
312     // we find it by finding the first ":" before the first ctor initializer.
313     SourceLocation InitStart;
314     // Find the first initializer.
315     for (const auto *CInit : CD->inits()) {
316       // We don't care about in-class initializers.
317       if (CInit->isInClassMemberInitializer())
318         continue;
319       if (InitStart.isInvalid() ||
320           SM.isBeforeInTranslationUnit(CInit->getSourceLocation(), InitStart))
321         InitStart = CInit->getSourceLocation();
322     }
323     if (InitStart.isValid()) {
324       auto Toks = TokBuf.expandedTokens(CD->getSourceRange());
325       // Drop any tokens after the initializer.
326       Toks = Toks.take_while([&TokBuf, &InitStart](const syntax::Token &Tok) {
327         return TokBuf.sourceManager().isBeforeInTranslationUnit(Tok.location(),
328                                                                 InitStart);
329       });
330       // Look for the first colon.
331       auto Tok =
332           llvm::find_if(llvm::reverse(Toks), [](const syntax::Token &Tok) {
333             return Tok.kind() == tok::colon;
334           });
335       assert(Tok != Toks.rend());
336       DeletionRange.setBegin(Tok->location());
337     }
338   }
339   return DeletionRange;
340 }
341
342 /// Moves definition of a function/method to an appropriate implementation file.
343 ///
344 /// Before:
345 /// a.h
346 ///   void foo() { return; }
347 /// a.cc
348 ///   #include "a.h"
349 ///
350 /// ----------------
351 ///
352 /// After:
353 /// a.h
354 ///   void foo();
355 /// a.cc
356 ///   #include "a.h"
357 ///   void foo() { return; }
358 class DefineOutline : public Tweak {
359 public:
360   const char *id() const override;
361
362   bool hidden() const override { return false; }
363   Intent intent() const override { return Intent::Refactor; }
364   std::string title() const override {
365     return "Move function body to out-of-line.";
366   }
367
368   bool prepare(const Selection &Sel) override {
369     // Bail out if we are not in a header file.
370     // FIXME: We might want to consider moving method definitions below class
371     // definition even if we are inside a source file.
372     if (!isHeaderFile(Sel.AST->getSourceManager().getFilename(Sel.Cursor),
373                       Sel.AST->getLangOpts()))
374       return false;
375
376     Source = getSelectedFunction(Sel.ASTSelection.commonAncestor());
377     // Bail out if the selection is not a in-line function definition.
378     if (!Source || !Source->doesThisDeclarationHaveABody() ||
379         Source->isOutOfLine())
380       return false;
381
382     // Bail out in templated classes, as it is hard to spell the class name, i.e
383     // if the template parameter is unnamed.
384     if (auto *MD = llvm::dyn_cast<CXXMethodDecl>(Source)) {
385       if (MD->getParent()->isTemplated())
386         return false;
387     }
388
389     // Note that we don't check whether an implementation file exists or not in
390     // the prepare, since performing disk IO on each prepare request might be
391     // expensive.
392     return true;
393   }
394
395   Expected<Effect> apply(const Selection &Sel) override {
396     const SourceManager &SM = Sel.AST->getSourceManager();
397     auto MainFileName =
398         getCanonicalPath(SM.getFileEntryForID(SM.getMainFileID()), SM);
399     if (!MainFileName)
400       return llvm::createStringError(
401           llvm::inconvertibleErrorCode(),
402           "Couldn't get absolute path for mainfile.");
403
404     auto CCFile = getSourceFile(*MainFileName, Sel);
405     if (!CCFile)
406       return llvm::createStringError(
407           llvm::inconvertibleErrorCode(),
408           "Couldn't find a suitable implementation file.");
409
410     auto &FS =
411         Sel.AST->getSourceManager().getFileManager().getVirtualFileSystem();
412     auto Buffer = FS.getBufferForFile(*CCFile);
413     // FIXME: Maybe we should consider creating the implementation file if it
414     // doesn't exist?
415     if (!Buffer)
416       return llvm::createStringError(Buffer.getError(),
417                                      Buffer.getError().message());
418     auto Contents = Buffer->get()->getBuffer();
419     auto LangOpts = format::getFormattingLangOpts(
420         getFormatStyleForFile(*CCFile, Contents, &FS));
421     auto InsertionPoint = getInsertionPoint(
422         Contents, Source->getQualifiedNameAsString(), LangOpts);
423     if (!InsertionPoint)
424       return InsertionPoint.takeError();
425
426     auto FuncDef = getFunctionSourceCode(
427         Source, InsertionPoint->EnclosingNamespace, Sel.AST->getTokens());
428     if (!FuncDef)
429       return FuncDef.takeError();
430
431     SourceManagerForFile SMFF(*CCFile, Contents);
432     const tooling::Replacement InsertFunctionDef(
433         *CCFile, InsertionPoint->Offset, 0, *FuncDef);
434     auto Effect = Effect::mainFileEdit(
435         SMFF.get(), tooling::Replacements(InsertFunctionDef));
436     if (!Effect)
437       return Effect.takeError();
438
439     // FIXME: We should also get rid of inline qualifier.
440     const tooling::Replacement DeleteFuncBody(
441         Sel.AST->getSourceManager(),
442         CharSourceRange::getTokenRange(*toHalfOpenFileRange(
443             SM, Sel.AST->getLangOpts(),
444             getDeletionRange(Source, Sel.AST->getTokens()))),
445         ";");
446     auto HeaderFE = Effect::fileEdit(SM, SM.getMainFileID(),
447                                      tooling::Replacements(DeleteFuncBody));
448     if (!HeaderFE)
449       return HeaderFE.takeError();
450
451     Effect->ApplyEdits.try_emplace(HeaderFE->first,
452                                    std::move(HeaderFE->second));
453     return std::move(*Effect);
454   }
455
456 private:
457   const FunctionDecl *Source = nullptr;
458 };
459
460 REGISTER_TWEAK(DefineOutline)
461
462 } // namespace
463 } // namespace clangd
464 } // namespace clang