[clang] [Clang] Partially reapply "Refactor instantiation of declarations within concepts" (PR #223311)
Younan Zhang via cfe-commits
cfe-commits at lists.llvm.org
Sun Sep 13 23:15:09 PDT 2026
https://github.com/zyn0217 created https://github.com/llvm/llvm-project/pull/223311
This partially reverts db65ace07
It turns out that we cannot simply reuse the instantiated declarations on stack, and our LSI is lack of ability of distinguishing different template arguments.
This PR relands the refactoring part of the previous attempt, still making it possible to have instantiated parameters before checking C++26 fold expression constraints, plus a regression test.
Fixes #198052
>From 17376bf90f1a0e8dd4345fc13b6132bc46489806 Mon Sep 17 00:00:00 2001
From: Younan Zhang <zyn7109 at gmail.com>
Date: Mon, 14 Sep 2026 14:08:26 +0800
Subject: [PATCH] [Clang] Partially reapply "Refactor instantiation of
declarations within concepts"
This partially reverts db65ace07
It turns out that we cannot simply reuse the instantiated declarations on stack,
and our LSI is lack of ability of distinguishing different template
arguments.
This PR relands the refactoring part of the previous attempt, still making
it possible to have instantiated parameters before checking C++26 fold expression
constraints, plus a regression test.
---
clang/docs/ReleaseNotes.md | 3 +
clang/include/clang/Sema/Sema.h | 9 +-
clang/lib/Sema/SemaConcept.cpp | 52 ++++--------
clang/lib/Sema/SemaTemplateInstantiate.cpp | 95 ++++++++++++++--------
clang/test/SemaCXX/cxx2c-fold-exprs.cpp | 26 ++++++
clang/test/SemaTemplate/concepts.cpp | 14 ++++
6 files changed, 126 insertions(+), 73 deletions(-)
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index 043a0ddae2a6c..e5de8fce6f20d 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -589,6 +589,9 @@ features cannot lower the translation-unit ABI level;
- Fixed a crash when module directive export module foo not following a
semicolon and there are no rest pp-tokens in current module file. (#GH187771)
+- Fixed concept evaluation bugs where some declarations were not added to
+ the current instantiation scope. (#GH198052)
+
- Fixed a crash when a lambda parameter pack was given a default argument that
is a pack expansion referencing an enclosing function's parameter pack (e.g.
`[](Types... = args...) {}`). Clang now diagnoses the illegal default
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 4ff4c669a6b70..7440a1806c7df 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -15129,11 +15129,17 @@ class Sema final : public SemaBase {
const NamedDecl *D1, ArrayRef<AssociatedConstraint> AC1,
const NamedDecl *D2, ArrayRef<AssociatedConstraint> AC2);
+private:
+ friend class ConstraintSatisfactionChecker;
+ friend class SubstituteParameterMappings;
+
+ UnsignedOrNone EvaluateFoldExpandedConstraintSize(
+ const Expr *Pattern, const MultiLevelTemplateArgumentList &MLTAL);
+
/// Cache the satisfaction of an atomic constraint.
/// The key is based on the unsubstituted expression and the parameter
/// mapping. This lets us not substituting the mapping more than once,
/// which is (very!) expensive.
- /// FIXME: this should be private.
llvm::DenseMap<llvm::FoldingSetNodeID,
UnsubstitutedConstraintSatisfactionCacheResult>
UnsubstitutedConstraintSatisfactionCache;
@@ -15145,7 +15151,6 @@ class Sema final : public SemaBase {
llvm::DenseMap<llvm::FoldingSetNodeID, TemplateArgumentLoc>
*CurrentCachedTemplateArgs = nullptr;
-private:
/// Caches pairs of template-like decls whose associated constraints were
/// checked for subsumption and whether or not the first's constraints did in
/// fact subsume the second's.
diff --git a/clang/lib/Sema/SemaConcept.cpp b/clang/lib/Sema/SemaConcept.cpp
index ee17f826dc32c..fb66bdd6d2bd0 100644
--- a/clang/lib/Sema/SemaConcept.cpp
+++ b/clang/lib/Sema/SemaConcept.cpp
@@ -549,7 +549,9 @@ class HashParameterMapping : public RecursiveASTVisitor<HashParameterMapping> {
}
}
};
+} // namespace
+namespace clang {
class ConstraintSatisfactionChecker {
Sema &S;
const NamedDecl *Template;
@@ -652,10 +654,6 @@ class ConstraintSatisfactionChecker {
EvaluateAtomicConstraint(const Expr *AtomicExpr,
const MultiLevelTemplateArgumentList &MLTAL);
- UnsignedOrNone EvaluateFoldExpandedConstraintSize(
- const FoldExpandedConstraint &FE,
- const MultiLevelTemplateArgumentList &MLTAL);
-
// XXX: It is SLOW! Use it very carefully.
std::optional<MultiLevelTemplateArgumentList> SubstitutionInTemplateArguments(
const NormalizedConstraintWithParamMapping &Constraint,
@@ -700,7 +698,7 @@ class ConstraintSatisfactionChecker {
const MultiLevelTemplateArgumentList &MLTAL);
};
-} // namespace
+} // namespace clang
ExprResult ConstraintSatisfactionChecker::EvaluateAtomicConstraint(
const Expr *AtomicExpr, const MultiLevelTemplateArgumentList &MLTAL) {
@@ -948,31 +946,6 @@ ExprResult ConstraintSatisfactionChecker::Evaluate(
return PMCache.cache(EvaluateSlow(Constraint, MLTAL));
}
-UnsignedOrNone
-ConstraintSatisfactionChecker::EvaluateFoldExpandedConstraintSize(
- const FoldExpandedConstraint &FE,
- const MultiLevelTemplateArgumentList &MLTAL) {
-
- Expr *Pattern = const_cast<Expr *>(FE.getPattern());
-
- SmallVector<UnexpandedParameterPack, 2> Unexpanded;
- S.collectUnexpandedParameterPacks(Pattern, Unexpanded);
- assert(!Unexpanded.empty() && "Pack expansion without parameter packs?");
- bool Expand = true;
- bool RetainExpansion = false;
- UnsignedOrNone NumExpansions(std::nullopt);
- if (S.CheckParameterPacksForExpansion(
- Pattern->getExprLoc(), Pattern->getSourceRange(), Unexpanded, MLTAL,
- /*FailOnPackProducingTemplates=*/false, Expand, RetainExpansion,
- NumExpansions, /*Diagnose=*/false) ||
- !Expand || RetainExpansion)
- return std::nullopt;
-
- if (NumExpansions && S.getLangOpts().BracketDepth < *NumExpansions)
- return std::nullopt;
- return NumExpansions;
-}
-
ExprResult ConstraintSatisfactionChecker::EvaluateSlow(
const FoldExpandedConstraint &Constraint,
const MultiLevelTemplateArgumentList &MLTAL) {
@@ -993,9 +966,15 @@ ExprResult ConstraintSatisfactionChecker::EvaluateSlow(
return ExprError();
}
- ExprResult Out;
- UnsignedOrNone NumExpansions =
- EvaluateFoldExpandedConstraintSize(Constraint, *SubstitutedArgs);
+ UnsignedOrNone NumExpansions(std::nullopt);
+ {
+ Sema::InstantiatingTemplate InstTemplate(
+ S, TemplateNameLoc,
+ Sema::InstantiatingTemplate::ConstraintSubstitution{},
+ const_cast<NamedDecl *>(Template), Constraint.getSourceRange());
+ NumExpansions = S.EvaluateFoldExpandedConstraintSize(
+ Constraint.getPattern(), *SubstitutedArgs);
+ }
if (!NumExpansions)
return ExprEmpty();
@@ -1004,6 +983,7 @@ ExprResult ConstraintSatisfactionChecker::EvaluateSlow(
return ExprEmpty();
}
+ ExprResult Out;
for (unsigned I = 0; I < *NumExpansions; I++) {
Sema::ArgPackSubstIndexRAII SubstIndex(S, I);
Satisfaction.IsSatisfied = false;
@@ -2087,7 +2067,7 @@ void Sema::DiagnoseUnsatisfiedConstraint(
ConstraintExpr->getBeginLoc(), First);
}
-namespace {
+namespace clang {
class SubstituteParameterMappings {
Sema &SemaRef;
@@ -2125,6 +2105,8 @@ class SubstituteParameterMappings {
bool substitute(NormalizedConstraint &N);
};
+} // namespace clang
+
void SubstituteParameterMappings::buildParameterMapping(
NormalizedConstraintWithParamMapping &N) {
TemplateParameterList *TemplateParams =
@@ -2418,8 +2400,6 @@ bool SubstituteParameterMappings::substitute(NormalizedConstraint &N) {
llvm_unreachable("Unknown ConstraintKind enum");
}
-} // namespace
-
NormalizedConstraint *NormalizedConstraint::fromAssociatedConstraints(
Sema &S, const NamedDecl *D, ArrayRef<AssociatedConstraint> ACs) {
assert(ACs.size() != 0);
diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp b/clang/lib/Sema/SemaTemplateInstantiate.cpp
index ffbe8bb0506bc..20b16c3e49bea 100644
--- a/clang/lib/Sema/SemaTemplateInstantiate.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp
@@ -1324,11 +1324,10 @@ namespace {
bool BailOutOnIncomplete;
std::optional<llvm::FoldingSetNodeID> TemplateArgsHashValue;
+ llvm::DenseMap<llvm::FoldingSetNodeID, TemplateArgumentLoc>
+ *CurrentCachedTemplateArgs = nullptr;
- // CWG2770: Function parameters should be instantiated when they are
- // needed by a satisfaction check of an atomic constraint or
- // (recursively) by another function parameter.
- bool maybeInstantiateFunctionParameterToScope(ParmVarDecl *OldParm);
+ bool instantiateMissingDeclsToScopeForConcepts(Decl *D);
public:
typedef TreeTransform<TemplateInstantiator> inherited;
@@ -1358,12 +1357,14 @@ namespace {
inline static struct ForConstraintSubstitution_t {
} ForConstraintSubstitution;
- TemplateInstantiator(ForParameterMappingSubstitution_t, Sema &SemaRef,
- SourceLocation Loc,
- const MultiLevelTemplateArgumentList &TemplateArgs)
+ TemplateInstantiator(
+ ForParameterMappingSubstitution_t, Sema &SemaRef, SourceLocation Loc,
+ const MultiLevelTemplateArgumentList &TemplateArgs,
+ llvm::DenseMap<llvm::FoldingSetNodeID, TemplateArgumentLoc> *Cache)
: inherited(SemaRef), TemplateArgs(TemplateArgs), Loc(Loc),
- EvaluateLambdaConstraint(true), BailOutOnIncomplete(false) {
- if (!SemaRef.CurrentCachedTemplateArgs)
+ EvaluateLambdaConstraint(true), BailOutOnIncomplete(false),
+ CurrentCachedTemplateArgs(Cache) {
+ if (!Cache)
return;
auto &V = TemplateArgsHashValue.emplace();
for (auto &Level : TemplateArgs)
@@ -1410,22 +1411,18 @@ namespace {
ArrayRef<UnexpandedParameterPack> Unexpanded,
bool FailOnPackProducingTemplates,
bool &ShouldExpand, bool &RetainExpansion,
- UnsignedOrNone &NumExpansions) {
- if (SemaRef.CurrentInstantiationScope &&
- (SemaRef.inConstraintSubstitution() ||
- SemaRef.inParameterMappingSubstitution())) {
- for (UnexpandedParameterPack ParmPack : Unexpanded) {
- NamedDecl *VD = ParmPack.first.dyn_cast<NamedDecl *>();
- if (auto *PVD = dyn_cast_if_present<ParmVarDecl>(VD);
- PVD && maybeInstantiateFunctionParameterToScope(PVD))
- return true;
- }
+ UnsignedOrNone &NumExpansions,
+ bool Diagnose = true) {
+ for (UnexpandedParameterPack ParmPack : Unexpanded) {
+ if (instantiateMissingDeclsToScopeForConcepts(
+ dyn_cast<NamedDecl *>(ParmPack.first)))
+ return true;
}
return getSema().CheckParameterPacksForExpansion(
EllipsisLoc, PatternRange, Unexpanded, TemplateArgs,
FailOnPackProducingTemplates, ShouldExpand, RetainExpansion,
- NumExpansions);
+ NumExpansions, Diagnose);
}
void ExpandingFunctionParameterPack(ParmVarDecl *Pack) {
@@ -1637,7 +1634,7 @@ namespace {
TemplateArgumentLoc &Output,
bool Uneval = false) {
const TemplateArgument &Arg = Input.getArgument();
- if (auto *Cache = SemaRef.CurrentCachedTemplateArgs;
+ if (auto *Cache = CurrentCachedTemplateArgs;
Cache && TemplateArgsHashValue) {
llvm::FoldingSetNodeID ID = *TemplateArgsHashValue;
ID.AddInteger(SemaRef.ArgPackSubstIndex.toInternalRepresentation());
@@ -1980,11 +1977,7 @@ Decl *TemplateInstantiator::TransformDecl(SourceLocation Loc, Decl *D) {
// template parameter.
}
- if (ParmVarDecl *PVD = dyn_cast<ParmVarDecl>(D);
- PVD && SemaRef.CurrentInstantiationScope &&
- (SemaRef.inConstraintSubstitution() ||
- SemaRef.inParameterMappingSubstitution()) &&
- maybeInstantiateFunctionParameterToScope(PVD))
+ if (instantiateMissingDeclsToScopeForConcepts(D))
return nullptr;
if (isa<CXXExpansionStmtDecl>(D)) {
@@ -1996,9 +1989,20 @@ Decl *TemplateInstantiator::TransformDecl(SourceLocation Loc, Decl *D) {
return SemaRef.FindInstantiatedDecl(Loc, cast<NamedDecl>(D), TemplateArgs);
}
-bool TemplateInstantiator::maybeInstantiateFunctionParameterToScope(
- ParmVarDecl *OldParm) {
- if (SemaRef.CurrentInstantiationScope->getInstantiationOfIfExists(OldParm))
+bool TemplateInstantiator::instantiateMissingDeclsToScopeForConcepts(Decl *D) {
+ if (!(D && (SemaRef.inConstraintSubstitution() ||
+ SemaRef.inParameterMappingSubstitution())))
+ return false;
+
+ auto *Current = SemaRef.CurrentInstantiationScope;
+ if (!Current || Current->getInstantiationOfIfExists(D))
+ return false;
+
+ // CWG2770: Function parameters should be instantiated when they are
+ // needed by a satisfaction check of an atomic constraint or
+ // (recursively) by another function parameter.
+ auto *OldParm = dyn_cast<ParmVarDecl>(D);
+ if (!OldParm)
return false;
if (!OldParm->isParameterPack())
@@ -2459,11 +2463,7 @@ TemplateInstantiator::TransformDeclRefExpr(DeclRefExpr *E) {
// Handle references to function parameter packs.
if (VarDecl *PD = dyn_cast<VarDecl>(D))
if (PD->isParameterPack()) {
- if (ParmVarDecl *PVD = dyn_cast<ParmVarDecl>(PD);
- PVD && SemaRef.CurrentInstantiationScope &&
- (SemaRef.inConstraintSubstitution() ||
- SemaRef.inParameterMappingSubstitution()) &&
- maybeInstantiateFunctionParameterToScope(PVD))
+ if (instantiateMissingDeclsToScopeForConcepts(PD))
return ExprError();
return TransformFunctionParmPackRefExpr(E, PD);
@@ -4486,10 +4486,35 @@ bool Sema::SubstTemplateArgumentsInParameterMapping(
TemplateArgumentListInfo &Out) {
TemplateInstantiator Instantiator(
TemplateInstantiator::ForParameterMappingSubstitution, *this, BaseLoc,
- TemplateArgs);
+ TemplateArgs, CurrentCachedTemplateArgs);
return Instantiator.TransformTemplateArguments(Args.begin(), Args.end(), Out);
}
+UnsignedOrNone Sema::EvaluateFoldExpandedConstraintSize(
+ const Expr *Pattern, const MultiLevelTemplateArgumentList &TemplateArgs) {
+ TemplateInstantiator Instantiator(
+ TemplateInstantiator::ForConstraintSubstitution, *this, TemplateArgs,
+ SourceLocation(), DeclarationName());
+
+ SmallVector<UnexpandedParameterPack, 2> Unexpanded;
+ collectUnexpandedParameterPacks(const_cast<Expr *>(Pattern), Unexpanded);
+ assert(!Unexpanded.empty() && "Pack expansion without parameter packs?");
+
+ bool Expand = true;
+ bool RetainExpansion = false;
+ UnsignedOrNone NumExpansions(std::nullopt);
+ if (Instantiator.TryExpandParameterPacks(
+ Pattern->getExprLoc(), Pattern->getSourceRange(), Unexpanded,
+ /*FailOnPackProducingTemplates=*/false, Expand, RetainExpansion,
+ NumExpansions, /*Diagnose=*/false) ||
+ !Expand || RetainExpansion)
+ return std::nullopt;
+
+ if (NumExpansions && getLangOpts().BracketDepth < *NumExpansions)
+ return std::nullopt;
+ return NumExpansions;
+}
+
ExprResult
Sema::SubstExpr(Expr *E, const MultiLevelTemplateArgumentList &TemplateArgs) {
if (!E)
diff --git a/clang/test/SemaCXX/cxx2c-fold-exprs.cpp b/clang/test/SemaCXX/cxx2c-fold-exprs.cpp
index d3e681f22ab26..b4f763f7fdb7d 100644
--- a/clang/test/SemaCXX/cxx2c-fold-exprs.cpp
+++ b/clang/test/SemaCXX/cxx2c-fold-exprs.cpp
@@ -692,3 +692,29 @@ void g() {
}
}
+
+namespace GH198052 {
+
+template <class T, class U>
+concept is_same = __is_same(T, U);
+
+constexpr int NumberOfTrueInstances(auto... booleans)
+ requires (is_same<bool, decltype(booleans)> && ...)
+{
+ bool the_booleans[] = {booleans...};
+ int nrvo = 0;
+ for (bool a_boolean : the_booleans) {
+ if (a_boolean) nrvo += 1;
+ }
+ return nrvo;
+}
+
+constexpr bool a = true;
+constexpr bool b = false;
+constexpr bool c = true;
+constexpr bool d = false;
+constexpr int count = NumberOfTrueInstances(a, b, c, d);
+static_assert(count == 2);
+
+}
+
diff --git a/clang/test/SemaTemplate/concepts.cpp b/clang/test/SemaTemplate/concepts.cpp
index a4e737f97de7e..58755e44ba8d1 100644
--- a/clang/test/SemaTemplate/concepts.cpp
+++ b/clang/test/SemaTemplate/concepts.cpp
@@ -2115,3 +2115,17 @@ void baz() {
auto qux = bar<S>(true, [] {}); // expected-error {{no matching function for call to 'bar'}}
}
}
+
+namespace GH223220 {
+
+template <class> struct pair {
+ template <class _Tp>
+ auto operator()(_Tp __t) requires requires { __t < __t; } {return 0;}
+};
+
+pair<int> P;
+template <class _U1>
+decltype(P(_U1())) operator<=>(pair<_U1>, pair<_U1>);
+decltype(P(pair<int>())) g;
+
+}
More information about the cfe-commits
mailing list