Skip to content

Commit b9b6938

Browse files
committed
[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 clangd/clangd#493 Fixes clangd/clangd#1015 Differential Revision: https://reviews.llvm.org/D119537
1 parent 90faaf8 commit b9b6938

File tree

5 files changed

+175
-16
lines changed

5 files changed

+175
-16
lines changed

clang-tools-extra/clangd/AST.cpp

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,87 @@ class DeducedTypeVisitor : public RecursiveASTVisitor<DeducedTypeVisitor> {
486486
return true;
487487
}
488488

489+
// Handle functions/lambdas with `auto` typed parameters.
490+
// We'll examine visible specializations and see if they yield a unique type.
491+
bool VisitParmVarDecl(ParmVarDecl *PVD) {
492+
if (!PVD->getType()->isDependentType())
493+
return true;
494+
// 'auto' here does not name an AutoType, but an implicit template param.
495+
TemplateTypeParmTypeLoc Auto =
496+
findContainedAutoTTPLoc(PVD->getTypeSourceInfo()->getTypeLoc());
497+
if (Auto.isNull() || Auto.getNameLoc() != SearchedLocation)
498+
return true;
499+
// We expect the TTP to be attached to this function template.
500+
// Find the template and the param index.
501+
auto *FD = llvm::dyn_cast<FunctionDecl>(PVD->getDeclContext());
502+
if (!FD)
503+
return true;
504+
auto *FTD = FD->getDescribedFunctionTemplate();
505+
if (!FTD)
506+
return true;
507+
int ParamIndex = paramIndex(*FTD, *Auto.getDecl());
508+
if (ParamIndex < 0) {
509+
assert(false && "auto TTP is not from enclosing function?");
510+
return true;
511+
}
512+
513+
// Now determine the unique type arg among the implicit specializations.
514+
const ASTContext &Ctx = PVD->getASTContext();
515+
QualType UniqueType;
516+
CanQualType CanUniqueType;
517+
for (const FunctionDecl *Spec : FTD->specializations()) {
518+
// Meaning `auto` is a bit overloaded if the function is specialized.
519+
if (Spec->getTemplateSpecializationKind() == TSK_ExplicitSpecialization)
520+
return true;
521+
// Find the type for this specialization.
522+
const auto *Args = Spec->getTemplateSpecializationArgs();
523+
if (Args->size() != FTD->getTemplateParameters()->size())
524+
continue; // no weird variadic stuff
525+
QualType SpecType = Args->get(ParamIndex).getAsType();
526+
if (SpecType.isNull())
527+
continue;
528+
529+
// Deduced types need only be *canonically* equal.
530+
CanQualType CanSpecType = Ctx.getCanonicalType(SpecType);
531+
if (CanUniqueType.isNull()) {
532+
CanUniqueType = CanSpecType;
533+
UniqueType = SpecType;
534+
continue;
535+
}
536+
if (CanUniqueType != CanSpecType)
537+
return true; // deduced type is not unique
538+
}
539+
DeducedType = UniqueType;
540+
return true;
541+
}
542+
543+
// Find the abbreviated-function-template `auto` within a type.
544+
// Similar to getContainedAutoTypeLoc, but these `auto`s are
545+
// TemplateTypeParmTypes for implicit TTPs, instead of AutoTypes.
546+
// Also we don't look very hard, just stripping const, references, pointers.
547+
// FIXME: handle more types: vector<auto>?
548+
static TemplateTypeParmTypeLoc findContainedAutoTTPLoc(TypeLoc TL) {
549+
if (auto QTL = TL.getAs<QualifiedTypeLoc>())
550+
return findContainedAutoTTPLoc(QTL.getUnqualifiedLoc());
551+
if (llvm::isa<PointerType, ReferenceType>(TL.getTypePtr()))
552+
return findContainedAutoTTPLoc(TL.getNextTypeLoc());
553+
if (auto TTPTL = TL.getAs<TemplateTypeParmTypeLoc>()) {
554+
if (TTPTL.getTypePtr()->getDecl()->isImplicit())
555+
return TTPTL;
556+
}
557+
return {};
558+
}
559+
560+
static int paramIndex(const TemplateDecl &TD, NamedDecl &Param) {
561+
unsigned I = 0;
562+
for (auto *ND : *TD.getTemplateParameters()) {
563+
if (&Param == ND)
564+
return I;
565+
++I;
566+
}
567+
return -1;
568+
}
569+
489570
QualType DeducedType;
490571
};
491572
} // namespace

clang-tools-extra/clangd/refactor/tweaks/ExpandAutoType.cpp

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,7 @@ class ExpandAutoType : public Tweak {
4545
std::string title() const override;
4646

4747
private:
48-
/// Cache the AutoTypeLoc, so that we do not need to search twice.
49-
llvm::Optional<clang::AutoTypeLoc> CachedLocation;
48+
SourceRange AutoRange;
5049
};
5150

5251
REGISTER_TWEAK(ExpandAutoType)
@@ -91,27 +90,35 @@ bool isTemplateParam(const SelectionTree::Node *Node) {
9190
return false;
9291
}
9392

94-
bool ExpandAutoType::prepare(const Selection& Inputs) {
95-
CachedLocation = llvm::None;
93+
bool ExpandAutoType::prepare(const Selection &Inputs) {
9694
if (auto *Node = Inputs.ASTSelection.commonAncestor()) {
9795
if (auto *TypeNode = Node->ASTNode.get<TypeLoc>()) {
9896
if (const AutoTypeLoc Result = TypeNode->getAs<AutoTypeLoc>()) {
9997
if (!isStructuredBindingType(Node) &&
10098
!isDeducedAsLambda(Node, Result.getBeginLoc()) &&
10199
!isTemplateParam(Node))
102-
CachedLocation = Result;
100+
AutoRange = Result.getSourceRange();
101+
}
102+
if (auto TTPAuto = TypeNode->getAs<TemplateTypeParmTypeLoc>()) {
103+
// We exclude concept constraints for now, as the SourceRange is wrong.
104+
// void foo(C auto x) {};
105+
// ^^^^
106+
// TTPAuto->getSourceRange only covers "auto", not "C auto".
107+
if (TTPAuto.getDecl()->isImplicit() &&
108+
!TTPAuto.getDecl()->hasTypeConstraint())
109+
AutoRange = TTPAuto.getSourceRange();
103110
}
104111
}
105112
}
106113

107-
return (bool) CachedLocation;
114+
return AutoRange.isValid();
108115
}
109116

110117
Expected<Tweak::Effect> ExpandAutoType::apply(const Selection& Inputs) {
111118
auto &SrcMgr = Inputs.AST->getSourceManager();
112119

113-
llvm::Optional<clang::QualType> DeducedType = getDeducedType(
114-
Inputs.AST->getASTContext(), CachedLocation->getBeginLoc());
120+
llvm::Optional<clang::QualType> DeducedType =
121+
getDeducedType(Inputs.AST->getASTContext(), AutoRange.getBegin());
115122

116123
// if we can't resolve the type, return an error message
117124
if (DeducedType == llvm::None || (*DeducedType)->isUndeducedAutoType())
@@ -133,9 +140,8 @@ Expected<Tweak::Effect> ExpandAutoType::apply(const Selection& Inputs) {
133140
std::string PrettyTypeName = printType(*DeducedType,
134141
Inputs.ASTSelection.commonAncestor()->getDeclContext());
135142

136-
tooling::Replacement
137-
Expansion(SrcMgr, CharSourceRange(CachedLocation->getSourceRange(), true),
138-
PrettyTypeName);
143+
tooling::Replacement Expansion(SrcMgr, CharSourceRange(AutoRange, true),
144+
PrettyTypeName);
139145

140146
return Effect::mainFileEdit(SrcMgr, tooling::Replacements(Expansion));
141147
}

clang-tools-extra/clangd/unittests/ASTTests.cpp

Lines changed: 60 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -179,20 +179,76 @@ TEST(GetDeducedType, KwAutoKwDecltypeExpansion) {
179179
)cpp",
180180
"Bar",
181181
},
182+
{
183+
R"cpp(
184+
// Generic lambda param.
185+
struct Foo{};
186+
auto Generic = [](^auto x) { return 0; };
187+
int m = Generic(Foo{});
188+
)cpp",
189+
"struct Foo",
190+
},
191+
{
192+
R"cpp(
193+
// Generic lambda instantiated twice, matching deduction.
194+
struct Foo{};
195+
using Bar = Foo;
196+
auto Generic = [](^auto x, auto y) { return 0; };
197+
int m = Generic(Bar{}, "one");
198+
int n = Generic(Foo{}, 2);
199+
)cpp",
200+
"struct Foo",
201+
},
202+
{
203+
R"cpp(
204+
// Generic lambda instantiated twice, conflicting deduction.
205+
struct Foo{};
206+
auto Generic = [](^auto y) { return 0; };
207+
int m = Generic("one");
208+
int n = Generic(2);
209+
)cpp",
210+
nullptr,
211+
},
212+
{
213+
R"cpp(
214+
// Generic function param.
215+
struct Foo{};
216+
int generic(^auto x) { return 0; }
217+
int m = generic(Foo{});
218+
)cpp",
219+
"struct Foo",
220+
},
221+
{
222+
R"cpp(
223+
// More complicated param type involving auto.
224+
template <class> concept C = true;
225+
struct Foo{};
226+
int generic(C ^auto *x) { return 0; }
227+
const Foo *Ptr = nullptr;
228+
int m = generic(Ptr);
229+
)cpp",
230+
"const struct Foo",
231+
},
182232
};
183233
for (Test T : Tests) {
184234
Annotations File(T.AnnotatedCode);
185-
auto AST = TestTU::withCode(File.code()).build();
235+
auto TU = TestTU::withCode(File.code());
236+
TU.ExtraArgs.push_back("-std=c++20");
237+
auto AST = TU.build();
186238
SourceManagerForFile SM("foo.cpp", File.code());
187239

188-
SCOPED_TRACE(File.code());
240+
SCOPED_TRACE(T.AnnotatedCode);
189241
EXPECT_FALSE(File.points().empty());
190242
for (Position Pos : File.points()) {
191243
auto Location = sourceLocationInMainFile(SM.get(), Pos);
192244
ASSERT_TRUE(!!Location) << llvm::toString(Location.takeError());
193245
auto DeducedType = getDeducedType(AST.getASTContext(), *Location);
194-
ASSERT_TRUE(DeducedType);
195-
EXPECT_EQ(DeducedType->getAsString(), T.DeducedType);
246+
if (T.DeducedType == nullptr) {
247+
EXPECT_FALSE(DeducedType);
248+
} else {
249+
ASSERT_TRUE(DeducedType);
250+
EXPECT_EQ(DeducedType->getAsString(), T.DeducedType);
251+
}
196252
}
197253
}
198254
}

clang-tools-extra/clangd/unittests/XRefsTests.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -815,6 +815,12 @@ TEST(LocateSymbol, All) {
815815
}
816816
)cpp",
817817

818+
R"cpp(// auto lambda param where there's a single instantiation
819+
struct [[Bar]] {};
820+
auto Lambda = [](^auto){ return 0; };
821+
int x = Lambda(Bar{});
822+
)cpp",
823+
818824
R"cpp(// decltype(auto) in function return
819825
struct [[Bar]] {};
820826
^decltype(auto) test() {

clang-tools-extra/clangd/unittests/tweaks/ExpandAutoTypeTests.cpp

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,18 @@ TEST_F(ExpandAutoTypeTest, Test) {
8080
EXPECT_THAT(apply("template <typename T> void x() { ^auto y = T::z(); }"),
8181
StartsWith("fail: Could not deduce type for 'auto' type"));
8282

83-
ExtraArgs.push_back("-std=c++17");
83+
ExtraArgs.push_back("-std=c++20");
8484
EXPECT_UNAVAILABLE("template <au^to X> class Y;");
85+
86+
EXPECT_THAT(apply("auto X = [](^auto){};"),
87+
StartsWith("fail: Could not deduce"));
88+
EXPECT_EQ(apply("auto X = [](^auto){return 0;}; int Y = X(42);"),
89+
"auto X = [](int){return 0;}; int Y = X(42);");
90+
EXPECT_THAT(apply("auto X = [](^auto){return 0;}; int Y = X(42) + X('c');"),
91+
StartsWith("fail: Could not deduce"));
92+
// FIXME: should work on constrained auto params, once SourceRange is fixed.
93+
EXPECT_UNAVAILABLE("template<class> concept C = true;"
94+
"auto X = [](C ^auto *){return 0;};");
8595
}
8696

8797
} // namespace

0 commit comments

Comments
 (0)