Skip to content

Commit 8b4d813

Browse files
committed
[clangd] Use SymbolName to represent Objective-C selectors
This is a cleaner design than using identifier and an optional `Selector`. It also allows rename of Objective-C method names if no declaration is at hand and thus no `Selector` instance can be formed. For example, when finding the ranges to rename based on an index that’s not clangd’s built-in index.
1 parent b3050f5 commit 8b4d813

File tree

9 files changed

+150
-55
lines changed

9 files changed

+150
-55
lines changed

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

Lines changed: 25 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ namespace clang {
4040
namespace clangd {
4141
namespace {
4242

43+
using tooling::SymbolName;
44+
4345
std::optional<std::string> filePath(const SymbolLocation &Loc,
4446
llvm::StringRef HintFilePath) {
4547
if (!Loc)
@@ -591,11 +593,11 @@ bool isMatchingSelectorName(const syntax::Token &Cur, const syntax::Token &Next,
591593
// The search will terminate upon seeing Terminator or a ; at the top level.
592594
std::optional<SymbolRange>
593595
findAllSelectorPieces(llvm::ArrayRef<syntax::Token> Tokens,
594-
const SourceManager &SM, Selector Sel,
596+
const SourceManager &SM, const SymbolName &Name,
595597
tok::TokenKind Terminator) {
596598
assert(!Tokens.empty());
597599

598-
unsigned NumArgs = Sel.getNumArgs();
600+
unsigned NumArgs = Name.getNamePieces().size();
599601
llvm::SmallVector<tok::TokenKind, 8> Closes;
600602
std::vector<Range> SelectorPieces;
601603
for (unsigned Index = 0, Last = Tokens.size(); Index < Last - 1; ++Index) {
@@ -605,12 +607,12 @@ findAllSelectorPieces(llvm::ArrayRef<syntax::Token> Tokens,
605607
auto PieceCount = SelectorPieces.size();
606608
if (PieceCount < NumArgs &&
607609
isMatchingSelectorName(Tok, Tokens[Index + 1], SM,
608-
Sel.getNameForSlot(PieceCount))) {
610+
Name.getNamePieces()[PieceCount])) {
609611
// If 'foo:' instead of ':' (empty selector), we need to skip the ':'
610612
// token after the name. We don't currently properly support empty
611613
// selectors since we may lex them improperly due to ternary statements
612614
// as well as don't properly support storing their ranges for edits.
613-
if (!Sel.getNameForSlot(PieceCount).empty())
615+
if (!Name.getNamePieces()[PieceCount].empty())
614616
++Index;
615617
SelectorPieces.push_back(
616618
halfOpenToRange(SM, Tok.range(SM).toCharRange(SM)));
@@ -662,16 +664,17 @@ findAllSelectorPieces(llvm::ArrayRef<syntax::Token> Tokens,
662664

663665
/// Collects all ranges of the given identifier/selector in the source code.
664666
///
665-
/// If a selector is given, this does a full lex of the given source code in
666-
/// order to identify all selector fragments (e.g. in method exprs/decls) since
667-
/// they are non-contiguous.
668-
std::vector<SymbolRange> collectRenameIdentifierRanges(
669-
llvm::StringRef Identifier, llvm::StringRef Content,
670-
const LangOptions &LangOpts, std::optional<Selector> Selector) {
667+
/// If `Name` is an Objective-C symbol name, this does a full lex of the given
668+
/// source code in order to identify all selector fragments (e.g. in method
669+
/// exprs/decls) since they are non-contiguous.
670+
std::vector<SymbolRange>
671+
collectRenameIdentifierRanges(const tooling::SymbolName &Name,
672+
llvm::StringRef Content,
673+
const LangOptions &LangOpts) {
671674
std::vector<SymbolRange> Ranges;
672-
if (!Selector) {
675+
if (auto SinglePiece = Name.getSinglePiece()) {
673676
auto IdentifierRanges =
674-
collectIdentifierRanges(Identifier, Content, LangOpts);
677+
collectIdentifierRanges(*SinglePiece, Content, LangOpts);
675678
for (const auto &R : IdentifierRanges)
676679
Ranges.emplace_back(R);
677680
return Ranges;
@@ -685,7 +688,7 @@ std::vector<SymbolRange> collectRenameIdentifierRanges(
685688
// parsing a method declaration or definition which isn't at the top level or
686689
// similar looking expressions (e.g. an @selector() expression).
687690
llvm::SmallVector<tok::TokenKind, 8> Closes;
688-
llvm::StringRef FirstSelPiece = Selector->getNameForSlot(0);
691+
llvm::StringRef FirstSelPiece = Name.getNamePieces()[0];
689692

690693
auto Tokens = syntax::tokenize(SM.getMainFileID(), SM, LangOpts);
691694
unsigned Last = Tokens.size() - 1;
@@ -717,7 +720,7 @@ std::vector<SymbolRange> collectRenameIdentifierRanges(
717720
// Check if we can find all the relevant selector peices starting from
718721
// this token
719722
auto SelectorRanges =
720-
findAllSelectorPieces(ArrayRef(Tokens).slice(Index), SM, *Selector,
723+
findAllSelectorPieces(ArrayRef(Tokens).slice(Index), SM, Name,
721724
Closes.empty() ? tok::l_brace : Closes.back());
722725
if (SelectorRanges)
723726
Ranges.emplace_back(std::move(*SelectorRanges));
@@ -764,7 +767,6 @@ renameObjCMethodWithinFile(ParsedAST &AST, const ObjCMethodDecl *MD,
764767
std::vector<SourceLocation> SelectorOccurences) {
765768
const SourceManager &SM = AST.getSourceManager();
766769
auto Code = SM.getBufferData(SM.getMainFileID());
767-
auto RenameIdentifier = MD->getSelector().getNameForSlot(0).str();
768770
llvm::SmallVector<llvm::StringRef, 8> NewNames;
769771
NewName.split(NewNames, ":");
770772

@@ -774,7 +776,7 @@ renameObjCMethodWithinFile(ParsedAST &AST, const ObjCMethodDecl *MD,
774776
Ranges.push_back(tokenRangeForLoc(AST, Loc, SM, LangOpts));
775777
auto FilePath = AST.tuPath();
776778
auto RenameRanges = collectRenameIdentifierRanges(
777-
RenameIdentifier, Code, LangOpts, MD->getSelector());
779+
SymbolName(MD->getDeclName()), Code, LangOpts);
778780
auto RenameEdit = buildRenameEdit(FilePath, Code, RenameRanges, NewNames);
779781
if (!RenameEdit)
780782
return error("failed to rename in file {0}: {1}", FilePath,
@@ -926,22 +928,14 @@ renameOutsideFile(const NamedDecl &RenameDecl, llvm::StringRef MainFilePath,
926928
ExpBuffer.getError().message());
927929
continue;
928930
}
929-
std::string RenameIdentifier = RenameDecl.getNameAsString();
930-
std::optional<Selector> Selector = std::nullopt;
931+
SymbolName RenameName(RenameDecl.getDeclName());
931932
llvm::SmallVector<llvm::StringRef, 8> NewNames;
932-
if (const auto *MD = dyn_cast<ObjCMethodDecl>(&RenameDecl)) {
933-
if (MD->getSelector().getNumArgs() > 1) {
934-
RenameIdentifier = MD->getSelector().getNameForSlot(0).str();
935-
Selector = MD->getSelector();
936-
}
937-
}
938933
NewName.split(NewNames, ":");
939934

940935
auto AffectedFileCode = (*ExpBuffer)->getBuffer();
941-
auto RenameRanges =
942-
adjustRenameRanges(AffectedFileCode, RenameIdentifier,
943-
std::move(FileAndOccurrences.second),
944-
RenameDecl.getASTContext().getLangOpts(), Selector);
936+
auto RenameRanges = adjustRenameRanges(
937+
AffectedFileCode, RenameName, std::move(FileAndOccurrences.second),
938+
RenameDecl.getASTContext().getLangOpts());
945939
if (!RenameRanges) {
946940
// Our heuristics fails to adjust rename ranges to the current state of
947941
// the file, it is most likely the index is stale, so we give up the
@@ -1226,14 +1220,13 @@ llvm::Expected<Edit> buildRenameEdit(llvm::StringRef AbsFilePath,
12261220
// were inserted). If such a "near miss" is found, the rename is still
12271221
// possible
12281222
std::optional<std::vector<SymbolRange>>
1229-
adjustRenameRanges(llvm::StringRef DraftCode, llvm::StringRef Identifier,
1230-
std::vector<Range> Indexed, const LangOptions &LangOpts,
1231-
std::optional<Selector> Selector) {
1223+
adjustRenameRanges(llvm::StringRef DraftCode, const tooling::SymbolName &Name,
1224+
std::vector<Range> Indexed, const LangOptions &LangOpts) {
12321225
trace::Span Tracer("AdjustRenameRanges");
12331226
assert(!Indexed.empty());
12341227
assert(llvm::is_sorted(Indexed));
12351228
std::vector<SymbolRange> Lexed =
1236-
collectRenameIdentifierRanges(Identifier, DraftCode, LangOpts, Selector);
1229+
collectRenameIdentifierRanges(Name, DraftCode, LangOpts);
12371230
llvm::sort(Lexed);
12381231
return getMappedRanges(Indexed, Lexed);
12391232
}

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "SourceCode.h"
1414
#include "clang/Basic/IdentifierTable.h"
1515
#include "clang/Basic/LangOptions.h"
16+
#include "clang/Tooling/Refactoring/Rename/SymbolName.h"
1617
#include "llvm/ADT/SmallVector.h"
1718
#include "llvm/Support/Error.h"
1819
#include <optional>
@@ -108,9 +109,8 @@ llvm::Expected<Edit> buildRenameEdit(llvm::StringRef AbsFilePath,
108109
/// occurrence has the same length).
109110
/// REQUIRED: Indexed is sorted.
110111
std::optional<std::vector<SymbolRange>>
111-
adjustRenameRanges(llvm::StringRef DraftCode, llvm::StringRef Identifier,
112-
std::vector<Range> Indexed, const LangOptions &LangOpts,
113-
std::optional<Selector> Selector);
112+
adjustRenameRanges(llvm::StringRef DraftCode, const tooling::SymbolName &Name,
113+
std::vector<Range> Indexed, const LangOptions &LangOpts);
114114

115115
/// Calculates the lexed occurrences that the given indexed occurrences map to.
116116
/// Returns std::nullopt if we don't find a mapping.

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2040,9 +2040,9 @@ TEST(CrossFileRenameTests, adjustRenameRanges) {
20402040
for (const auto &T : Tests) {
20412041
SCOPED_TRACE(T.DraftCode);
20422042
Annotations Draft(T.DraftCode);
2043-
auto ActualRanges = adjustRenameRanges(Draft.code(), "x",
2044-
Annotations(T.IndexedCode).ranges(),
2045-
LangOpts, std::nullopt);
2043+
auto ActualRanges = adjustRenameRanges(
2044+
Draft.code(), tooling::SymbolName("x", /*IsObjectiveCSelector=*/false),
2045+
Annotations(T.IndexedCode).ranges(), LangOpts);
20462046
if (!ActualRanges)
20472047
EXPECT_THAT(Draft.ranges(), testing::IsEmpty());
20482048
else

clang/include/clang/Tooling/Refactoring/Rename/SymbolName.h

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,16 @@
99
#ifndef LLVM_CLANG_TOOLING_REFACTORING_RENAME_SYMBOLNAME_H
1010
#define LLVM_CLANG_TOOLING_REFACTORING_RENAME_SYMBOLNAME_H
1111

12+
#include "clang/AST/DeclarationName.h"
1213
#include "clang/Basic/LLVM.h"
1314
#include "llvm/ADT/ArrayRef.h"
1415
#include "llvm/ADT/SmallVector.h"
1516
#include "llvm/ADT/StringRef.h"
1617

1718
namespace clang {
19+
20+
class LangOptions;
21+
1822
namespace tooling {
1923

2024
/// A name of a symbol.
@@ -27,19 +31,45 @@ namespace tooling {
2731
/// // ^~ string 0 ~~~~~ ^~ string 1 ~~~~~
2832
/// \endcode
2933
class SymbolName {
34+
llvm::SmallVector<std::string, 1> NamePieces;
35+
3036
public:
31-
explicit SymbolName(StringRef Name) {
32-
// While empty symbol names are valid (Objective-C selectors can have empty
33-
// name pieces), occurrences Objective-C selectors are created using an
34-
// array of strings instead of just one string.
35-
assert(!Name.empty() && "Invalid symbol name!");
36-
this->Name.push_back(Name.str());
37-
}
37+
SymbolName();
38+
39+
/// Create a new \c SymbolName with the specified pieces.
40+
explicit SymbolName(ArrayRef<StringRef> NamePieces);
41+
explicit SymbolName(ArrayRef<std::string> NamePieces);
42+
43+
explicit SymbolName(const DeclarationName &Name);
3844

39-
ArrayRef<std::string> getNamePieces() const { return Name; }
45+
/// Creates a \c SymbolName from the given string representation.
46+
///
47+
/// For Objective-C symbol names, this splits a selector into multiple pieces
48+
/// on `:`. For all other languages the name is used as the symbol name.
49+
SymbolName(StringRef Name, bool IsObjectiveCSelector);
50+
SymbolName(StringRef Name, const LangOptions &LangOpts);
4051

41-
private:
42-
llvm::SmallVector<std::string, 1> Name;
52+
ArrayRef<std::string> getNamePieces() const { return NamePieces; }
53+
54+
/// If this symbol consists of a single piece return it, otherwise return
55+
/// `None`.
56+
///
57+
/// Only symbols in Objective-C can consist of multiple pieces, so this
58+
/// function always returns a value for non-Objective-C symbols.
59+
std::optional<std::string> getSinglePiece() const;
60+
61+
/// Returns a human-readable version of this symbol name.
62+
///
63+
/// If the symbol consists of multiple pieces (aka. it is an Objective-C
64+
/// selector/method name), the pieces are separated by `:`, otherwise just an
65+
/// identifier name.
66+
std::string getAsString() const;
67+
68+
void print(raw_ostream &OS) const;
69+
70+
bool operator==(const SymbolName &Other) const {
71+
return NamePieces == Other.NamePieces;
72+
}
4373
};
4474

4575
} // end namespace tooling

clang/lib/Tooling/Refactoring/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ add_clang_library(clangToolingRefactoring
1313
Rename/USRFinder.cpp
1414
Rename/USRFindingAction.cpp
1515
Rename/USRLocFinder.cpp
16+
SymbolName.cpp
1617

1718
LINK_LIBS
1819
clangAST
@@ -23,6 +24,7 @@ add_clang_library(clangToolingRefactoring
2324
clangLex
2425
clangRewrite
2526
clangToolingCore
27+
clangToolingSyntax
2628

2729
DEPENDS
2830
omp_gen

clang/lib/Tooling/Refactoring/Rename/RenamingAction.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ RenameOccurrences::createSourceReplacements(RefactoringRuleContext &Context) {
8282
if (!Occurrences)
8383
return Occurrences.takeError();
8484
// FIXME: Verify that the new name is valid.
85-
SymbolName Name(NewName);
85+
SymbolName Name(NewName, /*IsObjectiveCSelector=*/false);
8686
return createRenameReplacements(
8787
*Occurrences, Context.getASTContext().getSourceManager(), Name);
8888
}
@@ -219,7 +219,7 @@ class RenamingASTConsumer : public ASTConsumer {
219219
}
220220
// FIXME: Support multi-piece names.
221221
// FIXME: better error handling (propagate error out).
222-
SymbolName NewNameRef(NewName);
222+
SymbolName NewNameRef(NewName, /*IsObjectiveCSelector=*/false);
223223
Expected<std::vector<AtomicChange>> Change =
224224
createRenameReplacements(Occurrences, SourceMgr, NewNameRef);
225225
if (!Change) {

clang/lib/Tooling/Refactoring/Rename/USRLocFinder.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,8 @@ class USRLocFindingASTVisitor
6060
const ASTContext &Context)
6161
: RecursiveSymbolVisitor(Context.getSourceManager(),
6262
Context.getLangOpts()),
63-
USRSet(USRs.begin(), USRs.end()), PrevName(PrevName), Context(Context) {
64-
}
63+
USRSet(USRs.begin(), USRs.end()),
64+
PrevName(PrevName, /*IsObjectiveCSelector=*/false), Context(Context) {}
6565

6666
bool visitSymbolOccurrence(const NamedDecl *ND,
6767
ArrayRef<SourceRange> NameRanges) {
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
//===--- SymbolName.cpp - Clang refactoring library -----------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "clang/Tooling/Refactoring/Rename/SymbolName.h"
10+
#include "clang/Basic/LangOptions.h"
11+
#include "llvm/Support/raw_ostream.h"
12+
13+
namespace clang {
14+
namespace tooling {
15+
16+
SymbolName::SymbolName() : NamePieces({}) {}
17+
18+
SymbolName::SymbolName(const DeclarationName &DeclName)
19+
: SymbolName(DeclName.getAsString(),
20+
/*IsObjectiveCSelector=*/DeclName.getNameKind() ==
21+
DeclarationName::NameKind::ObjCMultiArgSelector ||
22+
DeclName.getNameKind() ==
23+
DeclarationName::NameKind::ObjCOneArgSelector) {}
24+
25+
SymbolName::SymbolName(StringRef Name, const LangOptions &LangOpts)
26+
: SymbolName(Name, LangOpts.ObjC) {}
27+
28+
SymbolName::SymbolName(StringRef Name, bool IsObjectiveCSelector) {
29+
if (!IsObjectiveCSelector) {
30+
NamePieces.push_back(Name.str());
31+
return;
32+
}
33+
// Decompose an Objective-C selector name into multiple strings.
34+
do {
35+
auto StringAndName = Name.split(':');
36+
NamePieces.push_back(StringAndName.first.str());
37+
Name = StringAndName.second;
38+
} while (!Name.empty());
39+
}
40+
41+
SymbolName::SymbolName(ArrayRef<StringRef> NamePieces) {
42+
for (const auto &Piece : NamePieces)
43+
this->NamePieces.push_back(Piece.str());
44+
}
45+
46+
SymbolName::SymbolName(ArrayRef<std::string> NamePieces) {
47+
for (const auto &Piece : NamePieces)
48+
this->NamePieces.push_back(Piece);
49+
}
50+
51+
std::optional<std::string> SymbolName::getSinglePiece() const {
52+
if (getNamePieces().size() == 1) {
53+
return NamePieces.front();
54+
}
55+
return std::nullopt;
56+
}
57+
58+
std::string SymbolName::getAsString() const {
59+
std::string Result;
60+
llvm::raw_string_ostream OS(Result);
61+
this->print(OS);
62+
return Result;
63+
}
64+
65+
void SymbolName::print(raw_ostream &OS) const {
66+
llvm::interleave(NamePieces, OS, ":");
67+
}
68+
69+
} // end namespace tooling
70+
} // end namespace clang

clang/unittests/Tooling/RefactoringActionRulesTest.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -211,9 +211,9 @@ TEST_F(RefactoringActionRulesTest, ReturnSymbolOccurrences) {
211211
Expected<SymbolOccurrences>
212212
findSymbolOccurrences(RefactoringRuleContext &) override {
213213
SymbolOccurrences Occurrences;
214-
Occurrences.push_back(SymbolOccurrence(SymbolName("test"),
215-
SymbolOccurrence::MatchingSymbol,
216-
Selection.getBegin()));
214+
Occurrences.push_back(SymbolOccurrence(
215+
SymbolName("test", /*IsObjectiveCSelector=*/false),
216+
SymbolOccurrence::MatchingSymbol, Selection.getBegin()));
217217
return std::move(Occurrences);
218218
}
219219
};

0 commit comments

Comments
 (0)