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.
getSelectedFunction(const SelectionTree::Node * SelNode)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
getSourceFile(llvm::StringRef FileName,const Tweak::Selection & Sel)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 *>
findContextForNS(llvm::StringRef TargetNS,const DeclContext * CurContext)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>
getFunctionSourceAfterReplacements(const FunctionDecl * FD,const tooling::Replacements & Replacements)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>
getFunctionSourceCode(const FunctionDecl * FD,llvm::StringRef TargetNamespace,const syntax::TokenBuffer & TokBuf)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>
getInsertionPoint(llvm::StringRef Contents,llvm::StringRef QualifiedName,const format::FormatStyle & Style)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.
getDeletionRange(const FunctionDecl * FD,const syntax::TokenBuffer & TokBuf)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 // AST doesn't contain the location for ":" in ctor initializers. Therefore
310 // we find it by finding the first ":" before the first ctor initializer.
311 SourceLocation InitStart;
312 // Find the first initializer.
313 for (const auto *CInit : CD->inits()) {
314 // SourceOrder is -1 for implicit initializers.
315 if (CInit->getSourceOrder() != 0)
316 continue;
317 InitStart = CInit->getSourceLocation();
318 break;
319 }
320 if (InitStart.isValid()) {
321 auto Toks = TokBuf.expandedTokens(CD->getSourceRange());
322 // Drop any tokens after the initializer.
323 Toks = Toks.take_while([&TokBuf, &InitStart](const syntax::Token &Tok) {
324 return TokBuf.sourceManager().isBeforeInTranslationUnit(Tok.location(),
325 InitStart);
326 });
327 // Look for the first colon.
328 auto Tok =
329 llvm::find_if(llvm::reverse(Toks), [](const syntax::Token &Tok) {
330 return Tok.kind() == tok::colon;
331 });
332 assert(Tok != Toks.rend());
333 DeletionRange.setBegin(Tok->location());
334 }
335 }
336 return DeletionRange;
337 }
338
339 /// Moves definition of a function/method to an appropriate implementation file.
340 ///
341 /// Before:
342 /// a.h
343 /// void foo() { return; }
344 /// a.cc
345 /// #include "a.h"
346 ///
347 /// ----------------
348 ///
349 /// After:
350 /// a.h
351 /// void foo();
352 /// a.cc
353 /// #include "a.h"
354 /// void foo() { return; }
355 class DefineOutline : public Tweak {
356 public:
357 const char *id() const override;
358
hidden() const359 bool hidden() const override { return true; }
intent() const360 Intent intent() const override { return Intent::Refactor; }
title() const361 std::string title() const override {
362 return "Move function body to out-of-line.";
363 }
364
prepare(const Selection & Sel)365 bool prepare(const Selection &Sel) override {
366 // Bail out if we are not in a header file.
367 // FIXME: We might want to consider moving method definitions below class
368 // definition even if we are inside a source file.
369 if (!isHeaderFile(Sel.AST->getSourceManager().getFilename(Sel.Cursor),
370 Sel.AST->getLangOpts()))
371 return false;
372
373 Source = getSelectedFunction(Sel.ASTSelection.commonAncestor());
374 // Bail out if the selection is not a in-line function definition.
375 if (!Source || !Source->doesThisDeclarationHaveABody() ||
376 Source->isOutOfLine())
377 return false;
378
379 // Bail out in templated classes, as it is hard to spell the class name, i.e
380 // if the template parameter is unnamed.
381 if (auto *MD = llvm::dyn_cast<CXXMethodDecl>(Source)) {
382 if (MD->getParent()->isTemplated())
383 return false;
384 }
385
386 // Note that we don't check whether an implementation file exists or not in
387 // the prepare, since performing disk IO on each prepare request might be
388 // expensive.
389 return true;
390 }
391
apply(const Selection & Sel)392 Expected<Effect> apply(const Selection &Sel) override {
393 const SourceManager &SM = Sel.AST->getSourceManager();
394 auto MainFileName =
395 getCanonicalPath(SM.getFileEntryForID(SM.getMainFileID()), SM);
396 if (!MainFileName)
397 return llvm::createStringError(
398 llvm::inconvertibleErrorCode(),
399 "Couldn't get absolute path for mainfile.");
400
401 auto CCFile = getSourceFile(*MainFileName, Sel);
402 if (!CCFile)
403 return llvm::createStringError(
404 llvm::inconvertibleErrorCode(),
405 "Couldn't find a suitable implementation file.");
406
407 auto &FS =
408 Sel.AST->getSourceManager().getFileManager().getVirtualFileSystem();
409 auto Buffer = FS.getBufferForFile(*CCFile);
410 // FIXME: Maybe we should consider creating the implementation file if it
411 // doesn't exist?
412 if (!Buffer)
413 return llvm::createStringError(Buffer.getError(),
414 Buffer.getError().message());
415 auto Contents = Buffer->get()->getBuffer();
416 auto InsertionPoint =
417 getInsertionPoint(Contents, Source->getQualifiedNameAsString(),
418 getFormatStyleForFile(*CCFile, Contents, &FS));
419 if (!InsertionPoint)
420 return InsertionPoint.takeError();
421
422 auto FuncDef = getFunctionSourceCode(
423 Source, InsertionPoint->EnclosingNamespace, Sel.AST->getTokens());
424 if (!FuncDef)
425 return FuncDef.takeError();
426
427 SourceManagerForFile SMFF(*CCFile, Contents);
428 const tooling::Replacement InsertFunctionDef(
429 *CCFile, InsertionPoint->Offset, 0, *FuncDef);
430 auto Effect = Effect::mainFileEdit(
431 SMFF.get(), tooling::Replacements(InsertFunctionDef));
432 if (!Effect)
433 return Effect.takeError();
434
435 // FIXME: We should also get rid of inline qualifier.
436 const tooling::Replacement DeleteFuncBody(
437 Sel.AST->getSourceManager(),
438 CharSourceRange::getTokenRange(*toHalfOpenFileRange(
439 SM, Sel.AST->getLangOpts(),
440 getDeletionRange(Source, Sel.AST->getTokens()))),
441 ";");
442 auto HeaderFE = Effect::fileEdit(SM, SM.getMainFileID(),
443 tooling::Replacements(DeleteFuncBody));
444 if (!HeaderFE)
445 return HeaderFE.takeError();
446
447 Effect->ApplyEdits.try_emplace(HeaderFE->first,
448 std::move(HeaderFE->second));
449 return std::move(*Effect);
450 }
451
452 private:
453 const FunctionDecl *Source = nullptr;
454 };
455
456 REGISTER_TWEAK(DefineOutline)
457
458 } // namespace
459 } // namespace clangd
460 } // namespace clang
461