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);
+}
 
 }


        
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to