Skip to content

[Clang][Concepts] Fix a constraint comparison regression for out-of-line ClassTemplateDecls #102587

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,8 @@ Bug Fixes to C++ Support
substitutions in concepts, so it doesn't incorrectly complain of missing
module imports in those situations. (#GH60336)
- Fix init-capture packs having a size of one before being instantiated. (#GH63677)
- Clang now properly compares constraints on an out of line class template
declaration definition. (#GH102320)

Bug Fixes to AST Handling
^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down
36 changes: 30 additions & 6 deletions clang/lib/Sema/SemaConcept.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -947,12 +947,35 @@ namespace {
static const Expr *SubstituteConstraintExpressionWithoutSatisfaction(
Sema &S, const Sema::TemplateCompareNewDeclInfo &DeclInfo,
const Expr *ConstrExpr) {
const NamedDecl *ND = DeclInfo.getDecl();
// ND would be absent when we are parsing a template parameter header, and the
// template it pertains to is thus unavaliable. We collect the surrounding
// template arguments for evaluating constraints, starting from the current
// semantic context, in order for out-of-line constrained declarations to be
// properly evaluated.
//
// template <class T>
// class A {
// template <C<T>> class B;
// };
//
// template <class T> template <C<T> U> class A<T>::B {};
//
// (This is the case when comparing `template <C<T> U>` against its primary
// template parameter list `template <C<T>>`.)
//
// Parent specializations are also ignored because fully specialized parents
// of the out-of-line declaration don't contribute to the depth of templates.
//
// template <> template <C<void> U> class A<void>::B {};
// (U rests in the depth of 0.)
//
MultiLevelTemplateArgumentList MLTAL = S.getTemplateInstantiationArgs(
DeclInfo.getDecl(), DeclInfo.getLexicalDeclContext(), /*Final=*/false,
ND, DeclInfo.getDeclContext(), /*Final=*/false,
/*Innermost=*/std::nullopt,
/*RelativeToPrimary=*/true,
/*Pattern=*/nullptr, /*ForConstraintInstantiation=*/true,
/*SkipForSpecialization*/ false);
/*SkipForSpecialization=*/!ND);

if (MLTAL.getNumSubstitutedLevels() == 0)
return ConstrExpr;
Expand All @@ -962,7 +985,7 @@ static const Expr *SubstituteConstraintExpressionWithoutSatisfaction(
Sema::InstantiatingTemplate Inst(
S, DeclInfo.getLocation(),
Sema::InstantiatingTemplate::ConstraintNormalization{},
const_cast<NamedDecl *>(DeclInfo.getDecl()), SourceRange{});
const_cast<NamedDecl *>(ND), SourceRange{});
if (Inst.isInvalid())
return nullptr;

Expand All @@ -971,9 +994,10 @@ static const Expr *SubstituteConstraintExpressionWithoutSatisfaction(
// this may happen while we're comparing two templates' constraint
// equivalence.
LocalInstantiationScope ScopeForParameters(S);
if (auto *FD = DeclInfo.getDecl()->getAsFunction())
for (auto *PVD : FD->parameters())
ScopeForParameters.InstantiatedLocal(PVD, PVD);
if (ND)
if (const FunctionDecl *FD = ND->getAsFunction())
for (auto *PVD : FD->parameters())
ScopeForParameters.InstantiatedLocal(PVD, PVD);

std::optional<Sema::CXXThisScopeRAII> ThisScope;

Expand Down
36 changes: 36 additions & 0 deletions clang/test/SemaTemplate/concepts-out-of-line-def.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -599,3 +599,39 @@ template <class DerT>
unsigned long DerivedCollection<DerTs...>::index() {}

} // namespace GH72557

namespace GH102320 {

template <class, class>
concept Constrained = true;

template <class T> class C {
template <Constrained<T>> class D;
template <class U>
requires Constrained<T, U>
class E;
};

template <class T> template <Constrained<T>> class C<T>::D {};

template <class T>
template <class U>
requires Constrained<T, U>
class C<T>::E {};

#if 0
// FIXME: Is it conforming? Only Clang rejects it in every released version.
template <>
template <Constrained<int> T>
class C<int>::D<T> {};
#endif

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need this regression. As pointed in this comment, we should not transform ConceptSpecializationExpr which makes the depth incorrect. I tested locally and my origin approach also makes sense to this case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I DIDN’T say this is a regression, but rather we’ve been rejecting it in many versions.

The function per se is called SubstituteConstraintExpressionWithoutSatisfaction, so not transforming the constraint appears to go against the intention of it to me.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think if we really need to end up with an approach that by not transforming it, we should do the check/special-casing inside getTemplateInstantiationArgs(), rather than doing that at the call site.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually there seems to be a slight discrepancy among the four compilers here:
https://gcc.godbolt.org/z/nxPoeGE5b
w/o constraints, MSVC and Clang reject such a pattern, while GCC and EDG accept it.
w/ constraints, only Clang complains. So I'm unclear about what is supposed to happen here.
@cor3ntin do you have any ideas?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zyn0217 I need more time to think about the concept case, but the first case looks definitively like a bug. The following looks related #53139

template <>
template <Constrained<int>>
class C<int>::D {};

template <>
template <class T> requires Constrained<int, T>
class C<int>::E {};

} // namespace GH102320
Loading