diff --git a/clang-tools-extra/clangd/Config.h b/clang-tools-extra/clangd/Config.h index 586d031d58481..f2c8b84b1d53b 100644 --- a/clang-tools-extra/clangd/Config.h +++ b/clang-tools-extra/clangd/Config.h @@ -166,7 +166,10 @@ struct Config { bool DeducedTypes = true; bool Designators = true; bool BlockEnd = false; + bool LambdaCaptures = false; + bool DefaultInitializations = false; bool DefaultArguments = false; + bool ImplicitThis = false; // Limit the length of type names in inlay hints. (0 means no limit) uint32_t TypeNameLimit = 32; } InlayHints; diff --git a/clang-tools-extra/clangd/ConfigCompile.cpp b/clang-tools-extra/clangd/ConfigCompile.cpp index aa2561e081047..ff4979b6246b0 100644 --- a/clang-tools-extra/clangd/ConfigCompile.cpp +++ b/clang-tools-extra/clangd/ConfigCompile.cpp @@ -552,10 +552,10 @@ struct FragmentCompiler { auto Fast = isFastTidyCheck(Str); if (!Fast.has_value()) { diag(Warning, - llvm::formatv( - "Latency of clang-tidy check '{0}' is not known. " - "It will only run if ClangTidy.FastCheckFilter is Loose or None", - Str) + llvm::formatv("Latency of clang-tidy check '{0}' is not known. " + "It will only run if ClangTidy.FastCheckFilter is " + "Loose or None", + Str) .str(), Arg.Range); } else if (!*Fast) { @@ -717,11 +717,26 @@ struct FragmentCompiler { Out.Apply.push_back([Value(**F.BlockEnd)](const Params &, Config &C) { C.InlayHints.BlockEnd = Value; }); + if (F.LambdaCaptures) + Out.Apply.push_back( + [Value(**F.LambdaCaptures)](const Params &, Config &C) { + C.InlayHints.LambdaCaptures = Value; + }); + if (F.DefaultInitializations) + Out.Apply.push_back( + [Value(**F.DefaultInitializations)](const Params &, Config &C) { + C.InlayHints.DefaultInitializations = Value; + }); if (F.DefaultArguments) Out.Apply.push_back( [Value(**F.DefaultArguments)](const Params &, Config &C) { C.InlayHints.DefaultArguments = Value; }); + if (F.ImplicitThis) + Out.Apply.push_back( + [Value(**F.ImplicitThis)](const Params &, Config &C) { + C.InlayHints.ImplicitThis = Value; + }); if (F.TypeNameLimit) Out.Apply.push_back( [Value(**F.TypeNameLimit)](const Params &, Config &C) { diff --git a/clang-tools-extra/clangd/ConfigFragment.h b/clang-tools-extra/clangd/ConfigFragment.h index 9535b20253b13..c7e3f29c2163e 100644 --- a/clang-tools-extra/clangd/ConfigFragment.h +++ b/clang-tools-extra/clangd/ConfigFragment.h @@ -356,9 +356,15 @@ struct Fragment { std::optional> Designators; /// Show defined symbol names at the end of a definition block. std::optional> BlockEnd; + /// Show names of captured variables by default capture groups in lambdas. + std::optional> LambdaCaptures; + /// Show curly braces at the end of implicit default initializations. + std::optional> DefaultInitializations; /// Show parameter names and default values of default arguments after all /// of the explicit arguments. std::optional> DefaultArguments; + /// Show implicit dereferencing of this pointer. + std::optional> ImplicitThis; /// Limit the length of type name hints. (0 means no limit) std::optional> TypeNameLimit; }; diff --git a/clang-tools-extra/clangd/ConfigYAML.cpp b/clang-tools-extra/clangd/ConfigYAML.cpp index 95cc5c1f9f1cf..ca54f530e75fd 100644 --- a/clang-tools-extra/clangd/ConfigYAML.cpp +++ b/clang-tools-extra/clangd/ConfigYAML.cpp @@ -275,10 +275,22 @@ class Parser { if (auto Value = boolValue(N, "BlockEnd")) F.BlockEnd = *Value; }); + Dict.handle("LambdaCaptures", [&](Node &N) { + if (auto Value = boolValue(N, "LambdaCaptures")) + F.LambdaCaptures = *Value; + }); + Dict.handle("DefaultInitializations", [&](Node &N) { + if (auto Value = boolValue(N, "DefaultInitializations")) + F.DefaultInitializations = *Value; + }); Dict.handle("DefaultArguments", [&](Node &N) { if (auto Value = boolValue(N, "DefaultArguments")) F.DefaultArguments = *Value; }); + Dict.handle("ImplicitThis", [&](Node &N) { + if (auto Value = boolValue(N, "ImplicitThis")) + F.ImplicitThis = *Value; + }); Dict.handle("TypeNameLimit", [&](Node &N) { if (auto Value = uint32Value(N, "TypeNameLimit")) F.TypeNameLimit = *Value; diff --git a/clang-tools-extra/clangd/InlayHints.cpp b/clang-tools-extra/clangd/InlayHints.cpp index 1b1bcf78c9855..5e707297bb15b 100644 --- a/clang-tools-extra/clangd/InlayHints.cpp +++ b/clang-tools-extra/clangd/InlayHints.cpp @@ -15,14 +15,17 @@ #include "clang/AST/ASTDiagnostic.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclBase.h" +#include "clang/AST/DeclBase.h" #include "clang/AST/DeclarationName.h" #include "clang/AST/Expr.h" #include "clang/AST/ExprCXX.h" +#include "clang/AST/LambdaCapture.h" #include "clang/AST/RecursiveASTVisitor.h" #include "clang/AST/Stmt.h" #include "clang/AST/StmtVisitor.h" #include "clang/AST/Type.h" #include "clang/Basic/Builtins.h" +#include "clang/Basic/Lambda.h" #include "clang/Basic/OperatorKinds.h" #include "clang/Basic/SourceLocation.h" #include "clang/Basic/SourceManager.h" @@ -381,20 +384,31 @@ maybeDropCxxExplicitObjectParameters(ArrayRef Params) { return Params; } -template -std::string joinAndTruncate(const R &Range, size_t MaxLength) { +llvm::StringRef getLambdaCaptureName(const LambdaCapture &Capture) { + if (Capture.capturesVariable()) + return Capture.getCapturedVar()->getName(); + if (Capture.capturesThis()) + return llvm::StringRef{"this"}; + return llvm::StringRef{"unknown"}; +} + +template +std::string joinAndTruncate(R &&Range, size_t MaxLength, + P &&GetAsStringFunction) { std::string Out; - llvm::raw_string_ostream OS(Out); - llvm::ListSeparator Sep(", "); + bool IsFirst = true; for (auto &&Element : Range) { - OS << Sep; - if (Out.size() + Element.size() >= MaxLength) { - OS << "..."; + if (!IsFirst) + Out.append(", "); + else + IsFirst = false; + auto AsString = GetAsStringFunction(Element); + if (Out.size() + AsString.size() >= MaxLength) { + Out.append("..."); break; } - OS << Element; + Out.append(AsString); } - OS.flush(); return Out; } @@ -478,6 +492,13 @@ class InlayHintVisitor : public RecursiveASTVisitor { return RecursiveASTVisitor::TraversePseudoObjectExpr(E); } + bool VisitCXXThisExpr(CXXThisExpr *CTE) { + if (Cfg.InlayHints.ImplicitThis && CTE->isImplicit()) + addInlayHint(CTE->getLocation(), HintSide::Left, + InlayHintKind::ImplicitThis, "", "this->", ""); + return true; + } + bool VisitCallExpr(CallExpr *E) { if (!Cfg.InlayHints.Parameters) return true; @@ -625,6 +646,15 @@ class InlayHintVisitor : public RecursiveASTVisitor { } bool VisitLambdaExpr(LambdaExpr *E) { + if (Cfg.InlayHints.LambdaCaptures && E->getCaptureDefault() != LCD_None && + !E->implicit_captures().empty()) { + std::string FormattedCaptureList = + joinAndTruncate(E->implicit_captures(), Cfg.InlayHints.TypeNameLimit, + [](const LambdaCapture &ImplicitCapture) { + return getLambdaCaptureName(ImplicitCapture); + }); + addImplicitCaptureHint(E->getCaptureDefaultLoc(), FormattedCaptureList); + } FunctionDecl *D = E->getCallOperator(); if (!E->hasExplicitResultType()) { SourceLocation TypeHintLoc; @@ -646,6 +676,14 @@ class InlayHintVisitor : public RecursiveASTVisitor { } bool VisitVarDecl(VarDecl *D) { + if (Cfg.InlayHints.DefaultInitializations && D->hasInit() && + isa(D->getInit()) && + !llvm::dyn_cast(D->getInit()) + ->getParenOrBraceRange() + .isValid()) + addInlayHint(D->getEndLoc(), HintSide::Right, InlayHintKind::DefaultInit, + "", "{}", ""); + // Do not show hints for the aggregate in a structured binding, // but show hints for the individual bindings. if (auto *DD = dyn_cast(D)) { @@ -741,7 +779,7 @@ class InlayHintVisitor : public RecursiveASTVisitor { private: using NameVec = SmallVector; - void processCall(Callee Callee, SourceLocation RParenOrBraceLoc, + void processCall(Callee Callee, SourceRange LParenOrBraceRange, llvm::ArrayRef Args) { assert(Callee.Decl || Callee.Loc); @@ -789,31 +827,23 @@ class InlayHintVisitor : public RecursiveASTVisitor { } StringRef Name = ParameterNames[I]; - const bool NameHint = - shouldHintName(Args[I], Name) && Cfg.InlayHints.Parameters; - const bool ReferenceHint = - shouldHintReference(Params[I], ForwardedParams[I]) && - Cfg.InlayHints.Parameters; + bool NameHint = shouldHintName(Args[I], Name); + bool ReferenceHint = shouldHintReference(Params[I], ForwardedParams[I]); - const bool IsDefault = isa(Args[I]); + bool IsDefault = isa(Args[I]); HasNonDefaultArgs |= !IsDefault; - if (IsDefault) { - if (Cfg.InlayHints.DefaultArguments) { - const auto SourceText = Lexer::getSourceText( - CharSourceRange::getTokenRange(Params[I]->getDefaultArgRange()), - AST.getSourceManager(), AST.getLangOpts()); - const auto Abbrev = - (SourceText.size() > Cfg.InlayHints.TypeNameLimit || - SourceText.contains("\n")) - ? "..." - : SourceText; - if (NameHint) - FormattedDefaultArgs.emplace_back( - llvm::formatv("{0}: {1}", Name, Abbrev)); - else - FormattedDefaultArgs.emplace_back(llvm::formatv("{0}", Abbrev)); - } - } else if (NameHint || ReferenceHint) { + if (Cfg.InlayHints.DefaultArguments && IsDefault) { + auto SourceText = Lexer::getSourceText( + CharSourceRange::getTokenRange(Params[I]->getDefaultArgRange()), + Callee.Decl->getASTContext().getSourceManager(), + Callee.Decl->getASTContext().getLangOpts()); + FormattedDefaultArgs.emplace_back(llvm::formatv( + "{0} = {1}", Name, + SourceText.size() > Cfg.InlayHints.TypeNameLimit ? "..." + : SourceText)); + } + + if (NameHint || ReferenceHint) { addInlayHint(Args[I]->getSourceRange(), HintSide::Left, InlayHintKind::Parameter, ReferenceHint ? "&" : "", NameHint ? Name : "", ": "); @@ -822,8 +852,9 @@ class InlayHintVisitor : public RecursiveASTVisitor { if (!FormattedDefaultArgs.empty()) { std::string Hint = - joinAndTruncate(FormattedDefaultArgs, Cfg.InlayHints.TypeNameLimit); - addInlayHint(SourceRange{RParenOrBraceLoc}, HintSide::Left, + joinAndTruncate(FormattedDefaultArgs, Cfg.InlayHints.TypeNameLimit, + [](const auto &E) { return E; }); + addInlayHint(LParenOrBraceRange, HintSide::Left, InlayHintKind::DefaultArgument, HasNonDefaultArgs ? ", " : "", Hint, ""); } @@ -1034,7 +1065,10 @@ class InlayHintVisitor : public RecursiveASTVisitor { CHECK_KIND(Type, DeducedTypes); CHECK_KIND(Designator, Designators); CHECK_KIND(BlockEnd, BlockEnd); + CHECK_KIND(LambdaCapture, LambdaCaptures); CHECK_KIND(DefaultArgument, DefaultArguments); + CHECK_KIND(DefaultInit, DefaultInitializations); + CHECK_KIND(ImplicitThis, ImplicitThis); #undef CHECK_KIND } @@ -1088,6 +1122,11 @@ class InlayHintVisitor : public RecursiveASTVisitor { /*Prefix=*/"", Text, /*Suffix=*/"="); } + void addImplicitCaptureHint(SourceRange R, llvm::StringRef Text) { + addInlayHint(R, HintSide::Right, InlayHintKind::LambdaCapture, + /*Prefix=*/": ", Text, /*Suffix=*/""); + } + bool shouldPrintTypeHint(llvm::StringRef TypeName) const noexcept { return Cfg.InlayHints.TypeNameLimit == 0 || TypeName.size() < Cfg.InlayHints.TypeNameLimit; diff --git a/clang-tools-extra/clangd/Protocol.cpp b/clang-tools-extra/clangd/Protocol.cpp index 05c8041df7de7..542dae82c8dbf 100644 --- a/clang-tools-extra/clangd/Protocol.cpp +++ b/clang-tools-extra/clangd/Protocol.cpp @@ -209,7 +209,7 @@ bool fromJSON(const llvm::json::Value &Params, ChangeAnnotation &R, O.map("needsConfirmation", R.needsConfirmation) && O.mapOptional("description", R.description); } -llvm::json::Value toJSON(const ChangeAnnotation & CA) { +llvm::json::Value toJSON(const ChangeAnnotation &CA) { llvm::json::Object Result{{"label", CA.label}}; if (CA.needsConfirmation) Result["needsConfirmation"] = *CA.needsConfirmation; @@ -1516,7 +1516,10 @@ llvm::json::Value toJSON(const InlayHintKind &Kind) { return 2; case InlayHintKind::Designator: case InlayHintKind::BlockEnd: + case InlayHintKind::LambdaCapture: case InlayHintKind::DefaultArgument: + case InlayHintKind::DefaultInit: + case InlayHintKind::ImplicitThis: // This is an extension, don't serialize. return nullptr; } @@ -1557,8 +1560,14 @@ llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, InlayHintKind Kind) { return "designator"; case InlayHintKind::BlockEnd: return "block-end"; + case InlayHintKind::LambdaCapture: + return "lambda-capture"; case InlayHintKind::DefaultArgument: return "default-argument"; + case InlayHintKind::DefaultInit: + return "default-init"; + case InlayHintKind::ImplicitThis: + return "implicit-this"; } llvm_unreachable("Unknown clang.clangd.InlayHintKind"); }; diff --git a/clang-tools-extra/clangd/Protocol.h b/clang-tools-extra/clangd/Protocol.h index 3a423bb9e3265..ce9fdb1e2f833 100644 --- a/clang-tools-extra/clangd/Protocol.h +++ b/clang-tools-extra/clangd/Protocol.h @@ -1688,15 +1688,36 @@ enum class InlayHintKind { /// This is a clangd extension. BlockEnd = 4, + /// An inlay hint that is for a variable captured implicitly in a lambda. + /// + /// An example of parameter hint for implicit lambda captures: + /// [&^] { return A; }; + /// Adds an inlay hint ": A". + LambdaCapture = 5, + /// An inlay hint that is for a default argument. /// /// An example of a parameter hint for a default argument: /// void foo(bool A = true); /// foo(^); - /// Adds an inlay hint "A: true". - /// This is a clangd extension. + /// Adds an inlay hint "A = true". DefaultArgument = 6, + /// A hint for an implicit default initializer. + /// + /// An example of implicit default construction: + /// MyObject O^; + /// Adds a hint for "{}". + DefaultInit = 7, + + /// A hint for an implicit usage of this pointer. + /// + /// An example of implicit this pointer: + /// struct MyObject { int foo; int bar(); }; + /// MyObject::foo() { return ^bar; } + /// Adds a hinted "this->". + ImplicitThis = 8, + /// Other ideas for hints that are not currently implemented: /// /// * Chaining hints, showing the types of intermediate expressions diff --git a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp index 77d78b8777fe3..d8870d76ce8b3 100644 --- a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp +++ b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp @@ -84,7 +84,10 @@ Config noHintsConfig() { C.InlayHints.DeducedTypes = false; C.InlayHints.Designators = false; C.InlayHints.BlockEnd = false; + C.InlayHints.LambdaCaptures = false; + C.InlayHints.DefaultInitializations = false; C.InlayHints.DefaultArguments = false; + C.InlayHints.ImplicitThis = false; return C; } @@ -1462,80 +1465,81 @@ TEST(TypeHints, DefaultTemplateArgs) { struct A {}; A foo(); auto $var[[var]] = foo(); - A bar[1]; + A baz; + A bar[1]{}; auto [$binding[[value]]] = bar; )cpp", ExpectedHint{": A", "var"}, ExpectedHint{": A", "binding"}); } -TEST(DefaultArguments, Smoke) { +TEST(LambdaCaptures, Smoke) { Config Cfg; - Cfg.InlayHints.Parameters = - true; // To test interplay of parameters and default parameters + Cfg.InlayHints.Parameters = false; Cfg.InlayHints.DeducedTypes = false; Cfg.InlayHints.Designators = false; Cfg.InlayHints.BlockEnd = false; + Cfg.InlayHints.DefaultInitializations = false; + Cfg.InlayHints.DefaultArguments = false; - Cfg.InlayHints.DefaultArguments = true; + Cfg.InlayHints.LambdaCaptures = true; + Cfg.InlayHints.TypeNameLimit = 10; WithContextValue WithCfg(Config::Key, std::move(Cfg)); - const auto *Code = R"cpp( - int foo(int A = 4) { return A; } - int bar(int A, int B = 1, bool C = foo($default1[[)]]) { return A; } - int A = bar($explicit[[2]]$default2[[)]]; + assertHints(InlayHintKind::LambdaCapture, R"cpp( + void foo() { + int A = 1; + int ReallyLongName = 1; + auto B = [$byvalue[[=]]] { return A; }; + auto C = [$byref[[&]]] { return A; }; + auto D = [$trunc[[=]], &A] { B(); (void)ReallyLongName; return A; }; + } + )cpp", + ExpectedHint{": A", "byvalue", Right}, + ExpectedHint{": A", "byref", Right}, + ExpectedHint{": B, ...", "trunc", Right}); +} - void baz(int = 5) { if (false) baz($unnamed[[)]]; }; - )cpp"; +TEST(DefaultInitializations, Smoke) { + Config Cfg; - assertHints(InlayHintKind::DefaultArgument, Code, - ExpectedHint{"A: 4", "default1", Left}, - ExpectedHint{", B: 1, C: foo()", "default2", Left}, - ExpectedHint{"5", "unnamed", Left}); + Cfg.InlayHints.Enabled = true; + Cfg.InlayHints.DefaultInitializations = true; + WithContextValue WithCfg(Config::Key, std::move(Cfg)); - assertHints(InlayHintKind::Parameter, Code, - ExpectedHint{"A: ", "explicit", Left}); + assertHints(InlayHintKind::DefaultInit, R"cpp( + struct A { }; + A $init[[B]]; + int c; + )cpp", + ExpectedHint{"{}", "init", Right}); } -TEST(DefaultArguments, WithoutParameterNames) { +TEST(DefaultArguments, Smoke) { Config Cfg; - Cfg.InlayHints.Parameters = false; // To test just default args this time + Cfg.InlayHints.Parameters = + true; // To test interplay of parameters and default parameters Cfg.InlayHints.DeducedTypes = false; Cfg.InlayHints.Designators = false; Cfg.InlayHints.BlockEnd = false; + Cfg.InlayHints.LambdaCaptures = false; + Cfg.InlayHints.DefaultInitializations = false; Cfg.InlayHints.DefaultArguments = true; WithContextValue WithCfg(Config::Key, std::move(Cfg)); const auto *Code = R"cpp( - struct Baz { - Baz(float a = 3 // - + 2); - }; - struct Foo { - Foo(int, Baz baz = // - Baz{$abbreviated[[}]] - - // - ) {} - }; - - int main() { - Foo foo1(1$paren[[)]]; - Foo foo2{2$brace1[[}]]; - Foo foo3 = {3$brace2[[}]]; - auto foo4 = Foo{4$brace3[[}]]; - } + int foo(int A = 4) { return A; } + int bar(int A, int B = 1, bool C = foo($default1[[)]]) { return A; } + int A = bar($explicit[[2]]$default2[[)]]; )cpp"; assertHints(InlayHintKind::DefaultArgument, Code, - ExpectedHint{"...", "abbreviated", Left}, - ExpectedHint{", Baz{}", "paren", Left}, - ExpectedHint{", Baz{}", "brace1", Left}, - ExpectedHint{", Baz{}", "brace2", Left}, - ExpectedHint{", Baz{}", "brace3", Left}); + ExpectedHint{"A = 4", "default1", Left}, + ExpectedHint{", B = 1, C = foo()", "default2", Left}); - assertHints(InlayHintKind::Parameter, Code); + assertHints(InlayHintKind::Parameter, Code, + ExpectedHint{"A: ", "explicit", Left}); } TEST(TypeHints, Deduplication) { @@ -1657,7 +1661,7 @@ TEST(TypeHints, SubstTemplateParameterAliases) { )cpp"; llvm::StringRef VectorIntPtr = R"cpp( - vector array; + vector $init[[array]]; auto $no_modifier[[x]] = array[3]; auto* $ptr_modifier[[ptr]] = &array[3]; auto& $ref_modifier[[ref]] = array[3]; @@ -1681,7 +1685,7 @@ TEST(TypeHints, SubstTemplateParameterAliases) { ExpectedHint{": non_template_iterator", "end"}); llvm::StringRef VectorInt = R"cpp( - vector array; + vector $init[[array]]; auto $no_modifier[[by_value]] = array[3]; auto* $ptr_modifier[[ptr]] = &array[3]; auto& $ref_modifier[[ref]] = array[3]; @@ -1830,7 +1834,7 @@ TEST(ParameterHints, PseudoObjectExpr) { int printf(const char *Format, ...); int main() { - S s; + S $init[[s]]; __builtin_dump_struct(&s, printf); // Not `Format: __builtin_dump_struct()` printf($Param[["Hello, %d"]], 42); // Normal calls are not affected. // This builds a PseudoObjectExpr, but here it's useful for showing the