[clangd] Delete ctor initializers while moving functions out-of-line

Summary:
Currently we only delete function body from declaration, in addition to
that we should also drop ctor initializers.

Unfortunately CXXConstructorDecl doesn't store the location of `:` before
initializers, therefore we make use of token buffer to figure out where to start
deletion.

Fixes https://github.com/clangd/clangd/issues/220

Reviewers: hokein, ilya-biryukov

Subscribers: MaskRay, jkorous, arphaman, usaxena95, cfe-commits

Tags: #clang

Differential Revision: https://reviews.llvm.org/D71188
This commit is contained in:
Kadir Cetinkaya 2019-12-09 11:54:21 +01:00
parent 898d7a0695
commit a209a8000e
No known key found for this signature in database
GPG Key ID: E39E36B8D2057ED6
2 changed files with 61 additions and 1 deletions

View File

@ -18,6 +18,7 @@
#include "clang/AST/ASTTypeTraits.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclBase.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/DeclTemplate.h"
#include "clang/AST/Stmt.h"
#include "clang/Basic/SourceLocation.h"
@ -141,6 +142,7 @@ getFunctionSourceAfterReplacements(const FunctionDecl *FD,
// Contains function signature, except defaulted parameter arguments, body and
// template parameters if applicable. No need to qualify parameters, as they are
// looked up in the context containing the function/method.
// FIXME: Drop attributes in function signature.
llvm::Expected<std::string>
getFunctionSourceCode(const FunctionDecl *FD, llvm::StringRef TargetNamespace,
const syntax::TokenBuffer &TokBuf) {
@ -238,6 +240,45 @@ getInsertionPoint(llvm::StringRef Contents, llvm::StringRef QualifiedName,
return InsertionPoint{Region.EnclosingNamespace, *Offset};
}
// Returns the range that should be deleted from declaration, which always
// contains function body. In addition to that it might contain constructor
// initializers.
SourceRange getDeletionRange(const FunctionDecl *FD,
const syntax::TokenBuffer &TokBuf) {
auto DeletionRange = FD->getBody()->getSourceRange();
if (auto *CD = llvm::dyn_cast<CXXConstructorDecl>(FD)) {
const auto &SM = TokBuf.sourceManager();
// AST doesn't contain the location for ":" in ctor initializers. Therefore
// we find it by finding the first ":" before the first ctor initializer.
SourceLocation InitStart;
// Find the first initializer.
for (const auto *CInit : CD->inits()) {
// We don't care about in-class initializers.
if (CInit->isInClassMemberInitializer())
continue;
if (InitStart.isInvalid() ||
SM.isBeforeInTranslationUnit(CInit->getSourceLocation(), InitStart))
InitStart = CInit->getSourceLocation();
}
if (InitStart.isValid()) {
auto Toks = TokBuf.expandedTokens(CD->getSourceRange());
// Drop any tokens after the initializer.
Toks = Toks.take_while([&TokBuf, &InitStart](const syntax::Token &Tok) {
return TokBuf.sourceManager().isBeforeInTranslationUnit(Tok.location(),
InitStart);
});
// Look for the first colon.
auto Tok =
llvm::find_if(llvm::reverse(Toks), [](const syntax::Token &Tok) {
return Tok.kind() == tok::colon;
});
assert(Tok != Toks.rend());
DeletionRange.setBegin(Tok->location());
}
}
return DeletionRange;
}
/// Moves definition of a function/method to an appropriate implementation file.
///
/// Before:
@ -338,7 +379,8 @@ public:
const tooling::Replacement DeleteFuncBody(
Sel.AST.getSourceManager(),
CharSourceRange::getTokenRange(*toHalfOpenFileRange(
SM, Sel.AST.getLangOpts(), Source->getBody()->getSourceRange())),
SM, Sel.AST.getLangOpts(),
getDeletionRange(Source, Sel.AST.getTokens()))),
";");
auto HeaderFE = Effect::fileEdit(SM, SM.getMainFileID(),
tooling::Replacements(DeleteFuncBody));

View File

@ -1979,6 +1979,24 @@ TEST_F(DefineOutlineTest, ApplyTest) {
"void foo(int x, int y = 5, int = 2, int (*foo)(int) = nullptr) ;",
"void foo(int x, int y , int , int (*foo)(int) ) {}",
},
// Ctor initializers.
{
R"cpp(
class Foo {
int y = 2;
F^oo(int z) __attribute__((weak)) : bar(2){}
int bar;
int z = 2;
};)cpp",
R"cpp(
class Foo {
int y = 2;
Foo(int z) __attribute__((weak)) ;
int bar;
int z = 2;
};)cpp",
"Foo::Foo(int z) __attribute__((weak)) : bar(2){}\n",
},
};
for (const auto &Case : Cases) {
SCOPED_TRACE(Case.Test);