[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.
This commit is contained in:
I 2026-03-11 20:52:31 +09:00 committed by GitHub
parent 1930089f25
commit 3669d2e4dc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 71 additions and 5 deletions

View File

@ -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
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

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

View File

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