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