forked from OSchip/llvm-project
[clangd] Add support for inline parameter hints
Differential Revision: https://reviews.llvm.org/D98748
This commit is contained in:
parent
02265ed7ad
commit
cbc9c4ea90
|
@ -76,6 +76,7 @@ add_clang_library(clangDaemon
|
|||
HeuristicResolver.cpp
|
||||
Hover.cpp
|
||||
IncludeFixer.cpp
|
||||
InlayHints.cpp
|
||||
JSONTransport.cpp
|
||||
PathMapping.cpp
|
||||
Protocol.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);
|
||||
|
|
|
@ -145,6 +145,7 @@ private:
|
|||
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>>);
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -262,6 +262,9 @@ public:
|
|||
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);
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -69,6 +69,7 @@ add_unittest(ClangdUnitTests ClangdTests
|
|||
HoverTests.cpp
|
||||
IndexActionTests.cpp
|
||||
IndexTests.cpp
|
||||
InlayHintTests.cpp
|
||||
JSONTransportTests.cpp
|
||||
LoggerTests.cpp
|
||||
LSPBinderTests.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
|
Loading…
Reference in New Issue