Skip to content

[clangd] Fix renaming single argument ObjC methods #82396

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Feb 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 14 additions & 5 deletions clang-tools-extra/clangd/refactor/Rename.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -811,8 +811,18 @@ renameWithinFile(ParsedAST &AST, const NamedDecl &RenameDecl,
continue;
Locs.push_back(RenameLoc);
}
if (const auto *MD = dyn_cast<ObjCMethodDecl>(&RenameDecl))
return renameObjCMethodWithinFile(AST, MD, NewName, std::move(Locs));
if (const auto *MD = dyn_cast<ObjCMethodDecl>(&RenameDecl)) {
// The custom ObjC selector logic doesn't handle the zero arg selector
// case, as it relies on parsing selectors via the trailing `:`.
// We also choose to use regular rename logic for the single-arg selectors
// as the AST/Index has the right locations in that case.
if (MD->getSelector().getNumArgs() > 1)
return renameObjCMethodWithinFile(AST, MD, NewName, std::move(Locs));

// Eat trailing : for single argument methods since they're actually
// considered a separate token during rename.
NewName.consume_back(":");
}
for (const auto &Loc : Locs) {
if (auto Err = FilteredChanges.add(tooling::Replacement(
SM, CharSourceRange::getTokenRange(Loc), NewName)))
Expand Down Expand Up @@ -930,10 +940,9 @@ renameOutsideFile(const NamedDecl &RenameDecl, llvm::StringRef MainFilePath,
std::optional<Selector> Selector = std::nullopt;
llvm::SmallVector<llvm::StringRef, 8> NewNames;
if (const auto *MD = dyn_cast<ObjCMethodDecl>(&RenameDecl)) {
if (MD->getSelector().getNumArgs() > 1) {
RenameIdentifier = MD->getSelector().getNameForSlot(0).str();
RenameIdentifier = MD->getSelector().getNameForSlot(0).str();
if (MD->getSelector().getNumArgs() > 1)
Selector = MD->getSelector();
}
}
NewName.split(NewNames, ":");

Expand Down
138 changes: 138 additions & 0 deletions clang-tools-extra/clangd/unittests/RenameTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1943,6 +1943,144 @@ TEST(CrossFileRenameTests, WithUpToDateIndex) {
}
}

TEST(CrossFileRenameTests, ObjC) {
MockCompilationDatabase CDB;
CDB.ExtraClangFlags = {"-xobjective-c"};
// rename is runnning on all "^" points in FooH.
struct Case {
llvm::StringRef FooH;
llvm::StringRef FooM;
llvm::StringRef NewName;
llvm::StringRef ExpectedFooH;
llvm::StringRef ExpectedFooM;
};
Case Cases[] = {// --- Zero arg selector
{
// Input
R"cpp(
@interface Foo
- (int)performA^ction;
@end
)cpp",
R"cpp(
@implementation Foo
- (int)performAction {
[self performAction];
}
@end
)cpp",
// New name
"performNewAction",
// Expected
R"cpp(
@interface Foo
- (int)performNewAction;
@end
)cpp",
R"cpp(
@implementation Foo
- (int)performNewAction {
[self performNewAction];
}
@end
)cpp",
},
// --- Single arg selector
{
// Input
R"cpp(
@interface Foo
- (int)performA^ction:(int)action;
@end
)cpp",
R"cpp(
@implementation Foo
- (int)performAction:(int)action {
[self performAction:action];
}
@end
)cpp",
// New name
"performNewAction:",
// Expected
R"cpp(
@interface Foo
- (int)performNewAction:(int)action;
@end
)cpp",
R"cpp(
@implementation Foo
- (int)performNewAction:(int)action {
[self performNewAction:action];
}
@end
)cpp",
},
// --- Multi arg selector
{
// Input
R"cpp(
@interface Foo
- (int)performA^ction:(int)action with:(int)value;
@end
)cpp",
R"cpp(
@implementation Foo
- (int)performAction:(int)action with:(int)value {
[self performAction:action with:value];
}
@end
)cpp",
// New name
"performNewAction:by:",
// Expected
R"cpp(
@interface Foo
- (int)performNewAction:(int)action by:(int)value;
@end
)cpp",
R"cpp(
@implementation Foo
- (int)performNewAction:(int)action by:(int)value {
[self performNewAction:action by:value];
}
@end
)cpp",
}};

trace::TestTracer Tracer;
for (const auto &T : Cases) {
SCOPED_TRACE(T.FooH);
Annotations FooH(T.FooH);
Annotations FooM(T.FooM);
std::string FooHPath = testPath("foo.h");
std::string FooMPath = testPath("foo.m");

MockFS FS;
FS.Files[FooHPath] = std::string(FooH.code());
FS.Files[FooMPath] = std::string(FooM.code());

auto ServerOpts = ClangdServer::optsForTest();
ServerOpts.BuildDynamicSymbolIndex = true;
ClangdServer Server(CDB, FS, ServerOpts);

// Add all files to clangd server to make sure the dynamic index has been
// built.
runAddDocument(Server, FooHPath, FooH.code());
runAddDocument(Server, FooMPath, FooM.code());

for (const auto &RenamePos : FooH.points()) {
EXPECT_THAT(Tracer.takeMetric("rename_files"), SizeIs(0));
auto FileEditsList =
llvm::cantFail(runRename(Server, FooHPath, RenamePos, T.NewName, {}));
EXPECT_THAT(Tracer.takeMetric("rename_files"), ElementsAre(2));
EXPECT_THAT(applyEdits(std::move(FileEditsList.GlobalChanges)),
UnorderedElementsAre(Pair(Eq(FooHPath), Eq(T.ExpectedFooH)),
Pair(Eq(FooMPath), Eq(T.ExpectedFooM))));
}
}
}

TEST(CrossFileRenameTests, CrossFileOnLocalSymbol) {
// cross-file rename should work for function-local symbols, even there is no
// index provided.
Expand Down