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 <[email protected]> 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@-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 <[email protected]> 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 65f371d1ded8a4b4b978dea12ddd8272769d954d Mon Sep 17 00:00:00 2001 From: Younan Zhang <[email protected]> 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 | 22 ++++++++++++++++------ 2 files changed, 20 insertions(+), 21 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..e57af7dd97f98 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,12 +16005,24 @@ TreeTransform<Derived>::TransformLambdaExpr(LambdaExpr *E) { auto FPTL = NewCallOpTSI->getTypeLoc().getAsAdjusted<FunctionProtoTypeLoc>(); assert(FPTL && "Not a FunctionProtoType?"); - AssociatedConstraint TRC = getDerived().TransformConstraint( - E->getCallOperator()->getTrailingRequiresClause()); + AssociatedConstraint AC = E->getCallOperator()->getTrailingRequiresClause(); + if (AC) { + ExprResult E = + getDerived().TransformConstraint(const_cast<Expr *>(AC.ConstraintExpr)); + if (E.isInvalid()) + return E; + AC.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 (AC && AC.ConstraintExpr->containsUnexpandedParameterPack() && + !AC.ArgPackSubstIndex) + AC.ArgPackSubstIndex = SemaRef.ArgPackSubstIndex; getSema().CompleteLambdaCallOperator( NewCallOperator, E->getCallOperator()->getLocation(), - E->getCallOperator()->getInnerLocStart(), TRC, NewCallOpTSI, + E->getCallOperator()->getInnerLocStart(), AC, NewCallOpTSI, E->getCallOperator()->getConstexprKind(), E->getCallOperator()->getStorageClass(), FPTL.getParams(), E->hasExplicitResultType()); _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
