llvm-project/clang-tools-extra/clangd/SemanticHighlighting.cpp
Nathan Ridge 9510b09402 [clangd] Factor out the heuristic resolver code into its own class
The patch also does some cleanup on the interface of the entry
points from TargetFinder into the heuristic resolution code.

Since the heuristic resolver is created in a place where the
ASTContext is available, it can store the ASTContext and the
NameFactory hack can be removed.

Differential revision: https://reviews.llvm.org/D92290
2021-02-16 04:10:52 -05:00

779 lines
28 KiB
C++

//===--- SemanticHighlighting.cpp - ------------------------- ---*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "SemanticHighlighting.h"
#include "FindTarget.h"
#include "ParsedAST.h"
#include "Protocol.h"
#include "SourceCode.h"
#include "support/Logger.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/DeclObjC.h"
#include "clang/AST/DeclTemplate.h"
#include "clang/AST/DeclarationName.h"
#include "clang/AST/ExprCXX.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/AST/Type.h"
#include "clang/AST/TypeLoc.h"
#include "clang/Basic/LangOptions.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Tooling/Syntax/Tokens.h"
#include "llvm/ADT/None.h"
#include "llvm/ADT/Optional.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/Support/Base64.h"
#include "llvm/Support/Casting.h"
#include <algorithm>
namespace clang {
namespace clangd {
namespace {
/// Some names are not written in the source code and cannot be highlighted,
/// e.g. anonymous classes. This function detects those cases.
bool canHighlightName(DeclarationName Name) {
if (Name.getNameKind() == DeclarationName::CXXConstructorName ||
Name.getNameKind() == DeclarationName::CXXUsingDirective)
return true;
auto *II = Name.getAsIdentifierInfo();
return II && !II->getName().empty();
}
llvm::Optional<HighlightingKind> kindForType(const Type *TP);
llvm::Optional<HighlightingKind> kindForDecl(const NamedDecl *D) {
if (auto *USD = dyn_cast<UsingShadowDecl>(D)) {
if (auto *Target = USD->getTargetDecl())
D = Target;
}
if (auto *TD = dyn_cast<TemplateDecl>(D)) {
if (auto *Templated = TD->getTemplatedDecl())
D = Templated;
}
if (auto *TD = dyn_cast<TypedefNameDecl>(D)) {
// We try to highlight typedefs as their underlying type.
if (auto K = kindForType(TD->getUnderlyingType().getTypePtrOrNull()))
return K;
// And fallback to a generic kind if this fails.
return HighlightingKind::Typedef;
}
// We highlight class decls, constructor decls and destructor decls as
// `Class` type. The destructor decls are handled in `VisitTagTypeLoc` (we
// will visit a TypeLoc where the underlying Type is a CXXRecordDecl).
if (auto *RD = llvm::dyn_cast<RecordDecl>(D)) {
// We don't want to highlight lambdas like classes.
if (RD->isLambda())
return llvm::None;
return HighlightingKind::Class;
}
if (isa<ClassTemplateDecl>(D) || isa<RecordDecl>(D) ||
isa<CXXConstructorDecl>(D))
return HighlightingKind::Class;
if (auto *MD = dyn_cast<CXXMethodDecl>(D))
return MD->isStatic() ? HighlightingKind::StaticMethod
: HighlightingKind::Method;
if (isa<FieldDecl>(D))
return HighlightingKind::Field;
if (isa<EnumDecl>(D))
return HighlightingKind::Enum;
if (isa<EnumConstantDecl>(D))
return HighlightingKind::EnumConstant;
if (isa<ParmVarDecl>(D))
return HighlightingKind::Parameter;
if (auto *VD = dyn_cast<VarDecl>(D))
return VD->isStaticDataMember()
? HighlightingKind::StaticField
: VD->isLocalVarDecl() ? HighlightingKind::LocalVariable
: HighlightingKind::Variable;
if (const auto *BD = dyn_cast<BindingDecl>(D))
return BD->getDeclContext()->isFunctionOrMethod()
? HighlightingKind::LocalVariable
: HighlightingKind::Variable;
if (isa<FunctionDecl>(D))
return HighlightingKind::Function;
if (isa<NamespaceDecl>(D) || isa<NamespaceAliasDecl>(D) ||
isa<UsingDirectiveDecl>(D))
return HighlightingKind::Namespace;
if (isa<TemplateTemplateParmDecl>(D) || isa<TemplateTypeParmDecl>(D) ||
isa<NonTypeTemplateParmDecl>(D))
return HighlightingKind::TemplateParameter;
if (isa<ConceptDecl>(D))
return HighlightingKind::Concept;
return llvm::None;
}
llvm::Optional<HighlightingKind> kindForType(const Type *TP) {
if (!TP)
return llvm::None;
if (TP->isBuiltinType()) // Builtins are special, they do not have decls.
return HighlightingKind::Primitive;
if (auto *TD = dyn_cast<TemplateTypeParmType>(TP))
return kindForDecl(TD->getDecl());
if (auto *TD = TP->getAsTagDecl())
return kindForDecl(TD);
return llvm::None;
}
// Whether T is const in a loose sense - is a variable with this type readonly?
bool isConst(QualType T) {
if (T.isNull() || T->isDependentType())
return false;
T = T.getNonReferenceType();
if (T.isConstQualified())
return true;
if (const auto *AT = T->getAsArrayTypeUnsafe())
return isConst(AT->getElementType());
if (isConst(T->getPointeeType()))
return true;
return false;
}
// Whether D is const in a loose sense (should it be highlighted as such?)
// FIXME: This is separate from whether *a particular usage* can mutate D.
// We may want V in V.size() to be readonly even if V is mutable.
bool isConst(const Decl *D) {
if (llvm::isa<EnumConstantDecl>(D) || llvm::isa<NonTypeTemplateParmDecl>(D))
return true;
if (llvm::isa<FieldDecl>(D) || llvm::isa<VarDecl>(D) ||
llvm::isa<MSPropertyDecl>(D) || llvm::isa<BindingDecl>(D)) {
if (isConst(llvm::cast<ValueDecl>(D)->getType()))
return true;
}
if (const auto *OCPD = llvm::dyn_cast<ObjCPropertyDecl>(D)) {
if (OCPD->isReadOnly())
return true;
}
if (const auto *MPD = llvm::dyn_cast<MSPropertyDecl>(D)) {
if (!MPD->hasSetter())
return true;
}
if (const auto *CMD = llvm::dyn_cast<CXXMethodDecl>(D)) {
if (CMD->isConst())
return true;
}
return false;
}
// "Static" means many things in C++, only some get the "static" modifier.
//
// Meanings that do:
// - Members associated with the class rather than the instance.
// This is what 'static' most often means across languages.
// - static local variables
// These are similarly "detached from their context" by the static keyword.
// In practice, these are rarely used inside classes, reducing confusion.
//
// Meanings that don't:
// - Namespace-scoped variables, which have static storage class.
// This is implicit, so the keyword "static" isn't so strongly associated.
// If we want a modifier for these, "global scope" is probably the concept.
// - Namespace-scoped variables/functions explicitly marked "static".
// There the keyword changes *linkage* , which is a totally different concept.
// If we want to model this, "file scope" would be a nice modifier.
//
// This is confusing, and maybe we should use another name, but because "static"
// is a standard LSP modifier, having one with that name has advantages.
bool isStatic(const Decl *D) {
if (const auto *CMD = llvm::dyn_cast<CXXMethodDecl>(D))
return CMD->isStatic();
if (const VarDecl *VD = llvm::dyn_cast<VarDecl>(D))
return VD->isStaticDataMember() || VD->isStaticLocal();
if (const auto *OPD = llvm::dyn_cast<ObjCPropertyDecl>(D))
return OPD->isClassProperty();
if (const auto *OMD = llvm::dyn_cast<ObjCMethodDecl>(D))
return OMD->isClassMethod();
return false;
}
bool isAbstract(const Decl *D) {
if (const auto *CMD = llvm::dyn_cast<CXXMethodDecl>(D))
return CMD->isPure();
if (const auto *CRD = llvm::dyn_cast<CXXRecordDecl>(D))
return CRD->hasDefinition() && CRD->isAbstract();
return false;
}
// For a macro usage `DUMP(foo)`, we want:
// - DUMP --> "macro"
// - foo --> "variable".
SourceLocation getHighlightableSpellingToken(SourceLocation L,
const SourceManager &SM) {
if (L.isFileID())
return SM.isWrittenInMainFile(L) ? L : SourceLocation{};
// Tokens expanded from the macro body contribute no highlightings.
if (!SM.isMacroArgExpansion(L))
return {};
// Tokens expanded from macro args are potentially highlightable.
return getHighlightableSpellingToken(SM.getImmediateSpellingLoc(L), SM);
}
unsigned evaluateHighlightPriority(const HighlightingToken &Tok) {
enum HighlightPriority { Dependent = 0, Resolved = 1 };
return (Tok.Modifiers & (1 << uint32_t(HighlightingModifier::DependentName)))
? Dependent
: Resolved;
}
// Sometimes we get multiple tokens at the same location:
//
// - findExplicitReferences() returns a heuristic result for a dependent name
// (e.g. Method) and CollectExtraHighlighting returning a fallback dependent
// highlighting (e.g. Unknown+Dependent).
// - macro arguments are expanded multiple times and have different roles
// - broken code recovery produces several AST nodes at the same location
//
// We should either resolve these to a single token, or drop them all.
// Our heuristics are:
//
// - token kinds that come with "dependent-name" modifiers are less reliable
// (these tend to be vague, like Type or Unknown)
// - if we have multiple equally reliable kinds, drop token rather than guess
// - take the union of modifiers from all tokens
//
// In particular, heuristically resolved dependent names get their heuristic
// kind, plus the dependent modifier.
llvm::Optional<HighlightingToken>
resolveConflict(ArrayRef<HighlightingToken> Tokens) {
if (Tokens.size() == 1)
return Tokens[0];
if (Tokens.size() != 2)
return llvm::None;
unsigned Priority1 = evaluateHighlightPriority(Tokens[0]);
unsigned Priority2 = evaluateHighlightPriority(Tokens[1]);
if (Priority1 == Priority2 && Tokens[0].Kind != Tokens[1].Kind)
return llvm::None;
auto Result = Priority1 > Priority2 ? Tokens[0] : Tokens[1];
Result.Modifiers = Tokens[0].Modifiers | Tokens[1].Modifiers;
return Result;
}
/// Consumes source locations and maps them to text ranges for highlightings.
class HighlightingsBuilder {
public:
HighlightingsBuilder(const ParsedAST &AST)
: TB(AST.getTokens()), SourceMgr(AST.getSourceManager()),
LangOpts(AST.getLangOpts()) {}
HighlightingToken &addToken(SourceLocation Loc, HighlightingKind Kind) {
Loc = getHighlightableSpellingToken(Loc, SourceMgr);
if (Loc.isInvalid())
return Dummy;
const auto *Tok = TB.spelledTokenAt(Loc);
assert(Tok);
return addToken(
halfOpenToRange(SourceMgr,
Tok->range(SourceMgr).toCharRange(SourceMgr)),
Kind);
}
HighlightingToken &addToken(Range R, HighlightingKind Kind) {
HighlightingToken HT;
HT.R = std::move(R);
HT.Kind = Kind;
Tokens.push_back(std::move(HT));
return Tokens.back();
}
std::vector<HighlightingToken> collect(ParsedAST &AST) && {
// Initializer lists can give duplicates of tokens, therefore all tokens
// must be deduplicated.
llvm::sort(Tokens);
auto Last = std::unique(Tokens.begin(), Tokens.end());
Tokens.erase(Last, Tokens.end());
// Macros can give tokens that have the same source range but conflicting
// kinds. In this case all tokens sharing this source range should be
// removed.
std::vector<HighlightingToken> NonConflicting;
NonConflicting.reserve(Tokens.size());
for (ArrayRef<HighlightingToken> TokRef = Tokens; !TokRef.empty();) {
ArrayRef<HighlightingToken> Conflicting =
TokRef.take_while([&](const HighlightingToken &T) {
// TokRef is guaranteed at least one element here because otherwise
// this predicate would never fire.
return T.R == TokRef.front().R;
});
if (auto Resolved = resolveConflict(Conflicting))
NonConflicting.push_back(*Resolved);
// TokRef[Conflicting.size()] is the next token with a different range (or
// the end of the Tokens).
TokRef = TokRef.drop_front(Conflicting.size());
}
const auto &SM = AST.getSourceManager();
StringRef MainCode = SM.getBufferOrFake(SM.getMainFileID()).getBuffer();
// Merge token stream with "inactive line" markers.
std::vector<HighlightingToken> WithInactiveLines;
auto SortedSkippedRanges = AST.getMacros().SkippedRanges;
llvm::sort(SortedSkippedRanges);
auto It = NonConflicting.begin();
for (const Range &R : SortedSkippedRanges) {
// Create one token for each line in the skipped range, so it works
// with line-based diffing.
assert(R.start.line <= R.end.line);
for (int Line = R.start.line; Line <= R.end.line; ++Line) {
// If the end of the inactive range is at the beginning
// of a line, that line is not inactive.
if (Line == R.end.line && R.end.character == 0)
continue;
// Copy tokens before the inactive line
for (; It != NonConflicting.end() && It->R.start.line < Line; ++It)
WithInactiveLines.push_back(std::move(*It));
// Add a token for the inactive line itself.
auto StartOfLine = positionToOffset(MainCode, Position{Line, 0});
if (StartOfLine) {
StringRef LineText =
MainCode.drop_front(*StartOfLine).take_until([](char C) {
return C == '\n';
});
HighlightingToken HT;
WithInactiveLines.emplace_back();
WithInactiveLines.back().Kind = HighlightingKind::InactiveCode;
WithInactiveLines.back().R.start.line = Line;
WithInactiveLines.back().R.end.line = Line;
WithInactiveLines.back().R.end.character =
static_cast<int>(lspLength(LineText));
} else {
elog("Failed to convert position to offset: {0}",
StartOfLine.takeError());
}
// Skip any other tokens on the inactive line. e.g.
// `#ifndef Foo` is considered as part of an inactive region when Foo is
// defined, and there is a Foo macro token.
// FIXME: we should reduce the scope of the inactive region to not
// include the directive itself.
while (It != NonConflicting.end() && It->R.start.line == Line)
++It;
}
}
// Copy tokens after the last inactive line
for (; It != NonConflicting.end(); ++It)
WithInactiveLines.push_back(std::move(*It));
return WithInactiveLines;
}
private:
const syntax::TokenBuffer &TB;
const SourceManager &SourceMgr;
const LangOptions &LangOpts;
std::vector<HighlightingToken> Tokens;
HighlightingToken Dummy; // returned from addToken(InvalidLoc)
};
llvm::Optional<HighlightingModifier> scopeModifier(const NamedDecl *D) {
const DeclContext *DC = D->getDeclContext();
// Injected "Foo" within the class "Foo" has file scope, not class scope.
if (auto *R = dyn_cast_or_null<RecordDecl>(D))
if (R->isInjectedClassName())
DC = DC->getParent();
// Lambda captures are considered function scope, not class scope.
if (llvm::isa<FieldDecl>(D))
if (const auto *RD = llvm::dyn_cast<RecordDecl>(DC))
if (RD->isLambda())
return HighlightingModifier::FunctionScope;
// Walk up the DeclContext hierarchy until we find something interesting.
for (; !DC->isFileContext(); DC = DC->getParent()) {
if (DC->isFunctionOrMethod())
return HighlightingModifier::FunctionScope;
if (DC->isRecord())
return HighlightingModifier::ClassScope;
}
// Some template parameters (e.g. those for variable templates) don't have
// meaningful DeclContexts. That doesn't mean they're global!
if (DC->isTranslationUnit() && D->isTemplateParameter())
return llvm::None;
// ExternalLinkage threshold could be tweaked, e.g. module-visible as global.
if (D->getLinkageInternal() < ExternalLinkage)
return HighlightingModifier::FileScope;
return HighlightingModifier::GlobalScope;
}
llvm::Optional<HighlightingModifier> scopeModifier(const Type *T) {
if (!T)
return llvm::None;
if (T->isBuiltinType())
return HighlightingModifier::GlobalScope;
if (auto *TD = dyn_cast<TemplateTypeParmType>(T))
return scopeModifier(TD->getDecl());
if (auto *TD = T->getAsTagDecl())
return scopeModifier(TD);
return llvm::None;
}
/// Produces highlightings, which are not captured by findExplicitReferences,
/// e.g. highlights dependent names and 'auto' as the underlying type.
class CollectExtraHighlightings
: public RecursiveASTVisitor<CollectExtraHighlightings> {
public:
CollectExtraHighlightings(HighlightingsBuilder &H) : H(H) {}
bool VisitDecltypeTypeLoc(DecltypeTypeLoc L) {
if (auto K = kindForType(L.getTypePtr())) {
auto &Tok = H.addToken(L.getBeginLoc(), *K)
.addModifier(HighlightingModifier::Deduced);
if (auto Mod = scopeModifier(L.getTypePtr()))
Tok.addModifier(*Mod);
}
return true;
}
bool VisitDeclaratorDecl(DeclaratorDecl *D) {
auto *AT = D->getType()->getContainedAutoType();
if (!AT)
return true;
if (auto K = kindForType(AT->getDeducedType().getTypePtrOrNull())) {
auto &Tok = H.addToken(D->getTypeSpecStartLoc(), *K)
.addModifier(HighlightingModifier::Deduced);
if (auto Mod = scopeModifier(AT->getDeducedType().getTypePtrOrNull()))
Tok.addModifier(*Mod);
}
return true;
}
bool VisitOverloadExpr(OverloadExpr *E) {
if (!E->decls().empty())
return true; // handled by findExplicitReferences.
auto &Tok = H.addToken(E->getNameLoc(), HighlightingKind::Unknown)
.addModifier(HighlightingModifier::DependentName);
if (llvm::isa<UnresolvedMemberExpr>(E))
Tok.addModifier(HighlightingModifier::ClassScope);
// other case is UnresolvedLookupExpr, scope is unknown.
return true;
}
bool VisitCXXDependentScopeMemberExpr(CXXDependentScopeMemberExpr *E) {
H.addToken(E->getMemberNameInfo().getLoc(), HighlightingKind::Unknown)
.addModifier(HighlightingModifier::DependentName)
.addModifier(HighlightingModifier::ClassScope);
return true;
}
bool VisitDependentScopeDeclRefExpr(DependentScopeDeclRefExpr *E) {
H.addToken(E->getNameInfo().getLoc(), HighlightingKind::Unknown)
.addModifier(HighlightingModifier::DependentName)
.addModifier(HighlightingModifier::ClassScope);
return true;
}
bool VisitDependentNameTypeLoc(DependentNameTypeLoc L) {
H.addToken(L.getNameLoc(), HighlightingKind::Type)
.addModifier(HighlightingModifier::DependentName)
.addModifier(HighlightingModifier::ClassScope);
return true;
}
bool VisitDependentTemplateSpecializationTypeLoc(
DependentTemplateSpecializationTypeLoc L) {
H.addToken(L.getTemplateNameLoc(), HighlightingKind::Type)
.addModifier(HighlightingModifier::DependentName)
.addModifier(HighlightingModifier::ClassScope);
return true;
}
bool TraverseTemplateArgumentLoc(TemplateArgumentLoc L) {
// 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);
TemplateName N = L.getArgument().getAsTemplateOrTemplatePattern();
switch (N.getKind()) {
case TemplateName::OverloadedTemplate:
// Template template params must always be class templates.
// Don't bother to try to work out the scope here.
H.addToken(L.getTemplateNameLoc(), HighlightingKind::Class);
break;
case TemplateName::DependentTemplate:
case TemplateName::AssumedTemplate:
H.addToken(L.getTemplateNameLoc(), HighlightingKind::Class)
.addModifier(HighlightingModifier::DependentName);
break;
case TemplateName::Template:
case TemplateName::QualifiedTemplate:
case TemplateName::SubstTemplateTemplateParm:
case TemplateName::SubstTemplateTemplateParmPack:
// Names that could be resolved to a TemplateDecl are handled elsewhere.
break;
}
return RecursiveASTVisitor::TraverseTemplateArgumentLoc(L);
}
// findExplicitReferences will walk nested-name-specifiers and
// find anything that can be resolved to a Decl. However, non-leaf
// components of nested-name-specifiers which are dependent names
// (kind "Identifier") cannot be resolved to a decl, so we visit
// them here.
bool TraverseNestedNameSpecifierLoc(NestedNameSpecifierLoc Q) {
if (NestedNameSpecifier *NNS = Q.getNestedNameSpecifier()) {
if (NNS->getKind() == NestedNameSpecifier::Identifier)
H.addToken(Q.getLocalBeginLoc(), HighlightingKind::Type)
.addModifier(HighlightingModifier::DependentName)
.addModifier(HighlightingModifier::ClassScope);
}
return RecursiveASTVisitor::TraverseNestedNameSpecifierLoc(Q);
}
private:
HighlightingsBuilder &H;
};
} // namespace
std::vector<HighlightingToken> getSemanticHighlightings(ParsedAST &AST) {
auto &C = AST.getASTContext();
// Add highlightings for AST nodes.
HighlightingsBuilder Builder(AST);
// Highlight 'decltype' and 'auto' as their underlying types.
CollectExtraHighlightings(Builder).TraverseAST(C);
// Highlight all decls and references coming from the AST.
findExplicitReferences(
C,
[&](ReferenceLoc R) {
for (const NamedDecl *Decl : R.Targets) {
if (!canHighlightName(Decl->getDeclName()))
continue;
auto Kind = kindForDecl(Decl);
if (!Kind)
continue;
auto &Tok = Builder.addToken(R.NameLoc, *Kind);
// The attribute tests don't want to look at the template.
if (auto *TD = dyn_cast<TemplateDecl>(Decl)) {
if (auto *Templated = TD->getTemplatedDecl())
Decl = Templated;
}
if (auto Mod = scopeModifier(Decl))
Tok.addModifier(*Mod);
if (isConst(Decl))
Tok.addModifier(HighlightingModifier::Readonly);
if (isStatic(Decl))
Tok.addModifier(HighlightingModifier::Static);
if (isAbstract(Decl))
Tok.addModifier(HighlightingModifier::Abstract);
if (Decl->isDeprecated())
Tok.addModifier(HighlightingModifier::Deprecated);
if (R.IsDecl)
Tok.addModifier(HighlightingModifier::Declaration);
}
},
AST.getHeuristicResolver());
// Add highlightings for macro references.
auto AddMacro = [&](const MacroOccurrence &M) {
auto &T = Builder.addToken(M.Rng, HighlightingKind::Macro);
T.addModifier(HighlightingModifier::GlobalScope);
if (M.IsDefinition)
T.addModifier(HighlightingModifier::Declaration);
};
for (const auto &SIDToRefs : AST.getMacros().MacroRefs)
for (const auto &M : SIDToRefs.second)
AddMacro(M);
for (const auto &M : AST.getMacros().UnknownMacros)
AddMacro(M);
return std::move(Builder).collect(AST);
}
llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, HighlightingKind K) {
switch (K) {
case HighlightingKind::Variable:
return OS << "Variable";
case HighlightingKind::LocalVariable:
return OS << "LocalVariable";
case HighlightingKind::Parameter:
return OS << "Parameter";
case HighlightingKind::Function:
return OS << "Function";
case HighlightingKind::Method:
return OS << "Method";
case HighlightingKind::StaticMethod:
return OS << "StaticMethod";
case HighlightingKind::Field:
return OS << "Field";
case HighlightingKind::StaticField:
return OS << "StaticField";
case HighlightingKind::Class:
return OS << "Class";
case HighlightingKind::Enum:
return OS << "Enum";
case HighlightingKind::EnumConstant:
return OS << "EnumConstant";
case HighlightingKind::Typedef:
return OS << "Typedef";
case HighlightingKind::Type:
return OS << "Type";
case HighlightingKind::Unknown:
return OS << "Unknown";
case HighlightingKind::Namespace:
return OS << "Namespace";
case HighlightingKind::TemplateParameter:
return OS << "TemplateParameter";
case HighlightingKind::Concept:
return OS << "Concept";
case HighlightingKind::Primitive:
return OS << "Primitive";
case HighlightingKind::Macro:
return OS << "Macro";
case HighlightingKind::InactiveCode:
return OS << "InactiveCode";
}
llvm_unreachable("invalid HighlightingKind");
}
llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, HighlightingModifier K) {
switch (K) {
case HighlightingModifier::Declaration:
return OS << "decl"; // abbrevation for common case
default:
return OS << toSemanticTokenModifier(K);
}
}
bool operator==(const HighlightingToken &L, const HighlightingToken &R) {
return std::tie(L.R, L.Kind, L.Modifiers) ==
std::tie(R.R, R.Kind, R.Modifiers);
}
bool operator<(const HighlightingToken &L, const HighlightingToken &R) {
return std::tie(L.R, L.Kind, R.Modifiers) <
std::tie(R.R, R.Kind, R.Modifiers);
}
std::vector<SemanticToken>
toSemanticTokens(llvm::ArrayRef<HighlightingToken> Tokens) {
assert(std::is_sorted(Tokens.begin(), Tokens.end()));
std::vector<SemanticToken> Result;
const HighlightingToken *Last = nullptr;
for (const HighlightingToken &Tok : Tokens) {
Result.emplace_back();
SemanticToken &Out = Result.back();
// deltaStart/deltaLine are relative if possible.
if (Last) {
assert(Tok.R.start.line >= Last->R.start.line);
Out.deltaLine = Tok.R.start.line - Last->R.start.line;
if (Out.deltaLine == 0) {
assert(Tok.R.start.character >= Last->R.start.character);
Out.deltaStart = Tok.R.start.character - Last->R.start.character;
} else {
Out.deltaStart = Tok.R.start.character;
}
} else {
Out.deltaLine = Tok.R.start.line;
Out.deltaStart = Tok.R.start.character;
}
assert(Tok.R.end.line == Tok.R.start.line);
Out.length = Tok.R.end.character - Tok.R.start.character;
Out.tokenType = static_cast<unsigned>(Tok.Kind);
Out.tokenModifiers = Tok.Modifiers;
Last = &Tok;
}
return Result;
}
llvm::StringRef toSemanticTokenType(HighlightingKind Kind) {
switch (Kind) {
case HighlightingKind::Variable:
case HighlightingKind::LocalVariable:
case HighlightingKind::StaticField:
return "variable";
case HighlightingKind::Parameter:
return "parameter";
case HighlightingKind::Function:
return "function";
case HighlightingKind::Method:
return "method";
case HighlightingKind::StaticMethod:
// FIXME: better method with static modifier?
return "function";
case HighlightingKind::Field:
return "property";
case HighlightingKind::Class:
return "class";
case HighlightingKind::Enum:
return "enum";
case HighlightingKind::EnumConstant:
return "enumMember";
case HighlightingKind::Typedef:
case HighlightingKind::Type:
return "type";
case HighlightingKind::Unknown:
return "unknown"; // nonstandard
case HighlightingKind::Namespace:
return "namespace";
case HighlightingKind::TemplateParameter:
return "typeParameter";
case HighlightingKind::Concept:
return "concept"; // nonstandard
case HighlightingKind::Primitive:
return "type";
case HighlightingKind::Macro:
return "macro";
case HighlightingKind::InactiveCode:
return "comment";
}
llvm_unreachable("unhandled HighlightingKind");
}
llvm::StringRef toSemanticTokenModifier(HighlightingModifier Modifier) {
switch (Modifier) {
case HighlightingModifier::Declaration:
return "declaration";
case HighlightingModifier::Deprecated:
return "deprecated";
case HighlightingModifier::Readonly:
return "readonly";
case HighlightingModifier::Static:
return "static";
case HighlightingModifier::Deduced:
return "deduced"; // nonstandard
case HighlightingModifier::Abstract:
return "abstract";
case HighlightingModifier::DependentName:
return "dependentName"; // nonstandard
case HighlightingModifier::FunctionScope:
return "functionScope"; // nonstandard
case HighlightingModifier::ClassScope:
return "classScope"; // nonstandard
case HighlightingModifier::FileScope:
return "fileScope"; // nonstandard
case HighlightingModifier::GlobalScope:
return "globalScope"; // nonstandard
}
llvm_unreachable("unhandled HighlightingModifier");
}
std::vector<SemanticTokensEdit>
diffTokens(llvm::ArrayRef<SemanticToken> Old,
llvm::ArrayRef<SemanticToken> New) {
// For now, just replace everything from the first-last modification.
// FIXME: use a real diff instead, this is bad with include-insertion.
unsigned Offset = 0;
while (!Old.empty() && !New.empty() && Old.front() == New.front()) {
++Offset;
Old = Old.drop_front();
New = New.drop_front();
}
while (!Old.empty() && !New.empty() && Old.back() == New.back()) {
Old = Old.drop_back();
New = New.drop_back();
}
if (Old.empty() && New.empty())
return {};
SemanticTokensEdit Edit;
Edit.startToken = Offset;
Edit.deleteTokens = Old.size();
Edit.tokens = New;
return {std::move(Edit)};
}
} // namespace clangd
} // namespace clang