[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:
Sam McCall 2022-02-21 18:15:53 +01:00
parent a0f6d12cd4
commit 2da5c5781e
6 changed files with 238 additions and 55 deletions

View File

@ -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>()) {

View File

@ -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 &);

View File

@ -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.

View File

@ -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.

View File

@ -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; };

View File

@ -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();