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