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/STLExtras.h"
31#include "llvm/ADT/StringRef.h"
32#include "llvm/Support/Casting.h"
33#include "llvm/Support/Error.h"
47const FunctionDecl *getSelectedFunction(
const SelectionTree::Node *SelNode) {
50 const DynTypedNode &AstNode = SelNode->ASTNode;
51 if (
const FunctionDecl *FD = AstNode.get<FunctionDecl>())
53 if (AstNode.get<CompoundStmt>() &&
55 if (
const SelectionTree::Node *P = SelNode->Parent)
56 return P->ASTNode.get<FunctionDecl>();
61std::optional<Path> getSourceFile(llvm::StringRef
FileName,
62 const Tweak::Selection &Sel) {
72std::optional<const DeclContext *>
73findContextForNS(llvm::StringRef TargetNS,
const DeclContext *CurContext) {
74 assert(TargetNS.empty() || TargetNS.ends_with(
"::"));
76 CurContext = CurContext->getEnclosingNamespaceContext();
78 if (TargetNS.empty()) {
79 while (!CurContext->isTranslationUnit())
80 CurContext = CurContext->getParent();
85 std::string TargetContextNS =
86 CurContext->isNamespace()
87 ? llvm::cast<NamespaceDecl>(CurContext)->getQualifiedNameAsString()
89 TargetContextNS.append(
"::");
91 llvm::StringRef CurrentContextNS(TargetContextNS);
94 if (!CurrentContextNS.starts_with(TargetNS))
97 while (CurrentContextNS != TargetNS) {
98 CurContext = CurContext->getParent();
101 CurrentContextNS = CurrentContextNS.take_front(
102 CurrentContextNS.drop_back(2).rfind(
"::") + 2);
110llvm::Expected<std::string>
111getFunctionSourceAfterReplacements(
const FunctionDecl *FD,
112 const tooling::Replacements &Replacements,
113 bool TargetFileIsHeader) {
114 const auto &SM = FD->getASTContext().getSourceManager();
116 SM, FD->getASTContext().getLangOpts(), FD->getSourceRange());
118 return error(
"Couldn't get range for function.");
121 unsigned FuncBegin = SM.getFileOffset(OrigFuncRange->getBegin());
122 unsigned FuncEnd = Replacements.getShiftedCodePosition(
123 SM.getFileOffset(OrigFuncRange->getEnd()));
126 auto QualifiedFunc = tooling::applyAllReplacements(
127 SM.getBufferData(SM.getMainFileID()), Replacements);
129 return QualifiedFunc.takeError();
131 auto Source = QualifiedFunc->substr(FuncBegin, FuncEnd - FuncBegin + 1);
132 std::string TemplatePrefix;
133 auto AddToTemplatePrefixIfApplicable = [&](
const Decl *D) {
134 const TemplateParameterList *Params = D->getDescribedTemplateParams();
137 for (
Decl *P : *Params) {
138 if (
auto *TTP = dyn_cast<TemplateTypeParmDecl>(P))
139 TTP->removeDefaultArgument();
140 else if (
auto *NTTP = dyn_cast<NonTypeTemplateParmDecl>(P))
141 NTTP->removeDefaultArgument();
142 else if (
auto *TTPD = dyn_cast<TemplateTemplateParmDecl>(P))
143 TTPD->removeDefaultArgument();
146 llvm::raw_string_ostream Stream(S);
147 Params->print(Stream, FD->getASTContext());
150 TemplatePrefix.insert(0, S);
152 AddToTemplatePrefixIfApplicable(FD);
153 if (
auto *MD = llvm::dyn_cast<CXXMethodDecl>(FD)) {
154 for (
const CXXRecordDecl *
Parent = MD->getParent();
Parent;
156 llvm::dyn_cast_or_null<const CXXRecordDecl>(
Parent->getParent())) {
157 AddToTemplatePrefixIfApplicable(
Parent);
161 if (TargetFileIsHeader)
162 Source.insert(0,
"inline ");
163 if (!TemplatePrefix.empty())
164 Source.insert(0, TemplatePrefix);
171llvm::Expected<tooling::Replacements>
172deleteTokensWithKind(
const syntax::TokenBuffer &TokBuf, tok::TokenKind
Kind,
173 SourceRange FromRange) {
174 tooling::Replacements DelKeywordCleanups;
175 llvm::Error
Errors = llvm::Error::success();
176 bool FoundAny =
false;
177 for (
const auto &Tok : TokBuf.expandedTokens(FromRange)) {
178 if (Tok.kind() !=
Kind)
181 auto Spelling = TokBuf.spelledForExpanded(llvm::ArrayRef(Tok));
183 Errors = llvm::joinErrors(
185 error(
"define outline: couldn't remove `{0}` keyword.",
186 tok::getKeywordSpelling(
Kind)));
189 auto &SM = TokBuf.sourceManager();
190 CharSourceRange DelRange =
191 syntax::Token::range(SM, Spelling->front(), Spelling->back())
194 DelKeywordCleanups.add(tooling::Replacement(SM, DelRange,
"")))
195 Errors = llvm::joinErrors(std::move(
Errors), std::move(Err));
198 Errors = llvm::joinErrors(
200 error(
"define outline: couldn't find `{0}` keyword to remove.",
201 tok::getKeywordSpelling(
Kind)));
206 return DelKeywordCleanups;
215llvm::Expected<std::string>
216getFunctionSourceCode(
const FunctionDecl *FD,
const DeclContext *TargetContext,
217 const syntax::TokenBuffer &TokBuf,
218 const HeuristicResolver *Resolver,
219 bool TargetFileIsHeader) {
220 auto &
AST = FD->getASTContext();
221 auto &SM =
AST.getSourceManager();
223 llvm::Error
Errors = llvm::Error::success();
224 tooling::Replacements DeclarationCleanups;
230 [&](ReferenceLoc Ref) {
234 if (Ref.Qualifier || Ref.Targets.empty() || Ref.NameLoc.isMacroID())
237 if (Ref.NameLoc != FD->getReturnTypeSourceRange().getBegin() &&
238 Ref.NameLoc != FD->getLocation())
241 for (
const NamedDecl *ND : Ref.Targets) {
242 if (ND->getKind() == Decl::TemplateTypeParm)
244 if (ND->getDeclContext() != Ref.Targets.front()->getDeclContext()) {
245 elog(
"Targets from multiple contexts: {0}, {1}",
246 printQualifiedName(*Ref.Targets.front()),
247 printQualifiedName(*ND));
251 const NamedDecl *ND = Ref.Targets.front();
252 std::string Qualifier =
254 SM.getLocForStartOfFile(SM.getMainFileID()), ND);
255 if (ND->getDeclContext()->isDependentContext() &&
256 llvm::isa<TypeDecl>(ND)) {
257 Qualifier.insert(0,
"typename ");
259 if (
auto Err = DeclarationCleanups.add(
260 tooling::Replacement(SM, Ref.NameLoc, 0, Qualifier)))
261 Errors = llvm::joinErrors(std::move(
Errors), std::move(Err));
270 if (
const auto *Destructor = llvm::dyn_cast<CXXDestructorDecl>(FD)) {
271 if (
auto Err = DeclarationCleanups.add(tooling::Replacement(
272 SM, Destructor->getLocation(), 0,
274 SM.getLocForStartOfFile(SM.getMainFileID()),
276 Errors = llvm::joinErrors(std::move(
Errors), std::move(Err));
281 for (
const auto *PVD : FD->parameters()) {
282 if (!PVD->hasDefaultArg())
285 auto DelRange = CharSourceRange::getTokenRange(PVD->getDefaultArgRange());
287 auto Tokens = TokBuf.expandedTokens(PVD->getSourceRange())
288 .take_while([&SM, &DelRange](
const syntax::Token &Tok) {
289 return SM.isBeforeInTranslationUnit(
290 Tok.location(), DelRange.getBegin());
292 if (TokBuf.expandedTokens(DelRange.getAsRange()).front().kind() !=
297 llvm::find_if(llvm::reverse(Tokens), [](
const syntax::Token &Tok) {
298 return Tok.kind() == tok::equal;
300 assert(Tok != Tokens.rend());
301 DelRange.setBegin(Tok->location());
304 DeclarationCleanups.add(tooling::Replacement(SM, DelRange,
"")))
305 Errors = llvm::joinErrors(std::move(
Errors), std::move(Err));
308 auto DelAttr = [&](
const Attr *
A) {
312 TokBuf.spelledForExpanded(TokBuf.expandedTokens(
A->getRange()));
313 assert(
A->getLocation().isValid());
314 if (!AttrTokens || AttrTokens->empty()) {
315 Errors = llvm::joinErrors(
316 std::move(
Errors),
error(
"define outline: Can't move out of line as "
317 "function has a macro `{0}` specifier.",
321 CharSourceRange DelRange =
322 syntax::Token::range(SM, AttrTokens->front(), AttrTokens->back())
325 DeclarationCleanups.add(tooling::Replacement(SM, DelRange,
"")))
326 Errors = llvm::joinErrors(std::move(
Errors), std::move(Err));
329 DelAttr(FD->getAttr<OverrideAttr>());
330 DelAttr(FD->getAttr<FinalAttr>());
332 auto DelKeyword = [&](tok::TokenKind
Kind, SourceRange FromRange) {
333 auto DelKeywords = deleteTokensWithKind(TokBuf,
Kind, FromRange);
335 Errors = llvm::joinErrors(std::move(
Errors), DelKeywords.takeError());
338 DeclarationCleanups = DeclarationCleanups.merge(*DelKeywords);
341 if (FD->isInlineSpecified())
342 DelKeyword(tok::kw_inline, {FD->getBeginLoc(), FD->getLocation()});
343 if (
const auto *MD = dyn_cast<CXXMethodDecl>(FD)) {
344 if (MD->isVirtualAsWritten())
345 DelKeyword(tok::kw_virtual, {FD->getBeginLoc(), FD->getLocation()});
347 DelKeyword(tok::kw_static, {FD->getBeginLoc(), FD->getLocation()});
349 if (
const auto *CD = dyn_cast<CXXConstructorDecl>(FD)) {
350 if (CD->isExplicit())
351 DelKeyword(tok::kw_explicit, {FD->getBeginLoc(), FD->getLocation()});
356 return getFunctionSourceAfterReplacements(FD, DeclarationCleanups,
360struct InsertionPoint {
368SourceRange getDeletionRange(
const FunctionDecl *FD,
369 const syntax::TokenBuffer &TokBuf) {
370 auto DeletionRange = FD->getBody()->getSourceRange();
371 if (
auto *CD = llvm::dyn_cast<CXXConstructorDecl>(FD)) {
374 SourceLocation InitStart;
376 for (
const auto *CInit : CD->inits()) {
378 if (CInit->getSourceOrder() != 0)
380 InitStart = CInit->getSourceLocation();
383 if (InitStart.isValid()) {
384 auto Toks = TokBuf.expandedTokens(CD->getSourceRange());
386 Toks = Toks.take_while([&TokBuf, &InitStart](
const syntax::Token &Tok) {
387 return TokBuf.sourceManager().isBeforeInTranslationUnit(Tok.location(),
392 llvm::find_if(llvm::reverse(Toks), [](
const syntax::Token &Tok) {
393 return Tok.kind() == tok::colon;
395 assert(Tok != Toks.rend());
396 DeletionRange.setBegin(Tok->location());
399 return DeletionRange;
418class DefineOutline :
public Tweak {
420 const char *id()
const override;
422 bool hidden()
const override {
return false; }
423 llvm::StringLiteral kind()
const override {
426 std::string title()
const override {
427 return "Move function body to out-of-line";
430 bool prepare(
const Selection &Sel)
override {
431 SameFile = !
isHeaderFile(Sel.AST->tuPath(), Sel.AST->getLangOpts());
432 Source = getSelectedFunction(Sel.ASTSelection.commonAncestor());
435 if (!Source || !Source->doesThisDeclarationHaveABody() ||
436 Source->isOutOfLine())
441 if (Source->getTemplateSpecializationInfo())
444 auto *MD = llvm::dyn_cast<CXXMethodDecl>(Source);
446 if (Source->getDescribedFunctionTemplate())
452 for (
const CXXRecordDecl *
Parent = MD->getParent();
Parent;
454 llvm::dyn_cast_or_null<const CXXRecordDecl>(
Parent->getParent())) {
455 if (
const TemplateParameterList *Params =
456 Parent->getDescribedTemplateParams()) {
463 for (NamedDecl *P : *Params) {
464 if (!P->getIdentifier())
471 if (MD->getDescribedTemplate())
476 for (
const DeclContext *DC = MD->getParent(); DC; DC = DC->getParent()) {
477 if (
auto *ND = llvm::dyn_cast<NamedDecl>(DC)) {
478 if (ND->getDeclName().isEmpty() &&
479 (!SameFile || !llvm::dyn_cast<NamespaceDecl>(ND)))
490 Expected<Effect> apply(
const Selection &Sel)
override {
491 const SourceManager &SM = Sel.AST->getSourceManager();
492 auto CCFile = SameFile ? Sel.AST->tuPath().str()
493 : getSourceFile(Sel.AST->tuPath(), Sel);
495 return error(
"Couldn't find a suitable implementation file.");
496 assert(Sel.FS &&
"FS Must be set in apply");
497 auto Buffer = Sel.FS->getBufferForFile(*CCFile);
501 return llvm::errorCodeToError(Buffer.getError());
502 auto Contents = Buffer->get()->getBuffer();
503 auto InsertionPoint = getInsertionPoint(Contents, Sel);
505 return InsertionPoint.takeError();
507 auto FuncDef = getFunctionSourceCode(
508 Source, InsertionPoint->EnclosingNamespace, Sel.AST->getTokens(),
509 Sel.AST->getHeuristicResolver(),
510 SameFile &&
isHeaderFile(Sel.AST->tuPath(), Sel.AST->getLangOpts()));
512 return FuncDef.takeError();
514 SourceManagerForFile SMFF(*CCFile, Contents);
515 const tooling::Replacement InsertFunctionDef(
516 *CCFile, InsertionPoint->Offset, 0, *FuncDef);
517 auto Effect = Effect::mainFileEdit(
518 SMFF.get(), tooling::Replacements(InsertFunctionDef));
520 return Effect.takeError();
522 tooling::Replacements HeaderUpdates(tooling::Replacement(
523 Sel.AST->getSourceManager(),
525 SM, Sel.AST->getLangOpts(),
526 getDeletionRange(Source, Sel.AST->getTokens()))),
529 if (Source->isInlineSpecified()) {
531 deleteTokensWithKind(Sel.AST->getTokens(), tok::kw_inline,
532 {Source->getBeginLoc(), Source->getLocation()});
534 return DelInline.takeError();
535 HeaderUpdates = HeaderUpdates.merge(*DelInline);
539 tooling::Replacements &R = Effect->ApplyEdits[*CCFile].Replacements;
540 R = R.merge(HeaderUpdates);
542 auto HeaderFE = Effect::fileEdit(SM, SM.getMainFileID(), HeaderUpdates);
544 return HeaderFE.takeError();
545 Effect->ApplyEdits.try_emplace(HeaderFE->first,
546 std::move(HeaderFE->second));
548 return std::move(*Effect);
560 llvm::Expected<InsertionPoint> getInsertionPoint(llvm::StringRef Contents,
561 const Selection &Sel) {
566 auto *Klass = Source->getDeclContext()->getOuterLexicalRecordContext();
568 return error(
"moving to same file not supported for free functions");
569 const SourceLocation EndLoc = Klass->getBraceRange().getEnd();
570 const auto &TokBuf = Sel.AST->getTokens();
571 auto Tokens = TokBuf.expandedTokens();
572 auto It = llvm::lower_bound(
573 Tokens, EndLoc, [](
const syntax::Token &Tok, SourceLocation EndLoc) {
574 return Tok.location() < EndLoc;
576 while (It != Tokens.end()) {
577 if (It->kind() != tok::semi) {
581 unsigned Offset = Sel.AST->getSourceManager()
582 .getDecomposedLoc(It->endLocation())
584 return InsertionPoint{Klass->getEnclosingNamespaceContext(),
Offset};
587 "failed to determine insertion location: no end of class found");
591 Contents, Source->getQualifiedNameAsString(), Sel.AST->getLangOpts());
593 assert(!Region.EligiblePoints.empty());
596 return Offset.takeError();
599 findContextForNS(Region.EnclosingNamespace, Source->getDeclContext());
601 return error(
"define outline: couldn't find a context for target");
603 return InsertionPoint{*TargetContext, *
Offset};
607 const FunctionDecl *Source =
nullptr;
608 bool SameFile =
false;
const FunctionDecl * Decl
std::optional< std::string > EnclosingNamespace
#define REGISTER_TWEAK(Subclass)
std::optional< SourceRange > toHalfOpenFileRange(const SourceManager &SM, const LangOptions &LangOpts, SourceRange R)
Turns a token range into a half-open range and checks its correctness.
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.
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.
llvm::Error error(std::error_code EC, const char *Fmt, Ts &&... Vals)
EligibleRegion getEligiblePoints(llvm::StringRef Code, llvm::StringRef FullyQualifiedName, const LangOptions &LangOpts)
Returns most eligible region to insert a definition for FullyQualifiedName in the Code.
llvm::Expected< size_t > positionToOffset(llvm::StringRef Code, Position P, bool AllowColumnsBeyondLineLength)
Turn a [line, column] pair into an offset in Code.
std::optional< Path > getCorrespondingHeaderOrSource(PathRef OriginalFile, llvm::IntrusiveRefCntPtr< llvm::vfs::FileSystem > VFS)
Given a header file, returns the best matching source file, and vice visa.
bool isHeaderFile(llvm::StringRef FileName, std::optional< LangOptions > LangOpts)
Infers whether this is a header from the FileName and LangOpts (if presents).
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
static const llvm::StringLiteral REFACTOR_KIND