[clang-tools-extra] cbc9c4e - [clangd] Add support for inline parameter hints
Nathan Ridge via cfe-commits
cfe-commits at lists.llvm.org
Tue Apr 13 23:31:34 PDT 2021
Author: Nathan Ridge
Date: 2021-04-14T02:31:20-04:00
New Revision: cbc9c4ea90e17980b7b65966f4bbdba26a395e45
URL: https://github.com/llvm/llvm-project/commit/cbc9c4ea90e17980b7b65966f4bbdba26a395e45
DIFF: https://github.com/llvm/llvm-project/commit/cbc9c4ea90e17980b7b65966f4bbdba26a395e45.diff
LOG: [clangd] Add support for inline parameter hints
Differential Revision: https://reviews.llvm.org/D98748
Added:
clang-tools-extra/clangd/InlayHints.cpp
clang-tools-extra/clangd/InlayHints.h
clang-tools-extra/clangd/unittests/InlayHintTests.cpp
Modified:
clang-tools-extra/clangd/CMakeLists.txt
clang-tools-extra/clangd/ClangdLSPServer.cpp
clang-tools-extra/clangd/ClangdLSPServer.h
clang-tools-extra/clangd/ClangdServer.cpp
clang-tools-extra/clangd/ClangdServer.h
clang-tools-extra/clangd/Protocol.cpp
clang-tools-extra/clangd/Protocol.h
clang-tools-extra/clangd/XRefs.cpp
clang-tools-extra/clangd/test/initialize-params.test
clang-tools-extra/clangd/unittests/CMakeLists.txt
Removed:
################################################################################
diff --git a/clang-tools-extra/clangd/CMakeLists.txt b/clang-tools-extra/clangd/CMakeLists.txt
index 11b8c7eadd1de..671e55e8622d3 100644
--- a/clang-tools-extra/clangd/CMakeLists.txt
+++ b/clang-tools-extra/clangd/CMakeLists.txt
@@ -76,6 +76,7 @@ add_clang_library(clangDaemon
HeuristicResolver.cpp
Hover.cpp
IncludeFixer.cpp
+ InlayHints.cpp
JSONTransport.cpp
PathMapping.cpp
Protocol.cpp
diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp
index aef849d8d8d97..4916dfaafd5ae 100644
--- a/clang-tools-extra/clangd/ClangdLSPServer.cpp
+++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp
@@ -571,6 +571,7 @@ void ClangdLSPServer::onInitialize(const InitializeParams &Params,
{"referencesProvider", true},
{"astProvider", true}, // clangd extension
{"typeHierarchyProvider", true},
+ {"clangdInlayHintsProvider", true},
{"memoryUsageProvider", true}, // clangd extension
{"compilationDatabase", // clangd extension
llvm::json::Object{{"automaticReload", true}}},
@@ -1208,6 +1209,11 @@ void ClangdLSPServer::onCallHierarchyOutgoingCalls(
Reply(std::vector<CallHierarchyOutgoingCall>{});
}
+void ClangdLSPServer::onInlayHints(const InlayHintsParams &Params,
+ Callback<std::vector<InlayHint>> Reply) {
+ Server->inlayHints(Params.textDocument.uri.file(), std::move(Reply));
+}
+
void ClangdLSPServer::applyConfiguration(
const ConfigurationSettings &Settings) {
// Per-file update to the compilation database.
@@ -1471,6 +1477,7 @@ void ClangdLSPServer::bindMethods(LSPBinder &Bind,
Bind.method("textDocument/documentLink", this, &ClangdLSPServer::onDocumentLink);
Bind.method("textDocument/semanticTokens/full", this, &ClangdLSPServer::onSemanticTokens);
Bind.method("textDocument/semanticTokens/full/delta", this, &ClangdLSPServer::onSemanticTokensDelta);
+ Bind.method("clangd/inlayHints", this, &ClangdLSPServer::onInlayHints);
Bind.method("$/memoryUsage", this, &ClangdLSPServer::onMemoryUsage);
if (Opts.FoldingRanges)
Bind.method("textDocument/foldingRange", this, &ClangdLSPServer::onFoldingRange);
diff --git a/clang-tools-extra/clangd/ClangdLSPServer.h b/clang-tools-extra/clangd/ClangdLSPServer.h
index fc13c6257ee60..577ee66b34243 100644
--- a/clang-tools-extra/clangd/ClangdLSPServer.h
+++ b/clang-tools-extra/clangd/ClangdLSPServer.h
@@ -145,6 +145,7 @@ class ClangdLSPServer : private ClangdServer::Callbacks,
void onCallHierarchyOutgoingCalls(
const CallHierarchyOutgoingCallsParams &,
Callback<std::vector<CallHierarchyOutgoingCall>>);
+ void onInlayHints(const InlayHintsParams &, Callback<std::vector<InlayHint>>);
void onChangeConfiguration(const DidChangeConfigurationParams &);
void onSymbolInfo(const TextDocumentPositionParams &,
Callback<std::vector<SymbolDetails>>);
diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp
index f045cd1b4f8d2..86eb53130d55d 100644
--- a/clang-tools-extra/clangd/ClangdServer.cpp
+++ b/clang-tools-extra/clangd/ClangdServer.cpp
@@ -15,6 +15,7 @@
#include "Format.h"
#include "HeaderSourceSwitch.h"
#include "Headers.h"
+#include "InlayHints.h"
#include "ParsedAST.h"
#include "Preamble.h"
#include "Protocol.h"
@@ -751,6 +752,17 @@ void ClangdServer::incomingCalls(
});
}
+void ClangdServer::inlayHints(PathRef File,
+ Callback<std::vector<InlayHint>> CB) {
+ auto Action = [File = File.str(),
+ CB = std::move(CB)](Expected<InputsAndAST> InpAST) mutable {
+ if (!InpAST)
+ return CB(InpAST.takeError());
+ CB(clangd::inlayHints(InpAST->AST));
+ };
+ WorkScheduler->runWithAST("InlayHints", File, std::move(Action));
+}
+
void ClangdServer::onFileEvent(const DidChangeWatchedFilesParams &Params) {
// FIXME: Do nothing for now. This will be used for indexing and potentially
// invalidating other caches.
diff --git a/clang-tools-extra/clangd/ClangdServer.h b/clang-tools-extra/clangd/ClangdServer.h
index 2dbf144552724..dc1ce22463f42 100644
--- a/clang-tools-extra/clangd/ClangdServer.h
+++ b/clang-tools-extra/clangd/ClangdServer.h
@@ -262,6 +262,9 @@ class ClangdServer {
void incomingCalls(const CallHierarchyItem &Item,
Callback<std::vector<CallHierarchyIncomingCall>>);
+ /// Resolve inlay hints for a given document.
+ void inlayHints(PathRef File, Callback<std::vector<InlayHint>>);
+
/// Retrieve the top symbols from the workspace matching a query.
void workspaceSymbols(StringRef Query, int Limit,
Callback<std::vector<SymbolInformation>> CB);
diff --git a/clang-tools-extra/clangd/InlayHints.cpp b/clang-tools-extra/clangd/InlayHints.cpp
new file mode 100644
index 0000000000000..54b109443b308
--- /dev/null
+++ b/clang-tools-extra/clangd/InlayHints.cpp
@@ -0,0 +1,221 @@
+//===--- InlayHints.cpp ------------------------------------------*- C++-*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+#include "InlayHints.h"
+#include "ParsedAST.h"
+#include "support/Logger.h"
+#include "clang/AST/DeclarationName.h"
+#include "clang/AST/ExprCXX.h"
+#include "clang/AST/RecursiveASTVisitor.h"
+#include "clang/Basic/SourceManager.h"
+
+namespace clang {
+namespace clangd {
+
+class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {
+public:
+ InlayHintVisitor(std::vector<InlayHint> &Results, ParsedAST &AST)
+ : Results(Results), AST(AST.getASTContext()) {}
+
+ bool VisitCXXConstructExpr(CXXConstructExpr *E) {
+ // Weed out constructor calls that don't look like a function call with
+ // an argument list, by checking the validity of getParenOrBraceRange().
+ // Also weed out std::initializer_list constructors as there are no names
+ // for the individual arguments.
+ if (!E->getParenOrBraceRange().isValid() ||
+ E->isStdInitListInitialization()) {
+ return true;
+ }
+
+ processCall(E->getParenOrBraceRange().getBegin(), E->getConstructor(),
+ {E->getArgs(), E->getNumArgs()});
+ return true;
+ }
+
+ bool VisitCallExpr(CallExpr *E) {
+ // 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
+ // argument and then we'd get two hints side by side).
+ if (isa<CXXOperatorCallExpr>(E) || isa<UserDefinedLiteral>(E))
+ return true;
+
+ processCall(E->getRParenLoc(),
+ dyn_cast_or_null<FunctionDecl>(E->getCalleeDecl()),
+ {E->getArgs(), E->getNumArgs()});
+ return true;
+ }
+
+ // FIXME: Handle RecoveryExpr to try to hint some invalid calls.
+
+private:
+ // The purpose of Anchor is to deal with macros. It should be the call's
+ // opening or closing parenthesis or brace. (Always using the opening would
+ // make more sense but CallExpr only exposes the closing.) We heuristically
+ // assume that if this location does not come from a macro definition, then
+ // the entire argument list likely appears in the main file and can be hinted.
+ void processCall(SourceLocation Anchor, const FunctionDecl *Callee,
+ llvm::ArrayRef<const Expr *const> Args) {
+ if (Args.size() == 0 || !Callee)
+ return;
+
+ // If the anchor location comes from a macro defintion, there's nowhere to
+ // put hints.
+ if (!AST.getSourceManager().getTopMacroCallerLoc(Anchor).isFileID())
+ return;
+
+ // The parameter name of a move or copy constructor is not very interesting.
+ if (auto *Ctor = dyn_cast<CXXConstructorDecl>(Callee))
+ if (Ctor->isCopyOrMoveConstructor())
+ return;
+
+ // FIXME: Exclude setters (i.e. functions with one argument whose name
+ // begins with "set"), as their parameter name is also not likely to be
+ // interesting.
+
+ // Don't show hints for variadic parameters.
+ size_t FixedParamCount = getFixedParamCount(Callee);
+ size_t ArgCount = std::min(FixedParamCount, Args.size());
+
+ NameVec ParameterNames = chooseParameterNames(Callee, ArgCount);
+
+ for (size_t I = 0; I < ArgCount; ++I) {
+ StringRef Name = ParameterNames[I];
+ if (!shouldHint(Args[I], Name))
+ continue;
+
+ addInlayHint(Args[I]->getSourceRange(), InlayHintKind::ParameterHint,
+ Name.str() + ": ");
+ }
+ }
+
+ bool shouldHint(const Expr *Arg, StringRef ParamName) {
+ if (ParamName.empty())
+ return false;
+
+ // If the argument expression is a single name and it matches the
+ // parameter name exactly, omit the hint.
+ if (ParamName == getSpelledIdentifier(Arg))
+ return false;
+
+ // FIXME: Exclude argument expressions preceded by a /*paramName=*/ comment.
+
+ return true;
+ }
+
+ // If "E" spells a single unqualified identifier, return that name.
+ // Otherwise, return an empty string.
+ static StringRef getSpelledIdentifier(const Expr *E) {
+ E = E->IgnoreUnlessSpelledInSource();
+
+ if (auto *DRE = dyn_cast<DeclRefExpr>(E))
+ if (!DRE->getQualifier())
+ return getSimpleName(*DRE->getDecl());
+
+ if (auto *ME = dyn_cast<MemberExpr>(E))
+ if (!ME->getQualifier() && ME->isImplicitAccess())
+ return getSimpleName(*ME->getMemberDecl());
+
+ return {};
+ }
+
+ using NameVec = SmallVector<StringRef, 8>;
+
+ NameVec chooseParameterNames(const FunctionDecl *Callee, size_t ArgCount) {
+ // The current strategy here is to use all the parameter names from the
+ // canonical declaration, unless they're all empty, in which case we
+ // use all the parameter names from the definition (in present in the
+ // translation unit).
+ // We could try a bit harder, e.g.:
+ // - try all re-declarations, not just canonical + definition
+ // - fall back arg-by-arg rather than wholesale
+
+ NameVec ParameterNames = getParameterNamesForDecl(Callee, ArgCount);
+
+ if (llvm::all_of(ParameterNames, std::mem_fn(&StringRef::empty))) {
+ if (const FunctionDecl *Def = Callee->getDefinition()) {
+ ParameterNames = getParameterNamesForDecl(Def, ArgCount);
+ }
+ }
+ assert(ParameterNames.size() == ArgCount);
+
+ // Standard library functions often have parameter names that start
+ // with underscores, which makes the hints noisy, so strip them out.
+ for (auto &Name : ParameterNames)
+ stripLeadingUnderscores(Name);
+
+ return ParameterNames;
+ }
+
+ static void stripLeadingUnderscores(StringRef &Name) {
+ Name = Name.ltrim('_');
+ }
+
+ // Return the number of fixed parameters Function has, that is, not counting
+ // parameters that are variadic (instantiated from a parameter pack) or
+ // C-style varargs.
+ static size_t getFixedParamCount(const FunctionDecl *Function) {
+ if (FunctionTemplateDecl *Template = Function->getPrimaryTemplate()) {
+ FunctionDecl *F = Template->getTemplatedDecl();
+ size_t Result = 0;
+ for (ParmVarDecl *Parm : F->parameters()) {
+ if (Parm->isParameterPack()) {
+ break;
+ }
+ ++Result;
+ }
+ return Result;
+ }
+ // C-style varargs don't need special handling, they're already
+ // not included in getNumParams().
+ return Function->getNumParams();
+ }
+
+ static StringRef getSimpleName(const NamedDecl &D) {
+ if (IdentifierInfo *Ident = D.getDeclName().getAsIdentifierInfo()) {
+ return Ident->getName();
+ }
+
+ return StringRef();
+ }
+
+ NameVec getParameterNamesForDecl(const FunctionDecl *Function,
+ size_t ArgCount) {
+ NameVec Result;
+ for (size_t I = 0; I < ArgCount; ++I) {
+ const ParmVarDecl *Parm = Function->getParamDecl(I);
+ assert(Parm);
+ Result.emplace_back(getSimpleName(*Parm));
+ }
+ return Result;
+ }
+
+ void addInlayHint(SourceRange R, InlayHintKind Kind, llvm::StringRef Label) {
+ auto FileRange =
+ toHalfOpenFileRange(AST.getSourceManager(), AST.getLangOpts(), R);
+ if (!FileRange)
+ return;
+ Results.push_back(InlayHint{
+ Range{
+ sourceLocToPosition(AST.getSourceManager(), FileRange->getBegin()),
+ sourceLocToPosition(AST.getSourceManager(), FileRange->getEnd())},
+ Kind, Label.str()});
+ }
+
+ std::vector<InlayHint> &Results;
+ ASTContext &AST;
+};
+
+std::vector<InlayHint> inlayHints(ParsedAST &AST) {
+ std::vector<InlayHint> Results;
+ InlayHintVisitor Visitor(Results, AST);
+ Visitor.TraverseAST(AST.getASTContext());
+ return Results;
+}
+
+} // namespace clangd
+} // namespace clang
diff --git a/clang-tools-extra/clangd/InlayHints.h b/clang-tools-extra/clangd/InlayHints.h
new file mode 100644
index 0000000000000..7fff916a24c03
--- /dev/null
+++ b/clang-tools-extra/clangd/InlayHints.h
@@ -0,0 +1,31 @@
+//===--- InlayHints.h --------------------------------------------*- C++-*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// Support for the proposed "inlay hints" LSP feature.
+// The version currently implemented is the one proposed here:
+// https://github.com/microsoft/vscode-languageserver-node/pull/609/.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_INLAY_HINTS_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_INLAY_HINTS_H
+
+#include "Protocol.h"
+#include <vector>
+
+namespace clang {
+namespace clangd {
+class ParsedAST;
+
+// Compute and return all inlay hints for a file.
+std::vector<InlayHint> inlayHints(ParsedAST &AST);
+
+} // namespace clangd
+} // namespace clang
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANGD_INLAY_HINTS_H
diff --git a/clang-tools-extra/clangd/Protocol.cpp b/clang-tools-extra/clangd/Protocol.cpp
index 3a94b2965a6d7..5432392649092 100644
--- a/clang-tools-extra/clangd/Protocol.cpp
+++ b/clang-tools-extra/clangd/Protocol.cpp
@@ -1303,6 +1303,25 @@ llvm::json::Value toJSON(const CallHierarchyOutgoingCall &C) {
return llvm::json::Object{{"to", C.to}, {"fromRanges", C.fromRanges}};
}
+bool fromJSON(const llvm::json::Value &Params, InlayHintsParams &R,
+ llvm::json::Path P) {
+ llvm::json::ObjectMapper O(Params, P);
+ return O && O.map("textDocument", R.textDocument);
+}
+
+llvm::json::Value toJSON(InlayHintKind K) {
+ switch (K) {
+ case InlayHintKind::ParameterHint:
+ return "parameter";
+ }
+ llvm_unreachable("Unknown clang.clangd.InlayHintKind");
+}
+
+llvm::json::Value toJSON(const InlayHint &H) {
+ return llvm::json::Object{
+ {"range", H.range}, {"kind", H.kind}, {"label", H.label}};
+}
+
static const char *toString(OffsetEncoding OE) {
switch (OE) {
case OffsetEncoding::UTF8:
diff --git a/clang-tools-extra/clangd/Protocol.h b/clang-tools-extra/clangd/Protocol.h
index 79c0e0ea15a74..8ee0bb00396dd 100644
--- a/clang-tools-extra/clangd/Protocol.h
+++ b/clang-tools-extra/clangd/Protocol.h
@@ -1485,6 +1485,48 @@ struct CallHierarchyOutgoingCall {
};
llvm::json::Value toJSON(const CallHierarchyOutgoingCall &);
+/// The parameter of a `textDocument/inlayHints` request.
+struct InlayHintsParams {
+ /// The text document for which inlay hints are requested.
+ TextDocumentIdentifier textDocument;
+};
+bool fromJSON(const llvm::json::Value &, InlayHintsParams &, llvm::json::Path);
+
+/// A set of predefined hint kinds.
+enum class InlayHintKind {
+ /// The hint corresponds to parameter information.
+ /// An example of a parameter hint is a hint in this position:
+ /// func(^arg);
+ /// which shows the name of the corresponding parameter.
+ ParameterHint,
+
+ /// Other ideas for hints that are not currently implemented:
+ ///
+ /// * Type hints, showing deduced types.
+ /// * Chaining hints, showing the types of intermediate expressions
+ /// in a chain of function calls.
+ /// * Hints indicating implicit conversions or implicit constructor calls.
+};
+llvm::json::Value toJSON(InlayHintKind);
+
+/// An annotation to be displayed inline next to a range of source code.
+struct InlayHint {
+ /// The range of source code to which the hint applies.
+ /// We provide the entire range, rather than just the endpoint
+ /// relevant to `position` (e.g. the start of the range for
+ /// InlayHintPosition::Before), to give clients the flexibility
+ /// to make choices like only displaying the hint while the cursor
+ /// is over the range, rather than displaying it all the time.
+ Range range;
+
+ /// The type of hint.
+ InlayHintKind kind;
+
+ /// The label that is displayed in the editor.
+ std::string label;
+};
+llvm::json::Value toJSON(const InlayHint &);
+
struct ReferenceContext {
/// Include the declaration of the current symbol.
bool includeDeclaration = false;
diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp
index 65bbbcd28b409..e4c8db24eb20f 100644
--- a/clang-tools-extra/clangd/XRefs.cpp
+++ b/clang-tools-extra/clangd/XRefs.cpp
@@ -30,6 +30,7 @@
#include "clang/AST/DeclObjC.h"
#include "clang/AST/DeclTemplate.h"
#include "clang/AST/ExprCXX.h"
+#include "clang/AST/ExternalASTSource.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/AST/Stmt.h"
#include "clang/AST/StmtCXX.h"
@@ -1980,5 +1981,6 @@ llvm::DenseSet<const Decl *> getNonLocalDeclRefs(ParsedAST &AST,
AST.getHeuristicResolver());
return DeclRefs;
}
+
} // namespace clangd
-} // namespace clang
+} // namespace clang
\ No newline at end of file
diff --git a/clang-tools-extra/clangd/test/initialize-params.test b/clang-tools-extra/clangd/test/initialize-params.test
index 4b40b17b2d64e..6cc6ac7272c58 100644
--- a/clang-tools-extra/clangd/test/initialize-params.test
+++ b/clang-tools-extra/clangd/test/initialize-params.test
@@ -7,6 +7,7 @@
# CHECK-NEXT: "capabilities": {
# CHECK-NEXT: "astProvider": true,
# CHECK-NEXT: "callHierarchyProvider": true,
+# CHECK-NEXT: "clangdInlayHintsProvider": true,
# CHECK-NEXT: "codeActionProvider": true,
# CHECK-NEXT: "compilationDatabase": {
# CHECK-NEXT: "automaticReload": true
diff --git a/clang-tools-extra/clangd/unittests/CMakeLists.txt b/clang-tools-extra/clangd/unittests/CMakeLists.txt
index 4fc0fa7f7a903..3a439b11e6322 100644
--- a/clang-tools-extra/clangd/unittests/CMakeLists.txt
+++ b/clang-tools-extra/clangd/unittests/CMakeLists.txt
@@ -69,6 +69,7 @@ add_unittest(ClangdUnitTests ClangdTests
HoverTests.cpp
IndexActionTests.cpp
IndexTests.cpp
+ InlayHintTests.cpp
JSONTransportTests.cpp
LoggerTests.cpp
LSPBinderTests.cpp
diff --git a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
new file mode 100644
index 0000000000000..6a39dc9d923a5
--- /dev/null
+++ b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
@@ -0,0 +1,327 @@
+//===-- InlayHintTests.cpp -------------------------------*- C++ -*-------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+#include "Annotations.h"
+#include "InlayHints.h"
+#include "Protocol.h"
+#include "TestTU.h"
+#include "XRefs.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace clang {
+namespace clangd {
+namespace {
+
+using ::testing::UnorderedElementsAre;
+
+std::vector<InlayHint> parameterHints(ParsedAST &AST) {
+ std::vector<InlayHint> Result;
+ for (auto &Hint : inlayHints(AST)) {
+ if (Hint.kind == InlayHintKind::ParameterHint)
+ Result.push_back(Hint);
+ }
+ return Result;
+}
+
+struct ExpectedHint {
+ std::string Label;
+ std::string RangeName;
+};
+
+MATCHER_P2(HintMatcher, Expected, Code, "") {
+ return arg.label == Expected.Label &&
+ arg.range == Code.range(Expected.RangeName);
+}
+
+template <typename... ExpectedHints>
+void assertParameterHints(llvm::StringRef AnnotatedSource,
+ ExpectedHints... Expected) {
+ Annotations Source(AnnotatedSource);
+ TestTU TU = TestTU::withCode(Source.code());
+ TU.ExtraArgs.push_back("-std=c++11");
+ auto AST = TU.build();
+
+ EXPECT_THAT(parameterHints(AST),
+ UnorderedElementsAre(HintMatcher(Expected, Source)...));
+}
+
+TEST(ParameterHints, Smoke) {
+ assertParameterHints(R"cpp(
+ void foo(int param);
+ void bar() {
+ foo($param[[42]]);
+ }
+ )cpp",
+ ExpectedHint{"param: ", "param"});
+}
+
+TEST(ParameterHints, NoName) {
+ // No hint for anonymous parameter.
+ assertParameterHints(R"cpp(
+ void foo(int);
+ void bar() {
+ foo(42);
+ }
+ )cpp");
+}
+
+TEST(ParameterHints, NameInDefinition) {
+ // Parameter name picked up from definition if necessary.
+ assertParameterHints(R"cpp(
+ void foo(int);
+ void bar() {
+ foo($param[[42]]);
+ }
+ void foo(int param) {};
+ )cpp",
+ ExpectedHint{"param: ", "param"});
+}
+
+TEST(ParameterHints, NameMismatch) {
+ // Prefer name from declaration.
+ assertParameterHints(R"cpp(
+ void foo(int good);
+ void bar() {
+ foo($good[[42]]);
+ }
+ void foo(int bad) {};
+ )cpp",
+ ExpectedHint{"good: ", "good"});
+}
+
+TEST(ParameterHints, Operator) {
+ // No hint for operator call with operator syntax.
+ assertParameterHints(R"cpp(
+ struct S {};
+ void operator+(S lhs, S rhs);
+ void bar() {
+ S a, b;
+ a + b;
+ }
+ )cpp");
+}
+
+TEST(ParameterHints, Macros) {
+ // Handling of macros depends on where the call's argument list comes from.
+
+ // If it comes from a macro definition, there's nothing to hint
+ // at the invocation site.
+ assertParameterHints(R"cpp(
+ void foo(int param);
+ #define ExpandsToCall() foo(42)
+ void bar() {
+ ExpandsToCall();
+ }
+ )cpp");
+
+ // The argument expression being a macro invocation shouldn't interfere
+ // with hinting.
+ assertParameterHints(R"cpp(
+ #define PI 3.14
+ void foo(double param);
+ void bar() {
+ foo($param[[PI]]);
+ }
+ )cpp",
+ ExpectedHint{"param: ", "param"});
+
+ // If the whole argument list comes from a macro parameter, hint it.
+ assertParameterHints(R"cpp(
+ void abort();
+ #define ASSERT(expr) if (!expr) abort()
+ int foo(int param);
+ void bar() {
+ ASSERT(foo($param[[42]]) == 0);
+ }
+ )cpp",
+ ExpectedHint{"param: ", "param"});
+}
+
+TEST(ParameterHints, ConstructorParens) {
+ assertParameterHints(R"cpp(
+ struct S {
+ S(int param);
+ };
+ void bar() {
+ S obj($param[[42]]);
+ }
+ )cpp",
+ ExpectedHint{"param: ", "param"});
+}
+
+TEST(ParameterHints, ConstructorBraces) {
+ assertParameterHints(R"cpp(
+ struct S {
+ S(int param);
+ };
+ void bar() {
+ S obj{$param[[42]]};
+ }
+ )cpp",
+ ExpectedHint{"param: ", "param"});
+}
+
+TEST(ParameterHints, ConstructorStdInitList) {
+ // Do not show hints for std::initializer_list constructors.
+ assertParameterHints(R"cpp(
+ namespace std {
+ template <typename> class initializer_list {};
+ }
+ struct S {
+ S(std::initializer_list<int> param);
+ };
+ void bar() {
+ S obj{42, 43};
+ }
+ )cpp");
+}
+
+TEST(ParameterHints, MemberInit) {
+ assertParameterHints(R"cpp(
+ struct S {
+ S(int param);
+ };
+ struct T {
+ S member;
+ T() : member($param[[42]]) {}
+ };
+ )cpp",
+ ExpectedHint{"param: ", "param"});
+}
+
+TEST(ParameterHints, ImplicitConstructor) {
+ assertParameterHints(R"cpp(
+ struct S {
+ S(int param);
+ };
+ void bar(S);
+ S foo() {
+ // Do not show hint for implicit constructor call in argument.
+ bar(42);
+ // Do not show hint for implicit constructor call in return.
+ return 42;
+ }
+ )cpp");
+}
+
+TEST(ParameterHints, ArgMatchesParam) {
+ assertParameterHints(R"cpp(
+ void foo(int param);
+ struct S {
+ static const int param = 42;
+ };
+ void bar() {
+ int param = 42;
+ // Do not show redundant "param: param".
+ foo(param);
+ // But show it if the argument is qualified.
+ foo($param[[S::param]]);
+ }
+ struct A {
+ int param;
+ void bar() {
+ // Do not show "param: param" for member-expr.
+ foo(param);
+ }
+ };
+ )cpp",
+ ExpectedHint{"param: ", "param"});
+}
+
+TEST(ParameterHints, LeadingUnderscore) {
+ assertParameterHints(R"cpp(
+ void foo(int p1, int _p2, int __p3);
+ void bar() {
+ foo($p1[[41]], $p2[[42]], $p3[[43]]);
+ }
+ )cpp",
+ ExpectedHint{"p1: ", "p1"}, ExpectedHint{"p2: ", "p2"},
+ ExpectedHint{"p3: ", "p3"});
+}
+
+TEST(ParameterHints, DependentCall) {
+ // FIXME: This doesn't currently produce a hint but should.
+ assertParameterHints(R"cpp(
+ template <typename T>
+ void foo(T param);
+
+ template <typename T>
+ struct S {
+ void bar(T par) {
+ foo($param[[par]]);
+ }
+ };
+ )cpp");
+}
+
+TEST(ParameterHints, VariadicFunction) {
+ assertParameterHints(R"cpp(
+ template <typename... T>
+ void foo(int fixed, T... variadic);
+
+ void bar() {
+ foo($fixed[[41]], 42, 43);
+ }
+ )cpp",
+ ExpectedHint{"fixed: ", "fixed"});
+}
+
+TEST(ParameterHints, VarargsFunction) {
+ assertParameterHints(R"cpp(
+ void foo(int fixed, ...);
+
+ void bar() {
+ foo($fixed[[41]], 42, 43);
+ }
+ )cpp",
+ ExpectedHint{"fixed: ", "fixed"});
+}
+
+TEST(ParameterHints, CopyOrMoveConstructor) {
+ // Do not show hint for parameter of copy or move constructor.
+ assertParameterHints(R"cpp(
+ struct S {
+ S();
+ S(const S& other);
+ S(S&& other);
+ };
+ void bar() {
+ S a;
+ S b(a); // copy
+ S c(S()); // move
+ }
+ )cpp");
+}
+
+TEST(ParameterHints, AggregateInit) {
+ // FIXME: This is not implemented yet, but it would be a natural
+ // extension to show member names as hints here.
+ assertParameterHints(R"cpp(
+ struct Point {
+ int x;
+ int y;
+ };
+ void bar() {
+ Point p{41, 42};
+ }
+ )cpp");
+}
+
+TEST(ParameterHints, UserDefinedLiteral) {
+ // Do not hint call to user-defined literal operator.
+ assertParameterHints(R"cpp(
+ long double operator"" _w(long double param);
+ void bar() {
+ 1.2_w;
+ }
+ )cpp");
+}
+
+} // namespace
+} // namespace clangd
+} // namespace clang
More information about the cfe-commits
mailing list