[clang-tools-extra] b9b6938 - [clangd] Treat 'auto' params as deduced if there's a single instantiation.

Sam McCall via cfe-commits cfe-commits at lists.llvm.org
Mon Feb 21 04:42:37 PST 2022


Author: Sam McCall
Date: 2022-02-21T13:24:11+01:00
New Revision: b9b6938183e837e66ff7450fb2b8a73dce5889c0

URL: https://github.com/llvm/llvm-project/commit/b9b6938183e837e66ff7450fb2b8a73dce5889c0
DIFF: https://github.com/llvm/llvm-project/commit/b9b6938183e837e66ff7450fb2b8a73dce5889c0.diff

LOG: [clangd] Treat 'auto' params as deduced if there's a single instantiation.

This makes hover/go-to-definition/expand-auto etc work for auto params in many
common cases.
This includes when a generic lambda is passed to a function accepting
std::function. (The tests don't use this case, it requires a lot of setup).

Note that this doesn't affect the AST of the function body itself, cause its
nodes not to be dependent, improve code completion etc.
(These sort of improvements seem possible, in a similar "if there's a single
instantiation, traverse it instead of the primary template" way).

Fixes https://github.com/clangd/clangd/issues/493
Fixes https://github.com/clangd/clangd/issues/1015

Differential Revision: https://reviews.llvm.org/D119537

Added: 
    

Modified: 
    clang-tools-extra/clangd/AST.cpp
    clang-tools-extra/clangd/refactor/tweaks/ExpandAutoType.cpp
    clang-tools-extra/clangd/unittests/ASTTests.cpp
    clang-tools-extra/clangd/unittests/XRefsTests.cpp
    clang-tools-extra/clangd/unittests/tweaks/ExpandAutoTypeTests.cpp

Removed: 
    


################################################################################
diff  --git a/clang-tools-extra/clangd/AST.cpp b/clang-tools-extra/clangd/AST.cpp
index b970325098c65..fe9c4c9f0b18a 100644
--- a/clang-tools-extra/clangd/AST.cpp
+++ b/clang-tools-extra/clangd/AST.cpp
@@ -486,6 +486,87 @@ class DeducedTypeVisitor : public RecursiveASTVisitor<DeducedTypeVisitor> {
     return true;
   }
 
+  // Handle functions/lambdas with `auto` typed parameters.
+  // We'll examine visible specializations and see if they yield a unique type.
+  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());
+    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)
+      return true;
+    auto *FTD = FD->getDescribedFunctionTemplate();
+    if (!FTD)
+      return true;
+    int ParamIndex = paramIndex(*FTD, *Auto.getDecl());
+    if (ParamIndex < 0) {
+      assert(false && "auto TTP is not from enclosing function?");
+      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;
+    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()) {
+      if (&Param == ND)
+        return I;
+      ++I;
+    }
+    return -1;
+  }
+
   QualType DeducedType;
 };
 } // namespace

diff  --git a/clang-tools-extra/clangd/refactor/tweaks/ExpandAutoType.cpp b/clang-tools-extra/clangd/refactor/tweaks/ExpandAutoType.cpp
index 914564e9ae218..a717743ce3a71 100644
--- a/clang-tools-extra/clangd/refactor/tweaks/ExpandAutoType.cpp
+++ b/clang-tools-extra/clangd/refactor/tweaks/ExpandAutoType.cpp
@@ -45,8 +45,7 @@ class ExpandAutoType : public Tweak {
   std::string title() const override;
 
 private:
-  /// Cache the AutoTypeLoc, so that we do not need to search twice.
-  llvm::Optional<clang::AutoTypeLoc> CachedLocation;
+  SourceRange AutoRange;
 };
 
 REGISTER_TWEAK(ExpandAutoType)
@@ -91,27 +90,35 @@ bool isTemplateParam(const SelectionTree::Node *Node) {
   return false;
 }
 
-bool ExpandAutoType::prepare(const Selection& Inputs) {
-  CachedLocation = llvm::None;
+bool ExpandAutoType::prepare(const Selection &Inputs) {
   if (auto *Node = Inputs.ASTSelection.commonAncestor()) {
     if (auto *TypeNode = Node->ASTNode.get<TypeLoc>()) {
       if (const AutoTypeLoc Result = TypeNode->getAs<AutoTypeLoc>()) {
         if (!isStructuredBindingType(Node) &&
             !isDeducedAsLambda(Node, Result.getBeginLoc()) &&
             !isTemplateParam(Node))
-          CachedLocation = Result;
+          AutoRange = Result.getSourceRange();
+      }
+      if (auto TTPAuto = TypeNode->getAs<TemplateTypeParmTypeLoc>()) {
+        // We exclude concept constraints for now, as the SourceRange is wrong.
+        // void foo(C auto x) {};
+        //            ^^^^
+        // TTPAuto->getSourceRange only covers "auto", not "C auto".
+        if (TTPAuto.getDecl()->isImplicit() &&
+            !TTPAuto.getDecl()->hasTypeConstraint())
+          AutoRange = TTPAuto.getSourceRange();
       }
     }
   }
 
-  return (bool) CachedLocation;
+  return AutoRange.isValid();
 }
 
 Expected<Tweak::Effect> ExpandAutoType::apply(const Selection& Inputs) {
   auto &SrcMgr = Inputs.AST->getSourceManager();
 
-  llvm::Optional<clang::QualType> DeducedType = getDeducedType(
-      Inputs.AST->getASTContext(), CachedLocation->getBeginLoc());
+  llvm::Optional<clang::QualType> DeducedType =
+      getDeducedType(Inputs.AST->getASTContext(), AutoRange.getBegin());
 
   // if we can't resolve the type, return an error message
   if (DeducedType == llvm::None || (*DeducedType)->isUndeducedAutoType())
@@ -133,9 +140,8 @@ Expected<Tweak::Effect> ExpandAutoType::apply(const Selection& Inputs) {
   std::string PrettyTypeName = printType(*DeducedType,
       Inputs.ASTSelection.commonAncestor()->getDeclContext());
 
-  tooling::Replacement
-      Expansion(SrcMgr, CharSourceRange(CachedLocation->getSourceRange(), true),
-                PrettyTypeName);
+  tooling::Replacement Expansion(SrcMgr, CharSourceRange(AutoRange, true),
+                                 PrettyTypeName);
 
   return Effect::mainFileEdit(SrcMgr, tooling::Replacements(Expansion));
 }

diff  --git a/clang-tools-extra/clangd/unittests/ASTTests.cpp b/clang-tools-extra/clangd/unittests/ASTTests.cpp
index 53d399eed72a1..08935d8ed7b58 100644
--- a/clang-tools-extra/clangd/unittests/ASTTests.cpp
+++ b/clang-tools-extra/clangd/unittests/ASTTests.cpp
@@ -179,20 +179,76 @@ TEST(GetDeducedType, KwAutoKwDecltypeExpansion) {
           )cpp",
           "Bar",
       },
+      {
+          R"cpp(
+            // Generic lambda param.
+            struct Foo{};
+            auto Generic = [](^auto x) { return 0; };
+            int m = Generic(Foo{});
+          )cpp",
+          "struct Foo",
+      },
+      {
+          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 n = Generic(Foo{}, 2);
+          )cpp",
+          "struct Foo",
+      },
+      {
+          R"cpp(
+            // Generic lambda instantiated twice, conflicting deduction.
+            struct Foo{};
+            auto Generic = [](^auto y) { return 0; };
+            int m = Generic("one");
+            int n = Generic(2);
+          )cpp",
+          nullptr,
+      },
+      {
+          R"cpp(
+            // Generic function param.
+            struct Foo{};
+            int generic(^auto x) { return 0; }
+            int m = generic(Foo{});
+          )cpp",
+          "struct Foo",
+      },
+      {
+          R"cpp(
+            // More complicated param type involving auto.
+            template <class> concept C = true;
+            struct Foo{};
+            int generic(C ^auto *x) { return 0; }
+            const Foo *Ptr = nullptr;
+            int m = generic(Ptr);
+          )cpp",
+          "const struct Foo",
+      },
   };
   for (Test T : Tests) {
     Annotations File(T.AnnotatedCode);
-    auto AST = TestTU::withCode(File.code()).build();
+    auto TU = TestTU::withCode(File.code());
+    TU.ExtraArgs.push_back("-std=c++20");
+    auto AST = TU.build();
     SourceManagerForFile SM("foo.cpp", File.code());
 
-    SCOPED_TRACE(File.code());
+    SCOPED_TRACE(T.AnnotatedCode);
     EXPECT_FALSE(File.points().empty());
     for (Position Pos : File.points()) {
       auto Location = sourceLocationInMainFile(SM.get(), Pos);
       ASSERT_TRUE(!!Location) << llvm::toString(Location.takeError());
       auto DeducedType = getDeducedType(AST.getASTContext(), *Location);
-      ASSERT_TRUE(DeducedType);
-      EXPECT_EQ(DeducedType->getAsString(), T.DeducedType);
+      if (T.DeducedType == nullptr) {
+        EXPECT_FALSE(DeducedType);
+      } else {
+        ASSERT_TRUE(DeducedType);
+        EXPECT_EQ(DeducedType->getAsString(), T.DeducedType);
+      }
     }
   }
 }

diff  --git a/clang-tools-extra/clangd/unittests/XRefsTests.cpp b/clang-tools-extra/clangd/unittests/XRefsTests.cpp
index 2921eaa66b579..36c5c9c045dea 100644
--- a/clang-tools-extra/clangd/unittests/XRefsTests.cpp
+++ b/clang-tools-extra/clangd/unittests/XRefsTests.cpp
@@ -815,6 +815,12 @@ TEST(LocateSymbol, All) {
         }
       )cpp",
 
+      R"cpp(// auto lambda param where there's a single instantiation
+        struct [[Bar]] {};
+        auto Lambda = [](^auto){ return 0; };
+        int x = Lambda(Bar{});
+      )cpp",
+
       R"cpp(// decltype(auto) in function return
         struct [[Bar]] {};
         ^decltype(auto) test() {

diff  --git a/clang-tools-extra/clangd/unittests/tweaks/ExpandAutoTypeTests.cpp b/clang-tools-extra/clangd/unittests/tweaks/ExpandAutoTypeTests.cpp
index 6d9d4362be7af..1ab4fb3fd2eaf 100644
--- a/clang-tools-extra/clangd/unittests/tweaks/ExpandAutoTypeTests.cpp
+++ b/clang-tools-extra/clangd/unittests/tweaks/ExpandAutoTypeTests.cpp
@@ -80,8 +80,18 @@ TEST_F(ExpandAutoTypeTest, Test) {
   EXPECT_THAT(apply("template <typename T> void x() { ^auto y = T::z(); }"),
               StartsWith("fail: Could not deduce type for 'auto' type"));
 
-  ExtraArgs.push_back("-std=c++17");
+  ExtraArgs.push_back("-std=c++20");
   EXPECT_UNAVAILABLE("template <au^to X> class Y;");
+
+  EXPECT_THAT(apply("auto X = [](^auto){};"),
+              StartsWith("fail: Could not deduce"));
+  EXPECT_EQ(apply("auto X = [](^auto){return 0;}; int Y = X(42);"),
+            "auto X = [](int){return 0;}; int Y = X(42);");
+  EXPECT_THAT(apply("auto X = [](^auto){return 0;}; int Y = X(42) + X('c');"),
+              StartsWith("fail: Could not deduce"));
+  // FIXME: should work on constrained auto params, once SourceRange is fixed.
+  EXPECT_UNAVAILABLE("template<class> concept C = true;"
+                     "auto X = [](C ^auto *){return 0;};");
 }
 
 } // namespace


        


More information about the cfe-commits mailing list