| Issue |
208972
|
| Summary |
Clang asserts "Template argument kind mismatch" checking a concept whose template template parameter's parameter list names an earlier type parameter
|
| Labels |
|
| Assignees |
|
| Reporter |
combinatorial
|
**Component:** clang (Sema / concepts)
**Version:** clang 22.x and trunk (reproduced on `22.1.8` / release-22.x commit `ca7933e`, and on `main`).
**Std:** `-std=c++20` (and later). Not modules- or STL-dependent.
## Summary
When a concept has a template template parameter whose own parameter list names an *earlier template parameter* of that concept — e.g.
```c++
template <class Kind, template <Kind> class First, template <Kind> class Second>
concept C = /* an atomic constraint that uses First/Second but not Kind directly */;
```
checking that concept can crash while substituting its parameter mapping:
```
clang/lib/Sema/SemaTemplateInstantiate.cpp:2453:
(anonymous namespace)::TemplateInstantiator::TransformTemplateTypeParmType(...):
Assertion `Arg.getKind() == TemplateArgument::Type && "Template argument kind mismatch"' failed.
```
In release builds (assertions off) this is a crash / silent misbehavior instead.
## Minimal reproducer
```c++
template <class T> concept EnumT = __is_enum(T);
enum class E { A, B };
template <template <auto> class F, template <auto> class S>
constexpr bool different = true;
template <template <auto> class V>
constexpr bool different<V, V> = false;
template <class Kind, template <Kind> class First, template <Kind> class Second>
concept Equiv = different<First, Second>;
template <EnumT Kind, template <Kind> class Value>
struct Variant {
Variant() = default;
template <template <Kind> class Other>
Variant(Variant<Kind, Other>) requires Equiv<Kind, Value, Other> {}
};
template <E> struct Val { int x; };
static_assert(__is_constructible(Variant<E, Val>, Variant<E, Val> &));
```
```
$ clang++ -std=c++20 -c repro.cpp
clang++: SemaTemplateInstantiate.cpp:2453: ... Assertion `Arg.getKind() == TemplateArgument::Type
&& "Template argument kind mismatch"' failed.
```
(The concept only needs a template template parameter whose non-type parameter's *type* is an earlier type parameter — `template <Kind> class First` — used in an atomic constraint, `different<First, Second>`, that does not mention `Kind` directly.)
## Backtrace (from the reproducer)
```
TransformTemplateTypeParmType SemaTemplateInstantiate.cpp:2432 <-- assert
TemplateDeclInstantiator::VisitNonTypeTemplateParmDecl SemaTemplateInstantiateDecl.cpp:3699
TemplateDeclInstantiator::SubstTemplateParams SemaTemplateInstantiateDecl.cpp:4830
Sema::SubstTemplateParams SemaTemplateInstantiateDecl.cpp:4854
Sema::CheckTemplateArgument SemaTemplate.cpp:5720
Sema::CheckTemplateArgumentList SemaTemplate.cpp:5974
SubstituteParameterMappings::substitute(NormalizedConstraintWithParamMapping&) SemaConcept.cpp:2154
SubstituteParameterMappings::substitute(NormalizedConstraint&) SemaConcept.cpp:2298
Sema::getNormalizedAssociatedConstraints SemaConcept.cpp:2466
CheckConstraintSatisfaction / Sema::CheckFunctionTemplateConstraints
Sema::FinishTemplateArgumentDeduction / overload resolution
```
## Root cause
`SubstituteParameterMappings::buildParameterMapping` (clang/lib/Sema/SemaConcept.cpp) computes the set of template parameters that occur in an atomic constraint (via `MarkUsedTemplateParameters` on the constraint _expression_), builds a **used-parameter list** containing only those, and renumbers the mapping accordingly.
`MarkUsedTemplateParameters` does not descend into a template template parameter's *own* parameter list. So in the reproducer, the atomic `different<First, Second>` marks `First` and `Second` as used but **not** `Kind` — `Kind` appears only as the type of `First`/`Second`'s non-type parameter (`template <Kind> class`). `Kind` is therefore dropped, and the used list `[First, Second]` is renumbered `First -> 0`, `Second -> 1`.
But `Second`'s parameter list still names `Kind` at its original index (0). When the mapping is checked (`CheckTemplateArgumentList` -> `SubstTemplateParams` -> `VisitNonTypeTemplateParmDecl` -> `SubstType(Kind)` -> `TransformTemplateTypeParmType`), `Kind` (depth 0, index 0) resolves to the renumbered index 0 = `First`'s argument, which is a *template* argument, not a type — tripping the assertion.
## Introduced by
`e9972debc98c` — "[Clang] Normalize constraints before checking for satisfaction" (#161671), the reland of #141776 (`9583b399d85c`, reverted by `047f8c8`). That change added `buildParameterMapping`; the used-parameter-list logic was buggy as introduced. Present in both release/22.x and trunk.
## Proposed fix
In `buildParameterMapping`, after computing the occurring parameters, also mark the parameters referenced by any occurring template template parameter's own signature (the types of its non-type parameters and the defaults of its type parameters; iterate to a fixed point to handle nesting), so the used-parameter list keeps a consistent index space:
```c++
auto MarkParamsReferencedByTemplateTemplateParms =
[&](llvm::SmallBitVector &Indices) {
bool Changed = true;
while (Changed) {
Changed = false;
for (unsigned I = 0, E = TemplateParams->size(); I != E; ++I) {
if (!Indices[I])
continue;
const auto *TTP =
dyn_cast<TemplateTemplateParmDecl>(TemplateParams->getParam(I));
if (!TTP)
continue;
llvm::SmallVector<TemplateArgument> Referenced;
for (const NamedDecl *P : *TTP->getTemplateParameters()) {
if (const auto *NTTP = dyn_cast<NonTypeTemplateParmDecl>(P))
Referenced.push_back(TemplateArgument(NTTP->getType()));
else if (const auto *TTPD = dyn_cast<TemplateTypeParmDecl>(P);
TTPD && TTPD->hasDefaultArgument())
Referenced.push_back(TTPD->getDefaultArgument().getArgument());
}
llvm::SmallBitVector Before = Indices;
SemaRef.MarkUsedTemplateParameters(Referenced, /*Depth=*/0, Indices);
if (Indices != Before)
Changed = true;
}
}
};
MarkParamsReferencedByTemplateTemplateParms(OccurringIndices);
MarkParamsReferencedByTemplateTemplateParms(OccurringIndicesForSubsumption);
```
With this, the reproducer compiles, and the constraint is evaluated correctly (verified: for two `template <Kind> class` arguments giving distinct types per `Kind` the concept is `false`; for the same template it is `false`; for a template and an equivalent alias it is `true`). Full `check-clang` passes with no new failures.
_______________________________________________
llvm-bugs mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-bugs