Skip to content

Commit 3ed9e9e

Browse files
committed
[Clang] Add captures to the instantiation scope of lambda call operators
Like concepts checking, a trailing return type of a lambda in a dependent context may refer to captures in which case they may need to be rebuilt, so the map of local decl should include captures. This patch reveal a pre-existing issue. `this` is always recomputed by TreeTransform. `*this` (like all captures) only become `const` after the parameter list. However, if try to recompute the value of `this` (in a parameter) during template instantiation while determining the type of the call operator, we will determine it to be const (unless the lambda is mutable). There is no good way to know at that point that we are in a parameter or not, the easiest/best solution is to transform the type of this. Note that doing so break a handful of HLSL tests. So this is a prototype at this point. Fixes #65067 Fixes #63675 Reviewed By: erichkeane Differential Revision: https://reviews.llvm.org/D159126
1 parent 679c0b4 commit 3ed9e9e

9 files changed

+158
-33
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,11 @@ Bug Fixes to C++ Support
270270
- Fix crash when parsing the requires clause of some generic lambdas.
271271
(`#64689 <https://github.com/llvm/llvm-project/issues/64689>`_)
272272

273+
- Fix crash when the trailing return type of a generic and dependent
274+
lambda refers to an init-capture.
275+
(`#65067 <https://github.com/llvm/llvm-project/issues/65067>`_` and
276+
`#63675 <https://github.com/llvm/llvm-project/issues/63675>`_`)
277+
273278
Bug Fixes to AST Handling
274279
^^^^^^^^^^^^^^^^^^^^^^^^^
275280
- Fixed an import failure of recursive friend class template.

clang/include/clang/Sema/Sema.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7365,6 +7365,14 @@ class Sema final {
73657365

73667366
sema::LambdaScopeInfo *RebuildLambdaScopeInfo(CXXMethodDecl *CallOperator);
73677367

7368+
class LambdaScopeForCallOperatorInstantiationRAII
7369+
: private FunctionScopeRAII {
7370+
public:
7371+
LambdaScopeForCallOperatorInstantiationRAII(
7372+
Sema &SemasRef, FunctionDecl *FD, MultiLevelTemplateArgumentList MLTAL,
7373+
LocalInstantiationScope &Scope);
7374+
};
7375+
73687376
/// Check whether the given expression is a valid constraint expression.
73697377
/// A diagnostic is emitted if it is not, false is returned, and
73707378
/// PossibleNonPrimary will be set to true if the failure might be due to a

clang/lib/Sema/SemaConcept.cpp

Lines changed: 5 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -600,11 +600,6 @@ bool Sema::SetupConstraintScope(
600600
if (addInstantiatedParametersToScope(FD, FromMemTempl->getTemplatedDecl(),
601601
Scope, MLTAL))
602602
return true;
603-
// Make sure the captures are also added to the instantiation scope.
604-
if (isLambdaCallOperator(FD) &&
605-
addInstantiatedCapturesToScope(FD, FromMemTempl->getTemplatedDecl(),
606-
Scope, MLTAL))
607-
return true;
608603
}
609604

610605
return false;
@@ -629,11 +624,6 @@ bool Sema::SetupConstraintScope(
629624
// child-function.
630625
if (addInstantiatedParametersToScope(FD, InstantiatedFrom, Scope, MLTAL))
631626
return true;
632-
633-
// Make sure the captures are also added to the instantiation scope.
634-
if (isLambdaCallOperator(FD) &&
635-
addInstantiatedCapturesToScope(FD, InstantiatedFrom, Scope, MLTAL))
636-
return true;
637627
}
638628

639629
return false;
@@ -712,20 +702,8 @@ bool Sema::CheckFunctionConstraints(const FunctionDecl *FD,
712702
}
713703
CXXThisScopeRAII ThisScope(*this, Record, ThisQuals, Record != nullptr);
714704

715-
// When checking the constraints of a lambda, we need to restore a
716-
// LambdaScopeInfo populated with correct capture information so that the type
717-
// of a variable referring to a capture is correctly const-adjusted.
718-
FunctionScopeRAII FuncScope(*this);
719-
if (isLambdaCallOperator(FD)) {
720-
LambdaScopeInfo *LSI = RebuildLambdaScopeInfo(
721-
const_cast<CXXMethodDecl *>(cast<CXXMethodDecl>(FD)));
722-
// Constraints are checked from the parent context of the lambda, so we set
723-
// AfterParameterList to false, so that `tryCaptureVariable` finds
724-
// explicit captures in the appropriate context.
725-
LSI->AfterParameterList = false;
726-
} else {
727-
FuncScope.disable();
728-
}
705+
LambdaScopeForCallOperatorInstantiationRAII LambdaScope(
706+
*this, const_cast<FunctionDecl *>(FD), *MLTAL, Scope);
729707

730708
return CheckConstraintSatisfaction(
731709
FD, {FD->getTrailingRequiresClause()}, *MLTAL,
@@ -913,15 +891,10 @@ bool Sema::CheckInstantiatedFunctionTemplateConstraints(
913891
ThisQuals = Method->getMethodQualifiers();
914892
Record = Method->getParent();
915893
}
916-
CXXThisScopeRAII ThisScope(*this, Record, ThisQuals, Record != nullptr);
917-
FunctionScopeRAII FuncScope(*this);
918894

919-
if (isLambdaCallOperator(Decl)) {
920-
LambdaScopeInfo *LSI = RebuildLambdaScopeInfo(cast<CXXMethodDecl>(Decl));
921-
LSI->AfterParameterList = false;
922-
} else {
923-
FuncScope.disable();
924-
}
895+
CXXThisScopeRAII ThisScope(*this, Record, ThisQuals, Record != nullptr);
896+
LambdaScopeForCallOperatorInstantiationRAII LambdaScope(
897+
*this, const_cast<FunctionDecl *>(Decl), *MLTAL, Scope);
925898

926899
llvm::SmallVector<Expr *, 1> Converted;
927900
return CheckConstraintSatisfaction(Template, TemplateAC, Converted, *MLTAL,

clang/lib/Sema/SemaDecl.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15382,6 +15382,10 @@ LambdaScopeInfo *Sema::RebuildLambdaScopeInfo(CXXMethodDecl *CallOperator) {
1538215382
LSI->CallOperator = CallOperator;
1538315383
LSI->Lambda = LambdaClass;
1538415384
LSI->ReturnType = CallOperator->getReturnType();
15385+
// This function in calls in situation where the context of the call operator
15386+
// is not entered, so we set AfterParameterList to false, so that
15387+
// `tryCaptureVariable` finds explicit captures in the appropriate context.
15388+
LSI->AfterParameterList = false;
1538515389
const LambdaCaptureDefault LCD = LambdaClass->getLambdaCaptureDefault();
1538615390

1538715391
if (LCD == LCD_None)

clang/lib/Sema/SemaLambda.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "clang/Sema/ScopeInfo.h"
2121
#include "clang/Sema/SemaInternal.h"
2222
#include "clang/Sema/SemaLambda.h"
23+
#include "clang/Sema/Template.h"
2324
#include "llvm/ADT/STLExtras.h"
2425
#include <optional>
2526
using namespace clang;
@@ -2254,3 +2255,34 @@ ExprResult Sema::BuildBlockForLambdaConversion(SourceLocation CurrentLocation,
22542255

22552256
return BuildBlock;
22562257
}
2258+
2259+
Sema::LambdaScopeForCallOperatorInstantiationRAII::
2260+
LambdaScopeForCallOperatorInstantiationRAII(
2261+
Sema &SemasRef, FunctionDecl *FD, MultiLevelTemplateArgumentList MLTAL,
2262+
LocalInstantiationScope &Scope)
2263+
: FunctionScopeRAII(SemasRef) {
2264+
if (!isLambdaCallOperator(FD)) {
2265+
FunctionScopeRAII::disable();
2266+
return;
2267+
}
2268+
2269+
if (FD->isTemplateInstantiation() && FD->getPrimaryTemplate()) {
2270+
FunctionTemplateDecl *PrimaryTemplate = FD->getPrimaryTemplate();
2271+
if (const auto *FromMemTempl =
2272+
PrimaryTemplate->getInstantiatedFromMemberTemplate()) {
2273+
SemasRef.addInstantiatedCapturesToScope(
2274+
FD, FromMemTempl->getTemplatedDecl(), Scope, MLTAL);
2275+
}
2276+
}
2277+
2278+
else if (FD->getTemplatedKind() == FunctionDecl::TK_MemberSpecialization ||
2279+
FD->getTemplatedKind() == FunctionDecl::TK_DependentNonTemplate) {
2280+
FunctionDecl *InstantiatedFrom =
2281+
FD->getTemplatedKind() == FunctionDecl::TK_MemberSpecialization
2282+
? FD->getInstantiatedFromMemberFunction()
2283+
: FD->getInstantiatedFromDecl();
2284+
SemasRef.addInstantiatedCapturesToScope(FD, InstantiatedFrom, Scope, MLTAL);
2285+
}
2286+
2287+
SemasRef.RebuildLambdaScopeInfo(cast<CXXMethodDecl>(FD));
2288+
}

clang/lib/Sema/SemaTemplateInstantiateDecl.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2426,6 +2426,9 @@ Decl *TemplateDeclInstantiator::VisitCXXMethodDecl(
24262426
cast<Decl>(Owner)->isDefinedOutsideFunctionOrMethod());
24272427
LocalInstantiationScope Scope(SemaRef, MergeWithParentScope);
24282428

2429+
Sema::LambdaScopeForCallOperatorInstantiationRAII LambdaScope(
2430+
SemaRef, const_cast<CXXMethodDecl *>(D), TemplateArgs, Scope);
2431+
24292432
// Instantiate enclosing template arguments for friends.
24302433
SmallVector<TemplateParameterList *, 4> TempParamLists;
24312434
unsigned NumTempParamLists = 0;

clang/lib/Sema/TreeTransform.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12325,7 +12325,16 @@ TreeTransform<Derived>::TransformCXXNullPtrLiteralExpr(
1232512325
template<typename Derived>
1232612326
ExprResult
1232712327
TreeTransform<Derived>::TransformCXXThisExpr(CXXThisExpr *E) {
12328-
QualType T = getSema().getCurrentThisType();
12328+
12329+
// In lambdas, the qualifiers of the type depends of where in
12330+
// the call operator `this` appear, and we do not have a good way to
12331+
// rebuild this information, so we transform the type.
12332+
//
12333+
// In other contexts, the type of `this` may be overrided
12334+
// for type deduction, so we need to recompute it.
12335+
QualType T = getSema().getCurLambda() ?
12336+
getDerived().TransformType(E->getType())
12337+
: getSema().getCurrentThisType();
1232912338

1233012339
if (!getDerived().AlwaysRebuild() && T == E->getType()) {
1233112340
// Mark it referenced in the new context regardless.

clang/test/SemaCXX/lambda-capture-type-deduction.cpp

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,3 +260,40 @@ void f(int) {
260260
void test() { f<int>(0); }
261261

262262
}
263+
264+
namespace GH65067 {
265+
266+
template <typename> class a {
267+
public:
268+
template <typename b> void c(b f) { d<int>(f)(0); }
269+
template <typename, typename b> auto d(b f) {
270+
return [f = f](auto arg) -> a<decltype(f(arg))> { return {}; };
271+
}
272+
};
273+
a<void> e;
274+
auto fn1() {
275+
e.c([](int) {});
276+
}
277+
278+
}
279+
280+
namespace GH63675 {
281+
282+
template <class _Tp> _Tp __declval();
283+
struct __get_tag {
284+
template <class _Tag> void operator()(_Tag);
285+
};
286+
template <class _ImplFn> struct __basic_sender {
287+
using __tag_t = decltype(__declval<_ImplFn>()(__declval<__get_tag>()));
288+
_ImplFn __impl_;
289+
};
290+
auto __make_basic_sender = []<class... _Children>(
291+
_Children... __children) {
292+
return __basic_sender{[... __children = __children]<class _Fun>(
293+
_Fun __fun) -> decltype(__fun(__children...)) {}};
294+
};
295+
void __trans_tmp_1() {
296+
__make_basic_sender(__trans_tmp_1);
297+
}
298+
299+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
2+
// This test case came up in the review of
3+
// https://reviews.llvm.org/D159126
4+
// when transforming `this` within a
5+
// requires expression, we need to make sure
6+
// the type of this (and its qualifiers) is respected.
7+
namespace D159126 {
8+
9+
template <class _Tp>
10+
concept __member_begin = requires(_Tp __t) {
11+
__t.begin();
12+
};
13+
14+
struct {
15+
template <class _Tp>
16+
requires __member_begin<_Tp>
17+
auto operator()(_Tp &&) {}
18+
} inline begin;
19+
20+
template <class>
21+
concept range = requires {
22+
begin;
23+
};
24+
25+
template <class _Tp>
26+
concept __can_compare_begin = requires(_Tp __t) {
27+
begin(__t);
28+
};
29+
30+
struct {
31+
template <__can_compare_begin _Tp> void operator()(_Tp &&);
32+
} empty;
33+
34+
template <range _Rp> struct owning_view {
35+
_Rp __r_;
36+
public:
37+
void empty() const requires requires { empty(__r_); };
38+
};
39+
40+
template <class T>
41+
concept HasEmpty = requires(T t) {
42+
t.empty();
43+
};
44+
45+
struct ComparableIters {
46+
void begin();
47+
};
48+
49+
static_assert(HasEmpty<owning_view<ComparableIters&>>);
50+
static_assert(HasEmpty<owning_view<ComparableIters&&>>);
51+
static_assert(!HasEmpty<owning_view<const ComparableIters&>>);
52+
static_assert(!HasEmpty<owning_view<const ComparableIters&&>>);
53+
54+
}

0 commit comments

Comments
 (0)