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
CollectExtraHighlightings(HighlightingsBuilder &H) : H(H) {}
bool VisitDecltypeTypeLoc(DecltypeTypeLoc L) {
- if (auto K = kindForType(L.getTypePtr()))
- H.addToken(L.getBeginLoc(), *K)
- .addModifier(HighlightingModifier::Deduced);
+ 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;
}
auto *AT = D->getType()->getContainedAutoType();
if (!AT)
return true;
- if (auto K = kindForType(AT->getDeducedType().getTypePtrOrNull()))
- H.addToken(D->getTypeSpecStartLoc(), *K)
- .addModifier(HighlightingModifier::Deduced);
+ 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.
- H.addToken(E->getNameLoc(), HighlightingKind::DependentName);
+ auto &Tok = H.addToken(E->getNameLoc(), HighlightingKind::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::DependentName)
+ .addModifier(HighlightingModifier::ClassScope);
return true;
}
bool VisitDependentScopeDeclRefExpr(DependentScopeDeclRefExpr *E) {
- H.addToken(E->getNameInfo().getLoc(), HighlightingKind::DependentName);
+ H.addToken(E->getNameInfo().getLoc(), HighlightingKind::DependentName)
+ .addModifier(HighlightingModifier::ClassScope);
return true;
}
bool VisitDependentNameTypeLoc(DependentNameTypeLoc L) {
- H.addToken(L.getNameLoc(), HighlightingKind::DependentType);
+ H.addToken(L.getNameLoc(), HighlightingKind::DependentType)
+ .addModifier(HighlightingModifier::ClassScope);
return true;
}
bool VisitDependentTemplateSpecializationTypeLoc(
DependentTemplateSpecializationTypeLoc L) {
- H.addToken(L.getTemplateNameLoc(), HighlightingKind::DependentType);
+ H.addToken(L.getTemplateNameLoc(), HighlightingKind::DependentType)
+ .addModifier(HighlightingModifier::ClassScope);
return true;
}
switch (L.getArgument().getKind()) {
case TemplateArgument::Template:
case TemplateArgument::TemplateExpansion:
+ // FIXME: this isn't *always* a dependent template name.
H.addToken(L.getTemplateNameLoc(), HighlightingKind::DependentType);
break;
default:
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::DependentType)
+ .addModifier(HighlightingModifier::ClassScope);
}
return RecursiveASTVisitor::TraverseNestedNameSpecifierLoc(Q);
}
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))
// 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);
};
return "deduced"; // nonstandard
case HighlightingModifier::Abstract:
return "abstract";
+ 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<TheiaSemanticHighlightingInformation>
void checkHighlightings(llvm::StringRef Code,
std::vector<std::pair</*FileName*/ llvm::StringRef,
/*FileContent*/ llvm::StringRef>>
- AdditionalFiles = {}) {
+ AdditionalFiles = {},
+ uint32_t ModifierMask = -1) {
Annotations Test(Code);
TestTU TU;
TU.Code = std::string(Test.code());
for (auto File : AdditionalFiles)
TU.AdditionalFiles.insert({File.first, std::string(File.second)});
auto AST = TU.build();
+ auto Actual = getSemanticHighlightings(AST);
+ for (auto &Token : Actual)
+ Token.Modifiers &= ModifierMask;
- EXPECT_EQ(Code, annotate(Test.code(), getSemanticHighlightings(AST)));
+ EXPECT_EQ(Code, annotate(Test.code(), Actual));
}
// Any annotations in OldCode and NewCode are converted into their corresponding
<< OldCode;
}
+constexpr static uint32_t ScopeModifierMask =
+ 1 << unsigned(HighlightingModifier::FunctionScope) |
+ 1 << unsigned(HighlightingModifier::ClassScope) |
+ 1 << unsigned(HighlightingModifier::FileScope) |
+ 1 << unsigned(HighlightingModifier::GlobalScope);
+
TEST(SemanticHighlighting, GetsCorrectTokens) {
const char *TestCases[] = {
R"cpp(
<:[deprecated]:> int $Variable_decl_deprecated[[x]];
)cpp",
};
- for (const auto &TestCase : TestCases) {
- checkHighlightings(TestCase);
- }
+ for (const auto &TestCase : TestCases)
+ // Mask off scope modifiers to keep the tests manageable.
+ // They're tested separately.
+ checkHighlightings(TestCase, {}, ~ScopeModifierMask);
checkHighlightings(R"cpp(
class $Class_decl[[A]] {
{{"imp.h", R"cpp(
int someMethod();
void otherMethod();
- )cpp"}});
+ )cpp"}},
+ ~ScopeModifierMask);
// A separate test for macros in headers.
checkHighlightings(R"cpp(
#define DXYZ_Y(Y) DXYZ(x##Y)
#define DEFINE(X) int X;
#define DEFINE_Y DEFINE(Y)
- )cpp"}});
+ )cpp"}},
+ ~ScopeModifierMask);
+}
+
+TEST(SemanticHighlighting, ScopeModifiers) {
+ const char *TestCases[] = {
+ R"cpp(
+ static int $Variable_fileScope[[x]];
+ namespace $Namespace_globalScope[[ns]] {
+ class $Class_globalScope[[x]];
+ }
+ namespace {
+ void $Function_fileScope[[foo]]();
+ }
+ )cpp",
+ R"cpp(
+ void $Function_globalScope[[foo]](int $Parameter_functionScope[[y]]) {
+ int $LocalVariable_functionScope[[z]];
+ }
+ )cpp",
+ R"cpp(
+ // Lambdas are considered functions, not classes.
+ auto $Variable_fileScope[[x]] = [m(42)] { // FIXME: annotate capture
+ return $LocalVariable_functionScope[[m]];
+ };
+ )cpp",
+ R"cpp(
+ // Classes in functions are classes.
+ void $Function_globalScope[[foo]]() {
+ class $Class_functionScope[[X]] {
+ int $Field_classScope[[x]];
+ };
+ };
+ )cpp",
+ R"cpp(
+ template <int $TemplateParameter_classScope[[T]]>
+ class $Class_globalScope[[X]] {
+ };
+ )cpp",
+ R"cpp(
+ // No useful scope for template parameters of variable templates.
+ template <typename $TemplateParameter[[A]]>
+ unsigned $Variable_globalScope[[X]] =
+ $TemplateParameter[[A]]::$DependentName_classScope[[x]];
+ )cpp",
+ R"cpp(
+ #define $Macro_globalScope[[X]] 1
+ int $Variable_globalScope[[Y]] = $Macro_globalScope[[X]];
+ )cpp",
+ };
+
+ for (const char *Test : TestCases)
+ checkHighlightings(Test, {}, ScopeModifierMask);
}
TEST(SemanticHighlighting, GeneratesHighlightsWhenFileChange) {