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.
getSelectedFunction(const SelectionTree::Node * SelNode)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
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 error("Couldn't get range for function.");
123 assert(!FD->getDescribedFunctionTemplate() &&
124 "Define out-of-line doesn't apply to function templates.");
125
126 // Get new begin and end positions for the qualified function definition.
127 unsigned FuncBegin = SM.getFileOffset(OrigFuncRange->getBegin());
128 unsigned FuncEnd = Replacements.getShiftedCodePosition(
129 SM.getFileOffset(OrigFuncRange->getEnd()));
130
131 // Trim the result to function definition.
132 auto QualifiedFunc = tooling::applyAllReplacements(
133 SM.getBufferData(SM.getMainFileID()), Replacements);
134 if (!QualifiedFunc)
135 return QualifiedFunc.takeError();
136 return QualifiedFunc->substr(FuncBegin, FuncEnd - FuncBegin + 1);
137 }
138
139 // Creates a modified version of function definition that can be inserted at a
140 // different location, qualifies return value and function name to achieve that.
141 // Contains function signature, except defaulted parameter arguments, body and
142 // template parameters if applicable. No need to qualify parameters, as they are
143 // looked up in the context containing the function/method.
144 // FIXME: Drop attributes in function signature.
145 llvm::Expected<std::string>
getFunctionSourceCode(const FunctionDecl * FD,llvm::StringRef TargetNamespace,const syntax::TokenBuffer & TokBuf)146 getFunctionSourceCode(const FunctionDecl *FD, llvm::StringRef TargetNamespace,
147 const syntax::TokenBuffer &TokBuf) {
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.
159 findExplicitReferences(FD, [&](ReferenceLoc Ref) {
160 // It is enough to qualify the first qualifier, so skip references with a
161 // qualifier. Also we can't do much if there are no targets or name is
162 // inside a macro body.
163 if (Ref.Qualifier || Ref.Targets.empty() || Ref.NameLoc.isMacroID())
164 return;
165 // Only qualify return type and function name.
166 if (Ref.NameLoc != FD->getReturnTypeSourceRange().getBegin() &&
167 Ref.NameLoc != FD->getLocation())
168 return;
169
170 for (const NamedDecl *ND : Ref.Targets) {
171 if (ND->getDeclContext() != Ref.Targets.front()->getDeclContext()) {
172 elog("Targets from multiple contexts: {0}, {1}",
173 printQualifiedName(*Ref.Targets.front()), printQualifiedName(*ND));
174 return;
175 }
176 }
177 const NamedDecl *ND = Ref.Targets.front();
178 const std::string Qualifier = getQualification(
179 AST, *TargetContext, SM.getLocForStartOfFile(SM.getMainFileID()), ND);
180 if (auto Err = DeclarationCleanups.add(
181 tooling::Replacement(SM, Ref.NameLoc, 0, Qualifier)))
182 Errors = llvm::joinErrors(std::move(Errors), std::move(Err));
183 });
184
185 // Get rid of default arguments, since they should not be specified in
186 // out-of-line definition.
187 for (const auto *PVD : FD->parameters()) {
188 if (PVD->hasDefaultArg()) {
189 // Deletion range initially spans the initializer, excluding the `=`.
190 auto DelRange = CharSourceRange::getTokenRange(PVD->getDefaultArgRange());
191 // Get all tokens before the default argument.
192 auto Tokens = TokBuf.expandedTokens(PVD->getSourceRange())
193 .take_while([&SM, &DelRange](const syntax::Token &Tok) {
194 return SM.isBeforeInTranslationUnit(
195 Tok.location(), DelRange.getBegin());
196 });
197 // Find the last `=` before the default arg.
198 auto Tok =
199 llvm::find_if(llvm::reverse(Tokens), [](const syntax::Token &Tok) {
200 return Tok.kind() == tok::equal;
201 });
202 assert(Tok != Tokens.rend());
203 DelRange.setBegin(Tok->location());
204 if (auto Err =
205 DeclarationCleanups.add(tooling::Replacement(SM, DelRange, "")))
206 Errors = llvm::joinErrors(std::move(Errors), std::move(Err));
207 }
208 }
209
210 auto DelAttr = [&](const Attr *A) {
211 if (!A)
212 return;
213 auto AttrTokens =
214 TokBuf.spelledForExpanded(TokBuf.expandedTokens(A->getRange()));
215 assert(A->getLocation().isValid());
216 if (!AttrTokens || AttrTokens->empty()) {
217 Errors = llvm::joinErrors(
218 std::move(Errors), error("define outline: Can't move out of line as "
219 "function has a macro `{0}` specifier.",
220 A->getSpelling()));
221 return;
222 }
223 CharSourceRange DelRange =
224 syntax::Token::range(SM, AttrTokens->front(), AttrTokens->back())
225 .toCharRange(SM);
226 if (auto Err =
227 DeclarationCleanups.add(tooling::Replacement(SM, DelRange, "")))
228 Errors = llvm::joinErrors(std::move(Errors), std::move(Err));
229 };
230
231 DelAttr(FD->getAttr<OverrideAttr>());
232 DelAttr(FD->getAttr<FinalAttr>());
233
234 auto DelKeyword = [&](tok::TokenKind Kind, SourceRange FromRange) {
235 bool FoundAny = false;
236 for (const auto &Tok : TokBuf.expandedTokens(FromRange)) {
237 if (Tok.kind() != Kind)
238 continue;
239 FoundAny = true;
240 auto Spelling = TokBuf.spelledForExpanded(llvm::makeArrayRef(Tok));
241 if (!Spelling) {
242 Errors = llvm::joinErrors(
243 std::move(Errors),
244 error("define outline: couldn't remove `{0}` keyword.",
245 tok::getKeywordSpelling(Kind)));
246 break;
247 }
248 CharSourceRange DelRange =
249 syntax::Token::range(SM, Spelling->front(), Spelling->back())
250 .toCharRange(SM);
251 if (auto Err =
252 DeclarationCleanups.add(tooling::Replacement(SM, DelRange, "")))
253 Errors = llvm::joinErrors(std::move(Errors), std::move(Err));
254 }
255 if (!FoundAny) {
256 Errors = llvm::joinErrors(
257 std::move(Errors),
258 error("define outline: couldn't find `{0}` keyword to remove.",
259 tok::getKeywordSpelling(Kind)));
260 }
261 };
262
263 if (const auto *MD = dyn_cast<CXXMethodDecl>(FD)) {
264 if (MD->isVirtualAsWritten())
265 DelKeyword(tok::kw_virtual, {FD->getBeginLoc(), FD->getLocation()});
266 if (MD->isStatic())
267 DelKeyword(tok::kw_static, {FD->getBeginLoc(), FD->getLocation()});
268 }
269
270 if (Errors)
271 return std::move(Errors);
272 return getFunctionSourceAfterReplacements(FD, DeclarationCleanups);
273 }
274
275 struct InsertionPoint {
276 std::string EnclosingNamespace;
277 size_t Offset;
278 };
279 // Returns the most natural insertion point for \p QualifiedName in \p Contents.
280 // This currently cares about only the namespace proximity, but in feature it
281 // should also try to follow ordering of declarations. For example, if decls
282 // come in order `foo, bar, baz` then this function should return some point
283 // between foo and baz for inserting bar.
getInsertionPoint(llvm::StringRef Contents,llvm::StringRef QualifiedName,const LangOptions & LangOpts)284 llvm::Expected<InsertionPoint> getInsertionPoint(llvm::StringRef Contents,
285 llvm::StringRef QualifiedName,
286 const LangOptions &LangOpts) {
287 auto Region = getEligiblePoints(Contents, QualifiedName, LangOpts);
288
289 assert(!Region.EligiblePoints.empty());
290 // FIXME: This selection can be made smarter by looking at the definition
291 // locations for adjacent decls to Source. Unfortunately pseudo parsing in
292 // getEligibleRegions only knows about namespace begin/end events so we
293 // can't match function start/end positions yet.
294 auto Offset = positionToOffset(Contents, Region.EligiblePoints.back());
295 if (!Offset)
296 return Offset.takeError();
297 return InsertionPoint{Region.EnclosingNamespace, *Offset};
298 }
299
300 // Returns the range that should be deleted from declaration, which always
301 // contains function body. In addition to that it might contain constructor
302 // initializers.
getDeletionRange(const FunctionDecl * FD,const syntax::TokenBuffer & TokBuf)303 SourceRange getDeletionRange(const FunctionDecl *FD,
304 const syntax::TokenBuffer &TokBuf) {
305 auto DeletionRange = FD->getBody()->getSourceRange();
306 if (auto *CD = llvm::dyn_cast<CXXConstructorDecl>(FD)) {
307 // AST doesn't contain the location for ":" in ctor initializers. Therefore
308 // we find it by finding the first ":" before the first ctor initializer.
309 SourceLocation InitStart;
310 // Find the first initializer.
311 for (const auto *CInit : CD->inits()) {
312 // SourceOrder is -1 for implicit initializers.
313 if (CInit->getSourceOrder() != 0)
314 continue;
315 InitStart = CInit->getSourceLocation();
316 break;
317 }
318 if (InitStart.isValid()) {
319 auto Toks = TokBuf.expandedTokens(CD->getSourceRange());
320 // Drop any tokens after the initializer.
321 Toks = Toks.take_while([&TokBuf, &InitStart](const syntax::Token &Tok) {
322 return TokBuf.sourceManager().isBeforeInTranslationUnit(Tok.location(),
323 InitStart);
324 });
325 // Look for the first colon.
326 auto Tok =
327 llvm::find_if(llvm::reverse(Toks), [](const syntax::Token &Tok) {
328 return Tok.kind() == tok::colon;
329 });
330 assert(Tok != Toks.rend());
331 DeletionRange.setBegin(Tok->location());
332 }
333 }
334 return DeletionRange;
335 }
336
337 /// Moves definition of a function/method to an appropriate implementation file.
338 ///
339 /// Before:
340 /// a.h
341 /// void foo() { return; }
342 /// a.cc
343 /// #include "a.h"
344 ///
345 /// ----------------
346 ///
347 /// After:
348 /// a.h
349 /// void foo();
350 /// a.cc
351 /// #include "a.h"
352 /// void foo() { return; }
353 class DefineOutline : public Tweak {
354 public:
355 const char *id() const override;
356
hidden() const357 bool hidden() const override { return false; }
kind() const358 llvm::StringLiteral kind() const override {
359 return CodeAction::REFACTOR_KIND;
360 }
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 if this is a function template or specialization, as their
380 // definitions need to be visible in all including translation units.
381 if (Source->getDescribedFunctionTemplate())
382 return false;
383 if (Source->getTemplateSpecializationInfo())
384 return false;
385
386 // Bail out in templated classes, as it is hard to spell the class name, i.e
387 // if the template parameter is unnamed.
388 if (auto *MD = llvm::dyn_cast<CXXMethodDecl>(Source)) {
389 if (MD->getParent()->isTemplated())
390 return false;
391 }
392
393 // Note that we don't check whether an implementation file exists or not in
394 // the prepare, since performing disk IO on each prepare request might be
395 // expensive.
396 return true;
397 }
398
apply(const Selection & Sel)399 Expected<Effect> apply(const Selection &Sel) override {
400 const SourceManager &SM = Sel.AST->getSourceManager();
401 auto MainFileName =
402 getCanonicalPath(SM.getFileEntryForID(SM.getMainFileID()), SM);
403 if (!MainFileName)
404 return error("Couldn't get absolute path for main file.");
405
406 auto CCFile = getSourceFile(*MainFileName, Sel);
407 if (!CCFile)
408 return error("Couldn't find a suitable implementation file.");
409
410 auto &FS =
411 Sel.AST->getSourceManager().getFileManager().getVirtualFileSystem();
412 auto Buffer = FS.getBufferForFile(*CCFile);
413 // FIXME: Maybe we should consider creating the implementation file if it
414 // doesn't exist?
415 if (!Buffer)
416 return llvm::errorCodeToError(Buffer.getError());
417 auto Contents = Buffer->get()->getBuffer();
418 auto InsertionPoint = getInsertionPoint(
419 Contents, Source->getQualifiedNameAsString(), Sel.AST->getLangOpts());
420 if (!InsertionPoint)
421 return InsertionPoint.takeError();
422
423 auto FuncDef = getFunctionSourceCode(
424 Source, InsertionPoint->EnclosingNamespace, Sel.AST->getTokens());
425 if (!FuncDef)
426 return FuncDef.takeError();
427
428 SourceManagerForFile SMFF(*CCFile, Contents);
429 const tooling::Replacement InsertFunctionDef(
430 *CCFile, InsertionPoint->Offset, 0, *FuncDef);
431 auto Effect = Effect::mainFileEdit(
432 SMFF.get(), tooling::Replacements(InsertFunctionDef));
433 if (!Effect)
434 return Effect.takeError();
435
436 // FIXME: We should also get rid of inline qualifier.
437 const tooling::Replacement DeleteFuncBody(
438 Sel.AST->getSourceManager(),
439 CharSourceRange::getTokenRange(*toHalfOpenFileRange(
440 SM, Sel.AST->getLangOpts(),
441 getDeletionRange(Source, Sel.AST->getTokens()))),
442 ";");
443 auto HeaderFE = Effect::fileEdit(SM, SM.getMainFileID(),
444 tooling::Replacements(DeleteFuncBody));
445 if (!HeaderFE)
446 return HeaderFE.takeError();
447
448 Effect->ApplyEdits.try_emplace(HeaderFE->first,
449 std::move(HeaderFE->second));
450 return std::move(*Effect);
451 }
452
453 private:
454 const FunctionDecl *Source = nullptr;
455 };
456
457 REGISTER_TWEAK(DefineOutline)
458
459 } // namespace
460 } // namespace clangd
461 } // namespace clang
462