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 @@ -486,6 +486,87 @@ 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(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? + 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()) { + 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 --- a/clang-tools-extra/clangd/refactor/tweaks/ExpandAutoType.cpp +++ b/clang-tools-extra/clangd/refactor/tweaks/ExpandAutoType.cpp @@ -45,8 +45,7 @@ std::string title() const override; private: - /// Cache the AutoTypeLoc, so that we do not need to search twice. - llvm::Optional CachedLocation; + SourceRange AutoRange; }; REGISTER_TWEAK(ExpandAutoType) @@ -91,27 +90,35 @@ 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()) { if (const AutoTypeLoc Result = TypeNode->getAs()) { if (!isStructuredBindingType(Node) && !isDeducedAsLambda(Node, Result.getBeginLoc()) && !isTemplateParam(Node)) - CachedLocation = Result; + AutoRange = Result.getSourceRange(); + } + if (auto TTPAuto = TypeNode->getAs()) { + // 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 ExpandAutoType::apply(const Selection& Inputs) { auto &SrcMgr = Inputs.AST->getSourceManager(); - llvm::Optional DeducedType = getDeducedType( - Inputs.AST->getASTContext(), CachedLocation->getBeginLoc()); + llvm::Optional 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 @@ 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 --- a/clang-tools-extra/clangd/unittests/ASTTests.cpp +++ b/clang-tools-extra/clangd/unittests/ASTTests.cpp @@ -179,20 +179,76 @@ )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 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 --- a/clang-tools-extra/clangd/unittests/XRefsTests.cpp +++ b/clang-tools-extra/clangd/unittests/XRefsTests.cpp @@ -815,6 +815,12 @@ } )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 --- a/clang-tools-extra/clangd/unittests/tweaks/ExpandAutoTypeTests.cpp +++ b/clang-tools-extra/clangd/unittests/tweaks/ExpandAutoTypeTests.cpp @@ -80,8 +80,18 @@ EXPECT_THAT(apply("template 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 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 concept C = true;" + "auto X = [](C ^auto *){return 0;};"); } } // namespace