From 22a34e01066004c9bd8d7ad418df90b8fbcb3749 Mon Sep 17 00:00:00 2001 From: Sam McCall Date: Thu, 6 Jan 2022 02:01:13 +0100 Subject: [clangd] Support configuration of inlay hints. The idea is that the feature will always be advertised at the LSP level, but depending on config we'll return partial or no responses. We try to avoid doing the analysis for hints we're not going to return. Examples of syntax: ``` InlayHints: Enabled: No --- InlayHints: ParameterNames: No --- InlayHints: ParameterNames: Yes DeducedTypes: Yes ``` Differential Revision: https://reviews.llvm.org/D116713 --- clang-tools-extra/clangd/Config.h | 9 +++++ clang-tools-extra/clangd/ConfigCompile.cpp | 17 +++++++++ clang-tools-extra/clangd/ConfigFragment.h | 12 ++++++ clang-tools-extra/clangd/ConfigYAML.cpp | 44 ++++++++++++++++------ clang-tools-extra/clangd/InlayHints.cpp | 36 +++++++++++++++--- .../clangd/unittests/ConfigYAMLTests.cpp | 17 +++++++++ .../clangd/unittests/InlayHintTests.cpp | 15 ++++++++ 7 files changed, 132 insertions(+), 18 deletions(-) diff --git a/clang-tools-extra/clangd/Config.h b/clang-tools-extra/clangd/Config.h index 38fc93fa361c..952626db31e4 100644 --- a/clang-tools-extra/clangd/Config.h +++ b/clang-tools-extra/clangd/Config.h @@ -122,6 +122,15 @@ struct Config { /// Whether hover show a.k.a type. bool ShowAKA = false; } Hover; + + struct { + /// If false, inlay hints are completely disabled. + bool Enabled = true; + + // Whether specific categories of hints are enabled. + bool Parameters = true; + bool DeducedTypes = true; + } InlayHints; }; } // namespace clangd diff --git a/clang-tools-extra/clangd/ConfigCompile.cpp b/clang-tools-extra/clangd/ConfigCompile.cpp index 18afdeb3cb5c..a606b98a2dba 100644 --- a/clang-tools-extra/clangd/ConfigCompile.cpp +++ b/clang-tools-extra/clangd/ConfigCompile.cpp @@ -197,6 +197,7 @@ struct FragmentCompiler { compile(std::move(F.Diagnostics)); compile(std::move(F.Completion)); compile(std::move(F.Hover)); + compile(std::move(F.InlayHints)); } void compile(Fragment::IfBlock &&F) { @@ -526,6 +527,22 @@ struct FragmentCompiler { } } + void compile(Fragment::InlayHintsBlock &&F) { + if (F.Enabled) + Out.Apply.push_back([Value(**F.Enabled)](const Params &, Config &C) { + C.InlayHints.Enabled = Value; + }); + if (F.ParameterNames) + Out.Apply.push_back( + [Value(**F.ParameterNames)](const Params &, Config &C) { + C.InlayHints.Parameters = Value; + }); + if (F.DeducedTypes) + Out.Apply.push_back([Value(**F.DeducedTypes)](const Params &, Config &C) { + C.InlayHints.DeducedTypes = Value; + }); + } + constexpr static llvm::SourceMgr::DiagKind Error = llvm::SourceMgr::DK_Error; constexpr static llvm::SourceMgr::DiagKind Warning = llvm::SourceMgr::DK_Warning; diff --git a/clang-tools-extra/clangd/ConfigFragment.h b/clang-tools-extra/clangd/ConfigFragment.h index 31c4636efa0b..d165b6305aa5 100644 --- a/clang-tools-extra/clangd/ConfigFragment.h +++ b/clang-tools-extra/clangd/ConfigFragment.h @@ -283,6 +283,18 @@ struct Fragment { llvm::Optional> ShowAKA; }; HoverBlock Hover; + + /// Configures labels shown inline with the code. + struct InlayHintsBlock { + /// Enables/disables the inlay-hints feature. + llvm::Optional> Enabled; + + /// Show parameter names before function arguments. + llvm::Optional> ParameterNames; + /// Show deduced types for `auto`. + llvm::Optional> DeducedTypes; + }; + InlayHintsBlock InlayHints; }; } // namespace config diff --git a/clang-tools-extra/clangd/ConfigYAML.cpp b/clang-tools-extra/clangd/ConfigYAML.cpp index 0487c3281576..04c0c633a3bb 100644 --- a/clang-tools-extra/clangd/ConfigYAML.cpp +++ b/clang-tools-extra/clangd/ConfigYAML.cpp @@ -66,6 +66,7 @@ public: Dict.handle("Diagnostics", [&](Node &N) { parse(F.Diagnostics, N); }); Dict.handle("Completion", [&](Node &N) { parse(F.Completion, N); }); Dict.handle("Hover", [&](Node &N) { parse(F.Hover, N); }); + Dict.handle("InlayHints", [&](Node &N) { parse(F.InlayHints, N); }); Dict.parse(N); return !(N.failed() || HadError); } @@ -199,12 +200,8 @@ private: void parse(Fragment::CompletionBlock &F, Node &N) { DictParser Dict("Completion", this); Dict.handle("AllScopes", [&](Node &N) { - if (auto Value = scalarValue(N, "AllScopes")) { - if (auto AllScopes = llvm::yaml::parseBool(**Value)) - F.AllScopes = *AllScopes; - else - warning("AllScopes should be a boolean", N); - } + if (auto AllScopes = boolValue(N, "AllScopes")) + F.AllScopes = *AllScopes; }); Dict.parse(N); } @@ -212,12 +209,25 @@ private: void parse(Fragment::HoverBlock &F, Node &N) { DictParser Dict("Hover", this); Dict.handle("ShowAKA", [&](Node &N) { - if (auto Value = scalarValue(N, "ShowAKA")) { - if (auto ShowAKA = llvm::yaml::parseBool(**Value)) - F.ShowAKA = *ShowAKA; - else - warning("ShowAKA should be a boolean", N); - } + if (auto ShowAKA = boolValue(N, "ShowAKA")) + F.ShowAKA = *ShowAKA; + }); + Dict.parse(N); + } + + void parse(Fragment::InlayHintsBlock &F, Node &N) { + DictParser Dict("InlayHints", this); + Dict.handle("Enabled", [&](Node &N) { + if (auto Value = boolValue(N, "Enabled")) + F.Enabled = *Value; + }); + Dict.handle("ParameterNames", [&](Node &N) { + if (auto Value = boolValue(N, "ParameterNames")) + F.ParameterNames = *Value; + }); + Dict.handle("DeducedTypes", [&](Node &N) { + if (auto Value = boolValue(N, "DeducedTypes")) + F.DeducedTypes = *Value; }); Dict.parse(N); } @@ -331,6 +341,16 @@ private: return llvm::None; } + llvm::Optional> boolValue(Node &N, llvm::StringRef Desc) { + if (auto Scalar = scalarValue(N, Desc)) { + if (auto Bool = llvm::yaml::parseBool(**Scalar)) + return Located(*Bool, Scalar->Range); + else + warning(Desc + " should be a boolean", N); + } + return llvm::None; + } + // Try to parse a list of single scalar values, or just a single value. llvm::Optional>> scalarValues(Node &N) { std::vector> Result; diff --git a/clang-tools-extra/clangd/InlayHints.cpp b/clang-tools-extra/clangd/InlayHints.cpp index 60d20a38ecd2..e534faa80c4b 100644 --- a/clang-tools-extra/clangd/InlayHints.cpp +++ b/clang-tools-extra/clangd/InlayHints.cpp @@ -6,6 +6,7 @@ // //===----------------------------------------------------------------------===// #include "InlayHints.h" +#include "Config.h" #include "HeuristicResolver.h" #include "ParsedAST.h" #include "clang/AST/DeclarationName.h" @@ -23,8 +24,8 @@ enum class HintSide { Left, Right }; class InlayHintVisitor : public RecursiveASTVisitor { public: InlayHintVisitor(std::vector &Results, ParsedAST &AST, - llvm::Optional RestrictRange) - : Results(Results), AST(AST.getASTContext()), + const Config &Cfg, llvm::Optional RestrictRange) + : Results(Results), AST(AST.getASTContext()), Cfg(Cfg), RestrictRange(std::move(RestrictRange)), MainFileID(AST.getSourceManager().getMainFileID()), Resolver(AST.getHeuristicResolver()), @@ -65,6 +66,9 @@ public: } bool VisitCallExpr(CallExpr *E) { + if (!Cfg.InlayHints.Parameters) + return true; + // Do not show parameter hints for operator calls written using operator // syntax or user-defined literals. (Among other reasons, the resulting // hints can look awkard, e.g. the expression can itself be a function @@ -135,7 +139,7 @@ private: // the entire argument list likely appears in the main file and can be hinted. void processCall(SourceLocation Anchor, const FunctionDecl *Callee, llvm::ArrayRef Args) { - if (Args.size() == 0 || !Callee) + if (!Cfg.InlayHints.Parameters || Args.size() == 0 || !Callee) return; // If the anchor location comes from a macro defintion, there's nowhere to @@ -323,6 +327,23 @@ private: void addInlayHint(SourceRange R, HintSide Side, InlayHintKind Kind, llvm::StringRef Prefix, llvm::StringRef Label, llvm::StringRef Suffix) { + // We shouldn't get as far as adding a hint if the category is disabled. + // We'd like to disable as much of the analysis as possible above instead. + // Assert in debug mode but add a dynamic check in production. + assert(Cfg.InlayHints.Enabled && "Shouldn't get here if disabled!"); + switch (Kind) { +#define CHECK_KIND(Enumerator, ConfigProperty) \ + case InlayHintKind::Enumerator: \ + assert(Cfg.InlayHints.ConfigProperty && \ + "Shouldn't get here if kind is disabled!"); \ + if (!Cfg.InlayHints.ConfigProperty) \ + return; \ + break + CHECK_KIND(ParameterHint, Parameters); + CHECK_KIND(TypeHint, DeducedTypes); +#undef CHECK_KIND + } + auto FileRange = toHalfOpenFileRange(AST.getSourceManager(), AST.getLangOpts(), R); if (!FileRange) @@ -348,8 +369,7 @@ private: void addTypeHint(SourceRange R, QualType T, llvm::StringRef Prefix, const PrintingPolicy &Policy) { - // Do not print useless "NULL TYPE" hint. - if (!T.getTypePtrOrNull()) + if (!Cfg.InlayHints.DeducedTypes || T.isNull()) return; std::string TypeName = T.getAsString(Policy); @@ -360,6 +380,7 @@ private: std::vector &Results; ASTContext &AST; + const Config &Cfg; llvm::Optional RestrictRange; FileID MainFileID; StringRef MainFileBuf; @@ -381,7 +402,10 @@ private: std::vector inlayHints(ParsedAST &AST, llvm::Optional RestrictRange) { std::vector Results; - InlayHintVisitor Visitor(Results, AST, std::move(RestrictRange)); + const auto &Cfg = Config::current(); + if (!Cfg.InlayHints.Enabled) + return Results; + InlayHintVisitor Visitor(Results, AST, Cfg, std::move(RestrictRange)); Visitor.TraverseAST(AST.getASTContext()); // De-duplicate hints. Duplicates can sometimes occur due to e.g. explicit diff --git a/clang-tools-extra/clangd/unittests/ConfigYAMLTests.cpp b/clang-tools-extra/clangd/unittests/ConfigYAMLTests.cpp index aeab1beb2ec2..3722ee297665 100644 --- a/clang-tools-extra/clangd/unittests/ConfigYAMLTests.cpp +++ b/clang-tools-extra/clangd/unittests/ConfigYAMLTests.cpp @@ -228,6 +228,23 @@ Hover: ASSERT_EQ(Results.size(), 1u); EXPECT_THAT(Results[0].Hover.ShowAKA, llvm::ValueIs(Val(true))); } + +TEST(ParseYAML, InlayHints) { + CapturedDiags Diags; + Annotations YAML(R"yaml( +InlayHints: + Enabled: No + ParameterNames: Yes + )yaml"); + auto Results = + Fragment::parseYAML(YAML.code(), "config.yaml", Diags.callback()); + ASSERT_THAT(Diags.Diagnostics, IsEmpty()); + ASSERT_EQ(Results.size(), 1u); + EXPECT_THAT(Results[0].InlayHints.Enabled, llvm::ValueIs(Val(false))); + EXPECT_THAT(Results[0].InlayHints.ParameterNames, llvm::ValueIs(Val(true))); + EXPECT_EQ(Results[0].InlayHints.DeducedTypes, llvm::None); +} + } // namespace } // namespace config } // namespace clangd diff --git a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp index e8880981c1a4..992ec0e012ae 100644 --- a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp +++ b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp @@ -6,11 +6,13 @@ // //===----------------------------------------------------------------------===// #include "Annotations.h" +#include "Config.h" #include "InlayHints.h" #include "Protocol.h" #include "TestTU.h" #include "TestWorkspace.h" #include "XRefs.h" +#include "support/Context.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -24,6 +26,8 @@ std::ostream &operator<<(std::ostream &Stream, const InlayHint &Hint) { namespace { using ::testing::ElementsAre; +using ::testing::IsEmpty; +using ::testing::UnorderedElementsAre; std::vector hintsOfKind(ParsedAST &AST, InlayHintKind Kind) { std::vector Result; @@ -56,6 +60,13 @@ MATCHER_P2(HintMatcher, Expected, Code, "") { MATCHER_P(labelIs, Label, "") { return arg.label == Label; } +Config noHintsConfig() { + Config C; + C.InlayHints.Parameters = false; + C.InlayHints.DeducedTypes = false; + return C; +} + template void assertHints(InlayHintKind Kind, llvm::StringRef AnnotatedSource, ExpectedHints... Expected) { @@ -66,6 +77,10 @@ void assertHints(InlayHintKind Kind, llvm::StringRef AnnotatedSource, EXPECT_THAT(hintsOfKind(AST, Kind), ElementsAre(HintMatcher(Expected, Source)...)); + // Sneak in a cross-cutting check that hints are disabled by config. + // We'll hit an assertion failure if addInlayHint still gets called. + WithContextValue WithCfg(Config::Key, noHintsConfig()); + EXPECT_THAT(inlayHints(AST, llvm::None), IsEmpty()); } // Hack to allow expression-statements operating on parameter packs in C++14. -- cgit v1.2.1