clang-tools  14.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/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 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  assert(Sel.FS);
68  if (auto Source = getCorrespondingHeaderOrSource(FileName, Sel.FS))
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 error("Couldn't get range for function.");
122  assert(!FD->getDescribedFunctionTemplate() &&
123  "Define out-of-line doesn't apply to function templates.");
124 
125  // Get new begin and end positions for the qualified function definition.
126  unsigned FuncBegin = SM.getFileOffset(OrigFuncRange->getBegin());
127  unsigned FuncEnd = Replacements.getShiftedCodePosition(
128  SM.getFileOffset(OrigFuncRange->getEnd()));
129 
130  // Trim the result to function definition.
131  auto QualifiedFunc = tooling::applyAllReplacements(
132  SM.getBufferData(SM.getMainFileID()), Replacements);
133  if (!QualifiedFunc)
134  return QualifiedFunc.takeError();
135  return QualifiedFunc->substr(FuncBegin, FuncEnd - FuncBegin + 1);
136 }
137 
138 // Creates a modified version of function definition that can be inserted at a
139 // different location, qualifies return value and function name to achieve that.
140 // Contains function signature, except defaulted parameter arguments, body and
141 // template parameters if applicable. No need to qualify parameters, as they are
142 // looked up in the context containing the function/method.
143 // FIXME: Drop attributes in function signature.
144 llvm::Expected<std::string>
145 getFunctionSourceCode(const FunctionDecl *FD, llvm::StringRef TargetNamespace,
146  const syntax::TokenBuffer &TokBuf,
147  const HeuristicResolver *Resolver) {
148  auto &AST = FD->getASTContext();
149  auto &SM = AST.getSourceManager();
150  auto TargetContext = findContextForNS(TargetNamespace, FD->getDeclContext());
151  if (!TargetContext)
152  return error("define outline: couldn't find a context for target");
153 
154  llvm::Error Errors = llvm::Error::success();
155  tooling::Replacements DeclarationCleanups;
156 
157  // Finds the first unqualified name in function return type and name, then
158  // qualifies those to be valid in TargetContext.
160  FD,
161  [&](ReferenceLoc Ref) {
162  // It is enough to qualify the first qualifier, so skip references with
163  // a qualifier. Also we can't do much if there are no targets or name is
164  // inside a macro body.
165  if (Ref.Qualifier || Ref.Targets.empty() || Ref.NameLoc.isMacroID())
166  return;
167  // Only qualify return type and function name.
168  if (Ref.NameLoc != FD->getReturnTypeSourceRange().getBegin() &&
169  Ref.NameLoc != FD->getLocation())
170  return;
171 
172  for (const NamedDecl *ND : Ref.Targets) {
173  if (ND->getDeclContext() != Ref.Targets.front()->getDeclContext()) {
174  elog("Targets from multiple contexts: {0}, {1}",
175  printQualifiedName(*Ref.Targets.front()),
176  printQualifiedName(*ND));
177  return;
178  }
179  }
180  const NamedDecl *ND = Ref.Targets.front();
181  const std::string Qualifier =
182  getQualification(AST, *TargetContext,
183  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  Resolver);
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), error("define outline: Can't move out of line as "
224  "function has a macro `{0}` specifier.",
225  A->getSpelling()));
226  return;
227  }
228  CharSourceRange DelRange =
229  syntax::Token::range(SM, AttrTokens->front(), AttrTokens->back())
230  .toCharRange(SM);
231  if (auto Err =
232  DeclarationCleanups.add(tooling::Replacement(SM, DelRange, "")))
233  Errors = llvm::joinErrors(std::move(Errors), std::move(Err));
234  };
235 
236  DelAttr(FD->getAttr<OverrideAttr>());
237  DelAttr(FD->getAttr<FinalAttr>());
238 
239  auto DelKeyword = [&](tok::TokenKind Kind, SourceRange FromRange) {
240  bool FoundAny = false;
241  for (const auto &Tok : TokBuf.expandedTokens(FromRange)) {
242  if (Tok.kind() != Kind)
243  continue;
244  FoundAny = true;
245  auto Spelling = TokBuf.spelledForExpanded(llvm::makeArrayRef(Tok));
246  if (!Spelling) {
247  Errors = llvm::joinErrors(
248  std::move(Errors),
249  error("define outline: couldn't remove `{0}` keyword.",
250  tok::getKeywordSpelling(Kind)));
251  break;
252  }
253  CharSourceRange DelRange =
254  syntax::Token::range(SM, Spelling->front(), Spelling->back())
255  .toCharRange(SM);
256  if (auto Err =
257  DeclarationCleanups.add(tooling::Replacement(SM, DelRange, "")))
258  Errors = llvm::joinErrors(std::move(Errors), std::move(Err));
259  }
260  if (!FoundAny) {
261  Errors = llvm::joinErrors(
262  std::move(Errors),
263  error("define outline: couldn't find `{0}` keyword to remove.",
264  tok::getKeywordSpelling(Kind)));
265  }
266  };
267 
268  if (const auto *MD = dyn_cast<CXXMethodDecl>(FD)) {
269  if (MD->isVirtualAsWritten())
270  DelKeyword(tok::kw_virtual, {FD->getBeginLoc(), FD->getLocation()});
271  if (MD->isStatic())
272  DelKeyword(tok::kw_static, {FD->getBeginLoc(), FD->getLocation()});
273  }
274  if (const auto *CD = dyn_cast<CXXConstructorDecl>(FD)) {
275  if (CD->isExplicit())
276  DelKeyword(tok::kw_explicit, {FD->getBeginLoc(), FD->getLocation()});
277  }
278 
279  if (Errors)
280  return std::move(Errors);
281  return getFunctionSourceAfterReplacements(FD, DeclarationCleanups);
282 }
283 
284 struct InsertionPoint {
285  std::string EnclosingNamespace;
286  size_t Offset;
287 };
288 // Returns the most natural insertion point for \p QualifiedName in \p Contents.
289 // This currently cares about only the namespace proximity, but in feature it
290 // should also try to follow ordering of declarations. For example, if decls
291 // come in order `foo, bar, baz` then this function should return some point
292 // between foo and baz for inserting bar.
293 llvm::Expected<InsertionPoint> getInsertionPoint(llvm::StringRef Contents,
294  llvm::StringRef QualifiedName,
295  const LangOptions &LangOpts) {
296  auto Region = getEligiblePoints(Contents, QualifiedName, LangOpts);
297 
298  assert(!Region.EligiblePoints.empty());
299  // FIXME: This selection can be made smarter by looking at the definition
300  // locations for adjacent decls to Source. Unfortunately pseudo parsing in
301  // getEligibleRegions only knows about namespace begin/end events so we
302  // can't match function start/end positions yet.
303  auto Offset = positionToOffset(Contents, Region.EligiblePoints.back());
304  if (!Offset)
305  return Offset.takeError();
306  return InsertionPoint{Region.EnclosingNamespace, *Offset};
307 }
308 
309 // Returns the range that should be deleted from declaration, which always
310 // contains function body. In addition to that it might contain constructor
311 // initializers.
312 SourceRange getDeletionRange(const FunctionDecl *FD,
313  const syntax::TokenBuffer &TokBuf) {
314  auto DeletionRange = FD->getBody()->getSourceRange();
315  if (auto *CD = llvm::dyn_cast<CXXConstructorDecl>(FD)) {
316  // AST doesn't contain the location for ":" in ctor initializers. Therefore
317  // we find it by finding the first ":" before the first ctor initializer.
318  SourceLocation InitStart;
319  // Find the first initializer.
320  for (const auto *CInit : CD->inits()) {
321  // SourceOrder is -1 for implicit initializers.
322  if (CInit->getSourceOrder() != 0)
323  continue;
324  InitStart = CInit->getSourceLocation();
325  break;
326  }
327  if (InitStart.isValid()) {
328  auto Toks = TokBuf.expandedTokens(CD->getSourceRange());
329  // Drop any tokens after the initializer.
330  Toks = Toks.take_while([&TokBuf, &InitStart](const syntax::Token &Tok) {
331  return TokBuf.sourceManager().isBeforeInTranslationUnit(Tok.location(),
332  InitStart);
333  });
334  // Look for the first colon.
335  auto Tok =
336  llvm::find_if(llvm::reverse(Toks), [](const syntax::Token &Tok) {
337  return Tok.kind() == tok::colon;
338  });
339  assert(Tok != Toks.rend());
340  DeletionRange.setBegin(Tok->location());
341  }
342  }
343  return DeletionRange;
344 }
345 
346 /// Moves definition of a function/method to an appropriate implementation file.
347 ///
348 /// Before:
349 /// a.h
350 /// void foo() { return; }
351 /// a.cc
352 /// #include "a.h"
353 ///
354 /// ----------------
355 ///
356 /// After:
357 /// a.h
358 /// void foo();
359 /// a.cc
360 /// #include "a.h"
361 /// void foo() { return; }
362 class DefineOutline : public Tweak {
363 public:
364  const char *id() const override;
365 
366  bool hidden() const override { return false; }
367  llvm::StringLiteral kind() const override {
369  }
370  std::string title() const override {
371  return "Move function body to out-of-line";
372  }
373 
374  bool prepare(const Selection &Sel) override {
375  // Bail out if we are not in a header file.
376  // FIXME: We might want to consider moving method definitions below class
377  // definition even if we are inside a source file.
378  if (!isHeaderFile(Sel.AST->getSourceManager().getFilename(Sel.Cursor),
379  Sel.AST->getLangOpts()))
380  return false;
381 
382  Source = getSelectedFunction(Sel.ASTSelection.commonAncestor());
383  // Bail out if the selection is not a in-line function definition.
384  if (!Source || !Source->doesThisDeclarationHaveABody() ||
385  Source->isOutOfLine())
386  return false;
387 
388  // Bail out if this is a function template or specialization, as their
389  // definitions need to be visible in all including translation units.
390  if (Source->getDescribedFunctionTemplate())
391  return false;
392  if (Source->getTemplateSpecializationInfo())
393  return false;
394 
395  // Bail out in templated classes, as it is hard to spell the class name, i.e
396  // if the template parameter is unnamed.
397  if (auto *MD = llvm::dyn_cast<CXXMethodDecl>(Source)) {
398  if (MD->getParent()->isTemplated())
399  return false;
400  }
401 
402  // Note that we don't check whether an implementation file exists or not in
403  // the prepare, since performing disk IO on each prepare request might be
404  // expensive.
405  return true;
406  }
407 
408  Expected<Effect> apply(const Selection &Sel) override {
409  const SourceManager &SM = Sel.AST->getSourceManager();
410  auto MainFileName =
411  getCanonicalPath(SM.getFileEntryForID(SM.getMainFileID()), SM);
412  if (!MainFileName)
413  return error("Couldn't get absolute path for main file.");
414 
415  auto CCFile = getSourceFile(*MainFileName, Sel);
416 
417  if (!CCFile)
418  return error("Couldn't find a suitable implementation file.");
419  assert(Sel.FS && "FS Must be set in apply");
420  auto Buffer = Sel.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::errorCodeToError(Buffer.getError());
425  auto Contents = Buffer->get()->getBuffer();
426  auto InsertionPoint = getInsertionPoint(
427  Contents, Source->getQualifiedNameAsString(), Sel.AST->getLangOpts());
428  if (!InsertionPoint)
429  return InsertionPoint.takeError();
430 
431  auto FuncDef = getFunctionSourceCode(
432  Source, InsertionPoint->EnclosingNamespace, Sel.AST->getTokens(),
433  Sel.AST->getHeuristicResolver());
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:424
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:80
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:539
Kind
BindArgumentKind Kind
Definition: AvoidBindCheck.cpp:59
InsertionPoint
SourceLocation InsertionPoint
Definition: ExtractFunction.cpp:350
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:514
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:175
if
if(CLANGD_ENABLE_REMOTE) generate_protos(RemoteIndexProto "Index.proto") generate_protos(MonitoringServiceProto "MonitoringService.proto" GRPC) generate_protos(RemoteIndexServiceProto "Service.proto" DEPENDS "Index.proto" GRPC) target_link_libraries(RemoteIndexServiceProto PRIVATE RemoteIndexProto MonitoringServiceProto) include_directories($
Definition: clangd/index/remote/CMakeLists.txt:1
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:1106
FileName
StringRef FileName
Definition: KernelNameRestrictionCheck.cpp:46
EnclosingNamespace
std::string EnclosingNamespace
Definition: DefineOutline.cpp:285
Offset
size_t Offset
Definition: DefineOutline.cpp:286
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:510
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:1159
clang
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Definition: ApplyReplacements.h:27
clang::clangd::CodeAction::REFACTOR_KIND
const static llvm::StringLiteral REFACTOR_KIND
Definition: Protocol.h:980
SM
const SourceManager & SM
Definition: IncludeCleaner.cpp:108
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:19
clang::clangd::ParsedAST::getSourceManager
SourceManager & getSourceManager()
Definition: ParsedAST.h:75
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:1115
REGISTER_TWEAK
#define REGISTER_TWEAK(Subclass)
Definition: Tweak.h:132
AST.h
ParsedAST.h