llvm / llvm/llvm-project

Clang asserts "Template argument kind mismatch" checking a concept whose template template parameter's parameter list names an earlier type parameter

Open
#208,972 4 comments 0 reactions 1 assignee Claimed by @zyn0217 View on GitHub
clang:frontend concepts crash regression:22
Dominant language
LLVM
Stars
40.5k
Forks
18.7k
PR merge metrics
PR metrics pending

Description

**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 First, template 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 concept EnumT = __is_enum(T);
enum class E { A, B };

template class F, template class S>
constexpr bool different = true;
template class V>
constexpr bool different = false;

template class First, template class Second>
concept Equiv = different;

template class Value>
struct Variant {
Variant() = default;
template class Other>
Variant(Variant) requires Equiv {}
};

template struct Val { int x; };
static_assert(__is_constructible(Variant, Variant &));
```

```
$ 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 class First` — used in an atomic constraint, `different`, 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` marks `First` and `Second` as used but **not** `Kind` — `Kind` appears only as the type of `First`/`Second`'s non-type parameter (`template 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(TemplateParams->getParam(I));
if (!TTP)
continue;
llvm::SmallVector Referenced;
for (const NamedDecl *P : *TTP->getTemplateParameters()) {
if (const auto *NTTP = dyn_cast(P))
Referenced.push_back(TemplateArgument(NTTP->getType()));
else if (const auto *TTPD = dyn_cast(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 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.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.