diff --git a/clang-tools-extra/clangd/AST.h b/clang-tools-extra/clangd/AST.h --- a/clang-tools-extra/clangd/AST.h +++ b/clang-tools-extra/clangd/AST.h @@ -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 @@ /// If the type is an undeduced auto, returns the type itself. llvm::Optional 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 getAttributes(const DynTypedNode &); diff --git a/clang-tools-extra/clangd/AST.cpp b/clang-tools-extra/clangd/AST.cpp --- a/clang-tools-extra/clangd/AST.cpp +++ b/clang-tools-extra/clangd/AST.cpp @@ -485,21 +485,22 @@ } // 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(PVD->getDeclContext()); - if (!FD) + auto *Templated = llvm::dyn_cast(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 @@ 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(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? - static TemplateTypeParmTypeLoc findContainedAutoTTPLoc(TypeLoc TL) { - if (auto QTL = TL.getAs()) - return findContainedAutoTTPLoc(QTL.getUnqualifiedLoc()); - if (llvm::isa(TL.getTypePtr())) - return findContainedAutoTTPLoc(TL.getNextTypeLoc()); - if (auto TTPTL = TL.getAs()) { - 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 @@ return V.DeducedType; } +TemplateTypeParmTypeLoc getContainedAutoParamType(TypeLoc TL) { + if (auto QTL = TL.getAs()) + return getContainedAutoParamType(QTL.getUnqualifiedLoc()); + if (llvm::isa(TL.getTypePtr())) + return getContainedAutoParamType(TL.getNextTypeLoc()); + if (auto FTL = TL.getAs()) + return getContainedAutoParamType(FTL.getReturnLoc()); + if (auto TTPTL = TL.getAs()) { + if (TTPTL.getTypePtr()->getDecl()->isImplicit()) + return TTPTL; + } + return {}; +} + +template +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(TD)) + return getOnlyInstantiationImpl(CTD); + if (auto *FTD = llvm::dyn_cast(TD)) + return getOnlyInstantiationImpl(FTD); + if (auto *VTD = llvm::dyn_cast(TD)) + return getOnlyInstantiationImpl(VTD); + } + return nullptr; +} + std::vector getAttributes(const DynTypedNode &N) { std::vector Result; if (const auto *TL = N.get()) { diff --git a/clang-tools-extra/clangd/InlayHints.cpp b/clang-tools-extra/clangd/InlayHints.cpp --- a/clang-tools-extra/clangd/InlayHints.cpp +++ b/clang-tools-extra/clangd/InlayHints.cpp @@ -6,6 +6,7 @@ // //===----------------------------------------------------------------------===// #include "InlayHints.h" +#include "AST.h" #include "Config.h" #include "HeuristicResolver.h" #include "ParsedAST.h" @@ -299,9 +300,46 @@ addTypeHint(D->getLocation(), D->getType(), /*Prefix=*/": "); } } + + // Handle templates like `int foo(auto x)` with exactly one instantiation. + if (auto *PVD = llvm::dyn_cast(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(D->getDeclContext()); + if (!TemplateFunction) + return nullptr; + auto *InstantiatedFunction = llvm::dyn_cast_or_null( + 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. diff --git a/clang-tools-extra/clangd/unittests/ASTTests.cpp b/clang-tools-extra/clangd/unittests/ASTTests.cpp --- a/clang-tools-extra/clangd/unittests/ASTTests.cpp +++ b/clang-tools-extra/clangd/unittests/ASTTests.cpp @@ -30,6 +30,7 @@ namespace { using testing::Contains; using testing::Each; +using testing::IsEmpty; TEST(GetDeducedType, KwAutoKwDecltypeExpansion) { struct Test { @@ -192,12 +193,12 @@ 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(ClangdAST, GetOnlyInstantiation) { + struct { + const char *Code; + llvm::StringLiteral NodeType; + const char *Name; + } Cases[] = { + { + R"cpp( + template class X {}; + X x; + )cpp", + "CXXRecord", + "template<> class X {}", + }, + { + R"cpp( + template T X = T{}; + int y = X; + )cpp", + "Var", + // VarTemplateSpecializationDecl doesn't print as template<>... + "char X = char{}", + }, + { + R"cpp( + template int X(T) { return 42; } + int y = X("text"); + )cpp", + "Function", + "template<> int X(const char *)", + }, + { + R"cpp( + int X(auto *x) { return 42; } + int y = X("text"); + )cpp", + "Function", + "template<> int X(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(&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(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(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. diff --git a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp --- a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp +++ b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp @@ -58,7 +58,7 @@ 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 @@ 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 @@ 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(nullptr, 'c', 2.0, 2); + )cpp", + ExpectedHint{": void *", "a"}); +} + TEST(DesignatorHints, Basic) { assertDesignatorHints(R"cpp( struct S { int x, y, z; }; diff --git a/clang-tools-extra/clangd/unittests/TestTU.cpp b/clang-tools-extra/clangd/unittests/TestTU.cpp --- a/clang-tools-extra/clangd/unittests/TestTU.cpp +++ b/clang-tools-extra/clangd/unittests/TestTU.cpp @@ -245,7 +245,7 @@ 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();