Skip to content

[Sema] Fix computations of "unexpanded packs" in substituted lambdas #99882

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

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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: 1 addition & 1 deletion clang/include/clang/AST/ComputeDependence.h
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ ExprDependence computeDependence(CXXTemporaryObjectExpr *E);
ExprDependence computeDependence(CXXDefaultInitExpr *E);
ExprDependence computeDependence(CXXDefaultArgExpr *E);
ExprDependence computeDependence(LambdaExpr *E,
bool ContainsUnexpandedParameterPack);
bool BodyContainsUnexpandedPacks);
ExprDependence computeDependence(CXXUnresolvedConstructExpr *E);
ExprDependence computeDependence(CXXDependentScopeMemberExpr *E);
ExprDependence computeDependence(MaterializeTemporaryExpr *E);
Expand Down
15 changes: 4 additions & 11 deletions clang/include/clang/AST/ExprCXX.h
Original file line number Diff line number Diff line change
Expand Up @@ -1975,7 +1975,8 @@ class LambdaExpr final : public Expr,
LambdaCaptureDefault CaptureDefault,
SourceLocation CaptureDefaultLoc, bool ExplicitParams,
bool ExplicitResultType, ArrayRef<Expr *> CaptureInits,
SourceLocation ClosingBrace, bool ContainsUnexpandedParameterPack);
SourceLocation ClosingBrace,
bool BodyContainsUnexpandedParameterPack);

/// Construct an empty lambda expression.
LambdaExpr(EmptyShell Empty, unsigned NumCaptures);
Expand All @@ -1996,7 +1997,7 @@ class LambdaExpr final : public Expr,
LambdaCaptureDefault CaptureDefault, SourceLocation CaptureDefaultLoc,
bool ExplicitParams, bool ExplicitResultType,
ArrayRef<Expr *> CaptureInits, SourceLocation ClosingBrace,
bool ContainsUnexpandedParameterPack);
bool BodyContainsUnexpandedParameterPack);

/// Construct a new lambda expression that will be deserialized from
/// an external source.
Expand Down Expand Up @@ -4854,15 +4855,7 @@ class CXXFoldExpr : public Expr {
CXXFoldExpr(QualType T, UnresolvedLookupExpr *Callee,
SourceLocation LParenLoc, Expr *LHS, BinaryOperatorKind Opcode,
SourceLocation EllipsisLoc, Expr *RHS, SourceLocation RParenLoc,
std::optional<unsigned> NumExpansions)
: Expr(CXXFoldExprClass, T, VK_PRValue, OK_Ordinary),
LParenLoc(LParenLoc), EllipsisLoc(EllipsisLoc), RParenLoc(RParenLoc),
NumExpansions(NumExpansions ? *NumExpansions + 1 : 0), Opcode(Opcode) {
SubExprs[SubExpr::Callee] = Callee;
SubExprs[SubExpr::LHS] = LHS;
SubExprs[SubExpr::RHS] = RHS;
setDependence(computeDependence(this));
}
std::optional<unsigned> NumExpansions);
Copy link
Contributor

Choose a reason for hiding this comment

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

This change is completely unrelated, feel free to make an NFC patch for that


CXXFoldExpr(EmptyShell Empty) : Expr(CXXFoldExprClass, Empty) {}

Expand Down
5 changes: 3 additions & 2 deletions clang/include/clang/Sema/ScopeInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -895,8 +895,9 @@ class LambdaScopeInfo final :
/// Whether any of the capture expressions requires cleanups.
CleanupInfo Cleanup;

/// Whether the lambda contains an unexpanded parameter pack.
bool ContainsUnexpandedParameterPack = false;
/// Whether the lambda body contains an unexpanded parameter pack.
/// Note that the captures and template paramters are handled separately.
bool BodyContainsUnexpandedParameterPack = false;

/// Packs introduced by this lambda, if any.
SmallVector<NamedDecl*, 4> LocalPacks;
Expand Down
35 changes: 33 additions & 2 deletions clang/lib/AST/ComputeDependence.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -850,9 +850,40 @@ ExprDependence clang::computeDependence(CXXDefaultArgExpr *E) {
}

ExprDependence clang::computeDependence(LambdaExpr *E,
bool ContainsUnexpandedParameterPack) {
bool BodyContainsUnexpandedPacks) {
auto D = toExprDependenceForImpliedType(E->getType()->getDependence());
if (ContainsUnexpandedParameterPack)

// Record the presence of unexpanded packs.
bool ContainsUnexpandedPack =
BodyContainsUnexpandedPacks ||
(E->getTemplateParameterList() &&
E->getTemplateParameterList()->containsUnexpandedParameterPack());
if (!ContainsUnexpandedPack) {
// Also look at captures.
for (const auto &C : E->explicit_captures()) {
if (!C.capturesVariable() || C.isPackExpansion())
continue;
auto *Var = C.getCapturedVar();
if ((!Var->isInitCapture() && Var->isParameterPack()) ||
(Var->isInitCapture() && !Var->isParameterPack() &&
cast<VarDecl>(Var)->getInit()->containsUnexpandedParameterPack())) {
ContainsUnexpandedPack = true;
break;
}
}
}
// FIXME: Support other attributes, e.g. by storing corresponding flag inside
// Attr (similar to Attr::IsPackExpansion).
if (!ContainsUnexpandedPack) {
for (auto *A : E->getCallOperator()->specific_attrs<DiagnoseIfAttr>()) {
if (A->getCond() && A->getCond()->containsUnexpandedParameterPack()) {
ContainsUnexpandedPack = true;
break;
}
}
}

if (ContainsUnexpandedPack)
D |= ExprDependence::UnexpandedPack;
return D;
}
Expand Down
38 changes: 27 additions & 11 deletions clang/lib/AST/DeclTemplate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,27 +61,43 @@ TemplateParameterList::TemplateParameterList(const ASTContext& C,

bool IsPack = P->isTemplateParameterPack();
if (const auto *NTTP = dyn_cast<NonTypeTemplateParmDecl>(P)) {
if (!IsPack && NTTP->getType()->containsUnexpandedParameterPack())
ContainsUnexpandedParameterPack = true;
if (!IsPack) {
if (NTTP->getType()->containsUnexpandedParameterPack())
ContainsUnexpandedParameterPack = true;
else if (NTTP->hasDefaultArgument() &&
NTTP->getDefaultArgument()
.getArgument()
.containsUnexpandedParameterPack())
ContainsUnexpandedParameterPack = true;
}
if (NTTP->hasPlaceholderTypeConstraint())
HasConstrainedParameters = true;
} else if (const auto *TTP = dyn_cast<TemplateTemplateParmDecl>(P)) {
if (!IsPack &&
TTP->getTemplateParameters()->containsUnexpandedParameterPack())
ContainsUnexpandedParameterPack = true;
} else if (const auto *TTP = dyn_cast<TemplateTypeParmDecl>(P)) {
if (const TypeConstraint *TC = TTP->getTypeConstraint()) {
if (TC->getImmediatelyDeclaredConstraint()
->containsUnexpandedParameterPack())
if (!IsPack) {
if (TTP->getTemplateParameters()->containsUnexpandedParameterPack())
ContainsUnexpandedParameterPack = true;
else if (TTP->hasDefaultArgument() &&
TTP->getDefaultArgument()
.getArgument()
.containsUnexpandedParameterPack())
ContainsUnexpandedParameterPack = true;
}
} else if (const auto *TTP = dyn_cast<TemplateTypeParmDecl>(P)) {
if (!IsPack && TTP->hasDefaultArgument() &&
TTP->getDefaultArgument()
.getArgument()
.containsUnexpandedParameterPack()) {
ContainsUnexpandedParameterPack = true;
} else if (const TypeConstraint *TC = TTP->getTypeConstraint();
TC && TC->getImmediatelyDeclaredConstraint()
->containsUnexpandedParameterPack()) {
ContainsUnexpandedParameterPack = true;
}
if (TTP->hasTypeConstraint())
HasConstrainedParameters = true;
} else {
llvm_unreachable("unexpected template parameter type");
}
// FIXME: If a default argument contains an unexpanded parameter pack, the
// template parameter list does too.
}

if (HasRequiresClause) {
Expand Down
26 changes: 22 additions & 4 deletions clang/lib/AST/ExprCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1254,7 +1254,7 @@ LambdaExpr::LambdaExpr(QualType T, SourceRange IntroducerRange,
SourceLocation CaptureDefaultLoc, bool ExplicitParams,
bool ExplicitResultType, ArrayRef<Expr *> CaptureInits,
SourceLocation ClosingBrace,
bool ContainsUnexpandedParameterPack)
bool BodyContainsUnexpandedParameterPack)
: Expr(LambdaExprClass, T, VK_PRValue, OK_Ordinary),
IntroducerRange(IntroducerRange), CaptureDefaultLoc(CaptureDefaultLoc),
ClosingBrace(ClosingBrace) {
Expand All @@ -1276,7 +1276,7 @@ LambdaExpr::LambdaExpr(QualType T, SourceRange IntroducerRange,
// Copy the body of the lambda.
*Stored++ = getCallOperator()->getBody();

setDependence(computeDependence(this, ContainsUnexpandedParameterPack));
setDependence(computeDependence(this, BodyContainsUnexpandedParameterPack));
}

LambdaExpr::LambdaExpr(EmptyShell Empty, unsigned NumCaptures)
Expand All @@ -1295,7 +1295,7 @@ LambdaExpr *LambdaExpr::Create(const ASTContext &Context, CXXRecordDecl *Class,
bool ExplicitParams, bool ExplicitResultType,
ArrayRef<Expr *> CaptureInits,
SourceLocation ClosingBrace,
bool ContainsUnexpandedParameterPack) {
bool BodyContainsUnexpandedParameterPack) {
// Determine the type of the expression (i.e., the type of the
// function object we're creating).
QualType T = Context.getTypeDeclType(Class);
Expand All @@ -1305,7 +1305,7 @@ LambdaExpr *LambdaExpr::Create(const ASTContext &Context, CXXRecordDecl *Class,
return new (Mem)
LambdaExpr(T, IntroducerRange, CaptureDefault, CaptureDefaultLoc,
ExplicitParams, ExplicitResultType, CaptureInits, ClosingBrace,
ContainsUnexpandedParameterPack);
BodyContainsUnexpandedParameterPack);
}

LambdaExpr *LambdaExpr::CreateDeserialized(const ASTContext &C,
Expand Down Expand Up @@ -1944,3 +1944,21 @@ CXXParenListInitExpr *CXXParenListInitExpr::CreateEmpty(ASTContext &C,
alignof(CXXParenListInitExpr));
return new (Mem) CXXParenListInitExpr(Empty, NumExprs);
}

CXXFoldExpr::CXXFoldExpr(QualType T, UnresolvedLookupExpr *Callee,
SourceLocation LParenLoc, Expr *LHS,
BinaryOperatorKind Opcode, SourceLocation EllipsisLoc,
Expr *RHS, SourceLocation RParenLoc,
std::optional<unsigned> NumExpansions)
: Expr(CXXFoldExprClass, T, VK_PRValue, OK_Ordinary), LParenLoc(LParenLoc),
EllipsisLoc(EllipsisLoc), RParenLoc(RParenLoc),
NumExpansions(NumExpansions ? *NumExpansions + 1 : 0), Opcode(Opcode) {
// We rely on asserted invariant to distnguish left and right folds.
assert(((LHS && LHS->containsUnexpandedParameterPack()) !=
(RHS && RHS->containsUnexpandedParameterPack())) &&
"Exactly one of LHS or RHS should contain an unexpanded pack");
SubExprs[SubExpr::Callee] = Callee;
SubExprs[SubExpr::LHS] = LHS;
SubExprs[SubExpr::RHS] = RHS;
setDependence(computeDependence(this));
}
21 changes: 7 additions & 14 deletions clang/lib/Sema/SemaLambda.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1109,8 +1109,6 @@ void Sema::ActOnLambdaExpressionAfterIntroducer(LambdaIntroducer &Intro,

PushDeclContext(CurScope, Method);

bool ContainsUnexpandedParameterPack = false;

// Distinct capture names, for diagnostics.
llvm::DenseMap<IdentifierInfo *, ValueDecl *> CaptureNames;

Expand Down Expand Up @@ -1312,8 +1310,6 @@ void Sema::ActOnLambdaExpressionAfterIntroducer(LambdaIntroducer &Intro,

// Just ignore the ellipsis.
}
} else if (Var->isParameterPack()) {
ContainsUnexpandedParameterPack = true;
}

if (C->Init.isUsable()) {
Expand All @@ -1328,7 +1324,6 @@ void Sema::ActOnLambdaExpressionAfterIntroducer(LambdaIntroducer &Intro,
LSI->ExplicitCaptureRanges[LSI->Captures.size() - 1] = C->ExplicitRange;
}
finishLambdaExplicitCaptures(LSI);
LSI->ContainsUnexpandedParameterPack |= ContainsUnexpandedParameterPack;
PopDeclContext();
}

Expand Down Expand Up @@ -1380,8 +1375,6 @@ void Sema::ActOnLambdaClosureParameters(
AddTemplateParametersToLambdaCallOperator(LSI->CallOperator, LSI->Lambda,
TemplateParams);
LSI->Lambda->setLambdaIsGeneric(true);
LSI->ContainsUnexpandedParameterPack |=
TemplateParams->containsUnexpandedParameterPack();
}
LSI->AfterParameterList = true;
}
Expand Down Expand Up @@ -2079,7 +2072,7 @@ ExprResult Sema::BuildLambdaExpr(SourceLocation StartLoc, SourceLocation EndLoc,
bool ExplicitParams;
bool ExplicitResultType;
CleanupInfo LambdaCleanup;
bool ContainsUnexpandedParameterPack;
bool BodyContainsUnexpandedParameterPack;
bool IsGenericLambda;
{
CallOperator = LSI->CallOperator;
Expand All @@ -2088,7 +2081,8 @@ ExprResult Sema::BuildLambdaExpr(SourceLocation StartLoc, SourceLocation EndLoc,
ExplicitParams = LSI->ExplicitParams;
ExplicitResultType = !LSI->HasImplicitReturnType;
LambdaCleanup = LSI->Cleanup;
ContainsUnexpandedParameterPack = LSI->ContainsUnexpandedParameterPack;
BodyContainsUnexpandedParameterPack =
LSI->BodyContainsUnexpandedParameterPack;
IsGenericLambda = Class->isGenericLambda();

CallOperator->setLexicalDeclContext(Class);
Expand Down Expand Up @@ -2227,11 +2221,10 @@ ExprResult Sema::BuildLambdaExpr(SourceLocation StartLoc, SourceLocation EndLoc,

Cleanup.mergeFrom(LambdaCleanup);

LambdaExpr *Lambda = LambdaExpr::Create(Context, Class, IntroducerRange,
CaptureDefault, CaptureDefaultLoc,
ExplicitParams, ExplicitResultType,
CaptureInits, EndLoc,
ContainsUnexpandedParameterPack);
LambdaExpr *Lambda = LambdaExpr::Create(
Context, Class, IntroducerRange, CaptureDefault, CaptureDefaultLoc,
ExplicitParams, ExplicitResultType, CaptureInits, EndLoc,
BodyContainsUnexpandedParameterPack);
// If the lambda expression's call operator is not explicitly marked constexpr
// and we are not in a dependent context, analyze the call operator to infer
// its constexpr-ness, suppressing diagnostics while doing so.
Expand Down
6 changes: 5 additions & 1 deletion clang/lib/Sema/SemaTemplateVariadic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,11 @@ Sema::DiagnoseUnexpandedParameterPacks(SourceLocation Loc,
}

if (!EnclosingStmtExpr) {
LSI->ContainsUnexpandedParameterPack = true;
// It is ok to have unexpanded packs in captures, template parameters
// and parameters too, but only the body statement does not store this
// flag, so we have to propagate it through LamdaScopeInfo.
if (LSI->AfterParameterList)
LSI->BodyContainsUnexpandedParameterPack = true;
Comment on lines +356 to +360
Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, one thing I didn't notice in #86265 is that we could propagate up the unexpanded pack flag for statements through calls to DiagnoseUnexpandedParameterPacks(). (IIUC we call this function at the end of every expressions?)

Copy link
Contributor

Choose a reason for hiding this comment

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

I think this just force us to do more work - going over the lambda twice is not ideal

Copy link
Contributor

Choose a reason for hiding this comment

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

Wait I thought it is my approach that goes through the lambda body twice in a transformation. I didn't see additional calls to DiagnoseUnexpandedParameterPacks in this patch?

Copy link
Contributor Author

@ilya-biryukov ilya-biryukov Jul 22, 2024

Choose a reason for hiding this comment

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

Yeah, there aren't any additional recursive traversals. It's only a shallow traversal over the fields of the lambda itself and a few of their members.

Copy link
Contributor

Choose a reason for hiding this comment

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

It's shallow but it's also work that we could avoid doing entirely - at least during parsing just by not having this if.

return false;
}
} else {
Expand Down
78 changes: 78 additions & 0 deletions clang/test/SemaCXX/fold_lambda_with_variadics.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// RUN: %clang_cc1 -fsyntax-only -std=c++20 -verify %s
Copy link
Contributor

Choose a reason for hiding this comment

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

It would be very nice if you can merge other tests from #86265 e.g. tests including constraints/noexcepts specifiers. That way this could completely supersede that.

// expected-no-diagnostics
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...>());
}

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);
}


int main() {
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);
Cartesian5(x, y);
}
Loading