clang-tools  16.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  continue;
192  // Deletion range spans the initializer, usually 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  if (TokBuf.expandedTokens(DelRange.getAsRange()).front().kind() !=
201  tok::equal) {
202  // Find the last `=` if it isn't included in the initializer, and update
203  // the DelRange to include it.
204  auto Tok =
205  llvm::find_if(llvm::reverse(Tokens), [](const syntax::Token &Tok) {
206  return Tok.kind() == tok::equal;
207  });
208  assert(Tok != Tokens.rend());
209  DelRange.setBegin(Tok->location());
210  }
211  if (auto Err =
212  DeclarationCleanups.add(tooling::Replacement(SM, DelRange, "")))
213  Errors = llvm::joinErrors(std::move(Errors), std::move(Err));
214  }
215 
216  auto DelAttr = [&](const Attr *A) {
217  if (!A)
218  return;
219  auto AttrTokens =
220  TokBuf.spelledForExpanded(TokBuf.expandedTokens(A->getRange()));
221  assert(A->getLocation().isValid());
222  if (!AttrTokens || AttrTokens->empty()) {
223  Errors = llvm::joinErrors(
224  std::move(Errors), error("define outline: Can't move out of line as "
225  "function has a macro `{0}` specifier.",
226  A->getSpelling()));
227  return;
228  }
229  CharSourceRange DelRange =
230  syntax::Token::range(SM, AttrTokens->front(), AttrTokens->back())
231  .toCharRange(SM);
232  if (auto Err =
233  DeclarationCleanups.add(tooling::Replacement(SM, DelRange, "")))
234  Errors = llvm::joinErrors(std::move(Errors), std::move(Err));
235  };
236 
237  DelAttr(FD->getAttr<OverrideAttr>());
238  DelAttr(FD->getAttr<FinalAttr>());
239 
240  auto DelKeyword = [&](tok::TokenKind Kind, SourceRange FromRange) {
241  bool FoundAny = false;
242  for (const auto &Tok : TokBuf.expandedTokens(FromRange)) {
243  if (Tok.kind() != Kind)
244  continue;
245  FoundAny = true;
246  auto Spelling = TokBuf.spelledForExpanded(llvm::makeArrayRef(Tok));
247  if (!Spelling) {
248  Errors = llvm::joinErrors(
249  std::move(Errors),
250  error("define outline: couldn't remove `{0}` keyword.",
251  tok::getKeywordSpelling(Kind)));
252  break;
253  }
254  CharSourceRange DelRange =
255  syntax::Token::range(SM, Spelling->front(), Spelling->back())
256  .toCharRange(SM);
257  if (auto Err =
258  DeclarationCleanups.add(tooling::Replacement(SM, DelRange, "")))
259  Errors = llvm::joinErrors(std::move(Errors), std::move(Err));
260  }
261  if (!FoundAny) {
262  Errors = llvm::joinErrors(
263  std::move(Errors),
264  error("define outline: couldn't find `{0}` keyword to remove.",
265  tok::getKeywordSpelling(Kind)));
266  }
267  };
268 
269  if (const auto *MD = dyn_cast<CXXMethodDecl>(FD)) {
270  if (MD->isVirtualAsWritten())
271  DelKeyword(tok::kw_virtual, {FD->getBeginLoc(), FD->getLocation()});
272  if (MD->isStatic())
273  DelKeyword(tok::kw_static, {FD->getBeginLoc(), FD->getLocation()});
274  }
275  if (const auto *CD = dyn_cast<CXXConstructorDecl>(FD)) {
276  if (CD->isExplicit())
277  DelKeyword(tok::kw_explicit, {FD->getBeginLoc(), FD->getLocation()});
278  }
279 
280  if (Errors)
281  return std::move(Errors);
282  return getFunctionSourceAfterReplacements(FD, DeclarationCleanups);
283 }
284 
285 struct InsertionPoint {
286  std::string EnclosingNamespace;
287  size_t Offset;
288 };
289 // Returns the most natural insertion point for \p QualifiedName in \p Contents.
290 // This currently cares about only the namespace proximity, but in feature it
291 // should also try to follow ordering of declarations. For example, if decls
292 // come in order `foo, bar, baz` then this function should return some point
293 // between foo and baz for inserting bar.
294 llvm::Expected<InsertionPoint> getInsertionPoint(llvm::StringRef Contents,
295  llvm::StringRef QualifiedName,
296  const LangOptions &LangOpts) {
297  auto Region = getEligiblePoints(Contents, QualifiedName, LangOpts);
298 
299  assert(!Region.EligiblePoints.empty());
300  // FIXME: This selection can be made smarter by looking at the definition
301  // locations for adjacent decls to Source. Unfortunately pseudo parsing in
302  // getEligibleRegions only knows about namespace begin/end events so we
303  // can't match function start/end positions yet.
304  auto Offset = positionToOffset(Contents, Region.EligiblePoints.back());
305  if (!Offset)
306  return Offset.takeError();
307  return InsertionPoint{Region.EnclosingNamespace, *Offset};
308 }
309 
310 // Returns the range that should be deleted from declaration, which always
311 // contains function body. In addition to that it might contain constructor
312 // initializers.
313 SourceRange getDeletionRange(const FunctionDecl *FD,
314  const syntax::TokenBuffer &TokBuf) {
315  auto DeletionRange = FD->getBody()->getSourceRange();
316  if (auto *CD = llvm::dyn_cast<CXXConstructorDecl>(FD)) {
317  // AST doesn't contain the location for ":" in ctor initializers. Therefore
318  // we find it by finding the first ":" before the first ctor initializer.
319  SourceLocation InitStart;
320  // Find the first initializer.
321  for (const auto *CInit : CD->inits()) {
322  // SourceOrder is -1 for implicit initializers.
323  if (CInit->getSourceOrder() != 0)
324  continue;
325  InitStart = CInit->getSourceLocation();
326  break;
327  }
328  if (InitStart.isValid()) {
329  auto Toks = TokBuf.expandedTokens(CD->getSourceRange());
330  // Drop any tokens after the initializer.
331  Toks = Toks.take_while([&TokBuf, &InitStart](const syntax::Token &Tok) {
332  return TokBuf.sourceManager().isBeforeInTranslationUnit(Tok.location(),
333  InitStart);
334  });
335  // Look for the first colon.
336  auto Tok =
337  llvm::find_if(llvm::reverse(Toks), [](const syntax::Token &Tok) {
338  return Tok.kind() == tok::colon;
339  });
340  assert(Tok != Toks.rend());
341  DeletionRange.setBegin(Tok->location());
342  }
343  }
344  return DeletionRange;
345 }
346 
347 /// Moves definition of a function/method to an appropriate implementation file.
348 ///
349 /// Before:
350 /// a.h
351 /// void foo() { return; }
352 /// a.cc
353 /// #include "a.h"
354 ///
355 /// ----------------
356 ///
357 /// After:
358 /// a.h
359 /// void foo();
360 /// a.cc
361 /// #include "a.h"
362 /// void foo() { return; }
363 class DefineOutline : public Tweak {
364 public:
365  const char *id() const override;
366 
367  bool hidden() const override { return false; }
368  llvm::StringLiteral kind() const override {
370  }
371  std::string title() const override {
372  return "Move function body to out-of-line";
373  }
374 
375  bool prepare(const Selection &Sel) override {
376  // Bail out if we are not in a header file.
377  // FIXME: We might want to consider moving method definitions below class
378  // definition even if we are inside a source file.
379  if (!isHeaderFile(Sel.AST->getSourceManager().getFilename(Sel.Cursor),
380  Sel.AST->getLangOpts()))
381  return false;
382 
383  Source = getSelectedFunction(Sel.ASTSelection.commonAncestor());
384  // Bail out if the selection is not a in-line function definition.
385  if (!Source || !Source->doesThisDeclarationHaveABody() ||
386  Source->isOutOfLine())
387  return false;
388 
389  // Bail out if this is a function template or specialization, as their
390  // definitions need to be visible in all including translation units.
391  if (Source->getDescribedFunctionTemplate())
392  return false;
393  if (Source->getTemplateSpecializationInfo())
394  return false;
395 
396  // Bail out in templated classes, as it is hard to spell the class name, i.e
397  // if the template parameter is unnamed.
398  if (auto *MD = llvm::dyn_cast<CXXMethodDecl>(Source)) {
399  if (MD->getParent()->isTemplated())
400  return false;
401  }
402 
403  // Note that we don't check whether an implementation file exists or not in
404  // the prepare, since performing disk IO on each prepare request might be
405  // expensive.
406  return true;
407  }
408 
409  Expected<Effect> apply(const Selection &Sel) override {
410  const SourceManager &SM = Sel.AST->getSourceManager();
411  auto CCFile = getSourceFile(Sel.AST->tuPath(), Sel);
412 
413  if (!CCFile)
414  return error("Couldn't find a suitable implementation file.");
415  assert(Sel.FS && "FS Must be set in apply");
416  auto Buffer = Sel.FS->getBufferForFile(*CCFile);
417  // FIXME: Maybe we should consider creating the implementation file if it
418  // doesn't exist?
419  if (!Buffer)
420  return llvm::errorCodeToError(Buffer.getError());
421  auto Contents = Buffer->get()->getBuffer();
422  auto InsertionPoint = getInsertionPoint(
423  Contents, Source->getQualifiedNameAsString(), Sel.AST->getLangOpts());
424  if (!InsertionPoint)
425  return InsertionPoint.takeError();
426 
427  auto FuncDef = getFunctionSourceCode(
428  Source, InsertionPoint->EnclosingNamespace, Sel.AST->getTokens(),
429  Sel.AST->getHeuristicResolver());
430  if (!FuncDef)
431  return FuncDef.takeError();
432 
433  SourceManagerForFile SMFF(*CCFile, Contents);
434  const tooling::Replacement InsertFunctionDef(
435  *CCFile, InsertionPoint->Offset, 0, *FuncDef);
436  auto Effect = Effect::mainFileEdit(
437  SMFF.get(), tooling::Replacements(InsertFunctionDef));
438  if (!Effect)
439  return Effect.takeError();
440 
441  // FIXME: We should also get rid of inline qualifier.
442  const tooling::Replacement DeleteFuncBody(
443  Sel.AST->getSourceManager(),
444  CharSourceRange::getTokenRange(*toHalfOpenFileRange(
445  SM, Sel.AST->getLangOpts(),
446  getDeletionRange(Source, Sel.AST->getTokens()))),
447  ";");
448  auto HeaderFE = Effect::fileEdit(SM, SM.getMainFileID(),
449  tooling::Replacements(DeleteFuncBody));
450  if (!HeaderFE)
451  return HeaderFE.takeError();
452 
453  Effect->ApplyEdits.try_emplace(HeaderFE->first,
454  std::move(HeaderFE->second));
455  return std::move(*Effect);
456  }
457 
458 private:
459  const FunctionDecl *Source = nullptr;
460 };
461 
462 REGISTER_TWEAK(DefineOutline)
463 
464 } // namespace
465 } // namespace clangd
466 } // 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:425
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:700
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
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:1109
FileName
StringRef FileName
Definition: KernelNameRestrictionCheck.cpp:46
EnclosingNamespace
std::string EnclosingNamespace
Definition: DefineOutline.cpp:286
Offset
size_t Offset
Definition: DefineOutline.cpp:287
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:641
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:1162
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:1009
LangOpts
const LangOptions * LangOpts
Definition: ExtractFunction.cpp:375
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:74
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:1125
REGISTER_TWEAK
#define REGISTER_TWEAK(Subclass)
Definition: Tweak.h:129
AST.h
ParsedAST.h