Skip to content

[Sema] Preserve ContainsUnexpandedParameterPack in TransformLambdaExpr #86265

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 32 commits into from
Aug 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
6e7b38b
[Sema] Preserve ContainsUnexpandedParameterPack in TransformLambdaExpr
zyn0217 Mar 22, 2024
e76fedd
Consider the captured pack variables - gh18873
zyn0217 Mar 23, 2024
cc74f6d
Merge branch 'main' into GH85667
zyn0217 Mar 23, 2024
faf5d87
comments
zyn0217 Mar 23, 2024
c5765ec
format
zyn0217 Mar 23, 2024
efb1e72
Merge branch 'main' into GH85667
zyn0217 May 1, 2024
6c2d311
Retain the 'unexpanded' Decls
zyn0217 May 3, 2024
1ac5ca7
Update ReleaseNotes
zyn0217 May 3, 2024
db36bc8
Cleanup & silence the unused-expression warning
zyn0217 May 8, 2024
66acffb
Fix the CI
zyn0217 May 24, 2024
5285c10
Fix typos
zyn0217 May 24, 2024
b336807
Merge branch 'main' into GH85667
zyn0217 Jul 17, 2024
b2d2836
Fixup
zyn0217 Jul 18, 2024
26872a6
Fix release notes
zyn0217 Jul 18, 2024
10b9d8b
clarify a bit more
zyn0217 Jul 18, 2024
54af6a1
Typo
zyn0217 Jul 22, 2024
81fd4c9
[Sema] Default arguments for template parameters affect ContainsUnexp…
ilya-biryukov Jul 22, 2024
97372b6
Merge tests from ilya's 99882 and handle template parameter cases
zyn0217 Jul 23, 2024
d1cc8a8
Handle more cases e.g. constraints, DeclStmt, etc.
zyn0217 Jul 25, 2024
45921ef
Merge branch 'main' into GH85667
zyn0217 Jul 25, 2024
ce2bf93
Co-author
zyn0217 Jul 25, 2024
c2a0bf9
Fix ReleaseNotes
zyn0217 Jul 26, 2024
ea94d6d
Use CurContext
zyn0217 Jul 30, 2024
412bf5e
Remove the fixes for constraints
zyn0217 Jul 30, 2024
de76522
Remove extra blanks
zyn0217 Jul 30, 2024
930e8f9
Remove changes for constraints in TransformLambdaExpr
zyn0217 Jul 30, 2024
9ce3250
Simplify the RebuildLambdaExpr()
zyn0217 Aug 1, 2024
0379733
Merge branch 'main' into GH85667
zyn0217 Aug 5, 2024
e74a968
Remove most self-explanatory comments
zyn0217 Aug 5, 2024
27bd832
Remove one more gratuitous comment
zyn0217 Aug 5, 2024
ac89937
FIXME
zyn0217 Aug 5, 2024
301037e
Fix CI
zyn0217 Aug 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,8 @@ Bug Fixes to C++ Support
substitutions in concepts, so it doesn't incorrectly complain of missing
module imports in those situations. (#GH60336)
- Fix init-capture packs having a size of one before being instantiated. (#GH63677)
- Clang now preserves the unexpanded flag in a lambda transform used for pack expansion. (#GH56852), (#GH85667),
(#GH99877).

Bug Fixes to AST Handling
^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down
4 changes: 4 additions & 0 deletions clang/lib/Sema/SemaLambda.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1021,6 +1021,8 @@ void Sema::CompleteLambdaCallOperator(
getGenericLambdaTemplateParameterList(LSI, *this);

DeclContext *DC = Method->getLexicalDeclContext();
// DeclContext::addDecl() assumes that the DeclContext we're adding to is the
// lexical context of the Method. Do so.
Method->setLexicalDeclContext(LSI->Lambda);
if (TemplateParams) {
FunctionTemplateDecl *TemplateMethod =
Expand Down Expand Up @@ -1105,6 +1107,8 @@ void Sema::ActOnLambdaExpressionAfterIntroducer(LambdaIntroducer &Intro,

CXXMethodDecl *Method = CreateLambdaCallOperator(Intro.Range, Class);
LSI->CallOperator = Method;
// Temporarily set the lexical declaration context to the current
// context, so that the Scope stack matches the lexical nesting.
Method->setLexicalDeclContext(CurContext);
Comment on lines +1110 to 1112
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I recovered this comment from https://reviews.llvm.org/D124351 because at least it tries to explain something - better than nothing that just makes the lambda parsing dimmer.


PushDeclContext(CurScope, Method);
Expand Down
20 changes: 9 additions & 11 deletions clang/lib/Sema/SemaTemplateInstantiate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
#include "llvm/ADT/STLForwardCompat.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/SaveAndRestore.h"
#include "llvm/Support/TimeProfiler.h"
#include <optional>

Expand Down Expand Up @@ -1657,11 +1658,12 @@ namespace {
LocalInstantiationScope Scope(SemaRef, /*CombineWithOuterScope=*/true);
Sema::ConstraintEvalRAII<TemplateInstantiator> RAII(*this);

ExprResult Result = inherited::TransformLambdaExpr(E);
if (Result.isInvalid())
return Result;
return inherited::TransformLambdaExpr(E);
}

CXXMethodDecl *MD = Result.getAs<LambdaExpr>()->getCallOperator();
ExprResult RebuildLambdaExpr(SourceLocation StartLoc, SourceLocation EndLoc,
LambdaScopeInfo *LSI) {
CXXMethodDecl *MD = LSI->CallOperator;
for (ParmVarDecl *PVD : MD->parameters()) {
assert(PVD && "null in a parameter list");
if (!PVD->hasDefaultArg())
Expand All @@ -1680,8 +1682,7 @@ namespace {
PVD->setDefaultArg(ErrorResult.get());
}
}

return Result;
return inherited::RebuildLambdaExpr(StartLoc, EndLoc, LSI);
}

StmtResult TransformLambdaBody(LambdaExpr *E, Stmt *Body) {
Expand All @@ -1694,11 +1695,8 @@ namespace {
// `true` to temporarily fix this issue.
// FIXME: This temporary fix can be removed after fully implementing
// p0588r1.
bool Prev = EvaluateConstraints;
EvaluateConstraints = true;
StmtResult Stmt = inherited::TransformLambdaBody(E, Body);
EvaluateConstraints = Prev;
return Stmt;
llvm::SaveAndRestore _(EvaluateConstraints, true);
return inherited::TransformLambdaBody(E, Body);
}

ExprResult TransformRequiresExpr(RequiresExpr *E) {
Expand Down
48 changes: 44 additions & 4 deletions clang/lib/Sema/TreeTransform.h
Original file line number Diff line number Diff line change
Expand Up @@ -4028,6 +4028,20 @@ class TreeTransform {
NumExpansions);
}

ExprResult RebuildLambdaExpr(SourceLocation StartLoc, SourceLocation EndLoc,
LambdaScopeInfo *LSI) {
for (ParmVarDecl *PVD : LSI->CallOperator->parameters()) {
if (Expr *Init = PVD->getInit())
LSI->ContainsUnexpandedParameterPack |=
Init->containsUnexpandedParameterPack();
else if (PVD->hasUninstantiatedDefaultArg())
LSI->ContainsUnexpandedParameterPack |=
PVD->getUninstantiatedDefaultArg()
->containsUnexpandedParameterPack();
}
return getSema().BuildLambdaExpr(StartLoc, EndLoc, LSI);
}

/// Build an empty C++1z fold-expression with the given operator.
///
/// By default, produces the fallback value for the fold-expression, or
Expand Down Expand Up @@ -8284,6 +8298,7 @@ StmtResult
TreeTransform<Derived>::TransformDeclStmt(DeclStmt *S) {
bool DeclChanged = false;
SmallVector<Decl *, 4> Decls;
LambdaScopeInfo *LSI = getSema().getCurLambda();
for (auto *D : S->decls()) {
Decl *Transformed = getDerived().TransformDefinition(D->getLocation(), D);
if (!Transformed)
Expand All @@ -8292,6 +8307,15 @@ TreeTransform<Derived>::TransformDeclStmt(DeclStmt *S) {
if (Transformed != D)
DeclChanged = true;

if (LSI && isa<TypeDecl>(Transformed))
LSI->ContainsUnexpandedParameterPack |=
getSema()
.getASTContext()
.getTypeDeclType(cast<TypeDecl>(Transformed))
.getCanonicalType()
.getTypePtr()
->containsUnexpandedParameterPack();

Comment on lines +8310 to +8318
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't think of any other case that owns an unexpanded flag, handling TypeDecls + Exprs is probably sufficient at this point.

Decls.push_back(Transformed);
}

Expand Down Expand Up @@ -14523,7 +14547,6 @@ TreeTransform<Derived>::TransformLambdaExpr(LambdaExpr *E) {

CXXMethodDecl *NewCallOperator =
getSema().CreateLambdaCallOperator(E->getIntroducerRange(), Class);
NewCallOperator->setLexicalDeclContext(getSema().CurContext);

// Enter the scope of the lambda.
getSema().buildLambdaScope(LSI, NewCallOperator, E->getIntroducerRange(),
Expand Down Expand Up @@ -14591,6 +14614,13 @@ TreeTransform<Derived>::TransformLambdaExpr(LambdaExpr *E) {
}
NewVDs.push_back(NewVD);
getSema().addInitCapture(LSI, NewVD, C->getCaptureKind() == LCK_ByRef);
// Cases we want to tackle:
// ([C(Pack)] {}, ...)
// But rule out cases e.g.
// [...C = Pack()] {}
if (NewC.EllipsisLoc.isInvalid())
LSI->ContainsUnexpandedParameterPack |=
Init.get()->containsUnexpandedParameterPack();
}

if (Invalid)
Expand Down Expand Up @@ -14658,6 +14688,11 @@ TreeTransform<Derived>::TransformLambdaExpr(LambdaExpr *E) {
continue;
}

// This is not an init-capture; however it contains an unexpanded pack e.g.
// ([Pack] {}(), ...)
if (auto *VD = dyn_cast<VarDecl>(CapturedVar); VD && !C->isPackExpansion())
LSI->ContainsUnexpandedParameterPack |= VD->isParameterPack();

// Capture the transformed variable.
getSema().tryCaptureVariable(CapturedVar, C->getLocation(), Kind,
EllipsisLoc);
Expand All @@ -14669,9 +14704,12 @@ TreeTransform<Derived>::TransformLambdaExpr(LambdaExpr *E) {
auto TPL = getDerived().TransformTemplateParameterList(
E->getTemplateParameterList());
LSI->GLTemplateParameterList = TPL;
if (TPL)
if (TPL) {
getSema().AddTemplateParametersToLambdaCallOperator(NewCallOperator, Class,
TPL);
LSI->ContainsUnexpandedParameterPack |=
TPL->containsUnexpandedParameterPack();
}

// Transform the type of the original lambda's call operator.
// The transformation MUST be done in the CurrentInstantiationScope since
Expand Down Expand Up @@ -14710,6 +14748,8 @@ TreeTransform<Derived>::TransformLambdaExpr(LambdaExpr *E) {

if (NewCallOpType.isNull())
return ExprError();
LSI->ContainsUnexpandedParameterPack |=
NewCallOpType->containsUnexpandedParameterPack();
NewCallOpTSI =
NewCallOpTLBuilder.getTypeSourceInfo(getSema().Context, NewCallOpType);
}
Expand Down Expand Up @@ -14824,8 +14864,8 @@ TreeTransform<Derived>::TransformLambdaExpr(LambdaExpr *E) {
Class->setTypeForDecl(nullptr);
getSema().Context.getTypeDeclType(Class);

return getSema().BuildLambdaExpr(E->getBeginLoc(), Body.get()->getEndLoc(),
&LSICopy);
return getDerived().RebuildLambdaExpr(E->getBeginLoc(),
Body.get()->getEndLoc(), &LSICopy);
}

template<typename Derived>
Expand Down
181 changes: 181 additions & 0 deletions clang/test/SemaCXX/fold_lambda_with_variadics.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
// RUN: %clang_cc1 -fsyntax-only -std=c++20 -verify %s

namespace GH85667 {

template <class T>
struct identity {
using type = T;
};

template <class = void> void f() {

static_assert([]<class... Is>(Is... x) {
return ([I(x)] {
return I;
}() + ...);
}(1, 2) == 3);

[]<class... Is>(Is... x) {
return ([](auto y = Is()) { return y + 1; }() + ...); // expected-error {{no matching function}} \
// expected-note {{couldn't infer template argument 'y:auto'}} \
// expected-note@-1 {{requested here}}
// expected-note@#instantiate-f {{requested here}}
}(1);

[]<class... Is>() {
([]<class = Is>(Is)
noexcept(bool(Is()))
{}(Is()),
...);
}.template operator()<char, int, float>();

static_assert(__is_same(decltype([]<class... Is>() {
return ([]() -> decltype(Is()) { return {}; }(),
...);
}.template operator()<int, char>()),
char));

[]<class... Is>() {
return ([]<class... Ts>() -> decltype(Is()) { return Ts(); }() + ...);
// expected-error@-1 {{unexpanded parameter pack 'Ts'}}
}.template operator()<int, int>();

// https://github.com/llvm/llvm-project/issues/56852
[]<class... Is>(Is...) {
([] {
using T = identity<Is>::type;
}(), ...);
}(1, 2);

[](auto ...y) {
([y] { }(), ...);
}();

[](auto ...x) {
([&](auto ...y) {
([x..., y] { }(), ...);
})(1);
}(2, 'b');

#if 0
// FIXME: https://github.com/llvm/llvm-project/issues/18873
[](auto ...x) { // #1
([&](auto ...y) { // #2
([x, y] { }(), ...); // #3
})(1, 'a'); // #4
}(2, 'b'); // #5

// We run into another crash for the above lambda because of the absence of a
// mechanism that rebuilds an unexpanded pack from an expanded Decls.
//
// Basically, this happens after `x` at #1 being expanded when the template
// arguments at #5, deduced as <int, char>, are ready. When we want to
// instantiate the body of #1, we first instantiate the CallExpr at #4, which
// boils down to the lambda's instantiation at #2. To that end, we have to
// instantiate the body of it, which turns out to be #3. #3 is a CXXFoldExpr,
// and we immediately have to hold off on the expansion because we don't have
// corresponding template arguments (arguments at #4 are not transformed yet) for it.
// Therefore, we want to rebuild a CXXFoldExpr, which requires another pattern
// transformation of the lambda inside #3. Then we need to find an unexpanded form
// of such a Decl of x at the time of transforming the capture, which is impossible
// because the instantiated form has been expanded at #1!

[](auto ...x) { // #outer
([&](auto ...y) { // #inner
([x, y] { }(), ...);
// expected-error@-1 {{parameter pack 'y' that has a different length (4 vs. 3) from outer parameter packs}}
// expected-note-re@#inner {{function template specialization {{.*}} requested here}}
// expected-note-re@#outer {{function template specialization {{.*}} requested here}}
// expected-note-re@#instantiate-f {{function template specialization {{.*}} requested here}}
})('a', 'b', 'c');
}(0, 1, 2, 3);
#endif
}

template void f(); // #instantiate-f

} // namespace GH85667

namespace GH99877 {

struct tuple {
int x[3];
};

template <class F> int apply(F f, tuple v) { return f(v.x[0], v.x[1], v.x[2]); }

int Cartesian1(auto x, auto y) {
return apply(
[&](auto... xs) {
return (apply([xs](auto... ys) { return (ys + ...); }, y) + ...);
},
x);
}

int Cartesian2(auto x, auto y) {
return apply(
[&](auto... xs) {
return (apply([zs = xs](auto... ys) { return (ys + ...); }, y) + ...);
},
x);
}

template <int...> struct Ints {};
template <int> struct Choose {
template <class> struct Templ;
};
template <int... x> int Cartesian3(auto y) {
return [&]<int... xs>(Ints<xs...>) {
// check in default template arguments for
// - type template parameters,
(void)(apply([]<class = decltype(xs)>(auto... ys) { return (ys + ...); },
y) +
...);
// - template template parameters.
(void)(apply([]<template <class> class = Choose<xs>::template Templ>(
auto... ys) { return (ys + ...); },
y) +
...);
// - non-type template parameters,
return (apply([]<int = xs>(auto... ys) { return (ys + ...); }, y) + ...);
}(Ints<x...>());
}

template <int... x> int Cartesian4(auto y) {
return [&]<int... xs>(Ints<xs...>) {
return (
apply([]<decltype(xs) xx = 1>(auto... ys) { return (ys + ...); }, y) +
...);
}(Ints<x...>());
}

// FIXME: Attributes should preserve the ContainsUnexpandedPack flag.
#if 0

int Cartesian5(auto x, auto y) {
return apply(
[&](auto... xs) {
return (apply([](auto... ys) __attribute__((
diagnose_if(!__is_same(decltype(xs), int), "message",
"error"))) { return (ys + ...); },
y) +
...);
},
x);
}

#endif

void foo() {
auto x = tuple({1, 2, 3});
auto y = tuple({4, 5, 6});
Cartesian1(x, y);
Cartesian2(x, y);
Cartesian3<1, 2, 3>(y);
Cartesian4<1, 2, 3>(y);
#if 0
Cartesian5(x, y);
#endif
}

} // namespace GH99877