[clang] 3669d2e - [Clang] Fix ICE in constraint normalization when substituting concept template parameters (#184406)
via cfe-commits
cfe-commits at lists.llvm.org
Wed Mar 11 04:52:36 PDT 2026
Author: I
Date: 2026-03-11T11:52:31Z
New Revision: 3669d2e4dc2bf21b646a638c6209dea86f94e3d8
URL: https://github.com/llvm/llvm-project/commit/3669d2e4dc2bf21b646a638c6209dea86f94e3d8
DIFF: https://github.com/llvm/llvm-project/commit/3669d2e4dc2bf21b646a638c6209dea86f94e3d8.diff
LOG: [Clang] Fix ICE in constraint normalization when substituting concept template parameters (#184406)
23341c3d139b889e8c46867f8d704ab3c22b51f8 introduced
`SubstituteConceptsInConstraintExpression` to substitute non-dependent
concept template arguments into a concept's constraint expression during
normalization, as part of the P2841R7 implementation
([temp.constr.normal]/1.4).
The `ConstraintExprTransformer` added in that commit overrides
`TransformTemplateArgument` to only transform concept-related arguments
and preserve all others. However, `TransformUnresolvedLookupExpr` called
`Sema::SubstExpr`, which creates a separate `TemplateInstantiator` that
performs full substitution bypassing the selective override entirely.
This caused all template parameters in the constraint expression to be
substituted using the concept's MLTAL. For example, given:
```cpp
template <class A, template <typename...> concept C>
concept maybe_cvref = C<std::remove_cvref_t<A>>;
template <maybe_cvref<std::integral> T, typename U>
auto f0(T&& t, U&& u); // fortunately it passes on Clang 22 due to
// maybe_cvref's first template parameter (A) has
// same kind (type) of f0's first one (T)
template <typename T, maybe_cvref<std::integral> U>
auto f1(T&& t, U&& u); // it causes ICE on Clang 22
```
`C<std::remove_cvref_t<A>>` was fully substituted into
`std::integral<std::remove_cvref_t<U>>`, where `U` refers to `f1`'s
parameter at depth 0, index 1. When `SubstituteParameterMappings` later
applied the concept's MLTAL, it resolved `U`
(`clang::TemplateArgument::Type`) at (0,1) to `std::integral`
(`clang::TemplateArgument::Template`) instead of the intended type
parameter, causing an ICE due to argument kind mismatch.
Fix this by resolving the concept declaration directly from the MLTAL
and routing template arguments through `ConstraintExprTransformer`'s own
`TransformTemplateArguments`, preserving non-concept arguments
correctly.
Added:
Modified:
clang/docs/ReleaseNotes.rst
clang/lib/Sema/SemaTemplateInstantiate.cpp
clang/test/SemaCXX/cxx2c-template-template-param.cpp
Removed:
################################################################################
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 715d153257191..3617786f09595 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -321,6 +321,7 @@ Bug Fixes in This Version
- Fixed an assertion failure caused by error recovery while extending a nested name specifier with results from ordinary lookup. (#GH181470)
- Fixed a crash when parsing ``#pragma clang attribute`` arguments for attributes that forbid arguments. (#GH182122)
- Fixed a bug with multiple-include optimization (MIOpt) state not being preserved in some cases during lexing, which could suppress header-guard mismatch diagnostics and interfere with include-guard optimization. (#GH180155)
+- Fixed a crash when normalizing constraints involving concept template parameters whose index coincided with non-concept template parameters in the same parameter mapping.
Bug Fixes to Compiler Builtins
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp b/clang/lib/Sema/SemaTemplateInstantiate.cpp
index a60d11d8eb36f..34ed5dffa11b4 100644
--- a/clang/lib/Sema/SemaTemplateInstantiate.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp
@@ -4529,11 +4529,40 @@ ExprResult Sema::SubstConceptTemplateArguments(
ExprResult TransformUnresolvedLookupExpr(UnresolvedLookupExpr *E,
bool IsAddressOfOperand = false) {
- if (E->isConceptReference()) {
- ExprResult Res = SemaRef.SubstExpr(E, MLTAL);
- return Res;
- }
- return E;
+ if (!E->isConceptReference())
+ return E;
+
+ assert(E->getNumDecls() == 1 &&
+ "ConceptReference must have single declaration");
+ NamedDecl *D = *E->decls_begin();
+ ConceptDecl *ResolvedConcept = nullptr;
+
+ if (auto *TTP = dyn_cast<TemplateTemplateParmDecl>(D)) {
+ unsigned Depth = TTP->getDepth();
+ unsigned Pos = TTP->getPosition();
+ if (Depth < MLTAL.getNumLevels() &&
+ MLTAL.hasTemplateArgument(Depth, Pos)) {
+ TemplateArgument Arg = MLTAL(Depth, Pos);
+ assert(Arg.getKind() == TemplateArgument::Template);
+ ResolvedConcept =
+ dyn_cast<ConceptDecl>(Arg.getAsTemplate().getAsTemplateDecl());
+ }
+ if (ResolvedConcept == nullptr)
+ return E;
+ } else
+ ResolvedConcept = cast<ConceptDecl>(D);
+
+ TemplateArgumentListInfo TransArgs(E->getLAngleLoc(), E->getRAngleLoc());
+ if (TransformTemplateArguments(E->getTemplateArgs(),
+ E->getNumTemplateArgs(), TransArgs))
+ return ExprError();
+
+ CXXScopeSpec SS;
+ DeclarationNameInfo NameInfo(ResolvedConcept->getDeclName(),
+ E->getNameLoc());
+ return SemaRef.CheckConceptTemplateId(SS, SourceLocation(), NameInfo,
+ ResolvedConcept, ResolvedConcept,
+ &TransArgs, false);
}
};
diff --git a/clang/test/SemaCXX/cxx2c-template-template-param.cpp b/clang/test/SemaCXX/cxx2c-template-template-param.cpp
index 704df3112277f..6214e96905409 100644
--- a/clang/test/SemaCXX/cxx2c-template-template-param.cpp
+++ b/clang/test/SemaCXX/cxx2c-template-template-param.cpp
@@ -432,5 +432,41 @@ template <typename T, template <typename...> concept C1>
concept TestBinary = T::a || C1<T>;
static_assert(TestBinary<int, A>);
+}
+
+namespace concept_template_parameter_as_second_parameter {
+
+template <typename>
+concept Any = true;
+
+template <typename T, template <typename...> concept C>
+concept Wrap = C<T>;
+
+template <typename T, Wrap<Any> U>
+void f(T&&, U&&) {}
+
+void test() {
+ f(0, 1);
+}
+
+}
+
+namespace multi_level_concept_template_parameter {
+
+template <typename>
+concept Any = true;
+
+template <typename T, template <typename...> concept C0, template <typename...> concept C1>
+concept Both = C0<T> && C1<T>;
+
+template <typename T, template <typename...> concept C>
+concept Wrap = Both<T, Any, C>;
+
+template <typename T, Wrap<Any> U>
+void f(T&&, U&&) {}
+
+void test() {
+ f(0, 1);
+}
}
More information about the cfe-commits
mailing list