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