Mercurial > hg > CbC > CbC_llvm
diff clang-tools-extra/clangd/SemanticHighlighting.cpp @ 221:79ff65ed7e25
LLVM12 Original
author | Shinji KONO <kono@ie.u-ryukyu.ac.jp> |
---|---|
date | Tue, 15 Jun 2021 19:15:29 +0900 |
parents | 0572611fdcc8 |
children | 5f17cb93ff66 |
line wrap: on
line diff
--- a/clang-tools-extra/clangd/SemanticHighlighting.cpp Tue Jun 15 19:13:43 2021 +0900 +++ b/clang-tools-extra/clangd/SemanticHighlighting.cpp Tue Jun 15 19:15:29 2021 +0900 @@ -8,6 +8,7 @@ #include "SemanticHighlighting.h" #include "FindTarget.h" +#include "HeuristicResolver.h" #include "ParsedAST.h" #include "Protocol.h" #include "SourceCode.h" @@ -15,6 +16,8 @@ #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" @@ -38,15 +41,34 @@ /// 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) + switch (Name.getNameKind()) { + case DeclarationName::Identifier: { + auto *II = Name.getAsIdentifierInfo(); + return II && !II->getName().empty(); + } + case DeclarationName::CXXConstructorName: + case DeclarationName::CXXDestructorName: return true; - auto *II = Name.getAsIdentifierInfo(); - return II && !II->getName().empty(); + case DeclarationName::ObjCZeroArgSelector: + case DeclarationName::ObjCOneArgSelector: + case DeclarationName::ObjCMultiArgSelector: + // Multi-arg selectors need special handling, and we handle 0/1 arg + // selectors there too. + return false; + case DeclarationName::CXXConversionFunctionName: + case DeclarationName::CXXOperatorName: + case DeclarationName::CXXDeductionGuideName: + case DeclarationName::CXXLiteralOperatorName: + case DeclarationName::CXXUsingDirective: + return false; + } + llvm_unreachable("invalid name kind"); } -llvm::Optional<HighlightingKind> kindForType(const Type *TP); -llvm::Optional<HighlightingKind> kindForDecl(const NamedDecl *D) { +llvm::Optional<HighlightingKind> kindForType(const Type *TP, + const HeuristicResolver *Resolver); +llvm::Optional<HighlightingKind> +kindForDecl(const NamedDecl *D, const HeuristicResolver *Resolver) { if (auto *USD = dyn_cast<UsingShadowDecl>(D)) { if (auto *Target = USD->getTargetDecl()) D = Target; @@ -57,7 +79,8 @@ } if (auto *TD = dyn_cast<TypedefNameDecl>(D)) { // We try to highlight typedefs as their underlying type. - if (auto K = kindForType(TD->getUnderlyingType().getTypePtrOrNull())) + if (auto K = + kindForType(TD->getUnderlyingType().getTypePtrOrNull(), Resolver)) return K; // And fallback to a generic kind if this fails. return HighlightingKind::Typedef; @@ -71,13 +94,20 @@ return llvm::None; return HighlightingKind::Class; } - if (isa<ClassTemplateDecl>(D) || isa<RecordDecl>(D) || - isa<CXXConstructorDecl>(D)) + if (isa<ClassTemplateDecl, RecordDecl, CXXConstructorDecl, ObjCInterfaceDecl, + ObjCImplementationDecl>(D)) return HighlightingKind::Class; + if (isa<ObjCProtocolDecl>(D)) + return HighlightingKind::Interface; + if (isa<ObjCCategoryDecl>(D)) + return HighlightingKind::Namespace; if (auto *MD = dyn_cast<CXXMethodDecl>(D)) return MD->isStatic() ? HighlightingKind::StaticMethod : HighlightingKind::Method; - if (isa<FieldDecl>(D)) + if (auto *OMD = dyn_cast<ObjCMethodDecl>(D)) + return OMD->isClassMethod() ? HighlightingKind::StaticMethod + : HighlightingKind::Method; + if (isa<FieldDecl, ObjCPropertyDecl>(D)) return HighlightingKind::Field; if (isa<EnumDecl>(D)) return HighlightingKind::Enum; @@ -85,13 +115,18 @@ return HighlightingKind::EnumConstant; if (isa<ParmVarDecl>(D)) return HighlightingKind::Parameter; - if (auto *VD = dyn_cast<VarDecl>(D)) + if (auto *VD = dyn_cast<VarDecl>(D)) { + if (isa<ImplicitParamDecl>(VD)) // e.g. ObjC Self + return llvm::None; return VD->isStaticDataMember() ? HighlightingKind::StaticField : VD->isLocalVarDecl() ? HighlightingKind::LocalVariable : HighlightingKind::Variable; - if (isa<BindingDecl>(D)) - return 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) || @@ -102,31 +137,136 @@ return HighlightingKind::TemplateParameter; if (isa<ConceptDecl>(D)) return HighlightingKind::Concept; + if (const auto *UUVD = dyn_cast<UnresolvedUsingValueDecl>(D)) { + auto Targets = Resolver->resolveUsingValueDecl(UUVD); + if (!Targets.empty()) { + return kindForDecl(Targets[0], Resolver); + } + return HighlightingKind::Unknown; + } return llvm::None; } -llvm::Optional<HighlightingKind> kindForType(const Type *TP) { +llvm::Optional<HighlightingKind> +kindForType(const Type *TP, const HeuristicResolver *Resolver) { 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()); + return kindForDecl(TD->getDecl(), Resolver); + if (isa<ObjCObjectPointerType>(TP)) + return HighlightingKind::Class; if (auto *TD = TP->getAsTagDecl()) - return kindForDecl(TD); + return kindForDecl(TD, Resolver); return llvm::None; } -llvm::Optional<HighlightingKind> kindForReference(const ReferenceLoc &R) { - llvm::Optional<HighlightingKind> Result; - for (const NamedDecl *Decl : R.Targets) { - if (!canHighlightName(Decl->getDeclName())) - return llvm::None; - auto Kind = kindForDecl(Decl); - if (!Kind || (Result && Kind != Result)) - return llvm::None; - Result = Kind; +// 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 Result; + 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; +} + +bool isDependent(const Decl *D) { + if (isa<UnresolvedUsingValueDecl>(D)) + return true; + return false; +} + +/// Returns true if `Decl` is considered to be from a default/system library. +/// This currently checks the systemness of the file by include type, although +/// different heuristics may be used in the future (e.g. sysroot paths). +bool isDefaultLibrary(const Decl *D) { + SourceLocation Loc = D->getLocation(); + if (!Loc.isValid()) + return false; + return D->getASTContext().getSourceManager().isInSystemHeader(Loc); +} + +bool isDefaultLibrary(const Type *T) { + if (!T) + return false; + const Type *Underlying = T->getPointeeOrArrayElementType(); + if (Underlying->isBuiltinType()) + return true; + if (auto *TD = dyn_cast<TemplateTypeParmType>(Underlying)) + return isDefaultLibrary(TD->getDecl()); + if (auto *TD = Underlying->getAsTagDecl()) + return isDefaultLibrary(TD); + return false; } // For a macro usage `DUMP(foo)`, we want: @@ -143,21 +283,31 @@ return getHighlightableSpellingToken(SM.getImmediateSpellingLoc(L), SM); } -unsigned evaluateHighlightPriority(HighlightingKind Kind) { +unsigned evaluateHighlightPriority(const HighlightingToken &Tok) { enum HighlightPriority { Dependent = 0, Resolved = 1 }; - return Kind == HighlightingKind::DependentType || - Kind == HighlightingKind::DependentName + return (Tok.Modifiers & (1 << uint32_t(HighlightingModifier::DependentName))) ? Dependent : Resolved; } -// Sometimes we get conflicts between findExplicitReferences() returning -// a heuristic result for a dependent name (e.g. Method) and -// CollectExtraHighlighting returning a fallback dependent highlighting (e.g. -// DependentName). In such cases, resolve the conflict in favour of the -// resolved (non-dependent) highlighting. -// With macros we can get other conflicts (if a spelled token has multiple -// expansions with different token types) which we can't usefully resolve. +// 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) @@ -166,11 +316,13 @@ if (Tokens.size() != 2) return llvm::None; - unsigned Priority1 = evaluateHighlightPriority(Tokens[0].Kind); - unsigned Priority2 = evaluateHighlightPriority(Tokens[1].Kind); - if (Priority1 == Priority2) + unsigned Priority1 = evaluateHighlightPriority(Tokens[0]); + unsigned Priority2 = evaluateHighlightPriority(Tokens[1]); + if (Priority1 == Priority2 && Tokens[0].Kind != Tokens[1].Kind) return llvm::None; - return Priority1 > Priority2 ? Tokens[0] : Tokens[1]; + 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. @@ -180,18 +332,24 @@ : TB(AST.getTokens()), SourceMgr(AST.getSourceManager()), LangOpts(AST.getLangOpts()) {} - void addToken(HighlightingToken T) { Tokens.push_back(T); } - - void addToken(SourceLocation Loc, HighlightingKind Kind) { + HighlightingToken &addToken(SourceLocation Loc, HighlightingKind Kind) { Loc = getHighlightableSpellingToken(Loc, SourceMgr); if (Loc.isInvalid()) - return; + return InvalidHighlightingToken; const auto *Tok = TB.spelledTokenAt(Loc); assert(Tok); + return addToken( + halfOpenToRange(SourceMgr, + Tok->range(SourceMgr).toCharRange(SourceMgr)), + Kind); + } - auto Range = halfOpenToRange(SourceMgr, - Tok->range(SourceMgr).toCharRange(SourceMgr)); - Tokens.push_back(HighlightingToken{Kind, std::move(Range)}); + 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) && { @@ -219,32 +377,112 @@ // the end of the Tokens). TokRef = TokRef.drop_front(Conflicting.size()); } - // Add tokens indicating lines skipped by the preprocessor. - for (const Range &R : AST.getMacros().SkippedRanges) { + 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) { - // Don't bother computing the offset for the end of the line, just use - // zero. The client will treat this highlighting kind specially, and - // highlight the entire line visually (i.e. not just to where the text - // on the line ends, but to the end of the screen). - NonConflicting.push_back({HighlightingKind::InactiveCode, - {Position{Line, 0}, Position{Line, 0}}}); + 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; } } - // Re-sort the tokens because that's what the diffing expects. - llvm::sort(NonConflicting); - return NonConflicting; + // Copy tokens after the last inactive line + for (; It != NonConflicting.end(); ++It) + WithInactiveLines.push_back(std::move(*It)); + return WithInactiveLines; } + const HeuristicResolver *getResolver() const { return Resolver; } + private: const syntax::TokenBuffer &TB; const SourceManager &SourceMgr; const LangOptions &LangOpts; std::vector<HighlightingToken> Tokens; + const HeuristicResolver *Resolver; + // returned from addToken(InvalidLoc) + HighlightingToken InvalidHighlightingToken; }; +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 @@ -253,8 +491,14 @@ CollectExtraHighlightings(HighlightingsBuilder &H) : H(H) {} bool VisitDecltypeTypeLoc(DecltypeTypeLoc L) { - if (auto K = kindForType(L.getTypePtr())) - H.addToken(L.getBeginLoc(), *K); + if (auto K = kindForType(L.getTypePtr(), H.getResolver())) { + auto &Tok = H.addToken(L.getBeginLoc(), *K) + .addModifier(HighlightingModifier::Deduced); + if (auto Mod = scopeModifier(L.getTypePtr())) + Tok.addModifier(*Mod); + if (isDefaultLibrary(L.getTypePtr())) + Tok.addModifier(HighlightingModifier::DefaultLibrary); + } return true; } @@ -262,40 +506,125 @@ auto *AT = D->getType()->getContainedAutoType(); if (!AT) return true; - if (auto K = kindForType(AT->getDeducedType().getTypePtrOrNull())) - H.addToken(D->getTypeSpecStartLoc(), *K); + if (auto K = kindForType(AT->getDeducedType().getTypePtrOrNull(), + H.getResolver())) { + auto &Tok = H.addToken(D->getTypeSpecStartLoc(), *K) + .addModifier(HighlightingModifier::Deduced); + const Type *Deduced = AT->getDeducedType().getTypePtrOrNull(); + if (auto Mod = scopeModifier(Deduced)) + Tok.addModifier(*Mod); + if (isDefaultLibrary(Deduced)) + Tok.addModifier(HighlightingModifier::DefaultLibrary); + } + return true; + } + + // We handle objective-C selectors specially, because one reference can + // cover several non-contiguous tokens. + void highlightObjCSelector(const ArrayRef<SourceLocation> &Locs, bool Decl, + bool Class, bool DefaultLibrary) { + HighlightingKind Kind = + Class ? HighlightingKind::StaticMethod : HighlightingKind::Method; + for (SourceLocation Part : Locs) { + auto &Tok = + H.addToken(Part, Kind).addModifier(HighlightingModifier::ClassScope); + if (Decl) + Tok.addModifier(HighlightingModifier::Declaration); + if (Class) + Tok.addModifier(HighlightingModifier::Static); + if (DefaultLibrary) + Tok.addModifier(HighlightingModifier::DefaultLibrary); + } + } + + bool VisitObjCMethodDecl(ObjCMethodDecl *OMD) { + llvm::SmallVector<SourceLocation> Locs; + OMD->getSelectorLocs(Locs); + highlightObjCSelector(Locs, /*Decl=*/true, OMD->isClassMethod(), + isDefaultLibrary(OMD)); + return true; + } + + bool VisitObjCMessageExpr(ObjCMessageExpr *OME) { + llvm::SmallVector<SourceLocation> Locs; + OME->getSelectorLocs(Locs); + bool DefaultLibrary = false; + if (ObjCMethodDecl *OMD = OME->getMethodDecl()) + DefaultLibrary = isDefaultLibrary(OMD); + highlightObjCSelector(Locs, /*Decl=*/false, OME->isClassMessage(), + DefaultLibrary); return true; } bool VisitOverloadExpr(OverloadExpr *E) { if (!E->decls().empty()) return true; // handled by findExplicitReferences. - H.addToken(E->getNameLoc(), HighlightingKind::DependentName); + 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::DependentName); + 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::DependentName); + H.addToken(E->getNameInfo().getLoc(), HighlightingKind::Unknown) + .addModifier(HighlightingModifier::DependentName) + .addModifier(HighlightingModifier::ClassScope); return true; } bool VisitDependentNameTypeLoc(DependentNameTypeLoc L) { - H.addToken(L.getNameLoc(), HighlightingKind::DependentType); + H.addToken(L.getNameLoc(), HighlightingKind::Type) + .addModifier(HighlightingModifier::DependentName) + .addModifier(HighlightingModifier::ClassScope); return true; } bool VisitDependentTemplateSpecializationTypeLoc( DependentTemplateSpecializationTypeLoc L) { - H.addToken(L.getTemplateNameLoc(), HighlightingKind::DependentType); + 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 @@ -304,7 +633,9 @@ bool TraverseNestedNameSpecifierLoc(NestedNameSpecifierLoc Q) { if (NestedNameSpecifier *NNS = Q.getNestedNameSpecifier()) { if (NNS->getKind() == NestedNameSpecifier::Identifier) - H.addToken(Q.getLocalBeginLoc(), HighlightingKind::DependentType); + H.addToken(Q.getLocalBeginLoc(), HighlightingKind::Type) + .addModifier(HighlightingModifier::DependentName) + .addModifier(HighlightingModifier::ClassScope); } return RecursiveASTVisitor::TraverseNestedNameSpecifierLoc(Q); } @@ -312,29 +643,6 @@ private: HighlightingsBuilder &H; }; - -void write32be(uint32_t I, llvm::raw_ostream &OS) { - std::array<char, 4> Buf; - llvm::support::endian::write32be(Buf.data(), I); - OS.write(Buf.data(), Buf.size()); -} - -void write16be(uint16_t I, llvm::raw_ostream &OS) { - std::array<char, 2> Buf; - llvm::support::endian::write16be(Buf.data(), I); - OS.write(Buf.data(), Buf.size()); -} - -// Get the highlightings on \c Line where the first entry of line is at \c -// StartLineIt. If it is not at \c StartLineIt an empty vector is returned. -ArrayRef<HighlightingToken> -takeLine(ArrayRef<HighlightingToken> AllTokens, - ArrayRef<HighlightingToken>::iterator StartLineIt, int Line) { - return ArrayRef<HighlightingToken>(StartLineIt, AllTokens.end()) - .take_while([Line](const HighlightingToken &Token) { - return Token.R.start.line == Line; - }); -} } // namespace std::vector<HighlightingToken> getSemanticHighlightings(ParsedAST &AST) { @@ -344,17 +652,56 @@ // 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) { - if (auto Kind = kindForReference(R)) - Builder.addToken(R.NameLoc, *Kind); - }); + findExplicitReferences( + C, + [&](ReferenceLoc R) { + for (const NamedDecl *Decl : R.Targets) { + if (!canHighlightName(Decl->getDeclName())) + continue; + auto Kind = kindForDecl(Decl, AST.getHeuristicResolver()); + 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 (isDependent(Decl)) + Tok.addModifier(HighlightingModifier::DependentName); + if (isDefaultLibrary(Decl)) + Tok.addModifier(HighlightingModifier::DefaultLibrary); + if (Decl->isDeprecated()) + Tok.addModifier(HighlightingModifier::Deprecated); + // Do not treat an UnresolvedUsingValueDecl as a declaration. + // It's more common to think of it as a reference to the + // underlying declaration. + if (R.IsDecl && !isa<UnresolvedUsingValueDecl>(Decl)) + Tok.addModifier(HighlightingModifier::Declaration); + } + }, + AST.getHeuristicResolver()); // Add highlightings for macro references. - for (const auto &SIDToRefs : AST.getMacros().MacroRefs) { + 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) - Builder.addToken({HighlightingKind::Macro, M}); - } + AddMacro(M); for (const auto &M : AST.getMacros().UnknownMacros) - Builder.addToken({HighlightingKind::Macro, M}); + AddMacro(M); return std::move(Builder).collect(AST); } @@ -379,16 +726,18 @@ return OS << "StaticField"; case HighlightingKind::Class: return OS << "Class"; + case HighlightingKind::Interface: + return OS << "Interface"; case HighlightingKind::Enum: return OS << "Enum"; case HighlightingKind::EnumConstant: return OS << "EnumConstant"; case HighlightingKind::Typedef: return OS << "Typedef"; - case HighlightingKind::DependentType: - return OS << "DependentType"; - case HighlightingKind::DependentName: - return OS << "DependentName"; + case HighlightingKind::Type: + return OS << "Type"; + case HighlightingKind::Unknown: + return OS << "Unknown"; case HighlightingKind::Namespace: return OS << "Namespace"; case HighlightingKind::TemplateParameter: @@ -404,73 +753,22 @@ } llvm_unreachable("invalid HighlightingKind"); } - -std::vector<LineHighlightings> -diffHighlightings(ArrayRef<HighlightingToken> New, - ArrayRef<HighlightingToken> Old) { - assert(std::is_sorted(New.begin(), New.end()) && - "New must be a sorted vector"); - assert(std::is_sorted(Old.begin(), Old.end()) && - "Old must be a sorted vector"); - - // FIXME: There's an edge case when tokens span multiple lines. If the first - // token on the line started on a line above the current one and the rest of - // the line is the equal to the previous one than we will remove all - // highlights but the ones for the token spanning multiple lines. This means - // that when we get into the LSP layer the only highlights that will be - // visible are the ones for the token spanning multiple lines. - // Example: - // EndOfMultilineToken Token Token Token - // If "Token Token Token" don't differ from previously the line is - // incorrectly removed. Suggestion to fix is to separate any multiline tokens - // into one token for every line it covers. This requires reading from the - // file buffer to figure out the length of each line though. - std::vector<LineHighlightings> DiffedLines; - // ArrayRefs to the current line in the highlightings. - ArrayRef<HighlightingToken> NewLine(New.begin(), - /*length*/ static_cast<size_t>(0)); - ArrayRef<HighlightingToken> OldLine(Old.begin(), - /*length*/ static_cast<size_t>(0)); - auto NewEnd = New.end(); - auto OldEnd = Old.end(); - auto NextLineNumber = [&]() { - int NextNew = NewLine.end() != NewEnd ? NewLine.end()->R.start.line - : std::numeric_limits<int>::max(); - int NextOld = OldLine.end() != OldEnd ? OldLine.end()->R.start.line - : std::numeric_limits<int>::max(); - return std::min(NextNew, NextOld); - }; - - for (int LineNumber = 0; NewLine.end() < NewEnd || OldLine.end() < OldEnd; - LineNumber = NextLineNumber()) { - NewLine = takeLine(New, NewLine.end(), LineNumber); - OldLine = takeLine(Old, OldLine.end(), LineNumber); - if (NewLine != OldLine) { - DiffedLines.push_back({LineNumber, NewLine, /*IsInactive=*/false}); - - // Turn a HighlightingKind::InactiveCode token into the IsInactive flag. - auto &AddedLine = DiffedLines.back(); - llvm::erase_if(AddedLine.Tokens, [&](const HighlightingToken &T) { - if (T.Kind == HighlightingKind::InactiveCode) { - AddedLine.IsInactive = true; - return true; - } - return false; - }); - } +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); } - - return DiffedLines; } bool operator==(const HighlightingToken &L, const HighlightingToken &R) { - return std::tie(L.R, L.Kind) == std::tie(R.R, R.Kind); + 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) < std::tie(R.R, R.Kind); -} -bool operator==(const LineHighlightings &L, const LineHighlightings &R) { - return std::tie(L.Line, L.Tokens) == std::tie(R.Line, R.Tokens); + return std::tie(L.R, L.Kind, R.Modifiers) < + std::tie(R.R, R.Kind, R.Modifiers); } std::vector<SemanticToken> @@ -479,9 +777,6 @@ std::vector<SemanticToken> Result; const HighlightingToken *Last = nullptr; for (const HighlightingToken &Tok : Tokens) { - // FIXME: support inactive code - we need to provide the actual bounds. - if (Tok.Kind == HighlightingKind::InactiveCode) - continue; Result.emplace_back(); SemanticToken &Out = Result.back(); // deltaStart/deltaLine are relative if possible. @@ -501,6 +796,7 @@ 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; } @@ -517,24 +813,25 @@ case HighlightingKind::Function: return "function"; case HighlightingKind::Method: - return "member"; + return "method"; case HighlightingKind::StaticMethod: - // FIXME: better function/member with static modifier? + // FIXME: better method with static modifier? return "function"; case HighlightingKind::Field: - return "member"; + return "property"; case HighlightingKind::Class: return "class"; + case HighlightingKind::Interface: + return "interface"; case HighlightingKind::Enum: return "enum"; case HighlightingKind::EnumConstant: - return "enumConstant"; // nonstandard + return "enumMember"; case HighlightingKind::Typedef: + case HighlightingKind::Type: return "type"; - case HighlightingKind::DependentType: - return "dependent"; // nonstandard - case HighlightingKind::DependentName: - return "dependent"; // nonstandard + case HighlightingKind::Unknown: + return "unknown"; // nonstandard case HighlightingKind::Namespace: return "namespace"; case HighlightingKind::TemplateParameter: @@ -551,81 +848,34 @@ llvm_unreachable("unhandled HighlightingKind"); } -std::vector<TheiaSemanticHighlightingInformation> -toTheiaSemanticHighlightingInformation( - llvm::ArrayRef<LineHighlightings> Tokens) { - if (Tokens.size() == 0) - return {}; - - // FIXME: Tokens might be multiple lines long (block comments) in this case - // this needs to add multiple lines for those tokens. - std::vector<TheiaSemanticHighlightingInformation> Lines; - Lines.reserve(Tokens.size()); - for (const auto &Line : Tokens) { - llvm::SmallVector<char, 128> LineByteTokens; - llvm::raw_svector_ostream OS(LineByteTokens); - for (const auto &Token : Line.Tokens) { - // Writes the token to LineByteTokens in the byte format specified by the - // LSP proposal. Described below. - // |<---- 4 bytes ---->|<-- 2 bytes -->|<--- 2 bytes -->| - // | character | length | index | - - write32be(Token.R.start.character, OS); - write16be(Token.R.end.character - Token.R.start.character, OS); - write16be(static_cast<int>(Token.Kind), OS); - } - - Lines.push_back({Line.Line, encodeBase64(LineByteTokens), Line.IsInactive}); +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::DefaultLibrary: + return "defaultLibrary"; + case HighlightingModifier::FunctionScope: + return "functionScope"; // nonstandard + case HighlightingModifier::ClassScope: + return "classScope"; // nonstandard + case HighlightingModifier::FileScope: + return "fileScope"; // nonstandard + case HighlightingModifier::GlobalScope: + return "globalScope"; // nonstandard } - - return Lines; -} - -llvm::StringRef toTextMateScope(HighlightingKind Kind) { - // FIXME: Add scopes for C and Objective C. - switch (Kind) { - case HighlightingKind::Function: - return "entity.name.function.cpp"; - case HighlightingKind::Method: - return "entity.name.function.method.cpp"; - case HighlightingKind::StaticMethod: - return "entity.name.function.method.static.cpp"; - case HighlightingKind::Variable: - return "variable.other.cpp"; - case HighlightingKind::LocalVariable: - return "variable.other.local.cpp"; - case HighlightingKind::Parameter: - return "variable.parameter.cpp"; - case HighlightingKind::Field: - return "variable.other.field.cpp"; - case HighlightingKind::StaticField: - return "variable.other.field.static.cpp"; - case HighlightingKind::Class: - return "entity.name.type.class.cpp"; - case HighlightingKind::Enum: - return "entity.name.type.enum.cpp"; - case HighlightingKind::EnumConstant: - return "variable.other.enummember.cpp"; - case HighlightingKind::Typedef: - return "entity.name.type.typedef.cpp"; - case HighlightingKind::DependentType: - return "entity.name.type.dependent.cpp"; - case HighlightingKind::DependentName: - return "entity.name.other.dependent.cpp"; - case HighlightingKind::Namespace: - return "entity.name.namespace.cpp"; - case HighlightingKind::TemplateParameter: - return "entity.name.type.template.cpp"; - case HighlightingKind::Concept: - return "entity.name.type.concept.cpp"; - case HighlightingKind::Primitive: - return "storage.type.primitive.cpp"; - case HighlightingKind::Macro: - return "entity.name.function.preprocessor.cpp"; - case HighlightingKind::InactiveCode: - return "meta.disabled"; - } - llvm_unreachable("unhandled HighlightingKind"); + llvm_unreachable("unhandled HighlightingModifier"); } std::vector<SemanticTokensEdit>