[clang-tools-extra] [clangd] Share semantic highlighting AST traversal code (PR #202628)
David Zbarsky via cfe-commits
cfe-commits at lists.llvm.org
Tue Jun 9 07:00:12 PDT 2026
https://github.com/dzbarsky created https://github.com/llvm/llvm-project/pull/202628
CollectExtraHighlightings derives from RecursiveASTVisitor, so
SemanticHighlighting.cpp emits a complete CRTP traversal implementation
specialized for one visitor.
Derive it from DynamicRecursiveASTVisitor instead. The visitor keeps the same
Visit* and TraverseTemplateArgumentLoc behavior while reusing traversal
functions that clangd already links. Mark each virtual callback override so
signature drift remains a compile-time error.
A fully stripped Release arm64 clangd decreases from 60,921,480 to 60,888,296
bytes, saving 33,184 bytes (0.054%). At this point in the cumulative patch stack, the stripped all-tools multicall
binary decreases from 150,586,136 to 150,569,536 bytes, saving 16,600 bytes
(0.011%). Measured independently, this change saves 49,120 bytes in the
multicall binary.
All seven SemanticHighlighting tests pass before and after the change. LLVM has
no semantic-highlighting benchmark. Seven focused test runs measured 1.432
seconds baseline versus 1.368 seconds patched mean user CPU time; wall time was
dominated by test scheduling noise.
Work towards #202616
>From 4495106d4584fa7a4d760186d1a25f6df0ba8fe3 Mon Sep 17 00:00:00 2001
From: David Zbarsky <dzbarsky at gmail.com>
Date: Mon, 8 Jun 2026 13:09:12 -0400
Subject: [PATCH] [clangd] Share semantic highlighting AST traversal code
CollectExtraHighlightings derives from RecursiveASTVisitor, so
SemanticHighlighting.cpp emits a complete CRTP traversal implementation
specialized for one visitor.
Derive it from DynamicRecursiveASTVisitor instead. The visitor keeps the same
Visit* and TraverseTemplateArgumentLoc behavior while reusing traversal
functions that clangd already links. Mark each virtual callback override so
signature drift remains a compile-time error.
A fully stripped Release arm64 clangd decreases from 60,921,480 to 60,888,296
bytes, saving 33,184 bytes (0.054%). At this point in the cumulative patch stack, the stripped all-tools multicall
binary decreases from 150,586,136 to 150,569,536 bytes, saving 16,600 bytes
(0.011%). Measured independently, this change saves 49,120 bytes in the
multicall binary.
All seven SemanticHighlighting tests pass before and after the change. LLVM has
no semantic-highlighting benchmark. Seven focused test runs measured 1.432
seconds baseline versus 1.368 seconds patched mean user CPU time; wall time was
dominated by test scheduling noise.
---
.../clangd/SemanticHighlighting.cpp | 84 ++++++++++---------
1 file changed, 43 insertions(+), 41 deletions(-)
diff --git a/clang-tools-extra/clangd/SemanticHighlighting.cpp b/clang-tools-extra/clangd/SemanticHighlighting.cpp
index 856904bc810d1..f85c8d2d7f2be 100644
--- a/clang-tools-extra/clangd/SemanticHighlighting.cpp
+++ b/clang-tools-extra/clangd/SemanticHighlighting.cpp
@@ -21,8 +21,8 @@
#include "clang/AST/DeclObjC.h"
#include "clang/AST/DeclTemplate.h"
#include "clang/AST/DeclarationName.h"
+#include "clang/AST/DynamicRecursiveASTVisitor.h"
#include "clang/AST/ExprCXX.h"
-#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/AST/TypeLoc.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Basic/SourceManager.h"
@@ -535,34 +535,33 @@ std::optional<HighlightingModifier> scopeModifier(const Type *T) {
/// Produces highlightings, which are not captured by findExplicitReferences,
/// e.g. highlights dependent names and 'auto' as the underlying type.
-class CollectExtraHighlightings
- : public RecursiveASTVisitor<CollectExtraHighlightings> {
- using Base = RecursiveASTVisitor<CollectExtraHighlightings>;
+class CollectExtraHighlightings : public DynamicRecursiveASTVisitor {
+ using Base = DynamicRecursiveASTVisitor;
public:
CollectExtraHighlightings(HighlightingsBuilder &H) : H(H) {}
- bool VisitCXXConstructExpr(CXXConstructExpr *E) {
+ bool VisitCXXConstructExpr(CXXConstructExpr *E) override {
highlightMutableReferenceArguments(E->getConstructor(),
{E->getArgs(), E->getNumArgs()});
return true;
}
- bool TraverseConstructorInitializer(CXXCtorInitializer *Init) {
+ bool TraverseConstructorInitializer(CXXCtorInitializer *Init) override {
if (Init->isMemberInitializer())
if (auto *Member = Init->getMember())
highlightMutableReferenceArgument(Member->getType(), Init->getInit());
return Base::TraverseConstructorInitializer(Init);
}
- bool TraverseTypeConstraint(const TypeConstraint *C) {
+ bool TraverseTypeConstraint(const TypeConstraint *C) override {
if (auto *Args = C->getTemplateArgsAsWritten())
H.addAngleBracketTokens(Args->getLAngleLoc(), Args->getRAngleLoc());
return Base::TraverseTypeConstraint(C);
}
- bool VisitPredefinedExpr(PredefinedExpr *E) {
+ bool VisitPredefinedExpr(PredefinedExpr *E) override {
H.addToken(E->getLocation(), HighlightingKind::LocalVariable)
.addModifier(HighlightingModifier::Static)
.addModifier(HighlightingModifier::Readonly)
@@ -570,26 +569,26 @@ class CollectExtraHighlightings
return true;
}
- bool VisitConceptSpecializationExpr(ConceptSpecializationExpr *E) {
+ bool VisitConceptSpecializationExpr(ConceptSpecializationExpr *E) override {
if (auto *Args = E->getTemplateArgsAsWritten())
H.addAngleBracketTokens(Args->getLAngleLoc(), Args->getRAngleLoc());
return true;
}
- bool VisitTemplateDecl(TemplateDecl *D) {
+ bool VisitTemplateDecl(TemplateDecl *D) override {
if (auto *TPL = D->getTemplateParameters())
H.addAngleBracketTokens(TPL->getLAngleLoc(), TPL->getRAngleLoc());
return true;
}
- bool VisitTagDecl(TagDecl *D) {
+ bool VisitTagDecl(TagDecl *D) override {
for (TemplateParameterList *TPL : D->getTemplateParameterLists())
H.addAngleBracketTokens(TPL->getLAngleLoc(), TPL->getRAngleLoc());
return true;
}
- bool
- VisitClassTemplateSpecializationDecl(ClassTemplateSpecializationDecl *D) {
+ bool VisitClassTemplateSpecializationDecl(
+ ClassTemplateSpecializationDecl *D) override {
if (const auto *Info = D->getExplicitInstantiationInfo()) {
H.addAngleBracketTokens(Info->TemplateArgsAsWritten->getLAngleLoc(),
Info->TemplateArgsAsWritten->getRAngleLoc());
@@ -602,7 +601,8 @@ class CollectExtraHighlightings
return true;
}
- bool VisitVarTemplateSpecializationDecl(VarTemplateSpecializationDecl *D) {
+ bool VisitVarTemplateSpecializationDecl(
+ VarTemplateSpecializationDecl *D) override {
if (const auto *Info = D->getExplicitInstantiationInfo()) {
H.addAngleBracketTokens(Info->TemplateArgsAsWritten->getLAngleLoc(),
Info->TemplateArgsAsWritten->getRAngleLoc());
@@ -615,16 +615,16 @@ class CollectExtraHighlightings
return true;
}
- bool VisitDeclRefExpr(DeclRefExpr *E) {
+ bool VisitDeclRefExpr(DeclRefExpr *E) override {
H.addAngleBracketTokens(E->getLAngleLoc(), E->getRAngleLoc());
return true;
}
- bool VisitMemberExpr(MemberExpr *E) {
+ bool VisitMemberExpr(MemberExpr *E) override {
H.addAngleBracketTokens(E->getLAngleLoc(), E->getRAngleLoc());
return true;
}
- bool VisitFunctionDecl(FunctionDecl *D) {
+ bool VisitFunctionDecl(FunctionDecl *D) override {
if (const TemplateParameterList *TPL =
D->getTemplateSpecializationParameters())
H.addAngleBracketTokens(TPL->getLAngleLoc(), TPL->getRAngleLoc());
@@ -646,7 +646,7 @@ class CollectExtraHighlightings
return true;
}
- bool VisitCXXOperatorCallExpr(CXXOperatorCallExpr *E) {
+ bool VisitCXXOperatorCallExpr(CXXOperatorCallExpr *E) override {
const auto AddOpToken = [&](SourceLocation Loc) {
H.addToken(Loc, HighlightingKind::Operator)
.addModifier(HighlightingModifier::UserDefined);
@@ -660,47 +660,47 @@ class CollectExtraHighlightings
return true;
}
- bool VisitUnaryOperator(UnaryOperator *Op) {
+ bool VisitUnaryOperator(UnaryOperator *Op) override {
auto &Token = H.addToken(Op->getOperatorLoc(), HighlightingKind::Operator);
if (Op->getSubExpr()->isTypeDependent())
Token.addModifier(HighlightingModifier::UserDefined);
return true;
}
- bool VisitBinaryOperator(BinaryOperator *Op) {
+ bool VisitBinaryOperator(BinaryOperator *Op) override {
auto &Token = H.addToken(Op->getOperatorLoc(), HighlightingKind::Operator);
if (Op->getLHS()->isTypeDependent() || Op->getRHS()->isTypeDependent())
Token.addModifier(HighlightingModifier::UserDefined);
return true;
}
- bool VisitConditionalOperator(ConditionalOperator *Op) {
+ bool VisitConditionalOperator(ConditionalOperator *Op) override {
H.addToken(Op->getQuestionLoc(), HighlightingKind::Operator);
H.addToken(Op->getColonLoc(), HighlightingKind::Operator);
return true;
}
- bool VisitCXXNewExpr(CXXNewExpr *E) {
+ bool VisitCXXNewExpr(CXXNewExpr *E) override {
auto &Token = H.addToken(E->getBeginLoc(), HighlightingKind::Operator);
if (isa_and_present<CXXMethodDecl>(E->getOperatorNew()))
Token.addModifier(HighlightingModifier::UserDefined);
return true;
}
- bool VisitCXXDeleteExpr(CXXDeleteExpr *E) {
+ bool VisitCXXDeleteExpr(CXXDeleteExpr *E) override {
auto &Token = H.addToken(E->getBeginLoc(), HighlightingKind::Operator);
if (isa_and_present<CXXMethodDecl>(E->getOperatorDelete()))
Token.addModifier(HighlightingModifier::UserDefined);
return true;
}
- bool VisitCXXNamedCastExpr(CXXNamedCastExpr *E) {
+ bool VisitCXXNamedCastExpr(CXXNamedCastExpr *E) override {
const auto &B = E->getAngleBrackets();
H.addAngleBracketTokens(B.getBegin(), B.getEnd());
return true;
}
- bool VisitCallExpr(CallExpr *E) {
+ bool VisitCallExpr(CallExpr *E) override {
// Highlighting parameters passed by non-const reference does not really
// make sense for literals...
if (isa<UserDefinedLiteral>(E))
@@ -779,7 +779,7 @@ class CollectExtraHighlightings
}
}
- bool VisitDecltypeTypeLoc(DecltypeTypeLoc L) {
+ bool VisitDecltypeTypeLoc(DecltypeTypeLoc L) override {
if (auto K = kindForType(L.getTypePtr(), H.getResolver())) {
auto &Tok = H.addToken(L.getBeginLoc(), *K)
.addModifier(HighlightingModifier::Deduced);
@@ -791,7 +791,7 @@ class CollectExtraHighlightings
return true;
}
- bool VisitCXXDestructorDecl(CXXDestructorDecl *D) {
+ bool VisitCXXDestructorDecl(CXXDestructorDecl *D) override {
SourceLocation Loc =
D->getNameInfo().getNamedTypeInfo()->getTypeLoc().getBeginLoc();
H.addExtraModifier(Loc, HighlightingModifier::ConstructorOrDestructor);
@@ -801,7 +801,7 @@ class CollectExtraHighlightings
return true;
}
- bool VisitCXXMemberCallExpr(CXXMemberCallExpr *CE) {
+ bool VisitCXXMemberCallExpr(CXXMemberCallExpr *CE) override {
// getMethodDecl can return nullptr with member pointers, e.g.
// `(foo.*pointer_to_member_fun)(arg);`
if (auto *D = CE->getMethodDecl()) {
@@ -823,7 +823,7 @@ class CollectExtraHighlightings
return true;
}
- bool VisitDeclaratorDecl(DeclaratorDecl *D) {
+ bool VisitDeclaratorDecl(DeclaratorDecl *D) override {
for (TemplateParameterList *TPL : D->getTemplateParameterLists())
H.addAngleBracketTokens(TPL->getLAngleLoc(), TPL->getRAngleLoc());
auto *AT = D->getType()->getContainedAutoType();
@@ -875,7 +875,7 @@ class CollectExtraHighlightings
}
}
- bool VisitObjCMethodDecl(ObjCMethodDecl *OMD) {
+ bool VisitObjCMethodDecl(ObjCMethodDecl *OMD) override {
llvm::SmallVector<SourceLocation> Locs;
OMD->getSelectorLocs(Locs);
highlightObjCSelector(Locs, /*Decl=*/true,
@@ -884,7 +884,7 @@ class CollectExtraHighlightings
return true;
}
- bool VisitObjCMessageExpr(ObjCMessageExpr *OME) {
+ bool VisitObjCMessageExpr(ObjCMessageExpr *OME) override {
llvm::SmallVector<SourceLocation> Locs;
OME->getSelectorLocs(Locs);
bool DefaultLibrary = false;
@@ -909,7 +909,7 @@ class CollectExtraHighlightings
Tok.addModifier(HighlightingModifier::DefaultLibrary);
}
- bool VisitObjCPropertyRefExpr(ObjCPropertyRefExpr *OPRE) {
+ bool VisitObjCPropertyRefExpr(ObjCPropertyRefExpr *OPRE) override {
// We need to handle implicit properties here since they will appear to
// reference `ObjCMethodDecl` via an implicit `ObjCMessageExpr`, so normal
// highlighting will not work.
@@ -931,7 +931,7 @@ class CollectExtraHighlightings
return true;
}
- bool VisitOverloadExpr(OverloadExpr *E) {
+ bool VisitOverloadExpr(OverloadExpr *E) override {
H.addAngleBracketTokens(E->getLAngleLoc(), E->getRAngleLoc());
if (!E->decls().empty())
return true; // handled by findExplicitReferences.
@@ -943,7 +943,8 @@ class CollectExtraHighlightings
return true;
}
- bool VisitCXXDependentScopeMemberExpr(CXXDependentScopeMemberExpr *E) {
+ bool
+ VisitCXXDependentScopeMemberExpr(CXXDependentScopeMemberExpr *E) override {
H.addToken(E->getMemberNameInfo().getLoc(), HighlightingKind::Unknown)
.addModifier(HighlightingModifier::DependentName)
.addModifier(HighlightingModifier::ClassScope);
@@ -951,7 +952,7 @@ class CollectExtraHighlightings
return true;
}
- bool VisitDependentScopeDeclRefExpr(DependentScopeDeclRefExpr *E) {
+ bool VisitDependentScopeDeclRefExpr(DependentScopeDeclRefExpr *E) override {
H.addToken(E->getNameInfo().getLoc(), HighlightingKind::Unknown)
.addModifier(HighlightingModifier::DependentName)
.addModifier(HighlightingModifier::ClassScope);
@@ -959,7 +960,7 @@ class CollectExtraHighlightings
return true;
}
- bool VisitAttr(Attr *A) {
+ bool VisitAttr(Attr *A) override {
switch (A->getKind()) {
case attr::Override:
case attr::Final:
@@ -971,14 +972,15 @@ class CollectExtraHighlightings
return true;
}
- bool VisitDependentNameTypeLoc(DependentNameTypeLoc L) {
+ bool VisitDependentNameTypeLoc(DependentNameTypeLoc L) override {
H.addToken(L.getNameLoc(), HighlightingKind::Type)
.addModifier(HighlightingModifier::DependentName)
.addModifier(HighlightingModifier::ClassScope);
return true;
}
- bool VisitTemplateSpecializationTypeLoc(TemplateSpecializationTypeLoc L) {
+ bool
+ VisitTemplateSpecializationTypeLoc(TemplateSpecializationTypeLoc L) override {
if (!L.getTypePtr()->getTemplateName().getAsTemplateDecl(
/*IgnoreDeduced=*/true))
H.addToken(L.getTemplateNameLoc(), HighlightingKind::Type)
@@ -988,12 +990,12 @@ class CollectExtraHighlightings
return true;
}
- bool TraverseTemplateArgumentLoc(TemplateArgumentLoc L) {
+ bool TraverseTemplateArgumentLoc(const TemplateArgumentLoc &L) override {
// Handle template template arguments only (other arguments are handled by
// their Expr, TypeLoc etc values).
if (L.getArgument().getKind() != TemplateArgument::Template &&
L.getArgument().getKind() != TemplateArgument::TemplateExpansion)
- return RecursiveASTVisitor::TraverseTemplateArgumentLoc(L);
+ return Base::TraverseTemplateArgumentLoc(L);
TemplateName N = L.getArgument().getAsTemplateOrTemplatePattern();
switch (N.getKind()) {
@@ -1016,7 +1018,7 @@ class CollectExtraHighlightings
// Names that could be resolved to a TemplateDecl are handled elsewhere.
break;
}
- return RecursiveASTVisitor::TraverseTemplateArgumentLoc(L);
+ return Base::TraverseTemplateArgumentLoc(L);
}
private:
More information about the cfe-commits
mailing list