From 8019b46bc70b15cf5551ffcf494c6df17d1e7437 Mon Sep 17 00:00:00 2001 From: Tom Praschan <13141438+tom-anders@users.noreply.github.com> Date: Wed, 7 Sep 2022 12:03:55 +0200 Subject: [PATCH] [clangd] Support renaming virtual methods Fixes https://github.com/clangd/clangd/issues/706 Differential Revision: https://reviews.llvm.org/D132797 --- clang-tools-extra/clangd/refactor/Rename.cpp | 31 ++++++++++---- clang-tools-extra/clangd/unittests/RenameTests.cpp | 48 +++++++++++++++++++--- 2 files changed, 66 insertions(+), 13 deletions(-) diff --git a/clang-tools-extra/clangd/refactor/Rename.cpp b/clang-tools-extra/clangd/refactor/Rename.cpp index af454f0..6358a05 100644 --- a/clang-tools-extra/clangd/refactor/Rename.cpp +++ b/clang-tools-extra/clangd/refactor/Rename.cpp @@ -214,13 +214,6 @@ llvm::Optional renameable(const NamedDecl &RenameDecl, IsMainFileOnly)) return ReasonToReject::NonIndexable; - - // FIXME: Renaming virtual methods requires to rename all overridens in - // subclasses, our index doesn't have this information. - if (const auto *S = llvm::dyn_cast(&RenameDecl)) { - if (S->isVirtual()) - return ReasonToReject::UnsupportedSymbol; - } return None; } @@ -551,6 +544,26 @@ Range toRange(const SymbolLocation &L) { return R; } +// Walk down from a virtual method to overriding methods, we rename them as a +// group. Note that canonicalRenameDecl() ensures we're starting from the base +// method. +void insertTransitiveOverrides(SymbolID Base, llvm::DenseSet &IDs, + const SymbolIndex &Index) { + RelationsRequest Req; + Req.Predicate = RelationKind::OverriddenBy; + + llvm::DenseSet Pending = {Base}; + while (!Pending.empty()) { + Req.Subjects = std::move(Pending); + Pending.clear(); + + Index.relations(Req, [&](const SymbolID &, const Symbol &Override) { + if (IDs.insert(Override.ID).second) + Pending.insert(Override.ID); + }); + } +} + // Return all rename occurrences (using the index) outside of the main file, // grouped by the absolute file path. llvm::Expected>> @@ -561,6 +574,10 @@ findOccurrencesOutsideFile(const NamedDecl &RenameDecl, RefsRequest RQuest; RQuest.IDs.insert(getSymbolID(&RenameDecl)); + if (const auto *MethodDecl = llvm::dyn_cast(&RenameDecl)) + if (MethodDecl->isVirtual()) + insertTransitiveOverrides(*RQuest.IDs.begin(), RQuest.IDs, Index); + // Absolute file path => rename occurrences in that file. llvm::StringMap> AffectedFiles; bool HasMore = Index.refs(RQuest, [&](const Ref &R) { diff --git a/clang-tools-extra/clangd/unittests/RenameTests.cpp b/clang-tools-extra/clangd/unittests/RenameTests.cpp index 70b0bbc..451ec37 100644 --- a/clang-tools-extra/clangd/unittests/RenameTests.cpp +++ b/clang-tools-extra/clangd/unittests/RenameTests.cpp @@ -886,12 +886,6 @@ TEST(RenameTest, Renameable) { @end )cpp", "not a supported kind", HeaderFile}, - {R"cpp(// FIXME: rename virtual/override methods is not supported yet. - struct A { - virtual void f^oo() {} - }; - )cpp", - "not a supported kind", !HeaderFile}, {R"cpp( void foo(int); void foo(char); @@ -1491,6 +1485,48 @@ TEST(CrossFileRenameTests, WithUpToDateIndex) { )cpp", }, { + // virtual methods. + R"cpp( + class Base { + virtual void [[foo]](); + }; + class Derived1 : public Base { + void [[f^oo]]() override; + }; + class NotDerived { + void foo() {}; + } + )cpp", + R"cpp( + #include "foo.h" + void Base::[[foo]]() {} + void Derived1::[[foo]]() {} + + class Derived2 : public Derived1 { + void [[foo]]() override {}; + }; + + void func(Base* b, Derived1* d1, + Derived2* d2, NotDerived* nd) { + b->[[foo]](); + d1->[[foo]](); + d2->[[foo]](); + nd->foo(); + } + )cpp", + }, + // FIXME: triggers an assertion failure due to a bug in canonicalization. + // See https://reviews.llvm.org/D132797 +#if 0 + { + // virtual templated method + R"cpp( + template class Foo { virtual void [[m]](); }; + class Bar : Foo { void [[^m]]() override; }; + )cpp", "" + }, +#endif + { // rename on constructor and destructor. R"cpp( class [[Foo]] { -- 2.7.4