[clang-tools-extra] 2da5c57 - [clangd] Add inlay hints for auto-typed parameters with one instantiation.
Sam McCall via cfe-commits
cfe-commits at lists.llvm.org
Wed Mar 23 09:26:32 PDT 2022
Author: Sam McCall
Date: 2022-03-23T17:26:25+01:00
New Revision: 2da5c5781e5a833f52a47752d76423bbb7bcf1a1
URL: https://github.com/llvm/llvm-project/commit/2da5c5781e5a833f52a47752d76423bbb7bcf1a1
DIFF: https://github.com/llvm/llvm-project/commit/2da5c5781e5a833f52a47752d76423bbb7bcf1a1.diff
LOG: [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
Added:
Modified:
clang-tools-extra/clangd/AST.cpp
clang-tools-extra/clangd/AST.h
clang-tools-extra/clangd/InlayHints.cpp
clang-tools-extra/clangd/unittests/ASTTests.cpp
clang-tools-extra/clangd/unittests/InlayHintTests.cpp
clang-tools-extra/clangd/unittests/TestTU.cpp
Removed:
################################################################################
diff --git a/clang-tools-extra/clangd/AST.cpp b/clang-tools-extra/clangd/AST.cpp
index 30c2a14b42bcf..660c7187c2268 100644
--- a/clang-tools-extra/clangd/AST.cpp
+++ b/clang-tools-extra/clangd/AST.cpp
@@ -485,21 +485,22 @@ class DeducedTypeVisitor : public RecursiveASTVisitor<DeducedTypeVisitor> {
}
// 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 @@ class DeducedTypeVisitor : public RecursiveASTVisitor<DeducedTypeVisitor> {
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>()) {
diff --git a/clang-tools-extra/clangd/AST.h b/clang-tools-extra/clangd/AST.h
index 3ea015574dda6..523e1d9a9a5f4 100644
--- 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 @@ 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 &);
diff --git a/clang-tools-extra/clangd/InlayHints.cpp b/clang-tools-extra/clangd/InlayHints.cpp
index 2f9624f68af3e..bd124aa2a23d5 100644
--- 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 @@ class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {
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.
diff --git a/clang-tools-extra/clangd/unittests/ASTTests.cpp b/clang-tools-extra/clangd/unittests/ASTTests.cpp
index 08935d8ed7b58..69285d019117c 100644
--- a/clang-tools-extra/clangd/unittests/ASTTests.cpp
+++ b/clang-tools-extra/clangd/unittests/ASTTests.cpp
@@ -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.
diff --git a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
index 1054090ae716b..b0f8b2556d2cf 100644
--- a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
+++ b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
@@ -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; };
diff --git a/clang-tools-extra/clangd/unittests/TestTU.cpp b/clang-tools-extra/clangd/unittests/TestTU.cpp
index 04ec670a8fa0c..1de663ac9caf2 100644
--- a/clang-tools-extra/clangd/unittests/TestTU.cpp
+++ b/clang-tools-extra/clangd/unittests/TestTU.cpp
@@ -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();
More information about the cfe-commits
mailing list