[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