clang-tools  11.0.0
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 "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/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  auto DelKeyword = [&](tok::TokenKind Kind, SourceRange FromRange) {
243  bool FoundAny = false;
244  for (const auto &Tok : TokBuf.expandedTokens(FromRange)) {
245  if (Tok.kind() != Kind)
246  continue;
247  FoundAny = true;
248  auto Spelling = TokBuf.spelledForExpanded(llvm::makeArrayRef(Tok));
249  if (!Spelling) {
250  Errors = llvm::joinErrors(
251  std::move(Errors),
252  llvm::createStringError(
253  llvm::inconvertibleErrorCode(),
254  llvm::formatv("define outline: couldn't remove `{0}` keyword.",
255  tok::getKeywordSpelling(Kind))));
256  break;
257  }
258  CharSourceRange DelRange =
259  syntax::Token::range(SM, Spelling->front(), Spelling->back())
260  .toCharRange(SM);
261  if (auto Err =
262  DeclarationCleanups.add(tooling::Replacement(SM, DelRange, "")))
263  Errors = llvm::joinErrors(std::move(Errors), std::move(Err));
264  }
265  if (!FoundAny) {
266  Errors = llvm::joinErrors(
267  std::move(Errors),
268  llvm::createStringError(
269  llvm::inconvertibleErrorCode(),
270  llvm::formatv(
271  "define outline: couldn't find `{0}` keyword to remove.",
272  tok::getKeywordSpelling(Kind))));
273  }
274  };
275 
276  if (const auto *MD = dyn_cast<CXXMethodDecl>(FD)) {
277  if (MD->isVirtualAsWritten())
278  DelKeyword(tok::kw_virtual, {FD->getBeginLoc(), FD->getLocation()});
279  if (MD->isStatic())
280  DelKeyword(tok::kw_static, {FD->getBeginLoc(), FD->getLocation()});
281  }
282 
283  if (Errors)
284  return std::move(Errors);
285  return getFunctionSourceAfterReplacements(FD, DeclarationCleanups);
286 }
287 
288 struct InsertionPoint {
289  std::string EnclosingNamespace;
290  size_t Offset;
291 };
292 // Returns the most natural insertion point for \p QualifiedName in \p Contents.
293 // This currently cares about only the namespace proximity, but in feature it
294 // should also try to follow ordering of declarations. For example, if decls
295 // come in order `foo, bar, baz` then this function should return some point
296 // between foo and baz for inserting bar.
297 llvm::Expected<InsertionPoint> getInsertionPoint(llvm::StringRef Contents,
298  llvm::StringRef QualifiedName,
299  const LangOptions &LangOpts) {
300  auto Region = getEligiblePoints(Contents, QualifiedName, LangOpts);
301 
302  assert(!Region.EligiblePoints.empty());
303  // FIXME: This selection can be made smarter by looking at the definition
304  // locations for adjacent decls to Source. Unfortunately pseudo parsing in
305  // getEligibleRegions only knows about namespace begin/end events so we
306  // can't match function start/end positions yet.
307  auto Offset = positionToOffset(Contents, Region.EligiblePoints.back());
308  if (!Offset)
309  return Offset.takeError();
310  return InsertionPoint{Region.EnclosingNamespace, *Offset};
311 }
312 
313 // Returns the range that should be deleted from declaration, which always
314 // contains function body. In addition to that it might contain constructor
315 // initializers.
316 SourceRange getDeletionRange(const FunctionDecl *FD,
317  const syntax::TokenBuffer &TokBuf) {
318  auto DeletionRange = FD->getBody()->getSourceRange();
319  if (auto *CD = llvm::dyn_cast<CXXConstructorDecl>(FD)) {
320  // AST doesn't contain the location for ":" in ctor initializers. Therefore
321  // we find it by finding the first ":" before the first ctor initializer.
322  SourceLocation InitStart;
323  // Find the first initializer.
324  for (const auto *CInit : CD->inits()) {
325  // SourceOrder is -1 for implicit initializers.
326  if (CInit->getSourceOrder() != 0)
327  continue;
328  InitStart = CInit->getSourceLocation();
329  break;
330  }
331  if (InitStart.isValid()) {
332  auto Toks = TokBuf.expandedTokens(CD->getSourceRange());
333  // Drop any tokens after the initializer.
334  Toks = Toks.take_while([&TokBuf, &InitStart](const syntax::Token &Tok) {
335  return TokBuf.sourceManager().isBeforeInTranslationUnit(Tok.location(),
336  InitStart);
337  });
338  // Look for the first colon.
339  auto Tok =
340  llvm::find_if(llvm::reverse(Toks), [](const syntax::Token &Tok) {
341  return Tok.kind() == tok::colon;
342  });
343  assert(Tok != Toks.rend());
344  DeletionRange.setBegin(Tok->location());
345  }
346  }
347  return DeletionRange;
348 }
349 
350 /// Moves definition of a function/method to an appropriate implementation file.
351 ///
352 /// Before:
353 /// a.h
354 /// void foo() { return; }
355 /// a.cc
356 /// #include "a.h"
357 ///
358 /// ----------------
359 ///
360 /// After:
361 /// a.h
362 /// void foo();
363 /// a.cc
364 /// #include "a.h"
365 /// void foo() { return; }
366 class DefineOutline : public Tweak {
367 public:
368  const char *id() const override;
369 
370  bool hidden() const override { return false; }
371  Intent intent() const override { return Intent::Refactor; }
372  std::string title() const override {
373  return "Move function body to out-of-line.";
374  }
375 
376  bool prepare(const Selection &Sel) override {
377  // Bail out if we are not in a header file.
378  // FIXME: We might want to consider moving method definitions below class
379  // definition even if we are inside a source file.
380  if (!isHeaderFile(Sel.AST->getSourceManager().getFilename(Sel.Cursor),
381  Sel.AST->getLangOpts()))
382  return false;
383 
384  Source = getSelectedFunction(Sel.ASTSelection.commonAncestor());
385  // Bail out if the selection is not a in-line function definition.
386  if (!Source || !Source->doesThisDeclarationHaveABody() ||
387  Source->isOutOfLine())
388  return false;
389 
390  // Bail out in templated classes, as it is hard to spell the class name, i.e
391  // if the template parameter is unnamed.
392  if (auto *MD = llvm::dyn_cast<CXXMethodDecl>(Source)) {
393  if (MD->getParent()->isTemplated())
394  return false;
395  }
396 
397  // Note that we don't check whether an implementation file exists or not in
398  // the prepare, since performing disk IO on each prepare request might be
399  // expensive.
400  return true;
401  }
402 
403  Expected<Effect> apply(const Selection &Sel) override {
404  const SourceManager &SM = Sel.AST->getSourceManager();
405  auto MainFileName =
406  getCanonicalPath(SM.getFileEntryForID(SM.getMainFileID()), SM);
407  if (!MainFileName)
408  return llvm::createStringError(
409  llvm::inconvertibleErrorCode(),
410  "Couldn't get absolute path for mainfile.");
411 
412  auto CCFile = getSourceFile(*MainFileName, Sel);
413  if (!CCFile)
414  return llvm::createStringError(
415  llvm::inconvertibleErrorCode(),
416  "Couldn't find a suitable implementation file.");
417 
418  auto &FS =
419  Sel.AST->getSourceManager().getFileManager().getVirtualFileSystem();
420  auto Buffer = FS.getBufferForFile(*CCFile);
421  // FIXME: Maybe we should consider creating the implementation file if it
422  // doesn't exist?
423  if (!Buffer)
424  return llvm::createStringError(Buffer.getError(),
425  Buffer.getError().message());
426  auto Contents = Buffer->get()->getBuffer();
427  auto InsertionPoint = getInsertionPoint(
428  Contents, Source->getQualifiedNameAsString(), Sel.AST->getLangOpts());
429  if (!InsertionPoint)
430  return InsertionPoint.takeError();
431 
432  auto FuncDef = getFunctionSourceCode(
433  Source, InsertionPoint->EnclosingNamespace, Sel.AST->getTokens());
434  if (!FuncDef)
435  return FuncDef.takeError();
436 
437  SourceManagerForFile SMFF(*CCFile, Contents);
438  const tooling::Replacement InsertFunctionDef(
439  *CCFile, InsertionPoint->Offset, 0, *FuncDef);
440  auto Effect = Effect::mainFileEdit(
441  SMFF.get(), tooling::Replacements(InsertFunctionDef));
442  if (!Effect)
443  return Effect.takeError();
444 
445  // FIXME: We should also get rid of inline qualifier.
446  const tooling::Replacement DeleteFuncBody(
447  Sel.AST->getSourceManager(),
448  CharSourceRange::getTokenRange(*toHalfOpenFileRange(
449  SM, Sel.AST->getLangOpts(),
450  getDeletionRange(Source, Sel.AST->getTokens()))),
451  ";");
452  auto HeaderFE = Effect::fileEdit(SM, SM.getMainFileID(),
453  tooling::Replacements(DeleteFuncBody));
454  if (!HeaderFE)
455  return HeaderFE.takeError();
456 
457  Effect->ApplyEdits.try_emplace(HeaderFE->first,
458  std::move(HeaderFE->second));
459  return std::move(*Effect);
460  }
461 
462 private:
463  const FunctionDecl *Source = nullptr;
464 };
465 
466 REGISTER_TWEAK(DefineOutline)
467 
468 } // namespace
469 } // namespace clangd
470 } // namespace clang
Selection.h
clang::clangd::toHalfOpenFileRange
llvm::Optional< SourceRange > toHalfOpenFileRange(const SourceManager &SM, const LangOptions &LangOpts, SourceRange R)
Turns a token range into a half-open range and checks its correctness.
Definition: SourceCode.cpp:428
clang::clangd::ParsedAST::getASTContext
ASTContext & getASTContext()
Note that the returned ast will not contain decls from the preamble that were not deserialized during...
Definition: ParsedAST.cpp:471
clang::doc::MD
static GeneratorRegistry::Add< MDGenerator > MD(MDGenerator::Format, "Generator for MD output.")
Path.h
Kind
BindArgumentKind Kind
Definition: AvoidBindCheck.cpp:59
InsertionPoint
SourceLocation InsertionPoint
Definition: ExtractFunction.cpp:305
Contents
llvm::StringRef Contents
Definition: DraftStoreTests.cpp:22
HeaderSourceSwitch.h
clang::clangd::TextDocumentSyncKind::None
Documents should not be synced at all.
FindTarget.h
clang::tidy::readability::@587::Qualifier
Qualifier
Definition: QualifiedAutoCheck.cpp:29
clang::clangd::SelectionTree::Complete
Definition: Selection.h:118
clang::clangd::getCanonicalPath
llvm::Optional< std::string > getCanonicalPath(const FileEntry *F, const SourceManager &SourceMgr)
Get the canonical path of F.
Definition: SourceCode.cpp:512
Tweak.h
clang::clangd::printQualifiedName
std::string printQualifiedName(const NamedDecl &ND)
Returns the qualified name of ND.
Definition: AST.cpp:168
Logger.h
FS
MockFS FS
Definition: ClangdLSPServerTests.cpp:66
clang::clangd::positionToOffset
llvm::Expected< size_t > positionToOffset(llvm::StringRef Code, Position P, bool AllowColumnsBeyondLineLength)
Turn a [line, column] pair into an offset in Code.
Definition: SourceCode.cpp:175
clang::clangd::getEligiblePoints
EligibleRegion getEligiblePoints(llvm::StringRef Code, llvm::StringRef FullyQualifiedName, const LangOptions &LangOpts)
Returns most eligible region to insert a definition for FullyQualifiedName in the Code.
Definition: SourceCode.cpp:1042
EnclosingNamespace
std::string EnclosingNamespace
Definition: DefineOutline.cpp:289
Offset
size_t Offset
Definition: DefineOutline.cpp:290
SourceCode.h
clang::clangd::getQualification
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:428
clang::clangd::isHeaderFile
bool isHeaderFile(llvm::StringRef FileName, llvm::Optional< LangOptions > LangOpts)
Infers whether this is a header from the FileName and LangOpts (if presents).
Definition: SourceCode.cpp:1095
clang::clangd::findExplicitReferences
void findExplicitReferences(const Stmt *S, llvm::function_ref< void(ReferenceLoc)> Out)
Recursively traverse S and report all references explicitly written in the code.
Definition: FindTarget.cpp:1017
clang
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Definition: ApplyReplacements.h:27
clang::clangd::getCorrespondingHeaderOrSource
llvm::Optional< Path > getCorrespondingHeaderOrSource(const Path &OriginalFile, llvm::IntrusiveRefCntPtr< llvm::vfs::FileSystem > VFS)
Given a header file, returns the best matching source file, and vice visa.
Definition: HeaderSourceSwitch.cpp:19
clang::clangd::elog
void elog(const char *Fmt, Ts &&... Vals)
Definition: Logger.h:56
clang::clangd::ParsedAST::getSourceManager
SourceManager & getSourceManager()
Definition: ParsedAST.h:74
FileName
PathRef FileName
Definition: CodeComplete.cpp:1043
clang::clangd::MessageType::Error
An error message.
REGISTER_TWEAK
#define REGISTER_TWEAK(Subclass)
Definition: Tweak.h:129
AST.h
ParsedAST.h