This is an archive of the discontinued LLVM Phabricator instance.

[clangd] Fix rename for symbol introduced by UsingDecl
ClosedPublic

Authored by tom-anders on Oct 7 2022, 2:30 PM.

Details

Summary

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

This patch actually consists of 2 fixes:

  1. Add handling for UsingShadowDecl to canonicalRenameDecl(). This fixes the issue described in https://github.com/clangd/clangd/issues/170.
  1. Avoid the "there are multiple symbols under the cursor error" by applying similar logic as in https://reviews.llvm.org/D133664. This also partly fixes https://github.com/clangd/clangd/issues/586.

Diff Detail

Event Timeline

tom-anders created this revision.Oct 7 2022, 2:30 PM
Herald added a project: Restricted Project. · View Herald TranscriptOct 7 2022, 2:30 PM
Herald added a subscriber: arphaman. · View Herald Transcript
tom-anders requested review of this revision.Oct 7 2022, 2:30 PM
tom-anders added inline comments.Oct 7 2022, 2:31 PM
clang-tools-extra/clangd/refactor/Rename.cpp
169

I'm not really happy with the name here, open for suggestions

nridge added a subscriber: nridge.Oct 7 2022, 2:37 PM

Want to probe a bit at the behavior we're aiming for here.
TL;DR is I think the handling is basically right but considering the general case tells us how to simplify filterBaseUsingDecl.


The testcase is great to illustrate the common case.
The thing we need to generalize it is multiple symbols (e.g. overloads).

namespace ns { int foo(int); char foo(char); }
using ns::foo;
int x = foo(42);
char y = foo('x');

What should happen when we rename foo(int) on line 1?

  • rename both functions
  • rename one function + the usingdecl
  • rename one function and not the usingdecl
  • rename one function and add a second usingdecl
  • return an error

In general the UsingDecl will be in another file and not visible in the AST. Index only knows "there's a reference there". So I think our only real option is to rename one function + the UsingDecl.
(Assuming we want the common non-overloaded case to work. And assuming we don't want to do something drastic like "silently rename overload sets together in all cases").

What should happen when we rename using ns::foo on line 2?

  • rename both functions
  • rename one arbitrarily
  • return an error

Renaming both functions sounds great here. However for now the rename implementation requires a single canonical decl, so we need to return an error for now.
If there's a single decl, renaming it seems fine.

What should happen when we rename foo(42) on line 3?

  • rename both functions
  • rename one function + the usingdecl
  • rename one function and not the usingdecl
  • rename one function and add a second usingdecl
  • return an error

We *can* rely on the UsingDecl being visible here. However we should be reasonably consistent with the first case, which I think rules out options 1, 3 and 5.
Splitting the usingdecl is a neat trick, but complicated, so let's start with renaming one + functiondecl and maybe tack that on later.

clang-tools-extra/clangd/refactor/Rename.cpp
169

This only comes up when renaming the UsingDecl itself (else we reach the UsingShadowDecl rather than this one).

I think we should just unconditionally drop the UsingDecl from the list. The target decls will be in the list, and we'll do the right thing (rename one if unambiguous, error if there are multiple).

169

I'm not sure it's right to handle BaseUsingDecl instead of UsingDecl here.

The other case is UsingEnumDecl, and I don't see any reason to treat that as a non-renaming alias as opposed to a simple reference. It doesn't actually introduce an alias of the enum it names! (I see that we *are* treating it that way in FindTarget, but I guess we should just fix that instead).

Certainly if we *are* deliberately handling UsingEnumDecl here we should have a testcase for it.

clang-tools-extra/clangd/unittests/RenameTests.cpp
832

Can you add a test case (or argue with me that the behavior should be something else!):

namespace ns { int [[^foo]](int); char foo(char); }
using ns::[[foo]];
void f() {
  [[^foo]](42);
  foo('x');
}

And a negative case to Renameable:

namespace ns { int foo(int); char foo(char); }
using ns::^foo;

(the error should be "multiple symbols" ideally - "not a supported kind" is confusing if we support renaming on a non-overload using)

(Thanks for fixing this BTW!)

sammccall added inline comments.Oct 7 2022, 7:33 PM
clang-tools-extra/clangd/refactor/Rename.cpp
169

I sent https://reviews.llvm.org/D135506 to remove this behavior from FindTarget.

tom-anders marked 4 inline comments as done.Oct 8 2022, 8:49 AM
namespace ns { int foo(int); char foo(char); }
using ns::foo;
int x = foo(42);
char y = foo('x');

What should happen when we rename foo(int) on line 1?

  • rename both functions
  • rename one function + the usingdecl
  • rename one function and not the usingdecl
  • rename one function and add a second usingdecl
  • return an error

In general the UsingDecl will be in another file and not visible in the AST. Index only knows "there's a reference there". So I think our only real option is to rename one function + the UsingDecl.
(Assuming we want the common non-overloaded case to work. And assuming we don't want to do something drastic like "silently rename overload sets together in all cases").

Agreed.

What should happen when we rename using ns::foo on line 2?

  • rename both functions
  • rename one arbitrarily
  • return an error

Renaming both functions sounds great here. However for now the rename implementation requires a single canonical decl, so we need to return an error for now.
If there's a single decl, renaming it seems fine.

Agreed, I added a FIXME in the test that we might want to extend canonicalRenameDecl to return multiple Decls (Probably a llvm::DenseSet?)
There already is a similar issue when renaming virtual methods with size_overridden_methods() > 1.

What should happen when we rename foo(42) on line 3?

  • rename both functions
  • rename one function + the usingdecl
  • rename one function and not the usingdecl
  • rename one function and add a second usingdecl
  • return an error

We *can* rely on the UsingDecl being visible here. However we should be reasonably consistent with the first case, which I think rules out options 1, 3 and 5.
Splitting the usingdecl is a neat trick, but complicated, so let's start with renaming one + functiondecl and maybe tack that on later.

Sounds good, splitting the using decl would be fancy though, maybe also add a FIXME for this?

tom-anders updated this revision to Diff 466292.Oct 8 2022, 8:50 AM

Add additional tests, only handle UsingDecl instead of BaseUsingDecl

sammccall accepted this revision.Oct 8 2022, 10:34 AM

Thanks!

clang-tools-extra/clangd/refactor/Rename.cpp
169

you might want to call this filterRenameTargets or something, with the idea that we may add restrictions other than UsingDecl in future

170

nit: llvm::erase_if(Decls, [](const NamedDecl *D) { ... })?

This revision is now accepted and ready to land.Oct 8 2022, 10:34 AM
tom-anders marked 2 inline comments as done.Oct 9 2022, 3:05 AM
tom-anders added inline comments.
clang-tools-extra/clangd/refactor/Rename.cpp
170

Does not work unfortunately, DenseSet does not have the erase(iterator first, iterator last) overload, there's only erase(iterator pos).

tom-anders marked an inline comment as done.Oct 9 2022, 3:08 AM
This revision was landed with ongoing or failed builds.Oct 9 2022, 3:16 AM
This revision was automatically updated to reflect the committed changes.