mirror of
https://github.com/llvm/llvm-project.git
synced 2025-04-18 19:16:43 +00:00
[clangd] Add inlay hints for auto-typed parameters with one instantiation.
This takes a similar approach as b9b6938183e, and shares some code. The code sharing is limited as inlay hints wants to deduce the type of the variable rather than the type of the `auto` per-se. It drops support (in both places) for multiple instantiations yielding the same type, as this is pretty rare and hard to build a nice API around. Differential Revision: https://reviews.llvm.org/D120258
This commit is contained in:
parent
a0f6d12cd4
commit
2da5c5781e
@ -485,21 +485,22 @@ public:
|
||||
}
|
||||
|
||||
// Handle functions/lambdas with `auto` typed parameters.
|
||||
// We'll examine visible specializations and see if they yield a unique type.
|
||||
// We deduce the type if there's exactly one instantiation visible.
|
||||
bool VisitParmVarDecl(ParmVarDecl *PVD) {
|
||||
if (!PVD->getType()->isDependentType())
|
||||
return true;
|
||||
// 'auto' here does not name an AutoType, but an implicit template param.
|
||||
TemplateTypeParmTypeLoc Auto =
|
||||
findContainedAutoTTPLoc(PVD->getTypeSourceInfo()->getTypeLoc());
|
||||
getContainedAutoParamType(PVD->getTypeSourceInfo()->getTypeLoc());
|
||||
if (Auto.isNull() || Auto.getNameLoc() != SearchedLocation)
|
||||
return true;
|
||||
|
||||
// We expect the TTP to be attached to this function template.
|
||||
// Find the template and the param index.
|
||||
auto *FD = llvm::dyn_cast<FunctionDecl>(PVD->getDeclContext());
|
||||
if (!FD)
|
||||
auto *Templated = llvm::dyn_cast<FunctionDecl>(PVD->getDeclContext());
|
||||
if (!Templated)
|
||||
return true;
|
||||
auto *FTD = FD->getDescribedFunctionTemplate();
|
||||
auto *FTD = Templated->getDescribedFunctionTemplate();
|
||||
if (!FTD)
|
||||
return true;
|
||||
int ParamIndex = paramIndex(*FTD, *Auto.getDecl());
|
||||
@ -508,53 +509,18 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
// Now determine the unique type arg among the implicit specializations.
|
||||
const ASTContext &Ctx = PVD->getASTContext();
|
||||
QualType UniqueType;
|
||||
CanQualType CanUniqueType;
|
||||
for (const FunctionDecl *Spec : FTD->specializations()) {
|
||||
// Meaning `auto` is a bit overloaded if the function is specialized.
|
||||
if (Spec->getTemplateSpecializationKind() == TSK_ExplicitSpecialization)
|
||||
return true;
|
||||
// Find the type for this specialization.
|
||||
const auto *Args = Spec->getTemplateSpecializationArgs();
|
||||
if (Args->size() != FTD->getTemplateParameters()->size())
|
||||
continue; // no weird variadic stuff
|
||||
QualType SpecType = Args->get(ParamIndex).getAsType();
|
||||
if (SpecType.isNull())
|
||||
continue;
|
||||
|
||||
// Deduced types need only be *canonically* equal.
|
||||
CanQualType CanSpecType = Ctx.getCanonicalType(SpecType);
|
||||
if (CanUniqueType.isNull()) {
|
||||
CanUniqueType = CanSpecType;
|
||||
UniqueType = SpecType;
|
||||
continue;
|
||||
}
|
||||
if (CanUniqueType != CanSpecType)
|
||||
return true; // deduced type is not unique
|
||||
}
|
||||
DeducedType = UniqueType;
|
||||
// Now find the instantiation and the deduced template type arg.
|
||||
auto *Instantiation =
|
||||
llvm::dyn_cast_or_null<FunctionDecl>(getOnlyInstantiation(Templated));
|
||||
if (!Instantiation)
|
||||
return true;
|
||||
const auto *Args = Instantiation->getTemplateSpecializationArgs();
|
||||
if (Args->size() != FTD->getTemplateParameters()->size())
|
||||
return true; // no weird variadic stuff
|
||||
DeducedType = Args->get(ParamIndex).getAsType();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Find the abbreviated-function-template `auto` within a type.
|
||||
// Similar to getContainedAutoTypeLoc, but these `auto`s are
|
||||
// TemplateTypeParmTypes for implicit TTPs, instead of AutoTypes.
|
||||
// Also we don't look very hard, just stripping const, references, pointers.
|
||||
// FIXME: handle more types: vector<auto>?
|
||||
static TemplateTypeParmTypeLoc findContainedAutoTTPLoc(TypeLoc TL) {
|
||||
if (auto QTL = TL.getAs<QualifiedTypeLoc>())
|
||||
return findContainedAutoTTPLoc(QTL.getUnqualifiedLoc());
|
||||
if (llvm::isa<PointerType, ReferenceType>(TL.getTypePtr()))
|
||||
return findContainedAutoTTPLoc(TL.getNextTypeLoc());
|
||||
if (auto TTPTL = TL.getAs<TemplateTypeParmTypeLoc>()) {
|
||||
if (TTPTL.getTypePtr()->getDecl()->isImplicit())
|
||||
return TTPTL;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
static int paramIndex(const TemplateDecl &TD, NamedDecl &Param) {
|
||||
unsigned I = 0;
|
||||
for (auto *ND : *TD.getTemplateParameters()) {
|
||||
@ -580,6 +546,45 @@ llvm::Optional<QualType> getDeducedType(ASTContext &ASTCtx,
|
||||
return V.DeducedType;
|
||||
}
|
||||
|
||||
TemplateTypeParmTypeLoc getContainedAutoParamType(TypeLoc TL) {
|
||||
if (auto QTL = TL.getAs<QualifiedTypeLoc>())
|
||||
return getContainedAutoParamType(QTL.getUnqualifiedLoc());
|
||||
if (llvm::isa<PointerType, ReferenceType, ParenType>(TL.getTypePtr()))
|
||||
return getContainedAutoParamType(TL.getNextTypeLoc());
|
||||
if (auto FTL = TL.getAs<FunctionTypeLoc>())
|
||||
return getContainedAutoParamType(FTL.getReturnLoc());
|
||||
if (auto TTPTL = TL.getAs<TemplateTypeParmTypeLoc>()) {
|
||||
if (TTPTL.getTypePtr()->getDecl()->isImplicit())
|
||||
return TTPTL;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
template <typename TemplateDeclTy>
|
||||
static NamedDecl *getOnlyInstantiationImpl(TemplateDeclTy *TD) {
|
||||
NamedDecl *Only = nullptr;
|
||||
for (auto *Spec : TD->specializations()) {
|
||||
if (Spec->getTemplateSpecializationKind() == TSK_ExplicitSpecialization)
|
||||
continue;
|
||||
if (Only != nullptr)
|
||||
return nullptr;
|
||||
Only = Spec;
|
||||
}
|
||||
return Only;
|
||||
}
|
||||
|
||||
NamedDecl *getOnlyInstantiation(NamedDecl *TemplatedDecl) {
|
||||
if (TemplateDecl *TD = TemplatedDecl->getDescribedTemplate()) {
|
||||
if (auto *CTD = llvm::dyn_cast<ClassTemplateDecl>(TD))
|
||||
return getOnlyInstantiationImpl(CTD);
|
||||
if (auto *FTD = llvm::dyn_cast<FunctionTemplateDecl>(TD))
|
||||
return getOnlyInstantiationImpl(FTD);
|
||||
if (auto *VTD = llvm::dyn_cast<VarTemplateDecl>(TD))
|
||||
return getOnlyInstantiationImpl(VTD);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<const Attr *> getAttributes(const DynTypedNode &N) {
|
||||
std::vector<const Attr *> Result;
|
||||
if (const auto *TL = N.get<TypeLoc>()) {
|
||||
|
@ -17,6 +17,7 @@
|
||||
#include "clang/AST/Decl.h"
|
||||
#include "clang/AST/DeclObjC.h"
|
||||
#include "clang/AST/NestedNameSpecifier.h"
|
||||
#include "clang/AST/TypeLoc.h"
|
||||
#include "clang/Basic/SourceLocation.h"
|
||||
#include "clang/Lex/MacroInfo.h"
|
||||
#include "llvm/ADT/StringRef.h"
|
||||
@ -127,6 +128,17 @@ QualType declaredType(const TypeDecl *D);
|
||||
/// If the type is an undeduced auto, returns the type itself.
|
||||
llvm::Optional<QualType> getDeducedType(ASTContext &, SourceLocation Loc);
|
||||
|
||||
// Find the abbreviated-function-template `auto` within a type, or returns null.
|
||||
// Similar to getContainedAutoTypeLoc, but these `auto`s are
|
||||
// TemplateTypeParmTypes for implicit TTPs, instead of AutoTypes.
|
||||
// Also we don't look very hard, just stripping const, references, pointers.
|
||||
// FIXME: handle more type patterns.
|
||||
TemplateTypeParmTypeLoc getContainedAutoParamType(TypeLoc TL);
|
||||
|
||||
// If TemplatedDecl is the generic body of a template, and the template has
|
||||
// exactly one visible instantiation, return the instantiated body.
|
||||
NamedDecl *getOnlyInstantiation(NamedDecl *TemplatedDecl);
|
||||
|
||||
/// Return attributes attached directly to a node.
|
||||
std::vector<const Attr *> getAttributes(const DynTypedNode &);
|
||||
|
||||
|
@ -6,6 +6,7 @@
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
#include "InlayHints.h"
|
||||
#include "AST.h"
|
||||
#include "Config.h"
|
||||
#include "HeuristicResolver.h"
|
||||
#include "ParsedAST.h"
|
||||
@ -299,9 +300,46 @@ public:
|
||||
addTypeHint(D->getLocation(), D->getType(), /*Prefix=*/": ");
|
||||
}
|
||||
}
|
||||
|
||||
// Handle templates like `int foo(auto x)` with exactly one instantiation.
|
||||
if (auto *PVD = llvm::dyn_cast<ParmVarDecl>(D)) {
|
||||
if (D->getIdentifier() && PVD->getType()->isDependentType() &&
|
||||
!getContainedAutoParamType(D->getTypeSourceInfo()->getTypeLoc())
|
||||
.isNull()) {
|
||||
if (auto *IPVD = getOnlyParamInstantiation(PVD))
|
||||
addTypeHint(D->getLocation(), IPVD->getType(), /*Prefix=*/": ");
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ParmVarDecl *getOnlyParamInstantiation(ParmVarDecl *D) {
|
||||
auto *TemplateFunction = llvm::dyn_cast<FunctionDecl>(D->getDeclContext());
|
||||
if (!TemplateFunction)
|
||||
return nullptr;
|
||||
auto *InstantiatedFunction = llvm::dyn_cast_or_null<FunctionDecl>(
|
||||
getOnlyInstantiation(TemplateFunction));
|
||||
if (!InstantiatedFunction)
|
||||
return nullptr;
|
||||
|
||||
unsigned ParamIdx = 0;
|
||||
for (auto *Param : TemplateFunction->parameters()) {
|
||||
// Can't reason about param indexes in the presence of preceding packs.
|
||||
// And if this param is a pack, it may expand to multiple params.
|
||||
if (Param->isParameterPack())
|
||||
return nullptr;
|
||||
if (Param == D)
|
||||
break;
|
||||
++ParamIdx;
|
||||
}
|
||||
assert(ParamIdx < TemplateFunction->getNumParams() &&
|
||||
"Couldn't find param in list?");
|
||||
assert(ParamIdx < InstantiatedFunction->getNumParams() &&
|
||||
"Instantiated function has fewer (non-pack) parameters?");
|
||||
return InstantiatedFunction->getParamDecl(ParamIdx);
|
||||
}
|
||||
|
||||
bool VisitInitListExpr(InitListExpr *Syn) {
|
||||
// We receive the syntactic form here (shouldVisitImplicitCode() is false).
|
||||
// This is the one we will ultimately attach designators to.
|
||||
|
@ -30,6 +30,7 @@ namespace clangd {
|
||||
namespace {
|
||||
using testing::Contains;
|
||||
using testing::Each;
|
||||
using testing::IsEmpty;
|
||||
|
||||
TEST(GetDeducedType, KwAutoKwDecltypeExpansion) {
|
||||
struct Test {
|
||||
@ -192,12 +193,12 @@ TEST(GetDeducedType, KwAutoKwDecltypeExpansion) {
|
||||
R"cpp(
|
||||
// Generic lambda instantiated twice, matching deduction.
|
||||
struct Foo{};
|
||||
using Bar = Foo;
|
||||
auto Generic = [](^auto x, auto y) { return 0; };
|
||||
int m = Generic(Bar{}, "one");
|
||||
int m = Generic(Foo{}, "one");
|
||||
int n = Generic(Foo{}, 2);
|
||||
)cpp",
|
||||
"struct Foo",
|
||||
// No deduction although both instantiations yield the same result :-(
|
||||
nullptr,
|
||||
},
|
||||
{
|
||||
R"cpp(
|
||||
@ -253,6 +254,117 @@ TEST(GetDeducedType, KwAutoKwDecltypeExpansion) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ClangdAST, GetOnlyInstantiation) {
|
||||
struct {
|
||||
const char *Code;
|
||||
llvm::StringLiteral NodeType;
|
||||
const char *Name;
|
||||
} Cases[] = {
|
||||
{
|
||||
R"cpp(
|
||||
template <typename> class X {};
|
||||
X<int> x;
|
||||
)cpp",
|
||||
"CXXRecord",
|
||||
"template<> class X<int> {}",
|
||||
},
|
||||
{
|
||||
R"cpp(
|
||||
template <typename T> T X = T{};
|
||||
int y = X<char>;
|
||||
)cpp",
|
||||
"Var",
|
||||
// VarTemplateSpecializationDecl doesn't print as template<>...
|
||||
"char X = char{}",
|
||||
},
|
||||
{
|
||||
R"cpp(
|
||||
template <typename T> int X(T) { return 42; }
|
||||
int y = X("text");
|
||||
)cpp",
|
||||
"Function",
|
||||
"template<> int X<const char *>(const char *)",
|
||||
},
|
||||
{
|
||||
R"cpp(
|
||||
int X(auto *x) { return 42; }
|
||||
int y = X("text");
|
||||
)cpp",
|
||||
"Function",
|
||||
"template<> int X<const char>(const char *x)",
|
||||
},
|
||||
};
|
||||
|
||||
for (const auto &Case : Cases) {
|
||||
SCOPED_TRACE(Case.Code);
|
||||
auto TU = TestTU::withCode(Case.Code);
|
||||
TU.ExtraArgs.push_back("-std=c++20");
|
||||
auto AST = TU.build();
|
||||
PrintingPolicy PP = AST.getASTContext().getPrintingPolicy();
|
||||
PP.TerseOutput = true;
|
||||
std::string Name;
|
||||
if (auto *Result = getOnlyInstantiation(
|
||||
const_cast<NamedDecl *>(&findDecl(AST, [&](const NamedDecl &D) {
|
||||
return D.getDescribedTemplate() != nullptr &&
|
||||
D.getDeclKindName() == Case.NodeType;
|
||||
})))) {
|
||||
llvm::raw_string_ostream OS(Name);
|
||||
Result->print(OS, PP);
|
||||
}
|
||||
|
||||
if (Case.Name)
|
||||
EXPECT_EQ(Case.Name, Name);
|
||||
else
|
||||
EXPECT_THAT(Name, IsEmpty());
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ClangdAST, GetContainedAutoParamType) {
|
||||
auto TU = TestTU::withCode(R"cpp(
|
||||
int withAuto(
|
||||
auto a,
|
||||
auto *b,
|
||||
const auto *c,
|
||||
auto &&d,
|
||||
auto *&e,
|
||||
auto (*f)(int)
|
||||
){};
|
||||
|
||||
int withoutAuto(
|
||||
int a,
|
||||
int *b,
|
||||
const int *c,
|
||||
int &&d,
|
||||
int *&e,
|
||||
int (*f)(int)
|
||||
){};
|
||||
)cpp");
|
||||
TU.ExtraArgs.push_back("-std=c++20");
|
||||
auto AST = TU.build();
|
||||
|
||||
const auto &WithAuto =
|
||||
llvm::cast<FunctionTemplateDecl>(findDecl(AST, "withAuto"));
|
||||
auto ParamsWithAuto = WithAuto.getTemplatedDecl()->parameters();
|
||||
auto *TemplateParamsWithAuto = WithAuto.getTemplateParameters();
|
||||
ASSERT_EQ(ParamsWithAuto.size(), TemplateParamsWithAuto->size());
|
||||
|
||||
for (unsigned I = 0; I < ParamsWithAuto.size(); ++I) {
|
||||
SCOPED_TRACE(ParamsWithAuto[I]->getNameAsString());
|
||||
auto Loc = getContainedAutoParamType(
|
||||
ParamsWithAuto[I]->getTypeSourceInfo()->getTypeLoc());
|
||||
ASSERT_FALSE(Loc.isNull());
|
||||
EXPECT_EQ(Loc.getTypePtr()->getDecl(), TemplateParamsWithAuto->getParam(I));
|
||||
}
|
||||
|
||||
const auto &WithoutAuto =
|
||||
llvm::cast<FunctionDecl>(findDecl(AST, "withoutAuto"));
|
||||
for (auto *ParamWithoutAuto : WithoutAuto.parameters()) {
|
||||
ASSERT_TRUE(getContainedAutoParamType(
|
||||
ParamWithoutAuto->getTypeSourceInfo()->getTypeLoc())
|
||||
.isNull());
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ClangdAST, GetQualification) {
|
||||
// Tries to insert the decl `Foo` into position of each decl named `insert`.
|
||||
// This is done to get an appropriate DeclContext for the insertion location.
|
||||
|
@ -58,7 +58,7 @@ MATCHER_P2(HintMatcher, Expected, Code, llvm::to_string(Expected)) {
|
||||
return false;
|
||||
}
|
||||
if (arg.range != Code.range(Expected.RangeName)) {
|
||||
*result_listener << "range is " << arg.label << " but $"
|
||||
*result_listener << "range is " << llvm::to_string(arg.range) << " but $"
|
||||
<< Expected.RangeName << " is "
|
||||
<< llvm::to_string(Code.range(Expected.RangeName));
|
||||
return false;
|
||||
@ -81,7 +81,7 @@ void assertHints(InlayHintKind Kind, llvm::StringRef AnnotatedSource,
|
||||
ExpectedHints... Expected) {
|
||||
Annotations Source(AnnotatedSource);
|
||||
TestTU TU = TestTU::withCode(Source.code());
|
||||
TU.ExtraArgs.push_back("-std=c++14");
|
||||
TU.ExtraArgs.push_back("-std=c++20");
|
||||
auto AST = TU.build();
|
||||
|
||||
EXPECT_THAT(hintsOfKind(AST, Kind),
|
||||
@ -688,6 +688,22 @@ TEST(TypeHints, Deduplication) {
|
||||
ExpectedHint{": int", "var"});
|
||||
}
|
||||
|
||||
TEST(TypeHints, SinglyInstantiatedTemplate) {
|
||||
assertTypeHints(R"cpp(
|
||||
auto $lambda[[x]] = [](auto *$param[[y]], auto) { return 42; };
|
||||
int m = x("foo", 3);
|
||||
)cpp",
|
||||
ExpectedHint{": (lambda)", "lambda"},
|
||||
ExpectedHint{": const char *", "param"});
|
||||
|
||||
// No hint for packs, or auto params following packs
|
||||
assertTypeHints(R"cpp(
|
||||
int x(auto $a[[a]], auto... b, auto c) { return 42; }
|
||||
int m = x<void*, char, float>(nullptr, 'c', 2.0, 2);
|
||||
)cpp",
|
||||
ExpectedHint{": void *", "a"});
|
||||
}
|
||||
|
||||
TEST(DesignatorHints, Basic) {
|
||||
assertDesignatorHints(R"cpp(
|
||||
struct S { int x, y, z; };
|
||||
|
@ -245,7 +245,7 @@ const NamedDecl &findDecl(ParsedAST &AST,
|
||||
Visitor.F = Filter;
|
||||
Visitor.TraverseDecl(AST.getASTContext().getTranslationUnitDecl());
|
||||
if (Visitor.Decls.size() != 1) {
|
||||
llvm::errs() << Visitor.Decls.size() << " symbols matched.";
|
||||
llvm::errs() << Visitor.Decls.size() << " symbols matched.\n";
|
||||
assert(Visitor.Decls.size() == 1);
|
||||
}
|
||||
return *Visitor.Decls.front();
|
||||
|
Loading…
x
Reference in New Issue
Block a user