Skip to content

Commit 98191d7

Browse files
authored
[CONCEPTS]Corrected comparison of constraints with out of line CTD (#69244)
Out of line class template declaration specializations aren't created at the time they have their template arguments checked, so we previously weren't doing any amount of work to substitute the constraints before comparison. This resulted in the out of line definition's difference in 'depth' causing the constraints to compare differently. This patch corrects that. Additionally, it handles ClassTemplateDecl when collecting template arguments. Fixes: #61763
1 parent 814a79a commit 98191d7

File tree

9 files changed

+158
-54
lines changed

9 files changed

+158
-54
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,10 @@ Bug Fixes to C++ Support
520520
with non-type template parameters of reference type. Fixes:
521521
(`#65153 <https://github.com/llvm/llvm-project/issues/65153>`_)
522522

523+
- Clang now properly compares constraints on an out of line class template
524+
declaration definition. Fixes:
525+
(`#61763 <https://github.com/llvm/llvm-project/issues/61763>`_)
526+
523527
Bug Fixes to AST Handling
524528
^^^^^^^^^^^^^^^^^^^^^^^^^
525529
- Fixed an import failure of recursive friend class template.

clang/include/clang/Sema/Sema.h

Lines changed: 58 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3809,17 +3809,6 @@ class Sema final {
38093809
// the purposes of [temp.friend] p9.
38103810
bool FriendConstraintsDependOnEnclosingTemplate(const FunctionDecl *FD);
38113811

3812-
// Calculates whether two constraint expressions are equal irrespective of a
3813-
// difference in 'depth'. This takes a pair of optional 'NamedDecl's 'Old' and
3814-
// 'New', which are the "source" of the constraint, since this is necessary
3815-
// for figuring out the relative 'depth' of the constraint. The depth of the
3816-
// 'primary template' and the 'instantiated from' templates aren't necessarily
3817-
// the same, such as a case when one is a 'friend' defined in a class.
3818-
bool AreConstraintExpressionsEqual(const NamedDecl *Old,
3819-
const Expr *OldConstr,
3820-
const NamedDecl *New,
3821-
const Expr *NewConstr);
3822-
38233812
enum class AllowedExplicit {
38243813
/// Allow no explicit functions to be used.
38253814
None,
@@ -8615,8 +8604,48 @@ class Sema final {
86158604
TPL_TemplateParamsEquivalent,
86168605
};
86178606

8607+
// A struct to represent the 'new' declaration, which is either itself just
8608+
// the named decl, or the important information we need about it in order to
8609+
// do constraint comparisons.
8610+
class TemplateCompareNewDeclInfo {
8611+
const NamedDecl *ND = nullptr;
8612+
const DeclContext *DC = nullptr;
8613+
const DeclContext *LexicalDC = nullptr;
8614+
SourceLocation Loc;
8615+
8616+
public:
8617+
TemplateCompareNewDeclInfo(const NamedDecl *ND) : ND(ND) {}
8618+
TemplateCompareNewDeclInfo(const DeclContext *DeclCtx,
8619+
const DeclContext *LexicalDeclCtx,
8620+
SourceLocation Loc)
8621+
8622+
: DC(DeclCtx), LexicalDC(LexicalDeclCtx), Loc(Loc) {
8623+
assert(DC && LexicalDC &&
8624+
"Constructor only for cases where we have the information to put "
8625+
"in here");
8626+
}
8627+
8628+
// If this was constructed with no information, we cannot do substitution
8629+
// for constraint comparison, so make sure we can check that.
8630+
bool isInvalid() const { return !ND && !DC; }
8631+
8632+
const NamedDecl *getDecl() const { return ND; }
8633+
8634+
bool ContainsDecl(const NamedDecl *ND) const { return this->ND == ND; }
8635+
8636+
const DeclContext *getLexicalDeclContext() const {
8637+
return ND ? ND->getLexicalDeclContext() : LexicalDC;
8638+
}
8639+
8640+
const DeclContext *getDeclContext() const {
8641+
return ND ? ND->getDeclContext() : DC;
8642+
}
8643+
8644+
SourceLocation getLocation() const { return ND ? ND->getLocation() : Loc; }
8645+
};
8646+
86188647
bool TemplateParameterListsAreEqual(
8619-
const NamedDecl *NewInstFrom, TemplateParameterList *New,
8648+
const TemplateCompareNewDeclInfo &NewInstFrom, TemplateParameterList *New,
86208649
const NamedDecl *OldInstFrom, TemplateParameterList *Old, bool Complain,
86218650
TemplateParameterListEqualKind Kind,
86228651
SourceLocation TemplateArgLoc = SourceLocation());
@@ -8629,6 +8658,17 @@ class Sema final {
86298658
Kind, TemplateArgLoc);
86308659
}
86318660

8661+
// Calculates whether two constraint expressions are equal irrespective of a
8662+
// difference in 'depth'. This takes a pair of optional 'NamedDecl's 'Old' and
8663+
// 'New', which are the "source" of the constraint, since this is necessary
8664+
// for figuring out the relative 'depth' of the constraint. The depth of the
8665+
// 'primary template' and the 'instantiated from' templates aren't necessarily
8666+
// the same, such as a case when one is a 'friend' defined in a class.
8667+
bool AreConstraintExpressionsEqual(const NamedDecl *Old,
8668+
const Expr *OldConstr,
8669+
const TemplateCompareNewDeclInfo &New,
8670+
const Expr *NewConstr);
8671+
86328672
bool CheckTemplateDeclScope(Scope *S, TemplateParameterList *TemplateParams);
86338673

86348674
/// Called when the parser has parsed a C++ typename
@@ -9368,13 +9408,12 @@ class Sema final {
93689408
// C++ Template Instantiation
93699409
//
93709410

9371-
MultiLevelTemplateArgumentList
9372-
getTemplateInstantiationArgs(const NamedDecl *D, bool Final = false,
9373-
const TemplateArgumentList *Innermost = nullptr,
9374-
bool RelativeToPrimary = false,
9375-
const FunctionDecl *Pattern = nullptr,
9376-
bool ForConstraintInstantiation = false,
9377-
bool SkipForSpecialization = false);
9411+
MultiLevelTemplateArgumentList getTemplateInstantiationArgs(
9412+
const NamedDecl *D, const DeclContext *DC = nullptr, bool Final = false,
9413+
const TemplateArgumentList *Innermost = nullptr,
9414+
bool RelativeToPrimary = false, const FunctionDecl *Pattern = nullptr,
9415+
bool ForConstraintInstantiation = false,
9416+
bool SkipForSpecialization = false);
93789417

93799418
/// A context in which code is being synthesized (where a source location
93809419
/// alone is not sufficient to identify the context). This covers template

clang/include/clang/Sema/Template.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,9 @@ enum class TemplateSubstitutionKind : char {
213213
"substituted args outside retained args?");
214214
assert(getKind() == TemplateSubstitutionKind::Specialization);
215215
TemplateArgumentLists.push_back(
216-
{{AssociatedDecl->getCanonicalDecl(), Final}, Args});
216+
{{AssociatedDecl ? AssociatedDecl->getCanonicalDecl() : nullptr,
217+
Final},
218+
Args});
217219
}
218220

219221
void addOuterTemplateArguments(ArgList Args) {

clang/lib/Sema/SemaConcept.cpp

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -657,11 +657,11 @@ Sema::SetupConstraintCheckingTemplateArgumentsAndScope(
657657
// Collect the list of template arguments relative to the 'primary' template.
658658
// We need the entire list, since the constraint is completely uninstantiated
659659
// at this point.
660-
MLTAL =
661-
getTemplateInstantiationArgs(FD, /*Final=*/false, /*Innermost=*/nullptr,
662-
/*RelativeToPrimary=*/true,
663-
/*Pattern=*/nullptr,
664-
/*ForConstraintInstantiation=*/true);
660+
MLTAL = getTemplateInstantiationArgs(FD, FD->getLexicalDeclContext(),
661+
/*Final=*/false, /*Innermost=*/nullptr,
662+
/*RelativeToPrimary=*/true,
663+
/*Pattern=*/nullptr,
664+
/*ForConstraintInstantiation=*/true);
665665
if (SetupConstraintScope(FD, TemplateArgs, MLTAL, Scope))
666666
return std::nullopt;
667667

@@ -736,7 +736,8 @@ static unsigned
736736
CalculateTemplateDepthForConstraints(Sema &S, const NamedDecl *ND,
737737
bool SkipForSpecialization = false) {
738738
MultiLevelTemplateArgumentList MLTAL = S.getTemplateInstantiationArgs(
739-
ND, /*Final=*/false, /*Innermost=*/nullptr, /*RelativeToPrimary=*/true,
739+
ND, ND->getLexicalDeclContext(), /*Final=*/false, /*Innermost=*/nullptr,
740+
/*RelativeToPrimary=*/true,
740741
/*Pattern=*/nullptr,
741742
/*ForConstraintInstantiation=*/true, SkipForSpecialization);
742743
return MLTAL.getNumLevels();
@@ -770,28 +771,31 @@ namespace {
770771
};
771772
} // namespace
772773

773-
static const Expr *SubstituteConstraintExpression(Sema &S, const NamedDecl *ND,
774-
const Expr *ConstrExpr) {
774+
static const Expr *
775+
SubstituteConstraintExpression(Sema &S,
776+
const Sema::TemplateCompareNewDeclInfo &DeclInfo,
777+
const Expr *ConstrExpr) {
775778
MultiLevelTemplateArgumentList MLTAL = S.getTemplateInstantiationArgs(
776-
ND, /*Final=*/false, /*Innermost=*/nullptr,
779+
DeclInfo.getDecl(), DeclInfo.getLexicalDeclContext(), /*Final=*/false,
780+
/*Innermost=*/nullptr,
777781
/*RelativeToPrimary=*/true,
778782
/*Pattern=*/nullptr, /*ForConstraintInstantiation=*/true,
779783
/*SkipForSpecialization*/ false);
784+
780785
if (MLTAL.getNumSubstitutedLevels() == 0)
781786
return ConstrExpr;
782787

783788
Sema::SFINAETrap SFINAE(S, /*AccessCheckingSFINAE=*/false);
784789

785790
Sema::InstantiatingTemplate Inst(
786-
S, ND->getLocation(),
791+
S, DeclInfo.getLocation(),
787792
Sema::InstantiatingTemplate::ConstraintNormalization{},
788-
const_cast<NamedDecl *>(ND), SourceRange{});
789-
793+
const_cast<NamedDecl *>(DeclInfo.getDecl()), SourceRange{});
790794
if (Inst.isInvalid())
791795
return nullptr;
792796

793797
std::optional<Sema::CXXThisScopeRAII> ThisScope;
794-
if (auto *RD = dyn_cast<CXXRecordDecl>(ND->getDeclContext()))
798+
if (auto *RD = dyn_cast<CXXRecordDecl>(DeclInfo.getDeclContext()))
795799
ThisScope.emplace(S, const_cast<CXXRecordDecl *>(RD), Qualifiers());
796800
ExprResult SubstConstr =
797801
S.SubstConstraintExpr(const_cast<clang::Expr *>(ConstrExpr), MLTAL);
@@ -802,13 +806,13 @@ static const Expr *SubstituteConstraintExpression(Sema &S, const NamedDecl *ND,
802806

803807
bool Sema::AreConstraintExpressionsEqual(const NamedDecl *Old,
804808
const Expr *OldConstr,
805-
const NamedDecl *New,
809+
const TemplateCompareNewDeclInfo &New,
806810
const Expr *NewConstr) {
807811
if (OldConstr == NewConstr)
808812
return true;
809813
// C++ [temp.constr.decl]p4
810-
if (Old && New && Old != New &&
811-
Old->getLexicalDeclContext() != New->getLexicalDeclContext()) {
814+
if (Old && !New.isInvalid() && !New.ContainsDecl(Old) &&
815+
Old->getLexicalDeclContext() != New.getLexicalDeclContext()) {
812816
if (const Expr *SubstConstr =
813817
SubstituteConstraintExpression(*this, Old, OldConstr))
814818
OldConstr = SubstConstr;
@@ -1252,7 +1256,8 @@ static bool substituteParameterMappings(Sema &S, NormalizedConstraint &N,
12521256
TemplateArgumentList TAL{TemplateArgumentList::OnStack,
12531257
CSE->getTemplateArguments()};
12541258
MultiLevelTemplateArgumentList MLTAL = S.getTemplateInstantiationArgs(
1255-
CSE->getNamedConcept(), /*Final=*/false, &TAL,
1259+
CSE->getNamedConcept(), CSE->getNamedConcept()->getLexicalDeclContext(),
1260+
/*Final=*/false, &TAL,
12561261
/*RelativeToPrimary=*/true,
12571262
/*Pattern=*/nullptr,
12581263
/*ForConstraintInstantiation=*/true);

clang/lib/Sema/SemaTemplate.cpp

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1995,10 +1995,13 @@ DeclResult Sema::CheckClassTemplate(
19951995
// for a friend in a dependent context: the template parameter list itself
19961996
// could be dependent.
19971997
if (!(TUK == TUK_Friend && CurContext->isDependentContext()) &&
1998-
!TemplateParameterListsAreEqual(TemplateParams,
1999-
PrevClassTemplate->getTemplateParameters(),
2000-
/*Complain=*/true,
2001-
TPL_TemplateMatch))
1998+
!TemplateParameterListsAreEqual(
1999+
TemplateCompareNewDeclInfo(SemanticContext ? SemanticContext
2000+
: CurContext,
2001+
CurContext, KWLoc),
2002+
TemplateParams, PrevClassTemplate,
2003+
PrevClassTemplate->getTemplateParameters(), /*Complain=*/true,
2004+
TPL_TemplateMatch))
20022005
return true;
20032006

20042007
// C++ [temp.class]p4:
@@ -6203,7 +6206,7 @@ bool Sema::CheckTemplateArgumentList(
62036206
CXXThisScopeRAII(*this, RD, ThisQuals, RD != nullptr);
62046207

62056208
MultiLevelTemplateArgumentList MLTAL = getTemplateInstantiationArgs(
6206-
Template, /*Final=*/false, &StackTemplateArgs,
6209+
Template, NewContext, /*Final=*/false, &StackTemplateArgs,
62076210
/*RelativeToPrimary=*/true,
62086211
/*Pattern=*/nullptr,
62096212
/*ForConceptInstantiation=*/true);
@@ -8017,7 +8020,8 @@ Sema::BuildExpressionFromIntegralTemplateArgument(const TemplateArgument &Arg,
80178020

80188021
/// Match two template parameters within template parameter lists.
80198022
static bool MatchTemplateParameterKind(
8020-
Sema &S, NamedDecl *New, const NamedDecl *NewInstFrom, NamedDecl *Old,
8023+
Sema &S, NamedDecl *New,
8024+
const Sema::TemplateCompareNewDeclInfo &NewInstFrom, NamedDecl *Old,
80218025
const NamedDecl *OldInstFrom, bool Complain,
80228026
Sema::TemplateParameterListEqualKind Kind, SourceLocation TemplateArgLoc) {
80238027
// Check the actual kind (type, non-type, template).
@@ -8105,8 +8109,8 @@ static bool MatchTemplateParameterKind(
81058109
// For template template parameters, check the template parameter types.
81068110
// The template parameter lists of template template
81078111
// parameters must agree.
8108-
else if (TemplateTemplateParmDecl *OldTTP
8109-
= dyn_cast<TemplateTemplateParmDecl>(Old)) {
8112+
else if (TemplateTemplateParmDecl *OldTTP =
8113+
dyn_cast<TemplateTemplateParmDecl>(Old)) {
81108114
TemplateTemplateParmDecl *NewTTP = cast<TemplateTemplateParmDecl>(New);
81118115
if (!S.TemplateParameterListsAreEqual(
81128116
NewInstFrom, NewTTP->getTemplateParameters(), OldInstFrom,
@@ -8210,7 +8214,7 @@ void DiagnoseTemplateParameterListArityMismatch(Sema &S,
82108214
/// \returns True if the template parameter lists are equal, false
82118215
/// otherwise.
82128216
bool Sema::TemplateParameterListsAreEqual(
8213-
const NamedDecl *NewInstFrom, TemplateParameterList *New,
8217+
const TemplateCompareNewDeclInfo &NewInstFrom, TemplateParameterList *New,
82148218
const NamedDecl *OldInstFrom, TemplateParameterList *Old, bool Complain,
82158219
TemplateParameterListEqualKind Kind, SourceLocation TemplateArgLoc) {
82168220
if (Old->size() != New->size() && Kind != TPL_TemplateTemplateArgumentMatch) {

clang/lib/Sema/SemaTemplateDeduction.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2889,7 +2889,7 @@ CheckDeducedArgumentConstraints(Sema &S, TemplateDeclT *Template,
28892889
CanonicalDeducedArgs};
28902890

28912891
MultiLevelTemplateArgumentList MLTAL = S.getTemplateInstantiationArgs(
2892-
Template, /*Final=*/false,
2892+
Template, Template->getDeclContext(), /*Final=*/false,
28932893
/*InnerMost=*/NeedsReplacement ? nullptr : &DeducedTAL,
28942894
/*RelativeToPrimary=*/true, /*Pattern=*/
28952895
nullptr, /*ForConstraintInstantiation=*/true);

clang/lib/Sema/SemaTemplateInstantiate.cpp

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,10 @@ Response HandleGenericDeclContext(const Decl *CurDecl) {
312312
/// \param ND the declaration for which we are computing template instantiation
313313
/// arguments.
314314
///
315+
/// \param DC In the event we don't HAVE a declaration yet, we instead provide
316+
/// the decl context where it will be created. In this case, the `Innermost`
317+
/// should likely be provided. If ND is non-null, this is ignored.
318+
///
315319
/// \param Innermost if non-NULL, specifies a template argument list for the
316320
/// template declaration passed as ND.
317321
///
@@ -331,10 +335,11 @@ Response HandleGenericDeclContext(const Decl *CurDecl) {
331335
/// arguments on an enclosing class template.
332336

333337
MultiLevelTemplateArgumentList Sema::getTemplateInstantiationArgs(
334-
const NamedDecl *ND, bool Final, const TemplateArgumentList *Innermost,
335-
bool RelativeToPrimary, const FunctionDecl *Pattern,
336-
bool ForConstraintInstantiation, bool SkipForSpecialization) {
337-
assert(ND && "Can't find arguments for a decl if one isn't provided");
338+
const NamedDecl *ND, const DeclContext *DC, bool Final,
339+
const TemplateArgumentList *Innermost, bool RelativeToPrimary,
340+
const FunctionDecl *Pattern, bool ForConstraintInstantiation,
341+
bool SkipForSpecialization) {
342+
assert((ND || DC) && "Can't find arguments for a decl if one isn't provided");
338343
// Accumulate the set of template argument lists in this structure.
339344
MultiLevelTemplateArgumentList Result;
340345

@@ -346,6 +351,9 @@ MultiLevelTemplateArgumentList Sema::getTemplateInstantiationArgs(
346351
CurDecl = Response::UseNextDecl(ND).NextDecl;
347352
}
348353

354+
if (!ND)
355+
CurDecl = Decl::castFromDeclContext(DC);
356+
349357
while (!CurDecl->isFileContextDecl()) {
350358
Response R;
351359
if (const auto *VarTemplSpec =
@@ -369,6 +377,8 @@ MultiLevelTemplateArgumentList Sema::getTemplateInstantiationArgs(
369377
R = HandleImplicitConceptSpecializationDecl(CSD, Result);
370378
} else if (const auto *FTD = dyn_cast<FunctionTemplateDecl>(CurDecl)) {
371379
R = HandleFunctionTemplateDecl(FTD, Result);
380+
} else if (const auto *CTD = dyn_cast<ClassTemplateDecl>(CurDecl)) {
381+
R = Response::ChangeDecl(CTD->getLexicalDeclContext());
372382
} else if (!isa<DeclContext>(CurDecl)) {
373383
R = Response::DontClearRelativeToPrimaryNextDecl(CurDecl);
374384
if (CurDecl->getDeclContext()->isTranslationUnit()) {

clang/lib/Sema/SemaTemplateInstantiateDecl.cpp

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4626,7 +4626,8 @@ bool Sema::InstantiateDefaultArgument(SourceLocation CallLoc, FunctionDecl *FD,
46264626
// template<typename T>
46274627
// A<T> Foo(int a = A<T>::FooImpl());
46284628
MultiLevelTemplateArgumentList TemplateArgs = getTemplateInstantiationArgs(
4629-
FD, /*Final=*/false, nullptr, /*RelativeToPrimary=*/true);
4629+
FD, FD->getLexicalDeclContext(), /*Final=*/false, nullptr,
4630+
/*RelativeToPrimary=*/true);
46304631

46314632
if (SubstDefaultArgument(CallLoc, Param, TemplateArgs, /*ForCallExpr*/ true))
46324633
return true;
@@ -4665,7 +4666,8 @@ void Sema::InstantiateExceptionSpec(SourceLocation PointOfInstantiation,
46654666
LocalInstantiationScope Scope(*this);
46664667

46674668
MultiLevelTemplateArgumentList TemplateArgs = getTemplateInstantiationArgs(
4668-
Decl, /*Final=*/false, nullptr, /*RelativeToPrimary*/ true);
4669+
Decl, Decl->getLexicalDeclContext(), /*Final=*/false, nullptr,
4670+
/*RelativeToPrimary*/ true);
46694671

46704672
// FIXME: We can't use getTemplateInstantiationPattern(false) in general
46714673
// here, because for a non-defining friend declaration in a class template,
@@ -5107,7 +5109,8 @@ void Sema::InstantiateFunctionDefinition(SourceLocation PointOfInstantiation,
51075109
SetDeclDefaulted(Function, PatternDecl->getLocation());
51085110
} else {
51095111
MultiLevelTemplateArgumentList TemplateArgs = getTemplateInstantiationArgs(
5110-
Function, /*Final=*/false, nullptr, false, PatternDecl);
5112+
Function, Function->getLexicalDeclContext(), /*Final=*/false, nullptr,
5113+
false, PatternDecl);
51115114

51125115
// Substitute into the qualifier; we can get a substitution failure here
51135116
// through evil use of alias templates.

0 commit comments

Comments
 (0)