Skip to content

Commit 7d2fba8

Browse files
committed
[clangd] ObjC fixes for semantic highlighting and xref highlights
- highlight references to protocols in class/protocol/extension decls - support multi-token selector highlights in semantic + xref highlights (method calls and declarations only) - In `@interface I(C)`, I now references the interface and C the category - highlight uses of interfaces as types - added semantic highlightings of protocol names (as "interface") and category names (as "namespace"). These are both standard kinds, maybe "extension" will be standardized... - highlight `auto` as "class" when it resolves to an ObjC pointer - don't highlight `self` as a variable even though the AST models it as one Not fixed: uses of protocols in type names (needs some refactoring of unrelated code first) Differential Revision: https://reviews.llvm.org/D97617
1 parent 87e854a commit 7d2fba8

File tree

7 files changed

+262
-18
lines changed

7 files changed

+262
-18
lines changed

clang-tools-extra/clangd/FindTarget.cpp

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "support/Logger.h"
1313
#include "clang/AST/ASTTypeTraits.h"
1414
#include "clang/AST/Decl.h"
15+
#include "clang/AST/DeclBase.h"
1516
#include "clang/AST/DeclCXX.h"
1617
#include "clang/AST/DeclTemplate.h"
1718
#include "clang/AST/DeclVisitor.h"
@@ -633,6 +634,61 @@ llvm::SmallVector<ReferenceLoc> refInDecl(const Decl *D,
633634
/*IsDecl=*/false,
634635
{DG->getDeducedTemplate()}});
635636
}
637+
638+
void VisitObjCMethodDecl(const ObjCMethodDecl *OMD) {
639+
// The name may have several tokens, we can only report the first.
640+
Refs.push_back(ReferenceLoc{NestedNameSpecifierLoc(),
641+
OMD->getSelectorStartLoc(),
642+
/*IsDecl=*/true,
643+
{OMD}});
644+
}
645+
646+
void visitProtocolList(
647+
llvm::iterator_range<ObjCProtocolList::iterator> Protocols,
648+
llvm::iterator_range<const SourceLocation *> Locations) {
649+
for (const auto &P : llvm::zip(Protocols, Locations)) {
650+
Refs.push_back(ReferenceLoc{NestedNameSpecifierLoc(),
651+
std::get<1>(P),
652+
/*IsDecl=*/false,
653+
{std::get<0>(P)}});
654+
}
655+
}
656+
657+
void VisitObjCInterfaceDecl(const ObjCInterfaceDecl *OID) {
658+
if (OID->isThisDeclarationADefinition())
659+
visitProtocolList(OID->protocols(), OID->protocol_locs());
660+
Base::VisitObjCInterfaceDecl(OID); // Visit the interface's name.
661+
}
662+
663+
void VisitObjCCategoryDecl(const ObjCCategoryDecl *OCD) {
664+
visitProtocolList(OCD->protocols(), OCD->protocol_locs());
665+
// getLocation is the extended class's location, not the category's.
666+
Refs.push_back(ReferenceLoc{NestedNameSpecifierLoc(),
667+
OCD->getLocation(),
668+
/*IsDecl=*/false,
669+
{OCD->getClassInterface()}});
670+
Refs.push_back(ReferenceLoc{NestedNameSpecifierLoc(),
671+
OCD->getCategoryNameLoc(),
672+
/*IsDecl=*/true,
673+
{OCD}});
674+
}
675+
676+
void VisitObjCCategoryImplDecl(const ObjCCategoryImplDecl *OCID) {
677+
Refs.push_back(ReferenceLoc{NestedNameSpecifierLoc(),
678+
OCID->getLocation(),
679+
/*IsDecl=*/false,
680+
{OCID->getClassInterface()}});
681+
Refs.push_back(ReferenceLoc{NestedNameSpecifierLoc(),
682+
OCID->getCategoryNameLoc(),
683+
/*IsDecl=*/true,
684+
{OCID->getCategoryDecl()}});
685+
}
686+
687+
void VisitObjCProtocolDecl(const ObjCProtocolDecl *OPD) {
688+
if (OPD->isThisDeclarationADefinition())
689+
visitProtocolList(OPD->protocols(), OPD->protocol_locs());
690+
Base::VisitObjCProtocolDecl(OPD); // Visit the protocol's name.
691+
}
636692
};
637693

638694
Visitor V{Resolver};
@@ -711,6 +767,14 @@ llvm::SmallVector<ReferenceLoc> refInStmt(const Stmt *S,
711767
explicitReferenceTargets(DynTypedNode::create(*E), {}, Resolver)});
712768
}
713769

770+
void VisitObjCMessageExpr(const ObjCMessageExpr *E) {
771+
// The name may have several tokens, we can only report the first.
772+
Refs.push_back(ReferenceLoc{NestedNameSpecifierLoc(),
773+
E->getSelectorStartLoc(),
774+
/*IsDecl=*/false,
775+
{E->getMethodDecl()}});
776+
}
777+
714778
void VisitDesignatedInitExpr(const DesignatedInitExpr *DIE) {
715779
for (const DesignatedInitExpr::Designator &D : DIE->designators()) {
716780
if (!D.isFieldDesignator())
@@ -824,6 +888,16 @@ refInTypeLoc(TypeLoc L, const HeuristicResolver *Resolver) {
824888
/*IsDecl=*/false,
825889
{L.getTypedefNameDecl()}};
826890
}
891+
892+
void VisitObjCInterfaceTypeLoc(ObjCInterfaceTypeLoc L) {
893+
Ref = ReferenceLoc{NestedNameSpecifierLoc(),
894+
L.getNameLoc(),
895+
/*IsDecl=*/false,
896+
{L.getIFaceDecl()}};
897+
}
898+
899+
// FIXME: add references to protocols in ObjCObjectTypeLoc and maybe
900+
// ObjCObjectPointerTypeLoc.
827901
};
828902

829903
Visitor V{Resolver};

clang-tools-extra/clangd/SemanticHighlighting.cpp

Lines changed: 70 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,27 @@ namespace {
4040
/// Some names are not written in the source code and cannot be highlighted,
4141
/// e.g. anonymous classes. This function detects those cases.
4242
bool canHighlightName(DeclarationName Name) {
43-
if (Name.getNameKind() == DeclarationName::CXXConstructorName ||
44-
Name.getNameKind() == DeclarationName::CXXUsingDirective)
43+
switch (Name.getNameKind()) {
44+
case DeclarationName::Identifier: {
45+
auto *II = Name.getAsIdentifierInfo();
46+
return II && !II->getName().empty();
47+
}
48+
case DeclarationName::CXXConstructorName:
49+
case DeclarationName::CXXDestructorName:
4550
return true;
46-
auto *II = Name.getAsIdentifierInfo();
47-
return II && !II->getName().empty();
51+
case DeclarationName::ObjCZeroArgSelector:
52+
case DeclarationName::ObjCOneArgSelector:
53+
case DeclarationName::ObjCMultiArgSelector:
54+
// Multi-arg selectors need special handling, and we handle 0/1 arg
55+
// selectors there too.
56+
return false;
57+
case DeclarationName::CXXConversionFunctionName:
58+
case DeclarationName::CXXOperatorName:
59+
case DeclarationName::CXXDeductionGuideName:
60+
case DeclarationName::CXXLiteralOperatorName:
61+
case DeclarationName::CXXUsingDirective:
62+
return false;
63+
}
4864
}
4965

5066
llvm::Optional<HighlightingKind> kindForType(const Type *TP);
@@ -73,25 +89,35 @@ llvm::Optional<HighlightingKind> kindForDecl(const NamedDecl *D) {
7389
return llvm::None;
7490
return HighlightingKind::Class;
7591
}
76-
if (isa<ClassTemplateDecl>(D) || isa<RecordDecl>(D) ||
77-
isa<CXXConstructorDecl>(D))
92+
if (isa<ClassTemplateDecl, RecordDecl, CXXConstructorDecl, ObjCInterfaceDecl,
93+
ObjCImplementationDecl>(D))
7894
return HighlightingKind::Class;
95+
if (isa<ObjCProtocolDecl>(D))
96+
return HighlightingKind::Interface;
97+
if (isa<ObjCCategoryDecl>(D))
98+
return HighlightingKind::Namespace;
7999
if (auto *MD = dyn_cast<CXXMethodDecl>(D))
80100
return MD->isStatic() ? HighlightingKind::StaticMethod
81101
: HighlightingKind::Method;
82-
if (isa<FieldDecl>(D))
102+
if (auto *OMD = dyn_cast<ObjCMethodDecl>(D))
103+
return OMD->isClassMethod() ? HighlightingKind::StaticMethod
104+
: HighlightingKind::Method;
105+
if (isa<FieldDecl, ObjCPropertyDecl>(D))
83106
return HighlightingKind::Field;
84107
if (isa<EnumDecl>(D))
85108
return HighlightingKind::Enum;
86109
if (isa<EnumConstantDecl>(D))
87110
return HighlightingKind::EnumConstant;
88111
if (isa<ParmVarDecl>(D))
89112
return HighlightingKind::Parameter;
90-
if (auto *VD = dyn_cast<VarDecl>(D))
113+
if (auto *VD = dyn_cast<VarDecl>(D)) {
114+
if (isa<ImplicitParamDecl>(VD)) // e.g. ObjC Self
115+
return llvm::None;
91116
return VD->isStaticDataMember()
92117
? HighlightingKind::StaticField
93118
: VD->isLocalVarDecl() ? HighlightingKind::LocalVariable
94119
: HighlightingKind::Variable;
120+
}
95121
if (const auto *BD = dyn_cast<BindingDecl>(D))
96122
return BD->getDeclContext()->isFunctionOrMethod()
97123
? HighlightingKind::LocalVariable
@@ -115,6 +141,8 @@ llvm::Optional<HighlightingKind> kindForType(const Type *TP) {
115141
return HighlightingKind::Primitive;
116142
if (auto *TD = dyn_cast<TemplateTypeParmType>(TP))
117143
return kindForDecl(TD->getDecl());
144+
if (isa<ObjCObjectPointerType>(TP))
145+
return HighlightingKind::Class;
118146
if (auto *TD = TP->getAsTagDecl())
119147
return kindForDecl(TD);
120148
return llvm::None;
@@ -439,6 +467,36 @@ class CollectExtraHighlightings
439467
return true;
440468
}
441469

470+
// We handle objective-C selectors specially, because one reference can
471+
// cover several non-contiguous tokens.
472+
void highlightObjCSelector(const ArrayRef<SourceLocation> &Locs, bool Decl,
473+
bool Class) {
474+
HighlightingKind Kind =
475+
Class ? HighlightingKind::StaticMethod : HighlightingKind::Method;
476+
for (SourceLocation Part : Locs) {
477+
auto &Tok =
478+
H.addToken(Part, Kind).addModifier(HighlightingModifier::ClassScope);
479+
if (Decl)
480+
Tok.addModifier(HighlightingModifier::Declaration);
481+
if (Class)
482+
Tok.addModifier(HighlightingModifier::Static);
483+
}
484+
}
485+
486+
bool VisitObjCMethodDecl(ObjCMethodDecl *OMD) {
487+
llvm::SmallVector<SourceLocation> Locs;
488+
OMD->getSelectorLocs(Locs);
489+
highlightObjCSelector(Locs, /*Decl=*/true, OMD->isClassMethod());
490+
return true;
491+
}
492+
493+
bool VisitObjCMessageExpr(ObjCMessageExpr *OME) {
494+
llvm::SmallVector<SourceLocation> Locs;
495+
OME->getSelectorLocs(Locs);
496+
highlightObjCSelector(Locs, /*Decl=*/false, OME->isClassMessage());
497+
return true;
498+
}
499+
442500
bool VisitOverloadExpr(OverloadExpr *E) {
443501
if (!E->decls().empty())
444502
return true; // handled by findExplicitReferences.
@@ -602,6 +660,8 @@ llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, HighlightingKind K) {
602660
return OS << "StaticField";
603661
case HighlightingKind::Class:
604662
return OS << "Class";
663+
case HighlightingKind::Interface:
664+
return OS << "Interface";
605665
case HighlightingKind::Enum:
606666
return OS << "Enum";
607667
case HighlightingKind::EnumConstant:
@@ -695,6 +755,8 @@ llvm::StringRef toSemanticTokenType(HighlightingKind Kind) {
695755
return "property";
696756
case HighlightingKind::Class:
697757
return "class";
758+
case HighlightingKind::Interface:
759+
return "interface";
698760
case HighlightingKind::Enum:
699761
return "enum";
700762
case HighlightingKind::EnumConstant:

clang-tools-extra/clangd/SemanticHighlighting.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ enum class HighlightingKind {
3737
Field,
3838
StaticField,
3939
Class,
40+
Interface,
4041
Enum,
4142
EnumConstant,
4243
Typedef,

clang-tools-extra/clangd/XRefs.cpp

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -882,8 +882,8 @@ class ReferenceFinder : public index::IndexDataConsumer {
882882
};
883883

884884
ReferenceFinder(const ParsedAST &AST,
885-
const llvm::DenseSet<SymbolID> &TargetIDs)
886-
: AST(AST), TargetIDs(TargetIDs) {}
885+
const llvm::DenseSet<SymbolID> &TargetIDs, bool PerToken)
886+
: PerToken(PerToken), AST(AST), TargetIDs(TargetIDs) {}
887887

888888
std::vector<Reference> take() && {
889889
llvm::sort(References, [](const Reference &L, const Reference &R) {
@@ -915,21 +915,43 @@ class ReferenceFinder : public index::IndexDataConsumer {
915915
if (!TargetIDs.contains(ID))
916916
return true;
917917
const auto &TB = AST.getTokens();
918-
Loc = SM.getFileLoc(Loc);
919-
if (const auto *Tok = TB.spelledTokenAt(Loc))
920-
References.push_back({*Tok, Roles, ID});
918+
919+
llvm::SmallVector<SourceLocation, 1> Locs;
920+
if (PerToken) {
921+
// Check whether this is one of the few constructs where the reference
922+
// can be split over several tokens.
923+
if (auto *OME = llvm::dyn_cast_or_null<ObjCMessageExpr>(ASTNode.OrigE)) {
924+
OME->getSelectorLocs(Locs);
925+
} else if (auto *OMD =
926+
llvm::dyn_cast_or_null<ObjCMethodDecl>(ASTNode.OrigD)) {
927+
OMD->getSelectorLocs(Locs);
928+
}
929+
// Sanity check: we expect the *first* token to match the reported loc.
930+
// Otherwise, maybe it was e.g. some other kind of reference to a Decl.
931+
if (!Locs.empty() && Locs.front() != Loc)
932+
Locs.clear(); // First token doesn't match, assume our guess was wrong.
933+
}
934+
if (Locs.empty())
935+
Locs.push_back(Loc);
936+
937+
for (SourceLocation L : Locs) {
938+
L = SM.getFileLoc(L);
939+
if (const auto *Tok = TB.spelledTokenAt(L))
940+
References.push_back({*Tok, Roles, ID});
941+
}
921942
return true;
922943
}
923944

924945
private:
946+
bool PerToken; // If true, report 3 references for split ObjC selector names.
925947
std::vector<Reference> References;
926948
const ParsedAST &AST;
927949
const llvm::DenseSet<SymbolID> &TargetIDs;
928950
};
929951

930952
std::vector<ReferenceFinder::Reference>
931-
findRefs(const llvm::DenseSet<SymbolID> &IDs, ParsedAST &AST) {
932-
ReferenceFinder RefFinder(AST, IDs);
953+
findRefs(const llvm::DenseSet<SymbolID> &IDs, ParsedAST &AST, bool PerToken) {
954+
ReferenceFinder RefFinder(AST, IDs, PerToken);
933955
index::IndexingOptions IndexOpts;
934956
IndexOpts.SystemSymbolFilter =
935957
index::IndexingOptions::SystemSymbolFilterKind::All;
@@ -1224,7 +1246,7 @@ std::vector<DocumentHighlight> findDocumentHighlights(ParsedAST &AST,
12241246
for (const NamedDecl *ND : Decls)
12251247
if (auto ID = getSymbolID(ND))
12261248
Targets.insert(ID);
1227-
for (const auto &Ref : findRefs(Targets, AST))
1249+
for (const auto &Ref : findRefs(Targets, AST, /*PerToken=*/true))
12281250
Result.push_back(toHighlight(Ref, SM));
12291251
return true;
12301252
}
@@ -1376,7 +1398,7 @@ ReferencesResult findReferences(ParsedAST &AST, Position Pos, uint32_t Limit,
13761398
}
13771399

13781400
// We traverse the AST to find references in the main file.
1379-
auto MainFileRefs = findRefs(Targets, AST);
1401+
auto MainFileRefs = findRefs(Targets, AST, /*PerToken=*/false);
13801402
// We may get multiple refs with the same location and different Roles, as
13811403
// cross-reference is only interested in locations, we deduplicate them
13821404
// by the location to avoid emitting duplicated locations.

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1593,6 +1593,35 @@ TEST_F(FindExplicitReferencesTest, All) {
15931593
"0: targets = {f}\n"
15941594
"1: targets = {I::x}\n"
15951595
"2: targets = {I::setY:}\n"},
1596+
{
1597+
// Objective-C: methods
1598+
R"cpp(
1599+
@interface I
1600+
-(void) a:(int)x b:(int)y;
1601+
@end
1602+
void foo(I *i) {
1603+
[$0^i $1^a:1 b:2];
1604+
}
1605+
)cpp",
1606+
"0: targets = {i}\n"
1607+
"1: targets = {I::a:b:}\n"
1608+
},
1609+
{
1610+
// Objective-C: protocols
1611+
R"cpp(
1612+
@interface I
1613+
@end
1614+
@protocol P
1615+
@end
1616+
void foo() {
1617+
// FIXME: should reference P
1618+
$0^I<P> *$1^x;
1619+
}
1620+
)cpp",
1621+
"0: targets = {I}\n"
1622+
"1: targets = {x}, decl\n"
1623+
},
1624+
15961625
// Designated initializers.
15971626
{R"cpp(
15981627
void foo() {

0 commit comments

Comments
 (0)