[clang] Reapply "[Clang] Transform lambda's constraints when instantiating parameter mapping (PR #207966)
Younan Zhang via cfe-commits
cfe-commits at lists.llvm.org
Wed Jul 8 04:50:54 PDT 2026
https://github.com/zyn0217 updated https://github.com/llvm/llvm-project/pull/207966
>From a1b5500a30273e494db7950ed3bf2cef53d92f79 Mon Sep 17 00:00:00 2001
From: Younan Zhang <zyn7109 at gmail.com>
Date: Tue, 7 Jul 2026 14:06:24 +0800
Subject: [PATCH 1/3] Reapply "[Clang] Transform lambda's constraints when
instantiating parameter mapping"
This reverts commit 4ad14a07f5704b950131f361edf05b96a871dfa2.
---
clang/lib/Parse/ParseTemplate.cpp | 6 ---
clang/lib/Sema/SemaConcept.cpp | 19 +++++-----
clang/lib/Sema/SemaTemplate.cpp | 2 +-
clang/lib/Sema/SemaTemplateDeduction.cpp | 14 -------
clang/lib/Sema/SemaTemplateInstantiate.cpp | 37 ++++++++++++++++---
.../lib/Sema/SemaTemplateInstantiateDecl.cpp | 7 ++++
clang/lib/Sema/TreeTransform.h | 14 ++++++-
clang/test/SemaTemplate/concepts-lambda.cpp | 36 ++++++++++++++++--
clang/test/SemaTemplate/concepts.cpp | 3 ++
9 files changed, 97 insertions(+), 41 deletions(-)
diff --git a/clang/lib/Parse/ParseTemplate.cpp b/clang/lib/Parse/ParseTemplate.cpp
index 735a9bd1f9f1c..1529da187614f 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 f41f37b8a3d19..fc52d1c4b83bb 100644
--- a/clang/lib/Sema/SemaConcept.cpp
+++ b/clang/lib/Sema/SemaConcept.cpp
@@ -746,17 +746,8 @@ ExprResult ConstraintSatisfactionChecker::EvaluateSlow(
// i.e they should not have access to the current class object or its
// non-public members.
std::optional<Sema::ContextRAII> ConceptContext;
- if (ParentConcept) {
+ if (ParentConcept)
ConceptContext.emplace(S, ParentConcept->getDeclContext());
- // 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(
@@ -2319,6 +2310,14 @@ bool SubstituteParameterMappings::substitute(NormalizedConstraint &N) {
}
assert(!ArgsAsWritten);
const ConceptSpecializationExpr *CSE = CC.getConceptSpecializationExpr();
+ // 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=*/
+ 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 ad0e653e254dd..e8ea2fbf13436 100644
--- a/clang/lib/Sema/SemaTemplate.cpp
+++ b/clang/lib/Sema/SemaTemplate.cpp
@@ -4921,7 +4921,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 d93b528facbcc..078cd0717bb25 100644
--- a/clang/lib/Sema/SemaTemplateDeduction.cpp
+++ b/clang/lib/Sema/SemaTemplateDeduction.cpp
@@ -5180,20 +5180,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 a77ea5fd3dfff..5d2f6a35e23fa 100644
--- a/clang/lib/Sema/SemaTemplateInstantiate.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp
@@ -1753,9 +1753,24 @@ 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);
}
+ ExprResult TransformConstraint(Expr *AC) {
+ // We don't want the template argument substitution into parameter
+ // mappings to preserve the outer depths.
+ if (AC && SemaRef.inConstraintSubstitution())
+ return TransformExpr(const_cast<Expr *>(AC));
+
+ 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.
@@ -1866,12 +1881,24 @@ 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() &&
+ OrigTPL->getDepth() >= 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 c56203f10ac3c..ae6a312f16a92 100644
--- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
@@ -4848,6 +4848,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 3c8fcbe582b43..477acaaea8576 100644
--- a/clang/lib/Sema/TreeTransform.h
+++ b/clang/lib/Sema/TreeTransform.h
@@ -837,6 +837,8 @@ class TreeTransform {
LSI->Lambda->getLambdaDependencyKind());
}
+ ExprResult TransformConstraint(Expr *AC) { return AC; }
+
QualType TransformReferenceType(TypeLocBuilder &TLB, ReferenceTypeLoc TL);
StmtResult TransformCompoundStmt(CompoundStmt *S, bool IsStmtExpr);
@@ -16095,8 +16097,16 @@ TreeTransform<Derived>::TransformLambdaExpr(LambdaExpr *E) {
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) {
+ 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;
diff --git a/clang/test/SemaTemplate/concepts-lambda.cpp b/clang/test/SemaTemplate/concepts-lambda.cpp
index 5a19105a5bf51..a08cd52843c73 100644
--- a/clang/test/SemaTemplate/concepts-lambda.cpp
+++ b/clang/test/SemaTemplate/concepts-lambda.cpp
@@ -56,9 +56,7 @@ 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 {
+namespace GH58368 {
using size_t = unsigned long;
template<size_t...Ts>
@@ -370,3 +368,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}}
+
+}
diff --git a/clang/test/SemaTemplate/concepts.cpp b/clang/test/SemaTemplate/concepts.cpp
index 6f7f00bf12e61..1d12badda88d8 100644
--- a/clang/test/SemaTemplate/concepts.cpp
+++ b/clang/test/SemaTemplate/concepts.cpp
@@ -1567,9 +1567,12 @@ template<generic_range_value<[]<
>() {}> T>
void x() {}
+// FIXME: Crashes because it produces a template type parameter with invalid depth
+#if 0
void foo() {
x<vector<int>>();
}
+#endif
}
namespace GH162770 {
>From 33b10341b8d0552e5e4625fdb1d68f4056ff458d Mon Sep 17 00:00:00 2001
From: Younan Zhang <zyn7109 at gmail.com>
Date: Tue, 7 Jul 2026 18:36:34 +0800
Subject: [PATCH 2/3] Fix regressions
---
clang/include/clang/Sema/Sema.h | 2 +-
clang/lib/Parse/ParseTemplate.cpp | 6 ++++
clang/lib/Sema/SemaConcept.cpp | 40 ++++++++++++++-------
clang/lib/Sema/SemaTemplateInstantiate.cpp | 37 ++++++++++++-------
clang/lib/Sema/TreeTransform.h | 4 +--
clang/test/SemaTemplate/concepts-lambda.cpp | 37 +++++++++++++++++--
clang/test/SemaTemplate/concepts.cpp | 3 --
7 files changed, 95 insertions(+), 34 deletions(-)
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index f7d0d493e7081..7c0b13335fc2b 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -13567,7 +13567,7 @@ class Sema final : public SemaBase {
bool SubstTemplateArgumentsInParameterMapping(
ArrayRef<TemplateArgumentLoc> Args, SourceLocation BaseLoc,
const MultiLevelTemplateArgumentList &TemplateArgs,
- TemplateArgumentListInfo &Out);
+ bool TransformLambdaConstraint, TemplateArgumentListInfo &Out);
/// Retrieve the template argument list(s) that should be used to
/// instantiate the definition of the given declaration.
diff --git a/clang/lib/Parse/ParseTemplate.cpp b/clang/lib/Parse/ParseTemplate.cpp
index 1529da187614f..735a9bd1f9f1c 100644
--- a/clang/lib/Parse/ParseTemplate.cpp
+++ b/clang/lib/Parse/ParseTemplate.cpp
@@ -533,6 +533,12 @@ 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 fc52d1c4b83bb..d683ac31850a8 100644
--- a/clang/lib/Sema/SemaConcept.cpp
+++ b/clang/lib/Sema/SemaConcept.cpp
@@ -491,6 +491,7 @@ class HashParameterMapping : public RecursiveASTVisitor<HashParameterMapping> {
class ConstraintSatisfactionChecker {
Sema &S;
const NamedDecl *Template;
+ const ConceptReference *TopLevelConceptId;
SourceLocation TemplateNameLoc;
UnsignedOrNone PackSubstitutionIndex;
ConstraintSatisfaction &Satisfaction;
@@ -549,11 +550,13 @@ class ConstraintSatisfactionChecker {
public:
ConstraintSatisfactionChecker(Sema &SemaRef, const NamedDecl *Template,
+ const ConceptReference *TopLevelConceptId,
SourceLocation TemplateNameLoc,
UnsignedOrNone PackSubstitutionIndex,
ConstraintSatisfaction &Satisfaction,
bool BuildExpression)
- : S(SemaRef), Template(Template), TemplateNameLoc(TemplateNameLoc),
+ : S(SemaRef), Template(Template), TopLevelConceptId(TopLevelConceptId),
+ TemplateNameLoc(TemplateNameLoc),
PackSubstitutionIndex(PackSubstitutionIndex),
Satisfaction(Satisfaction), BuildExpression(BuildExpression) {}
@@ -681,9 +684,11 @@ ConstraintSatisfactionChecker::SubstitutionInTemplateArguments(
llvm::SaveAndRestore PushTemplateArgsCache(S.CurrentCachedTemplateArgs,
&CachedTemplateArgs);
+ // We don't want the template argument substitution into parameter
+ // mappings to preserve the outer depths.
if (S.SubstTemplateArgumentsInParameterMapping(
Constraint.getParameterMapping(), Constraint.getBeginLoc(), MLTAL,
- SubstArgs)) {
+ /*TransformLambdaConstraint=*/true, SubstArgs)) {
Satisfaction.IsSatisfied = false;
return std::nullopt;
}
@@ -906,8 +911,9 @@ ExprResult ConstraintSatisfactionChecker::EvaluateSlow(
Satisfaction.IsSatisfied = false;
Satisfaction.ContainsErrors = false;
ExprResult Expr =
- ConstraintSatisfactionChecker(S, Template, TemplateNameLoc,
- UnsignedOrNone(I), Satisfaction,
+ ConstraintSatisfactionChecker(S, Template, TopLevelConceptId,
+ TemplateNameLoc, UnsignedOrNone(I),
+ Satisfaction,
/*BuildExpression=*/false)
.Evaluate(Constraint.getNormalizedPattern(), *SubstitutedArgs);
if (BuildExpression) {
@@ -993,8 +999,13 @@ ExprResult ConstraintSatisfactionChecker::EvaluateSlow(
const_cast<NamedDecl *>(Template), Constraint.getSourceRange());
TemplateArgumentListInfo OutArgs(Ori->LAngleLoc, Ori->RAngleLoc);
- if (S.SubstTemplateArguments(Ori->arguments(), *SubstitutedArgs, OutArgs) ||
- Trap.hasErrorOccurred()) {
+
+ if (ConceptId == TopLevelConceptId) {
+ for (auto &A : Ori->arguments())
+ OutArgs.addArgument(A);
+ } else if (S.SubstTemplateArguments(Ori->arguments(), *SubstitutedArgs,
+ OutArgs) ||
+ Trap.hasErrorOccurred()) {
Satisfaction.IsSatisfied = false;
if (!Trap.hasErrorOccurred())
return ExprError();
@@ -1232,11 +1243,12 @@ static bool CheckConstraintSatisfaction(
Template, /*CSE=*/nullptr,
S.ArgPackSubstIndex);
- ExprResult Res = ConstraintSatisfactionChecker(
- S, Template, TemplateIDRange.getBegin(),
- S.ArgPackSubstIndex, Satisfaction,
- /*BuildExpression=*/ConvertedExpr != nullptr)
- .Evaluate(*C, TemplateArgsLists);
+ ExprResult Res =
+ ConstraintSatisfactionChecker(
+ S, Template, TopLevelConceptId, TemplateIDRange.getBegin(),
+ S.ArgPackSubstIndex, Satisfaction,
+ /*BuildExpression=*/ConvertedExpr != nullptr)
+ .Evaluate(*C, TemplateArgsLists);
if (Res.isInvalid())
return true;
@@ -2192,7 +2204,8 @@ bool SubstituteParameterMappings::substitute(
llvm::SaveAndRestore<decltype(SemaRef.CurrentCachedTemplateArgs)>
DoNotCacheDependentArgs(SemaRef.CurrentCachedTemplateArgs, nullptr);
if (SemaRef.SubstTemplateArgumentsInParameterMapping(
- N.getParameterMapping(), N.getBeginLoc(), *MLTAL, SubstArgs))
+ N.getParameterMapping(), N.getBeginLoc(), *MLTAL,
+ /*TransformLambdaConstraint=*/false, SubstArgs))
return true;
Sema::CheckTemplateArgumentInfo CTAI;
auto *TD =
@@ -2264,7 +2277,8 @@ bool SubstituteParameterMappings::substitute(ConceptIdConstraint &CC) {
const ASTTemplateArgumentListInfo *ArgsAsWritten =
CSE->getTemplateArgsAsWritten();
if (SemaRef.SubstTemplateArgumentsInParameterMapping(
- ArgsAsWritten->arguments(), CC.getBeginLoc(), *MLTAL, Out))
+ ArgsAsWritten->arguments(), CC.getBeginLoc(), *MLTAL,
+ /*TransformLambdaConstraint=*/false, Out))
return true;
Sema::CheckTemplateArgumentInfo CTAI;
if (SemaRef.CheckTemplateArgumentList(CSE->getNamedConcept(),
diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp b/clang/lib/Sema/SemaTemplateInstantiate.cpp
index 5d2f6a35e23fa..70a9f40ddc7fc 100644
--- a/clang/lib/Sema/SemaTemplateInstantiate.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp
@@ -1308,6 +1308,7 @@ namespace {
DeclarationName Entity;
// Whether to evaluate the C++20 constraints or simply substitute into them.
bool EvaluateConstraints = true;
+ bool EvaluateLambdaConstraint = false;
// Whether Substitution was Incomplete, that is, we tried to substitute in
// any user provided template arguments which were null.
bool IsIncomplete = false;
@@ -1346,10 +1347,14 @@ namespace {
inline static struct ForParameterMappingSubstitution_t {
} ForParameterMappingSubstitution;
+ inline static struct ForConstraintSubstitution_t {
+ } ForConstraintSubstitution;
+
TemplateInstantiator(ForParameterMappingSubstitution_t, Sema &SemaRef,
- SourceLocation Loc,
+ SourceLocation Loc, bool TransformLambdaConstraint,
const MultiLevelTemplateArgumentList &TemplateArgs)
: inherited(SemaRef), TemplateArgs(TemplateArgs), Loc(Loc),
+ EvaluateLambdaConstraint(TransformLambdaConstraint),
BailOutOnIncomplete(false) {
if (!SemaRef.CurrentCachedTemplateArgs)
return;
@@ -1359,6 +1364,13 @@ namespace {
Arg.Profile(V, SemaRef.Context);
}
+ TemplateInstantiator(ForConstraintSubstitution_t, Sema &SemaRef,
+ const MultiLevelTemplateArgumentList &TemplateArgs,
+ SourceLocation Loc, DeclarationName Entity,
+ bool BailOutOnIncomplete = false)
+ : inherited(SemaRef), TemplateArgs(TemplateArgs), Loc(Loc),
+ EvaluateLambdaConstraint(true), BailOutOnIncomplete(false) {}
+
/// Determine whether the given type \p T has already been
/// transformed.
///
@@ -1762,10 +1774,8 @@ namespace {
return inherited::ComputeLambdaDependency(LSI);
}
- 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 TransformLambdaConstraint(Expr *AC) {
+ if (AC && EvaluateLambdaConstraint)
return TransformExpr(const_cast<Expr *>(AC));
return AC;
@@ -1885,8 +1895,7 @@ namespace {
// 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() &&
- OrigTPL->getDepth() >= TemplateArgs.getNumSubstitutedLevels())
+ if (SemaRef.inParameterMappingSubstitution())
OldMLTAL = ForgetSubstitution();
DeclContext *Owner = OrigTPL->getParam(0)->getDeclContext();
@@ -4439,10 +4448,10 @@ bool Sema::SubstTemplateArguments(
bool Sema::SubstTemplateArgumentsInParameterMapping(
ArrayRef<TemplateArgumentLoc> Args, SourceLocation BaseLoc,
const MultiLevelTemplateArgumentList &TemplateArgs,
- TemplateArgumentListInfo &Out) {
+ bool TransformLambdaConstraint, TemplateArgumentListInfo &Out) {
TemplateInstantiator Instantiator(
TemplateInstantiator::ForParameterMappingSubstitution, *this, BaseLoc,
- TemplateArgs);
+ TransformLambdaConstraint, TemplateArgs);
return Instantiator.TransformTemplateArguments(Args.begin(), Args.end(), Out);
}
@@ -4471,9 +4480,13 @@ Sema::SubstCXXIdExpr(Expr *E,
ExprResult
Sema::SubstConstraintExpr(Expr *E,
const MultiLevelTemplateArgumentList &TemplateArgs) {
- // FIXME: should call SubstExpr directly if this function is equivalent or
- // should it be different?
- return SubstExpr(E, TemplateArgs);
+ if (!E)
+ return E;
+
+ TemplateInstantiator Instantiator(
+ TemplateInstantiator::ForConstraintSubstitution, *this, TemplateArgs,
+ SourceLocation(), DeclarationName());
+ return Instantiator.TransformExpr(E);
}
ExprResult Sema::SubstConstraintExprWithoutSatisfaction(
diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h
index 477acaaea8576..07f2dcf496f4b 100644
--- a/clang/lib/Sema/TreeTransform.h
+++ b/clang/lib/Sema/TreeTransform.h
@@ -837,7 +837,7 @@ class TreeTransform {
LSI->Lambda->getLambdaDependencyKind());
}
- ExprResult TransformConstraint(Expr *AC) { return AC; }
+ ExprResult TransformLambdaConstraint(Expr *AC) { return AC; }
QualType TransformReferenceType(TypeLocBuilder &TLB, ReferenceTypeLoc TL);
@@ -16098,7 +16098,7 @@ TreeTransform<Derived>::TransformLambdaExpr(LambdaExpr *E) {
AssociatedConstraint TRC = E->getCallOperator()->getTrailingRequiresClause();
if (TRC) {
- ExprResult E = getDerived().TransformConstraint(
+ ExprResult E = getDerived().TransformLambdaConstraint(
const_cast<Expr *>(TRC.ConstraintExpr));
if (E.isInvalid())
return E;
diff --git a/clang/test/SemaTemplate/concepts-lambda.cpp b/clang/test/SemaTemplate/concepts-lambda.cpp
index a08cd52843c73..c6a7855b9a9e6 100644
--- a/clang/test/SemaTemplate/concepts-lambda.cpp
+++ b/clang/test/SemaTemplate/concepts-lambda.cpp
@@ -160,13 +160,10 @@ static_assert(E<int>); // previously Asserted.
namespace DIsFalse {
template<auto Q> concept C = requires { Q.template operator()<float>(); }; // #GH60642-C
template<class> concept D = false;
-// FIXME: Crashes because it produces a template type parameter with invalid depth
-#if 0
static_assert(C<[]<D>{}>);
// expected-error at -1{{static assertion failed}}
// expected-note at -2{{does not satisfy 'C'}}
// expected-note at -5{{because 'Q.template operator()<float>()' would be invalid: no matching member function for call to 'operator()'}}
-#endif
template<class> concept E = C<[]<D>{}>;
static_assert(E<int>);
// expected-error at -1{{static assertion failed}}
@@ -369,6 +366,40 @@ void test() {
}
}
+namespace GH199209 {
+
+template <class T> auto declval() -> T &&;
+struct Id {
+ template <class T> using f = T;
+};
+template <class... Ts> concept Ok = (__is_same(Ts, Ts) && ...);
+template <bool> struct I;
+template <class F, class... Ts>
+using minvoke = I<Ok<Ts...>>::template f<F>::template f<Ts...>;
+template <> struct I<true> {
+ template <template <class...> class F, class... Ts>
+ using g = F<Ts...>;
+ template <class F> using f = F;
+};
+template <template <class...> class F> struct Q {
+ template <class... Ts> using f = I<Ok<Ts...>>::template g<F, Ts...>;
+};
+template <class S> concept has_env = requires { declval<S>().get_env(); };
+struct Desc {
+ template <class F> using f = minvoke<F, int>;
+};
+template <class... C> auto captures(C...) {
+ return []<class Cv>(Cv) -> int requires Ok<minvoke<Cv, C>...> {};
+}
+template <class D> using captures_t = decltype(captures(declval<D>));
+template <class D> struct Sexpr {
+ minvoke<D, Q<captures_t>> impl;
+ auto get_env() -> decltype(impl(Id()));
+};
+static_assert(has_env<Sexpr<Desc>>);
+
+}
+
namespace GH193944 {
template<auto L, typename... Ts>
diff --git a/clang/test/SemaTemplate/concepts.cpp b/clang/test/SemaTemplate/concepts.cpp
index 1d12badda88d8..6f7f00bf12e61 100644
--- a/clang/test/SemaTemplate/concepts.cpp
+++ b/clang/test/SemaTemplate/concepts.cpp
@@ -1567,12 +1567,9 @@ template<generic_range_value<[]<
>() {}> T>
void x() {}
-// FIXME: Crashes because it produces a template type parameter with invalid depth
-#if 0
void foo() {
x<vector<int>>();
}
-#endif
}
namespace GH162770 {
>From 043e2dd4617576d896ccf8682e96f01a0012b22d Mon Sep 17 00:00:00 2001
From: Younan Zhang <zyn7109 at gmail.com>
Date: Wed, 8 Jul 2026 19:50:03 +0800
Subject: [PATCH 3/3] Really fix the lambda depths issue
---
clang/lib/Sema/SemaConcept.cpp | 12 +++++++++---
clang/lib/Sema/SemaLambda.cpp | 3 ++-
clang/test/SemaTemplate/concepts.cpp | 11 ++++++++++-
3 files changed, 21 insertions(+), 5 deletions(-)
diff --git a/clang/lib/Sema/SemaConcept.cpp b/clang/lib/Sema/SemaConcept.cpp
index d683ac31850a8..8e94945c48e71 100644
--- a/clang/lib/Sema/SemaConcept.cpp
+++ b/clang/lib/Sema/SemaConcept.cpp
@@ -735,9 +735,15 @@ ExprResult ConstraintSatisfactionChecker::EvaluateSlow(
const AtomicConstraint &Constraint,
const MultiLevelTemplateArgumentList &MLTAL) {
std::optional<EnterExpressionEvaluationContext> EvaluationContext;
- EvaluationContext.emplace(
- S, Sema::ExpressionEvaluationContext::ConstantEvaluated,
- Sema::ReuseLambdaContextDecl);
+ // The ConceptDecl as a ContextDecl ensures that, when evaluating constraints
+ // on transformed lambdas, we don't have extra outer template arguments.
+ if (ParentConcept)
+ EvaluationContext.emplace(
+ S, Sema::ExpressionEvaluationContext::ConstantEvaluated, ParentConcept);
+ else
+ EvaluationContext.emplace(
+ S, Sema::ExpressionEvaluationContext::ConstantEvaluated,
+ Sema::ReuseLambdaContextDecl);
llvm::SmallVector<TemplateArgument> SubstitutedOutermost;
std::optional<MultiLevelTemplateArgumentList> SubstitutedArgs =
diff --git a/clang/lib/Sema/SemaLambda.cpp b/clang/lib/Sema/SemaLambda.cpp
index 8572e3a742a6c..463163a31bca6 100644
--- a/clang/lib/Sema/SemaLambda.cpp
+++ b/clang/lib/Sema/SemaLambda.cpp
@@ -327,7 +327,8 @@ Sema::getCurrentMangleNumberContext(const DeclContext *DC) {
}
} else if (isa<FieldDecl>(ManglingContextDecl)) {
return DataMember;
- } else if (isa<ImplicitConceptSpecializationDecl>(ManglingContextDecl)) {
+ } else if (isa<ImplicitConceptSpecializationDecl, ConceptDecl>(
+ ManglingContextDecl)) {
return Concept;
}
diff --git a/clang/test/SemaTemplate/concepts.cpp b/clang/test/SemaTemplate/concepts.cpp
index 6f7f00bf12e61..7cf21910bdf57 100644
--- a/clang/test/SemaTemplate/concepts.cpp
+++ b/clang/test/SemaTemplate/concepts.cpp
@@ -1556,9 +1556,12 @@ struct vector;
template <typename T, typename U>
concept C = __is_same_as(T, U);
+template <typename T, typename U>
+concept D = false && __is_same_as(T, U);
+
template<class T, auto Cpt>
concept generic_range_value = requires {
- Cpt.template operator()<int>();
+ Cpt.template operator()<int>(); // expected-note {{would be invalid}}
};
@@ -1567,8 +1570,14 @@ template<generic_range_value<[]<
>() {}> T>
void x() {}
+template<generic_range_value<[]< // expected-note {{evaluated to false}}
+ D<int>
+ >() {}> T>
+void y() {} // expected-note {{ignored}}
+
void foo() {
x<vector<int>>();
+ y<vector<int>>(); // expected-error {{no matching function}}
}
}
More information about the cfe-commits
mailing list