[clang] [Clang] Transform lambda's constraints when instantiating parameter mapping (PR #195995)
Younan Zhang via cfe-commits
cfe-commits at lists.llvm.org
Fri May 8 21:05:46 PDT 2026
https://github.com/zyn0217 updated https://github.com/llvm/llvm-project/pull/195995
>From 6271200c059ff757333247e286b85538735ffe74 Mon Sep 17 00:00:00 2001
From: Younan Zhang <zyn7109 at gmail.com>
Date: Wed, 6 May 2026 12:04:13 +0800
Subject: [PATCH 1/3] [Clang] Transform lambda's constraints when instantiating
parameter mapping
This way we can remove a few workarounds of lambda expressions where
outer template arguments of concepts have to be preserved through
ImplicitConceptSpecializationDecls.
---
clang/lib/Parse/ParseTemplate.cpp | 6 ---
clang/lib/Sema/SemaConcept.cpp | 30 ++++--------
clang/lib/Sema/SemaTemplate.cpp | 2 +-
clang/lib/Sema/SemaTemplateDeduction.cpp | 14 ------
clang/lib/Sema/SemaTemplateInstantiate.cpp | 47 +++++++++++++++++--
.../lib/Sema/SemaTemplateInstantiateDecl.cpp | 7 +++
clang/lib/Sema/TreeTransform.h | 12 ++---
clang/test/SemaTemplate/concepts-lambda.cpp | 34 +++++++++++++-
8 files changed, 97 insertions(+), 55 deletions(-)
diff --git a/clang/lib/Parse/ParseTemplate.cpp b/clang/lib/Parse/ParseTemplate.cpp
index 330a9c6aea0c5..dbc7cbc6cdc0c 100644
--- a/clang/lib/Parse/ParseTemplate.cpp
+++ b/clang/lib/Parse/ParseTemplate.cpp
@@ -533,12 +533,6 @@ bool Parser::isTypeConstraintAnnotation() {
bool Parser::TryAnnotateTypeConstraint() {
if (!getLangOpts().CPlusPlus20)
return false;
- // The type constraint may declare template parameters, notably
- // if it contains a generic lambda, so we need to increment
- // the template depth as these parameters would not be instantiated
- // at the current depth.
- TemplateParameterDepthRAII CurTemplateDepthTracker(TemplateParameterDepth);
- ++CurTemplateDepthTracker;
CXXScopeSpec SS;
bool WasScopeAnnotation = Tok.is(tok::annot_cxxscope);
if (ParseOptionalCXXScopeSpecifier(SS, /*ObjectType=*/nullptr,
diff --git a/clang/lib/Sema/SemaConcept.cpp b/clang/lib/Sema/SemaConcept.cpp
index 3f04922a5647e..8bd3a8f6e1a45 100644
--- a/clang/lib/Sema/SemaConcept.cpp
+++ b/clang/lib/Sema/SemaConcept.cpp
@@ -482,11 +482,6 @@ class ConstraintSatisfactionChecker {
ConstraintSatisfaction &Satisfaction;
bool BuildExpression;
- // The most closest concept declaration when evaluating atomic constriants.
- // This is to make sure that lambdas in the atomic expression live in the
- // right context.
- ConceptDecl *ParentConcept = nullptr;
-
// This is for TemplateInstantiator to not instantiate the same template
// parameter mapping many times, in order to improve substitution performance.
llvm::DenseMap<llvm::FoldingSetNodeID, TemplateArgumentLoc>
@@ -730,20 +725,6 @@ ExprResult ConstraintSatisfactionChecker::EvaluateSlow(
return ExprEmpty();
}
- // Note that generic lambdas inside requires body require a lambda context
- // decl from which to fetch correct template arguments. But we don't have any
- // proper decls because the constraints are already normalized.
- if (ParentConcept) {
- // FIXME: the evaluation context should learn to track template arguments
- // separately from a Decl.
- EvaluationContext.emplace(
- S, Sema::ExpressionEvaluationContext::ConstantEvaluated,
- /*LambdaContextDecl=*/
- ImplicitConceptSpecializationDecl::Create(
- S.Context, ParentConcept->getDeclContext(),
- ParentConcept->getBeginLoc(), SubstitutedOutermost));
- }
-
Sema::ArgPackSubstIndexRAII SubstIndex(S, PackSubstitutionIndex);
ExprResult SubstitutedAtomicExpr = EvaluateAtomicConstraint(
Constraint.getConstraintExpr(), *SubstitutedArgs);
@@ -1052,9 +1033,6 @@ ExprResult ConstraintSatisfactionChecker::Evaluate(
if (InstTemplate.isInvalid())
return ExprError();
- llvm::SaveAndRestore PushConceptDecl(
- ParentConcept, cast<ConceptDecl>(ConceptId->getNamedConcept()));
-
unsigned Size = Satisfaction.Details.size();
ExprResult E = Evaluate(Constraint.getNormalizedConstraint(), MLTAL);
@@ -2291,6 +2269,14 @@ bool SubstituteParameterMappings::substitute(NormalizedConstraint &N) {
}
assert(!ArgsAsWritten);
const ConceptSpecializationExpr *CSE = CC.getConceptSpecializationExpr();
+ // This is to make sure that lambdas within template arguments live in a
+ // dependent context such that they are assured to be transformed in
+ // evaluation.
+ EnterExpressionEvaluationContext EECtx(
+ SemaRef, Sema::ExpressionEvaluationContext::ConstantEvaluated,
+ /*LambdaContextDecl=*/
+ const_cast<ImplicitConceptSpecializationDecl *>(
+ CSE->getSpecializationDecl()));
SmallVector<TemplateArgument> InnerArgs(CSE->getTemplateArguments());
ConceptDecl *Concept = CSE->getNamedConcept();
if (RemovePacksForFoldExpr) {
diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp
index c436b7018a2bd..6b7dadcf123a7 100644
--- a/clang/lib/Sema/SemaTemplate.cpp
+++ b/clang/lib/Sema/SemaTemplate.cpp
@@ -4912,7 +4912,7 @@ ExprResult Sema::CheckConceptTemplateId(
LocalInstantiationScope Scope(*this);
EnterExpressionEvaluationContext EECtx{
- *this, ExpressionEvaluationContext::Unevaluated, CSD};
+ *this, ExpressionEvaluationContext::Unevaluated};
Error = CheckConstraintSatisfaction(
NamedConcept, AssociatedConstraint(Concept->getConstraintExpr()), MLTAL,
diff --git a/clang/lib/Sema/SemaTemplateDeduction.cpp b/clang/lib/Sema/SemaTemplateDeduction.cpp
index c71c40526ccdc..f6665467fe120 100644
--- a/clang/lib/Sema/SemaTemplateDeduction.cpp
+++ b/clang/lib/Sema/SemaTemplateDeduction.cpp
@@ -5157,20 +5157,6 @@ static bool CheckDeducedPlaceholderConstraints(Sema &S, const AutoType &Type,
return true;
MultiLevelTemplateArgumentList MLTAL(Concept, CTAI.SugaredConverted,
/*Final=*/true);
- // Build up an EvaluationContext with an ImplicitConceptSpecializationDecl so
- // that the template arguments of the constraint can be preserved. For
- // example:
- //
- // template <class T>
- // concept C = []<D U = void>() { return true; }();
- //
- // We need the argument for T while evaluating type constraint D in
- // building the CallExpr to the lambda.
- EnterExpressionEvaluationContext EECtx(
- S, Sema::ExpressionEvaluationContext::Unevaluated,
- ImplicitConceptSpecializationDecl::Create(
- S.getASTContext(), Concept->getDeclContext(), Concept->getLocation(),
- CTAI.SugaredConverted));
if (S.CheckConstraintSatisfaction(
Concept, AssociatedConstraint(Concept->getConstraintExpr()), MLTAL,
TypeLoc.getLocalSourceRange(), Satisfaction))
diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp b/clang/lib/Sema/SemaTemplateInstantiate.cpp
index 8dfe33f8684bd..d7ba39171b243 100644
--- a/clang/lib/Sema/SemaTemplateInstantiate.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp
@@ -1763,9 +1763,34 @@ namespace {
if (TA.isDependent())
return CXXRecordDecl::LambdaDependencyKind::LDK_AlwaysDependent;
}
+ if (auto *CD = dyn_cast_if_present<ImplicitConceptSpecializationDecl>(
+ LSI->Lambda->getLambdaContextDecl())) {
+ if (llvm::any_of(CD->getTemplateArguments(),
+ [](const auto &TA) { return TA.isDependent(); }))
+ return CXXRecordDecl::LambdaDependencyKind::LDK_AlwaysDependent;
+ }
return inherited::ComputeLambdaDependency(LSI);
}
+ AssociatedConstraint TransformConstraint(AssociatedConstraint AC) {
+ // If the concept refers to any outer parameter packs, we track the
+ // SubstIndex for evaluation.
+ if (AC && AC.ConstraintExpr->containsUnexpandedParameterPack() &&
+ !AC.ArgPackSubstIndex)
+ AC.ArgPackSubstIndex = SemaRef.ArgPackSubstIndex;
+
+ // We don't want the template argument substitution into parameter
+ // mappings to preserve the outer depths.
+ if (AC && SemaRef.inConstraintSubstitution()) {
+ ExprResult E = TransformExpr(const_cast<Expr *>(AC.ConstraintExpr));
+ if (E.isInvalid())
+ return {};
+ AC.ConstraintExpr = E.get();
+ }
+
+ return AC;
+ }
+
ExprResult TransformLambdaExpr(LambdaExpr *E) {
// Do not rebuild lambdas to avoid creating a new type.
// Lambdas have already been processed inside their eval contexts.
@@ -1876,11 +1901,25 @@ namespace {
TemplateParameterList *OrigTPL) {
if (!OrigTPL || !OrigTPL->size()) return OrigTPL;
+ std::optional<MultiLevelTemplateArgumentList> OldMLTAL;
+ // We need to preserve the lambda depth in parameter mapping.
+ // Otherwise the template argument deduction would fail, if we reduced the
+ // depth too early.
+ if (SemaRef.inParameterMappingSubstitution() &&
+ getDepthAndIndex(OrigTPL->getParam(0)).first >=
+ TemplateArgs.getNumSubstitutedLevels())
+ OldMLTAL = ForgetSubstitution();
+
DeclContext *Owner = OrigTPL->getParam(0)->getDeclContext();
- TemplateDeclInstantiator DeclInstantiator(getSema(),
- /* DeclContext *Owner */ Owner, TemplateArgs);
- DeclInstantiator.setEvaluateConstraints(EvaluateConstraints);
- return DeclInstantiator.SubstTemplateParams(OrigTPL);
+ TemplateDeclInstantiator DeclInstantiator(getSema(), Owner, TemplateArgs);
+ // We don't want the template argument substitution into parameter
+ // mappings to preserve the outer depths.
+ DeclInstantiator.setEvaluateConstraints(
+ SemaRef.inConstraintSubstitution() || EvaluateConstraints);
+ auto *Transformed = DeclInstantiator.SubstTemplateParams(OrigTPL);
+ if (OldMLTAL)
+ RememberSubstitution(std::move(*OldMLTAL));
+ return Transformed;
}
concepts::TypeRequirement *
diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
index 09c2482168ab7..92fb8da29a773 100644
--- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
@@ -4849,6 +4849,13 @@ TemplateDeclInstantiator::SubstTemplateParams(TemplateParameterList *L) {
return nullptr;
Expr *InstRequiresClause = L->getRequiresClause();
+ if (InstRequiresClause && EvaluateConstraints) {
+ ExprResult E =
+ SemaRef.SubstConstraintExpr(InstRequiresClause, TemplateArgs);
+ if (E.isInvalid())
+ return nullptr;
+ InstRequiresClause = E.get();
+ }
TemplateParameterList *InstL
= TemplateParameterList::Create(SemaRef.Context, L->getTemplateLoc(),
diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h
index 4c941e234d78d..f2de8aa47ab25 100644
--- a/clang/lib/Sema/TreeTransform.h
+++ b/clang/lib/Sema/TreeTransform.h
@@ -836,6 +836,10 @@ class TreeTransform {
LSI->Lambda->getLambdaDependencyKind());
}
+ AssociatedConstraint TransformConstraint(AssociatedConstraint AC) {
+ return AC;
+ }
+
QualType TransformReferenceType(TypeLocBuilder &TLB, ReferenceTypeLoc TL);
StmtResult TransformCompoundStmt(CompoundStmt *S, bool IsStmtExpr);
@@ -16003,12 +16007,8 @@ TreeTransform<Derived>::TransformLambdaExpr(LambdaExpr *E) {
auto FPTL = NewCallOpTSI->getTypeLoc().getAsAdjusted<FunctionProtoTypeLoc>();
assert(FPTL && "Not a FunctionProtoType?");
- AssociatedConstraint TRC = E->getCallOperator()->getTrailingRequiresClause();
- // If the concept refers to any outer parameter packs, we track the SubstIndex
- // for evaluation.
- if (TRC && TRC.ConstraintExpr->containsUnexpandedParameterPack() &&
- !TRC.ArgPackSubstIndex)
- TRC.ArgPackSubstIndex = SemaRef.ArgPackSubstIndex;
+ AssociatedConstraint TRC = getDerived().TransformConstraint(
+ E->getCallOperator()->getTrailingRequiresClause());
getSema().CompleteLambdaCallOperator(
NewCallOperator, E->getCallOperator()->getLocation(),
diff --git a/clang/test/SemaTemplate/concepts-lambda.cpp b/clang/test/SemaTemplate/concepts-lambda.cpp
index ddee39b162c63..ac2a79d2300d2 100644
--- a/clang/test/SemaTemplate/concepts-lambda.cpp
+++ b/clang/test/SemaTemplate/concepts-lambda.cpp
@@ -56,8 +56,6 @@ namespace GH57971 {
function_ptr ptr = f<void>;
}
-// GH58368: A lambda defined in a concept requires we store
-// the concept as a part of the lambda context.
namespace LambdaInConcept {
using size_t = unsigned long;
@@ -367,3 +365,35 @@ void test() {
f<42>();
}
}
+
+namespace GH193944 {
+
+template<auto L, typename... Ts>
+concept pass_a_concept_inside_a_lambda = requires { L.template operator()<Ts...>(); }; // #requires_pass_a_concept_inside_a_lambda
+
+template<auto Pred, typename... Ts>
+concept PredicateFor_bad = pass_a_concept_inside_a_lambda<[]<typename... Xs> // #pass_a_concept_inside_a_lambda
+ requires(__is_same(decltype(Pred.template operator()<Xs>()), bool) and ...)
+ {},
+ Ts...>;
+
+template<auto Pred, typename... Ts>
+ requires PredicateFor_bad<Pred, Ts...> // #PredicateFor_bad
+constexpr const unsigned count_if_v_bad =
+ [] { return (Pred.template operator()<Ts>() + ... + 0); }();
+
+constexpr const auto L = []<typename T>
+{ return __is_same(T, long); };
+
+constexpr const auto L2 = []<typename T>
+{ return 114514; };
+
+static_assert(count_if_v_bad<L, double, int, long, void> == 1);
+
+static_assert(count_if_v_bad<L2, double> == 1);
+// expected-error at -1 {{constraints not satisfied}}
+// expected-note@#PredicateFor_bad {{evaluated to false}}
+// expected-note@#pass_a_concept_inside_a_lambda {{evaluated to false}}
+// expected-note@#requires_pass_a_concept_inside_a_lambda {{no matching member function}}
+
+}
>From d4c905a6fdd1b1b4bcc9b5ba150ecd352f03057e Mon Sep 17 00:00:00 2001
From: Younan Zhang <zyn7109 at gmail.com>
Date: Thu, 7 May 2026 16:22:22 +0800
Subject: [PATCH 2/3] address feedback
---
clang/lib/Sema/SemaConcept.cpp | 6 +++---
clang/test/SemaTemplate/concepts-lambda.cpp | 2 +-
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/clang/lib/Sema/SemaConcept.cpp b/clang/lib/Sema/SemaConcept.cpp
index 8bd3a8f6e1a45..c38adc3c6dcf8 100644
--- a/clang/lib/Sema/SemaConcept.cpp
+++ b/clang/lib/Sema/SemaConcept.cpp
@@ -2269,9 +2269,9 @@ bool SubstituteParameterMappings::substitute(NormalizedConstraint &N) {
}
assert(!ArgsAsWritten);
const ConceptSpecializationExpr *CSE = CC.getConceptSpecializationExpr();
- // This is to make sure that lambdas within template arguments live in a
- // dependent context such that they are assured to be transformed in
- // evaluation.
+ // Make sure that lambdas within template arguments live in a
+ // dependent context such that they are assured to be transformed during
+ // constraint evaluation.
EnterExpressionEvaluationContext EECtx(
SemaRef, Sema::ExpressionEvaluationContext::ConstantEvaluated,
/*LambdaContextDecl=*/
diff --git a/clang/test/SemaTemplate/concepts-lambda.cpp b/clang/test/SemaTemplate/concepts-lambda.cpp
index ac2a79d2300d2..a583589340bd0 100644
--- a/clang/test/SemaTemplate/concepts-lambda.cpp
+++ b/clang/test/SemaTemplate/concepts-lambda.cpp
@@ -56,7 +56,7 @@ namespace GH57971 {
function_ptr ptr = f<void>;
}
-namespace LambdaInConcept {
+namespace GH58368 {
using size_t = unsigned long;
template<size_t...Ts>
>From cc48f156f051a957b7f0d91dff27242ef9a66d7d Mon Sep 17 00:00:00 2001
From: Younan Zhang <zyn7109 at gmail.com>
Date: Sat, 9 May 2026 12:01:54 +0800
Subject: [PATCH 3/3] Address feedback
---
clang/lib/Sema/SemaTemplateInstantiate.cpp | 19 ++++---------------
clang/lib/Sema/TreeTransform.h | 20 +++++++++++++++-----
2 files changed, 19 insertions(+), 20 deletions(-)
diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp b/clang/lib/Sema/SemaTemplateInstantiate.cpp
index d7ba39171b243..a8ae22d54fe8d 100644
--- a/clang/lib/Sema/SemaTemplateInstantiate.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp
@@ -1772,21 +1772,11 @@ namespace {
return inherited::ComputeLambdaDependency(LSI);
}
- AssociatedConstraint TransformConstraint(AssociatedConstraint AC) {
- // If the concept refers to any outer parameter packs, we track the
- // SubstIndex for evaluation.
- if (AC && AC.ConstraintExpr->containsUnexpandedParameterPack() &&
- !AC.ArgPackSubstIndex)
- AC.ArgPackSubstIndex = SemaRef.ArgPackSubstIndex;
-
+ ExprResult TransformConstraint(Expr *AC) {
// We don't want the template argument substitution into parameter
// mappings to preserve the outer depths.
- if (AC && SemaRef.inConstraintSubstitution()) {
- ExprResult E = TransformExpr(const_cast<Expr *>(AC.ConstraintExpr));
- if (E.isInvalid())
- return {};
- AC.ConstraintExpr = E.get();
- }
+ if (AC && SemaRef.inConstraintSubstitution())
+ return TransformExpr(const_cast<Expr *>(AC));
return AC;
}
@@ -1906,8 +1896,7 @@ namespace {
// Otherwise the template argument deduction would fail, if we reduced the
// depth too early.
if (SemaRef.inParameterMappingSubstitution() &&
- getDepthAndIndex(OrigTPL->getParam(0)).first >=
- TemplateArgs.getNumSubstitutedLevels())
+ OrigTPL->getDepth() >= TemplateArgs.getNumSubstitutedLevels())
OldMLTAL = ForgetSubstitution();
DeclContext *Owner = OrigTPL->getParam(0)->getDeclContext();
diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h
index f2de8aa47ab25..d54d832c742fe 100644
--- a/clang/lib/Sema/TreeTransform.h
+++ b/clang/lib/Sema/TreeTransform.h
@@ -836,9 +836,7 @@ class TreeTransform {
LSI->Lambda->getLambdaDependencyKind());
}
- AssociatedConstraint TransformConstraint(AssociatedConstraint AC) {
- return AC;
- }
+ ExprResult TransformConstraint(Expr *AC) { return AC; }
QualType TransformReferenceType(TypeLocBuilder &TLB, ReferenceTypeLoc TL);
@@ -16007,8 +16005,20 @@ TreeTransform<Derived>::TransformLambdaExpr(LambdaExpr *E) {
auto FPTL = NewCallOpTSI->getTypeLoc().getAsAdjusted<FunctionProtoTypeLoc>();
assert(FPTL && "Not a FunctionProtoType?");
- AssociatedConstraint TRC = getDerived().TransformConstraint(
- E->getCallOperator()->getTrailingRequiresClause());
+ AssociatedConstraint TRC = E->getCallOperator()->getTrailingRequiresClause();
+ if (TRC) {
+ ExprResult E = getDerived().TransformConstraint(
+ const_cast<Expr *>(TRC.ConstraintExpr));
+ if (E.isInvalid())
+ return E;
+ TRC.ConstraintExpr = E.get();
+ }
+ // If the concept refers to any outer parameter packs, we track the
+ // SubstIndex for evaluation.
+ // FIXME: This seems unnecessary after transforming lambda constraints.
+ if (TRC && TRC.ConstraintExpr->containsUnexpandedParameterPack() &&
+ !TRC.ArgPackSubstIndex)
+ TRC.ArgPackSubstIndex = SemaRef.ArgPackSubstIndex;
getSema().CompleteLambdaCallOperator(
NewCallOperator, E->getCallOperator()->getLocation(),
More information about the cfe-commits
mailing list