diff --git a/clang-tools-extra/clangd/SemanticHighlighting.cpp b/clang-tools-extra/clangd/SemanticHighlighting.cpp --- a/clang-tools-extra/clangd/SemanticHighlighting.cpp +++ b/clang-tools-extra/clangd/SemanticHighlighting.cpp @@ -670,8 +670,11 @@ H.addAngleBracketTokens(E->getLAngleLoc(), E->getRAngleLoc()); return true; } + bool VisitMemberExpr(MemberExpr *E) { H.addAngleBracketTokens(E->getLAngleLoc(), E->getRAngleLoc()); + if (isConst(E->getType())) + H.addExtraModifier(E->getMemberLoc(), HighlightingModifier::Readonly); return true; } @@ -1003,9 +1006,19 @@ } bool VisitCXXDependentScopeMemberExpr(CXXDependentScopeMemberExpr *E) { - H.addToken(E->getMemberNameInfo().getLoc(), HighlightingKind::Unknown) - .addModifier(HighlightingModifier::DependentName) - .addModifier(HighlightingModifier::ClassScope); + auto &Tok = + H.addToken(E->getMemberNameInfo().getLoc(), HighlightingKind::Unknown) + .addModifier(HighlightingModifier::DependentName) + .addModifier(HighlightingModifier::ClassScope); + + // Best effort attempt to get const-ness for dependent member expressions. + // Note: this can yield false-positives if the member is mutable but this + // should be rare in dependent contexts. + const auto BaseType = E->getBaseType(); + if ((E->isArrow() && BaseType->getPointeeType().isConstQualified()) || + (!E->isArrow() && BaseType.isConstQualified())) + Tok.addModifier(HighlightingModifier::Readonly); + H.addAngleBracketTokens(E->getLAngleLoc(), E->getRAngleLoc()); return true; } diff --git a/clang-tools-extra/clangd/unittests/SemanticHighlightingTests.cpp b/clang-tools-extra/clangd/unittests/SemanticHighlightingTests.cpp --- a/clang-tools-extra/clangd/unittests/SemanticHighlightingTests.cpp +++ b/clang-tools-extra/clangd/unittests/SemanticHighlightingTests.cpp @@ -1154,6 +1154,109 @@ checkHighlightings(Test, {}, ScopeModifierMask); } +TEST(SemanticHighlighting, MemberReadonly) { + const char *TestCases[] = { + R"cpp( + struct $Class[[A]] { + int $Field[[x]]; + const int $Field_readonly[[y]]; + mutable int $Field[[z]]; + }; + void $Function[[foo]](){ + $Class[[A]] $LocalVariable[[a]]{}; + $LocalVariable[[a]].$Field[[x]]; + $LocalVariable[[a]].$Field_readonly[[y]]; + $LocalVariable[[a]].$Field[[z]]; + const $Class[[A]] $LocalVariable_readonly[[b]]{}; + $LocalVariable_readonly[[b]].$Field_readonly[[x]]; + $LocalVariable_readonly[[b]].$Field_readonly[[y]]; + $LocalVariable_readonly[[b]].$Field[[z]]; + } + )cpp", + R"cpp( + struct $Class[[A]] { + int $Field[[x]]; + const int $Field_readonly[[y]]; + mutable int $Field[[z]]; + }; + void $Function[[foo]](){ + $Class[[A]] *$LocalVariable[[a]] = $Operator[[new]] $Class[[A]]{}; + $LocalVariable[[a]]->$Field[[x]]; + $LocalVariable[[a]]->$Field_readonly[[y]]; + $LocalVariable[[a]]->$Field[[z]]; + const $Class[[A]] *$LocalVariable_readonly[[b]] = $Operator[[new]] $Class[[A]]{}; + $LocalVariable_readonly[[b]]->$Field_readonly[[x]]; + $LocalVariable_readonly[[b]]->$Field_readonly[[y]]; + $LocalVariable_readonly[[b]]->$Field[[z]]; + } + )cpp", + R"cpp( + struct $Class[[A]] { + int $Field[[x]]; + const int $Field_readonly[[y]]; + mutable int $Field[[z]]; + void $Method_readonly[[foo]]() const { + $Field_readonly[[x]]; + $Field_readonly[[y]]; + $Field[[z]]; + } + void $Method[[bar]]() { + $Field[[x]]; + $Field_readonly[[y]]; + $Field[[z]]; + } + }; + )cpp", + R"cpp( + struct $Class[[A]] { + int $Field[[x]]; + const int $Field_readonly[[y]]; + mutable int $Field[[z]]; + void $Method_readonly[[foo]]() const { + this->$Field_readonly[[x]]; + this->$Field_readonly[[y]]; + this->$Field[[z]]; + } + void $Method[[bar]]() { + this->$Field[[x]]; + this->$Field_readonly[[y]]; + this->$Field[[z]]; + } + }; + )cpp", + R"cpp( + struct $Class[[A]] { + void $Method_readonly[[foo]]() const {} + void $Method[[foo]]() {} + void $Method_readonly[[test]]() const { + $Method_readonly[[foo]](); + } + void $Method[[test]]() { + $Method[[foo]](); + } + }; + )cpp", + R"cpp( + template $Bracket[[<]]typename $TemplateParameter[[T]]$Bracket[[>]] + void $Function[[foo]]($TemplateParameter[[T]] $Parameter[[x]], const $TemplateParameter[[T]] $Parameter_readonly[[y]]) { + $Parameter[[x]].$Unknown[[a]]; + $Parameter_readonly[[y]].$Unknown_readonly[[b]]; + } + )cpp", + R"cpp( + template $Bracket[[<]]typename $TemplateParameter[[T]]$Bracket[[>]] + void $Function[[foo]]($TemplateParameter[[T]]* $Parameter[[x]], const $TemplateParameter[[T]]* $Parameter_readonly[[y]], $TemplateParameter[[T]]* const $Parameter_readonly[[z]]) { + $Parameter[[x]]->$Unknown[[a]]; + $Parameter_readonly[[y]]->$Unknown_readonly[[b]]; + $Parameter_readonly[[z]]->$Unknown[[c]]; + } + )cpp", + }; + + for (const char *Test : TestCases) + checkHighlightings(Test, {}, 1 << unsigned(HighlightingModifier::Readonly)); +} + // Ranges are highlighted as variables, unless highlighted as $Function etc. std::vector tokens(llvm::StringRef MarkedText) { Annotations A(MarkedText);