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.
getSelectedFunction(const SelectionTree::Node * SelNode)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
getSourceFile(llvm::StringRef FileName,const Tweak::Selection & Sel)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 *>
findContextForNS(llvm::StringRef TargetNS,const DeclContext * CurContext)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>
getFunctionSourceAfterReplacements(const FunctionDecl * FD,const tooling::Replacements & Replacements)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>
getFunctionSourceCode(const FunctionDecl * FD,llvm::StringRef TargetNamespace,const syntax::TokenBuffer & TokBuf,const HeuristicResolver * Resolver)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.
156 findExplicitReferences(
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.
getInsertionPoint(llvm::StringRef Contents,llvm::StringRef QualifiedName,const LangOptions & LangOpts)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.
getDeletionRange(const FunctionDecl * FD,const syntax::TokenBuffer & TokBuf)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
hidden() const367 bool hidden() const override { return false; }
kind() const368 llvm::StringLiteral kind() const override {
369 return CodeAction::REFACTOR_KIND;
370 }
title() const371 std::string title() const override {
372 return "Move function body to out-of-line";
373 }
374
prepare(const Selection & Sel)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
apply(const Selection & Sel)409 Expected<Effect> apply(const Selection &Sel) override {
410 const SourceManager &SM = Sel.AST->getSourceManager();
411 auto MainFileName =
412 getCanonicalPath(SM.getFileEntryForID(SM.getMainFileID()), SM);
413 if (!MainFileName)
414 return error("Couldn't get absolute path for main file.");
415
416 auto CCFile = getSourceFile(*MainFileName, Sel);
417
418 if (!CCFile)
419 return error("Couldn't find a suitable implementation file.");
420 assert(Sel.FS && "FS Must be set in apply");
421 auto Buffer = Sel.FS->getBufferForFile(*CCFile);
422 // FIXME: Maybe we should consider creating the implementation file if it
423 // doesn't exist?
424 if (!Buffer)
425 return llvm::errorCodeToError(Buffer.getError());
426 auto Contents = Buffer->get()->getBuffer();
427 auto InsertionPoint = getInsertionPoint(
428 Contents, Source->getQualifiedNameAsString(), Sel.AST->getLangOpts());
429 if (!InsertionPoint)
430 return InsertionPoint.takeError();
431
432 auto FuncDef = getFunctionSourceCode(
433 Source, InsertionPoint->EnclosingNamespace, Sel.AST->getTokens(),
434 Sel.AST->getHeuristicResolver());
435 if (!FuncDef)
436 return FuncDef.takeError();
437
438 SourceManagerForFile SMFF(*CCFile, Contents);
439 const tooling::Replacement InsertFunctionDef(
440 *CCFile, InsertionPoint->Offset, 0, *FuncDef);
441 auto Effect = Effect::mainFileEdit(
442 SMFF.get(), tooling::Replacements(InsertFunctionDef));
443 if (!Effect)
444 return Effect.takeError();
445
446 // FIXME: We should also get rid of inline qualifier.
447 const tooling::Replacement DeleteFuncBody(
448 Sel.AST->getSourceManager(),
449 CharSourceRange::getTokenRange(*toHalfOpenFileRange(
450 SM, Sel.AST->getLangOpts(),
451 getDeletionRange(Source, Sel.AST->getTokens()))),
452 ";");
453 auto HeaderFE = Effect::fileEdit(SM, SM.getMainFileID(),
454 tooling::Replacements(DeleteFuncBody));
455 if (!HeaderFE)
456 return HeaderFE.takeError();
457
458 Effect->ApplyEdits.try_emplace(HeaderFE->first,
459 std::move(HeaderFE->second));
460 return std::move(*Effect);
461 }
462
463 private:
464 const FunctionDecl *Source = nullptr;
465 };
466
467 REGISTER_TWEAK(DefineOutline)
468
469 } // namespace
470 } // namespace clangd
471 } // namespace clang
472