Skip to content

Commit 59e5519

Browse files
authored
[clangd] Fix renaming single argument ObjC methods (#82396)
Use the legacy non-ObjC rename logic when dealing with selectors that have zero or one arguments. In addition, make sure we don't add an extra `:` during the rename. Add a few more tests to verify this works (thanks to @ahoppen for the tests and finding this bug).
1 parent 07fd5ca commit 59e5519

File tree

2 files changed

+152
-5
lines changed

2 files changed

+152
-5
lines changed

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

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -811,8 +811,18 @@ renameWithinFile(ParsedAST &AST, const NamedDecl &RenameDecl,
811811
continue;
812812
Locs.push_back(RenameLoc);
813813
}
814-
if (const auto *MD = dyn_cast<ObjCMethodDecl>(&RenameDecl))
815-
return renameObjCMethodWithinFile(AST, MD, NewName, std::move(Locs));
814+
if (const auto *MD = dyn_cast<ObjCMethodDecl>(&RenameDecl)) {
815+
// The custom ObjC selector logic doesn't handle the zero arg selector
816+
// case, as it relies on parsing selectors via the trailing `:`.
817+
// We also choose to use regular rename logic for the single-arg selectors
818+
// as the AST/Index has the right locations in that case.
819+
if (MD->getSelector().getNumArgs() > 1)
820+
return renameObjCMethodWithinFile(AST, MD, NewName, std::move(Locs));
821+
822+
// Eat trailing : for single argument methods since they're actually
823+
// considered a separate token during rename.
824+
NewName.consume_back(":");
825+
}
816826
for (const auto &Loc : Locs) {
817827
if (auto Err = FilteredChanges.add(tooling::Replacement(
818828
SM, CharSourceRange::getTokenRange(Loc), NewName)))
@@ -930,10 +940,9 @@ renameOutsideFile(const NamedDecl &RenameDecl, llvm::StringRef MainFilePath,
930940
std::optional<Selector> Selector = std::nullopt;
931941
llvm::SmallVector<llvm::StringRef, 8> NewNames;
932942
if (const auto *MD = dyn_cast<ObjCMethodDecl>(&RenameDecl)) {
933-
if (MD->getSelector().getNumArgs() > 1) {
934-
RenameIdentifier = MD->getSelector().getNameForSlot(0).str();
943+
RenameIdentifier = MD->getSelector().getNameForSlot(0).str();
944+
if (MD->getSelector().getNumArgs() > 1)
935945
Selector = MD->getSelector();
936-
}
937946
}
938947
NewName.split(NewNames, ":");
939948

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

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1943,6 +1943,144 @@ TEST(CrossFileRenameTests, WithUpToDateIndex) {
19431943
}
19441944
}
19451945

1946+
TEST(CrossFileRenameTests, ObjC) {
1947+
MockCompilationDatabase CDB;
1948+
CDB.ExtraClangFlags = {"-xobjective-c"};
1949+
// rename is runnning on all "^" points in FooH.
1950+
struct Case {
1951+
llvm::StringRef FooH;
1952+
llvm::StringRef FooM;
1953+
llvm::StringRef NewName;
1954+
llvm::StringRef ExpectedFooH;
1955+
llvm::StringRef ExpectedFooM;
1956+
};
1957+
Case Cases[] = {// --- Zero arg selector
1958+
{
1959+
// Input
1960+
R"cpp(
1961+
@interface Foo
1962+
- (int)performA^ction;
1963+
@end
1964+
)cpp",
1965+
R"cpp(
1966+
@implementation Foo
1967+
- (int)performAction {
1968+
[self performAction];
1969+
}
1970+
@end
1971+
)cpp",
1972+
// New name
1973+
"performNewAction",
1974+
// Expected
1975+
R"cpp(
1976+
@interface Foo
1977+
- (int)performNewAction;
1978+
@end
1979+
)cpp",
1980+
R"cpp(
1981+
@implementation Foo
1982+
- (int)performNewAction {
1983+
[self performNewAction];
1984+
}
1985+
@end
1986+
)cpp",
1987+
},
1988+
// --- Single arg selector
1989+
{
1990+
// Input
1991+
R"cpp(
1992+
@interface Foo
1993+
- (int)performA^ction:(int)action;
1994+
@end
1995+
)cpp",
1996+
R"cpp(
1997+
@implementation Foo
1998+
- (int)performAction:(int)action {
1999+
[self performAction:action];
2000+
}
2001+
@end
2002+
)cpp",
2003+
// New name
2004+
"performNewAction:",
2005+
// Expected
2006+
R"cpp(
2007+
@interface Foo
2008+
- (int)performNewAction:(int)action;
2009+
@end
2010+
)cpp",
2011+
R"cpp(
2012+
@implementation Foo
2013+
- (int)performNewAction:(int)action {
2014+
[self performNewAction:action];
2015+
}
2016+
@end
2017+
)cpp",
2018+
},
2019+
// --- Multi arg selector
2020+
{
2021+
// Input
2022+
R"cpp(
2023+
@interface Foo
2024+
- (int)performA^ction:(int)action with:(int)value;
2025+
@end
2026+
)cpp",
2027+
R"cpp(
2028+
@implementation Foo
2029+
- (int)performAction:(int)action with:(int)value {
2030+
[self performAction:action with:value];
2031+
}
2032+
@end
2033+
)cpp",
2034+
// New name
2035+
"performNewAction:by:",
2036+
// Expected
2037+
R"cpp(
2038+
@interface Foo
2039+
- (int)performNewAction:(int)action by:(int)value;
2040+
@end
2041+
)cpp",
2042+
R"cpp(
2043+
@implementation Foo
2044+
- (int)performNewAction:(int)action by:(int)value {
2045+
[self performNewAction:action by:value];
2046+
}
2047+
@end
2048+
)cpp",
2049+
}};
2050+
2051+
trace::TestTracer Tracer;
2052+
for (const auto &T : Cases) {
2053+
SCOPED_TRACE(T.FooH);
2054+
Annotations FooH(T.FooH);
2055+
Annotations FooM(T.FooM);
2056+
std::string FooHPath = testPath("foo.h");
2057+
std::string FooMPath = testPath("foo.m");
2058+
2059+
MockFS FS;
2060+
FS.Files[FooHPath] = std::string(FooH.code());
2061+
FS.Files[FooMPath] = std::string(FooM.code());
2062+
2063+
auto ServerOpts = ClangdServer::optsForTest();
2064+
ServerOpts.BuildDynamicSymbolIndex = true;
2065+
ClangdServer Server(CDB, FS, ServerOpts);
2066+
2067+
// Add all files to clangd server to make sure the dynamic index has been
2068+
// built.
2069+
runAddDocument(Server, FooHPath, FooH.code());
2070+
runAddDocument(Server, FooMPath, FooM.code());
2071+
2072+
for (const auto &RenamePos : FooH.points()) {
2073+
EXPECT_THAT(Tracer.takeMetric("rename_files"), SizeIs(0));
2074+
auto FileEditsList =
2075+
llvm::cantFail(runRename(Server, FooHPath, RenamePos, T.NewName, {}));
2076+
EXPECT_THAT(Tracer.takeMetric("rename_files"), ElementsAre(2));
2077+
EXPECT_THAT(applyEdits(std::move(FileEditsList.GlobalChanges)),
2078+
UnorderedElementsAre(Pair(Eq(FooHPath), Eq(T.ExpectedFooH)),
2079+
Pair(Eq(FooMPath), Eq(T.ExpectedFooM))));
2080+
}
2081+
}
2082+
}
2083+
19462084
TEST(CrossFileRenameTests, CrossFileOnLocalSymbol) {
19472085
// cross-file rename should work for function-local symbols, even there is no
19482086
// index provided.

0 commit comments

Comments
 (0)