[clangd] Fix rename for explicit destructor calls

When triggering rename of the class name in the code with explicit destructor
calls, rename fails. Consider the following piece of code:

```
class Foo;

...

Foo f;
f.~/*...*/Foo();
```

`findExplicitReferences` will report two `ReferenceLoc` for destructor call:
one is comming from `MemberExpr` (i.e. destructor call itself) and would point
to the tilde:

```
f.~/*...*/Foo();
  ^
```

And the second one is pointing to the typename and is coming from `TypeLoc`.

```
f.~/*...*/Foo();
          ^
```

This causes rename to produce incorrect textual replacements. This patch
updates `MemberExpr` handler to detect destructor calls and prevents it
from reporting a duplicate reference.

Resolves: https://github.com/clangd/clangd/issues/236

Reviewers: kadircet, hokein

Differential Revision: https://reviews.llvm.org/D72638
This commit is contained in:
Kirill Bobyrev 2020-01-21 05:33:39 +01:00
parent c72aa27f91
commit 38bdb94120
No known key found for this signature in database
GPG Key ID: 2307C055C8384FA0
3 changed files with 81 additions and 0 deletions

View File

@ -614,6 +614,10 @@ llvm::SmallVector<ReferenceLoc, 2> refInExpr(const Expr *E) {
}
void VisitMemberExpr(const MemberExpr *E) {
// Skip destructor calls to avoid duplication: TypeLoc within will be
// visited separately.
if (llvm::dyn_cast<CXXDestructorDecl>(E->getFoundDecl().getDecl()))
return;
Refs.push_back(ReferenceLoc{E->getQualifierLoc(),
E->getMemberNameInfo().getLoc(),
/*IsDecl=*/false,

View File

@ -879,6 +879,56 @@ TEST_F(FindExplicitReferencesTest, All) {
"8: targets = {INT2}, decl\n"
"9: targets = {NS}, decl\n"
"10: targets = {ns}\n"},
// User-defined conversion operator.
{R"cpp(
void foo() {
class $0^Bar {};
class $1^Foo {
public:
// FIXME: This should have only one reference to Bar.
$2^operator $3^$4^Bar();
};
$5^Foo $6^f;
$7^f.$8^operator $9^Bar();
}
)cpp",
"0: targets = {Bar}, decl\n"
"1: targets = {Foo}, decl\n"
"2: targets = {foo()::Foo::operator Bar}, decl\n"
"3: targets = {Bar}\n"
"4: targets = {Bar}\n"
"5: targets = {Foo}\n"
"6: targets = {f}, decl\n"
"7: targets = {f}\n"
"8: targets = {foo()::Foo::operator Bar}\n"
"9: targets = {Bar}\n"},
// Destructor.
{R"cpp(
void foo() {
class $0^Foo {
public:
~$1^Foo() {}
void $2^destructMe() {
this->~$3^Foo();
}
};
$4^Foo $5^f;
$6^f.~ /*...*/ $7^Foo();
}
)cpp",
"0: targets = {Foo}, decl\n"
// FIXME: It's better to target destructor's FunctionDecl instead of
// the type itself (similar to constructor).
"1: targets = {Foo}\n"
"2: targets = {foo()::Foo::destructMe}, decl\n"
"3: targets = {Foo}\n"
"4: targets = {Foo}\n"
"5: targets = {f}, decl\n"
"6: targets = {f}\n"
"7: targets = {Foo}\n"},
// cxx constructor initializer.
{R"cpp(
class Base {};

View File

@ -265,6 +265,33 @@ TEST(RenameTest, WithinFileRename) {
}
)cpp",
// Destructor explicit call.
R"cpp(
class [[F^oo]] {
public:
~[[^Foo]]();
};
[[Foo^]]::~[[^Foo]]() {}
int main() {
[[Fo^o]] f;
f.~/*something*/[[^Foo]]();
f.~[[^Foo]]();
}
)cpp",
// Derived destructor explicit call.
R"cpp(
class [[Bas^e]] {};
class Derived : public [[Bas^e]] {}
int main() {
[[Bas^e]] *foo = new Derived();
foo->[[^Base]]::~[[^Base]]();
}
)cpp",
// CXXConstructor initializer list.
R"cpp(
class Baz {};