Skip to content

Commit 2da5c57

Browse files
committed
[clangd] Add inlay hints for auto-typed parameters with one instantiation.
This takes a similar approach as b9b6938, 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
1 parent a0f6d12 commit 2da5c57

File tree

6 files changed

+238
-55
lines changed

6 files changed

+238
-55
lines changed

clang-tools-extra/clangd/AST.cpp

Lines changed: 54 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -485,21 +485,22 @@ class DeducedTypeVisitor : public RecursiveASTVisitor<DeducedTypeVisitor> {
485485
}
486486

487487
// Handle functions/lambdas with `auto` typed parameters.
488-
// We'll examine visible specializations and see if they yield a unique type.
488+
// We deduce the type if there's exactly one instantiation visible.
489489
bool VisitParmVarDecl(ParmVarDecl *PVD) {
490490
if (!PVD->getType()->isDependentType())
491491
return true;
492492
// 'auto' here does not name an AutoType, but an implicit template param.
493493
TemplateTypeParmTypeLoc Auto =
494-
findContainedAutoTTPLoc(PVD->getTypeSourceInfo()->getTypeLoc());
494+
getContainedAutoParamType(PVD->getTypeSourceInfo()->getTypeLoc());
495495
if (Auto.isNull() || Auto.getNameLoc() != SearchedLocation)
496496
return true;
497+
497498
// We expect the TTP to be attached to this function template.
498499
// Find the template and the param index.
499-
auto *FD = llvm::dyn_cast<FunctionDecl>(PVD->getDeclContext());
500-
if (!FD)
500+
auto *Templated = llvm::dyn_cast<FunctionDecl>(PVD->getDeclContext());
501+
if (!Templated)
501502
return true;
502-
auto *FTD = FD->getDescribedFunctionTemplate();
503+
auto *FTD = Templated->getDescribedFunctionTemplate();
503504
if (!FTD)
504505
return true;
505506
int ParamIndex = paramIndex(*FTD, *Auto.getDecl());
@@ -508,53 +509,18 @@ class DeducedTypeVisitor : public RecursiveASTVisitor<DeducedTypeVisitor> {
508509
return true;
509510
}
510511

511-
// Now determine the unique type arg among the implicit specializations.
512-
const ASTContext &Ctx = PVD->getASTContext();
513-
QualType UniqueType;
514-
CanQualType CanUniqueType;
515-
for (const FunctionDecl *Spec : FTD->specializations()) {
516-
// Meaning `auto` is a bit overloaded if the function is specialized.
517-
if (Spec->getTemplateSpecializationKind() == TSK_ExplicitSpecialization)
518-
return true;
519-
// Find the type for this specialization.
520-
const auto *Args = Spec->getTemplateSpecializationArgs();
521-
if (Args->size() != FTD->getTemplateParameters()->size())
522-
continue; // no weird variadic stuff
523-
QualType SpecType = Args->get(ParamIndex).getAsType();
524-
if (SpecType.isNull())
525-
continue;
526-
527-
// Deduced types need only be *canonically* equal.
528-
CanQualType CanSpecType = Ctx.getCanonicalType(SpecType);
529-
if (CanUniqueType.isNull()) {
530-
CanUniqueType = CanSpecType;
531-
UniqueType = SpecType;
532-
continue;
533-
}
534-
if (CanUniqueType != CanSpecType)
535-
return true; // deduced type is not unique
536-
}
537-
DeducedType = UniqueType;
512+
// Now find the instantiation and the deduced template type arg.
513+
auto *Instantiation =
514+
llvm::dyn_cast_or_null<FunctionDecl>(getOnlyInstantiation(Templated));
515+
if (!Instantiation)
516+
return true;
517+
const auto *Args = Instantiation->getTemplateSpecializationArgs();
518+
if (Args->size() != FTD->getTemplateParameters()->size())
519+
return true; // no weird variadic stuff
520+
DeducedType = Args->get(ParamIndex).getAsType();
538521
return true;
539522
}
540523

541-
// Find the abbreviated-function-template `auto` within a type.
542-
// Similar to getContainedAutoTypeLoc, but these `auto`s are
543-
// TemplateTypeParmTypes for implicit TTPs, instead of AutoTypes.
544-
// Also we don't look very hard, just stripping const, references, pointers.
545-
// FIXME: handle more types: vector<auto>?
546-
static TemplateTypeParmTypeLoc findContainedAutoTTPLoc(TypeLoc TL) {
547-
if (auto QTL = TL.getAs<QualifiedTypeLoc>())
548-
return findContainedAutoTTPLoc(QTL.getUnqualifiedLoc());
549-
if (llvm::isa<PointerType, ReferenceType>(TL.getTypePtr()))
550-
return findContainedAutoTTPLoc(TL.getNextTypeLoc());
551-
if (auto TTPTL = TL.getAs<TemplateTypeParmTypeLoc>()) {
552-
if (TTPTL.getTypePtr()->getDecl()->isImplicit())
553-
return TTPTL;
554-
}
555-
return {};
556-
}
557-
558524
static int paramIndex(const TemplateDecl &TD, NamedDecl &Param) {
559525
unsigned I = 0;
560526
for (auto *ND : *TD.getTemplateParameters()) {
@@ -580,6 +546,45 @@ llvm::Optional<QualType> getDeducedType(ASTContext &ASTCtx,
580546
return V.DeducedType;
581547
}
582548

549+
TemplateTypeParmTypeLoc getContainedAutoParamType(TypeLoc TL) {
550+
if (auto QTL = TL.getAs<QualifiedTypeLoc>())
551+
return getContainedAutoParamType(QTL.getUnqualifiedLoc());
552+
if (llvm::isa<PointerType, ReferenceType, ParenType>(TL.getTypePtr()))
553+
return getContainedAutoParamType(TL.getNextTypeLoc());
554+
if (auto FTL = TL.getAs<FunctionTypeLoc>())
555+
return getContainedAutoParamType(FTL.getReturnLoc());
556+
if (auto TTPTL = TL.getAs<TemplateTypeParmTypeLoc>()) {
557+
if (TTPTL.getTypePtr()->getDecl()->isImplicit())
558+
return TTPTL;
559+
}
560+
return {};
561+
}
562+
563+
template <typename TemplateDeclTy>
564+
static NamedDecl *getOnlyInstantiationImpl(TemplateDeclTy *TD) {
565+
NamedDecl *Only = nullptr;
566+
for (auto *Spec : TD->specializations()) {
567+
if (Spec->getTemplateSpecializationKind() == TSK_ExplicitSpecialization)
568+
continue;
569+
if (Only != nullptr)
570+
return nullptr;
571+
Only = Spec;
572+
}
573+
return Only;
574+
}
575+
576+
NamedDecl *getOnlyInstantiation(NamedDecl *TemplatedDecl) {
577+
if (TemplateDecl *TD = TemplatedDecl->getDescribedTemplate()) {
578+
if (auto *CTD = llvm::dyn_cast<ClassTemplateDecl>(TD))
579+
return getOnlyInstantiationImpl(CTD);
580+
if (auto *FTD = llvm::dyn_cast<FunctionTemplateDecl>(TD))
581+
return getOnlyInstantiationImpl(FTD);
582+
if (auto *VTD = llvm::dyn_cast<VarTemplateDecl>(TD))
583+
return getOnlyInstantiationImpl(VTD);
584+
}
585+
return nullptr;
586+
}
587+
583588
std::vector<const Attr *> getAttributes(const DynTypedNode &N) {
584589
std::vector<const Attr *> Result;
585590
if (const auto *TL = N.get<TypeLoc>()) {

clang-tools-extra/clangd/AST.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "clang/AST/Decl.h"
1818
#include "clang/AST/DeclObjC.h"
1919
#include "clang/AST/NestedNameSpecifier.h"
20+
#include "clang/AST/TypeLoc.h"
2021
#include "clang/Basic/SourceLocation.h"
2122
#include "clang/Lex/MacroInfo.h"
2223
#include "llvm/ADT/StringRef.h"
@@ -127,6 +128,17 @@ QualType declaredType(const TypeDecl *D);
127128
/// If the type is an undeduced auto, returns the type itself.
128129
llvm::Optional<QualType> getDeducedType(ASTContext &, SourceLocation Loc);
129130

131+
// Find the abbreviated-function-template `auto` within a type, or returns null.
132+
// Similar to getContainedAutoTypeLoc, but these `auto`s are
133+
// TemplateTypeParmTypes for implicit TTPs, instead of AutoTypes.
134+
// Also we don't look very hard, just stripping const, references, pointers.
135+
// FIXME: handle more type patterns.
136+
TemplateTypeParmTypeLoc getContainedAutoParamType(TypeLoc TL);
137+
138+
// If TemplatedDecl is the generic body of a template, and the template has
139+
// exactly one visible instantiation, return the instantiated body.
140+
NamedDecl *getOnlyInstantiation(NamedDecl *TemplatedDecl);
141+
130142
/// Return attributes attached directly to a node.
131143
std::vector<const Attr *> getAttributes(const DynTypedNode &);
132144

clang-tools-extra/clangd/InlayHints.cpp

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
//
77
//===----------------------------------------------------------------------===//
88
#include "InlayHints.h"
9+
#include "AST.h"
910
#include "Config.h"
1011
#include "HeuristicResolver.h"
1112
#include "ParsedAST.h"
@@ -299,9 +300,46 @@ class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {
299300
addTypeHint(D->getLocation(), D->getType(), /*Prefix=*/": ");
300301
}
301302
}
303+
304+
// Handle templates like `int foo(auto x)` with exactly one instantiation.
305+
if (auto *PVD = llvm::dyn_cast<ParmVarDecl>(D)) {
306+
if (D->getIdentifier() && PVD->getType()->isDependentType() &&
307+
!getContainedAutoParamType(D->getTypeSourceInfo()->getTypeLoc())
308+
.isNull()) {
309+
if (auto *IPVD = getOnlyParamInstantiation(PVD))
310+
addTypeHint(D->getLocation(), IPVD->getType(), /*Prefix=*/": ");
311+
}
312+
}
313+
302314
return true;
303315
}
304316

317+
ParmVarDecl *getOnlyParamInstantiation(ParmVarDecl *D) {
318+
auto *TemplateFunction = llvm::dyn_cast<FunctionDecl>(D->getDeclContext());
319+
if (!TemplateFunction)
320+
return nullptr;
321+
auto *InstantiatedFunction = llvm::dyn_cast_or_null<FunctionDecl>(
322+
getOnlyInstantiation(TemplateFunction));
323+
if (!InstantiatedFunction)
324+
return nullptr;
325+
326+
unsigned ParamIdx = 0;
327+
for (auto *Param : TemplateFunction->parameters()) {
328+
// Can't reason about param indexes in the presence of preceding packs.
329+
// And if this param is a pack, it may expand to multiple params.
330+
if (Param->isParameterPack())
331+
return nullptr;
332+
if (Param == D)
333+
break;
334+
++ParamIdx;
335+
}
336+
assert(ParamIdx < TemplateFunction->getNumParams() &&
337+
"Couldn't find param in list?");
338+
assert(ParamIdx < InstantiatedFunction->getNumParams() &&
339+
"Instantiated function has fewer (non-pack) parameters?");
340+
return InstantiatedFunction->getParamDecl(ParamIdx);
341+
}
342+
305343
bool VisitInitListExpr(InitListExpr *Syn) {
306344
// We receive the syntactic form here (shouldVisitImplicitCode() is false).
307345
// This is the one we will ultimately attach designators to.

clang-tools-extra/clangd/unittests/ASTTests.cpp

Lines changed: 115 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ namespace clangd {
3030
namespace {
3131
using testing::Contains;
3232
using testing::Each;
33+
using testing::IsEmpty;
3334

3435
TEST(GetDeducedType, KwAutoKwDecltypeExpansion) {
3536
struct Test {
@@ -192,12 +193,12 @@ TEST(GetDeducedType, KwAutoKwDecltypeExpansion) {
192193
R"cpp(
193194
// Generic lambda instantiated twice, matching deduction.
194195
struct Foo{};
195-
using Bar = Foo;
196196
auto Generic = [](^auto x, auto y) { return 0; };
197-
int m = Generic(Bar{}, "one");
197+
int m = Generic(Foo{}, "one");
198198
int n = Generic(Foo{}, 2);
199199
)cpp",
200-
"struct Foo",
200+
// No deduction although both instantiations yield the same result :-(
201+
nullptr,
201202
},
202203
{
203204
R"cpp(
@@ -253,6 +254,117 @@ TEST(GetDeducedType, KwAutoKwDecltypeExpansion) {
253254
}
254255
}
255256

257+
TEST(ClangdAST, GetOnlyInstantiation) {
258+
struct {
259+
const char *Code;
260+
llvm::StringLiteral NodeType;
261+
const char *Name;
262+
} Cases[] = {
263+
{
264+
R"cpp(
265+
template <typename> class X {};
266+
X<int> x;
267+
)cpp",
268+
"CXXRecord",
269+
"template<> class X<int> {}",
270+
},
271+
{
272+
R"cpp(
273+
template <typename T> T X = T{};
274+
int y = X<char>;
275+
)cpp",
276+
"Var",
277+
// VarTemplateSpecializationDecl doesn't print as template<>...
278+
"char X = char{}",
279+
},
280+
{
281+
R"cpp(
282+
template <typename T> int X(T) { return 42; }
283+
int y = X("text");
284+
)cpp",
285+
"Function",
286+
"template<> int X<const char *>(const char *)",
287+
},
288+
{
289+
R"cpp(
290+
int X(auto *x) { return 42; }
291+
int y = X("text");
292+
)cpp",
293+
"Function",
294+
"template<> int X<const char>(const char *x)",
295+
},
296+
};
297+
298+
for (const auto &Case : Cases) {
299+
SCOPED_TRACE(Case.Code);
300+
auto TU = TestTU::withCode(Case.Code);
301+
TU.ExtraArgs.push_back("-std=c++20");
302+
auto AST = TU.build();
303+
PrintingPolicy PP = AST.getASTContext().getPrintingPolicy();
304+
PP.TerseOutput = true;
305+
std::string Name;
306+
if (auto *Result = getOnlyInstantiation(
307+
const_cast<NamedDecl *>(&findDecl(AST, [&](const NamedDecl &D) {
308+
return D.getDescribedTemplate() != nullptr &&
309+
D.getDeclKindName() == Case.NodeType;
310+
})))) {
311+
llvm::raw_string_ostream OS(Name);
312+
Result->print(OS, PP);
313+
}
314+
315+
if (Case.Name)
316+
EXPECT_EQ(Case.Name, Name);
317+
else
318+
EXPECT_THAT(Name, IsEmpty());
319+
}
320+
}
321+
322+
TEST(ClangdAST, GetContainedAutoParamType) {
323+
auto TU = TestTU::withCode(R"cpp(
324+
int withAuto(
325+
auto a,
326+
auto *b,
327+
const auto *c,
328+
auto &&d,
329+
auto *&e,
330+
auto (*f)(int)
331+
){};
332+
333+
int withoutAuto(
334+
int a,
335+
int *b,
336+
const int *c,
337+
int &&d,
338+
int *&e,
339+
int (*f)(int)
340+
){};
341+
)cpp");
342+
TU.ExtraArgs.push_back("-std=c++20");
343+
auto AST = TU.build();
344+
345+
const auto &WithAuto =
346+
llvm::cast<FunctionTemplateDecl>(findDecl(AST, "withAuto"));
347+
auto ParamsWithAuto = WithAuto.getTemplatedDecl()->parameters();
348+
auto *TemplateParamsWithAuto = WithAuto.getTemplateParameters();
349+
ASSERT_EQ(ParamsWithAuto.size(), TemplateParamsWithAuto->size());
350+
351+
for (unsigned I = 0; I < ParamsWithAuto.size(); ++I) {
352+
SCOPED_TRACE(ParamsWithAuto[I]->getNameAsString());
353+
auto Loc = getContainedAutoParamType(
354+
ParamsWithAuto[I]->getTypeSourceInfo()->getTypeLoc());
355+
ASSERT_FALSE(Loc.isNull());
356+
EXPECT_EQ(Loc.getTypePtr()->getDecl(), TemplateParamsWithAuto->getParam(I));
357+
}
358+
359+
const auto &WithoutAuto =
360+
llvm::cast<FunctionDecl>(findDecl(AST, "withoutAuto"));
361+
for (auto *ParamWithoutAuto : WithoutAuto.parameters()) {
362+
ASSERT_TRUE(getContainedAutoParamType(
363+
ParamWithoutAuto->getTypeSourceInfo()->getTypeLoc())
364+
.isNull());
365+
}
366+
}
367+
256368
TEST(ClangdAST, GetQualification) {
257369
// Tries to insert the decl `Foo` into position of each decl named `insert`.
258370
// This is done to get an appropriate DeclContext for the insertion location.

0 commit comments

Comments
 (0)