[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