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

Reply via email to