Skip to content

Commit

Permalink
[CONCEPTS]Corrected comparison of constraints with out of line CTD (#…
Browse files Browse the repository at this point in the history
…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
  • Loading branch information
erichkeane committed Oct 18, 2023
1 parent 814a79a commit 98191d7
Show file tree
Hide file tree
Showing 9 changed files with 158 additions and 54 deletions.
4 changes: 4 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,10 @@ Bug Fixes to C++ Support
with non-type template parameters of reference type. Fixes:
(`#65153 <https://github.com/llvm/llvm-project/issues/65153>`_)

- Clang now properly compares constraints on an out of line class template
declaration definition. Fixes:
(`#61763 <https://github.com/llvm/llvm-project/issues/61763>`_)

Bug Fixes to AST Handling
^^^^^^^^^^^^^^^^^^^^^^^^^
- Fixed an import failure of recursive friend class template.
Expand Down
77 changes: 58 additions & 19 deletions clang/include/clang/Sema/Sema.h
Original file line number Diff line number Diff line change
Expand Up @@ -3809,17 +3809,6 @@ class Sema final {
// the purposes of [temp.friend] p9.
bool FriendConstraintsDependOnEnclosingTemplate(const FunctionDecl *FD);

// Calculates whether two constraint expressions are equal irrespective of a
// difference in 'depth'. This takes a pair of optional 'NamedDecl's 'Old' and
// 'New', which are the "source" of the constraint, since this is necessary
// for figuring out the relative 'depth' of the constraint. The depth of the
// 'primary template' and the 'instantiated from' templates aren't necessarily
// the same, such as a case when one is a 'friend' defined in a class.
bool AreConstraintExpressionsEqual(const NamedDecl *Old,
const Expr *OldConstr,
const NamedDecl *New,
const Expr *NewConstr);

enum class AllowedExplicit {
/// Allow no explicit functions to be used.
None,
Expand Down Expand Up @@ -8615,8 +8604,48 @@ class Sema final {
TPL_TemplateParamsEquivalent,
};

// A struct to represent the 'new' declaration, which is either itself just
// the named decl, or the important information we need about it in order to
// do constraint comparisons.
class TemplateCompareNewDeclInfo {
const NamedDecl *ND = nullptr;
const DeclContext *DC = nullptr;
const DeclContext *LexicalDC = nullptr;
SourceLocation Loc;

public:
TemplateCompareNewDeclInfo(const NamedDecl *ND) : ND(ND) {}
TemplateCompareNewDeclInfo(const DeclContext *DeclCtx,
const DeclContext *LexicalDeclCtx,
SourceLocation Loc)

: DC(DeclCtx), LexicalDC(LexicalDeclCtx), Loc(Loc) {
assert(DC && LexicalDC &&
"Constructor only for cases where we have the information to put "
"in here");
}

// If this was constructed with no information, we cannot do substitution
// for constraint comparison, so make sure we can check that.
bool isInvalid() const { return !ND && !DC; }

const NamedDecl *getDecl() const { return ND; }

bool ContainsDecl(const NamedDecl *ND) const { return this->ND == ND; }

const DeclContext *getLexicalDeclContext() const {
return ND ? ND->getLexicalDeclContext() : LexicalDC;
}

const DeclContext *getDeclContext() const {
return ND ? ND->getDeclContext() : DC;
}

SourceLocation getLocation() const { return ND ? ND->getLocation() : Loc; }
};

bool TemplateParameterListsAreEqual(
const NamedDecl *NewInstFrom, TemplateParameterList *New,
const TemplateCompareNewDeclInfo &NewInstFrom, TemplateParameterList *New,
const NamedDecl *OldInstFrom, TemplateParameterList *Old, bool Complain,
TemplateParameterListEqualKind Kind,
SourceLocation TemplateArgLoc = SourceLocation());
Expand All @@ -8629,6 +8658,17 @@ class Sema final {
Kind, TemplateArgLoc);
}

// Calculates whether two constraint expressions are equal irrespective of a
// difference in 'depth'. This takes a pair of optional 'NamedDecl's 'Old' and
// 'New', which are the "source" of the constraint, since this is necessary
// for figuring out the relative 'depth' of the constraint. The depth of the
// 'primary template' and the 'instantiated from' templates aren't necessarily
// the same, such as a case when one is a 'friend' defined in a class.
bool AreConstraintExpressionsEqual(const NamedDecl *Old,
const Expr *OldConstr,
const TemplateCompareNewDeclInfo &New,
const Expr *NewConstr);

bool CheckTemplateDeclScope(Scope *S, TemplateParameterList *TemplateParams);

/// Called when the parser has parsed a C++ typename
Expand Down Expand Up @@ -9368,13 +9408,12 @@ class Sema final {
// C++ Template Instantiation
//

MultiLevelTemplateArgumentList
getTemplateInstantiationArgs(const NamedDecl *D, bool Final = false,
const TemplateArgumentList *Innermost = nullptr,
bool RelativeToPrimary = false,
const FunctionDecl *Pattern = nullptr,
bool ForConstraintInstantiation = false,
bool SkipForSpecialization = false);
MultiLevelTemplateArgumentList getTemplateInstantiationArgs(
const NamedDecl *D, const DeclContext *DC = nullptr, bool Final = false,
const TemplateArgumentList *Innermost = nullptr,
bool RelativeToPrimary = false, const FunctionDecl *Pattern = nullptr,
bool ForConstraintInstantiation = false,
bool SkipForSpecialization = false);

/// A context in which code is being synthesized (where a source location
/// alone is not sufficient to identify the context). This covers template
Expand Down
4 changes: 3 additions & 1 deletion clang/include/clang/Sema/Template.h
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,9 @@ enum class TemplateSubstitutionKind : char {
"substituted args outside retained args?");
assert(getKind() == TemplateSubstitutionKind::Specialization);
TemplateArgumentLists.push_back(
{{AssociatedDecl->getCanonicalDecl(), Final}, Args});
{{AssociatedDecl ? AssociatedDecl->getCanonicalDecl() : nullptr,
Final},
Args});
}

void addOuterTemplateArguments(ArgList Args) {
Expand Down
39 changes: 22 additions & 17 deletions clang/lib/Sema/SemaConcept.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -657,11 +657,11 @@ Sema::SetupConstraintCheckingTemplateArgumentsAndScope(
// Collect the list of template arguments relative to the 'primary' template.
// We need the entire list, since the constraint is completely uninstantiated
// at this point.
MLTAL =
getTemplateInstantiationArgs(FD, /*Final=*/false, /*Innermost=*/nullptr,
/*RelativeToPrimary=*/true,
/*Pattern=*/nullptr,
/*ForConstraintInstantiation=*/true);
MLTAL = getTemplateInstantiationArgs(FD, FD->getLexicalDeclContext(),
/*Final=*/false, /*Innermost=*/nullptr,
/*RelativeToPrimary=*/true,
/*Pattern=*/nullptr,
/*ForConstraintInstantiation=*/true);
if (SetupConstraintScope(FD, TemplateArgs, MLTAL, Scope))
return std::nullopt;

Expand Down Expand Up @@ -736,7 +736,8 @@ static unsigned
CalculateTemplateDepthForConstraints(Sema &S, const NamedDecl *ND,
bool SkipForSpecialization = false) {
MultiLevelTemplateArgumentList MLTAL = S.getTemplateInstantiationArgs(
ND, /*Final=*/false, /*Innermost=*/nullptr, /*RelativeToPrimary=*/true,
ND, ND->getLexicalDeclContext(), /*Final=*/false, /*Innermost=*/nullptr,
/*RelativeToPrimary=*/true,
/*Pattern=*/nullptr,
/*ForConstraintInstantiation=*/true, SkipForSpecialization);
return MLTAL.getNumLevels();
Expand Down Expand Up @@ -770,28 +771,31 @@ namespace {
};
} // namespace

static const Expr *SubstituteConstraintExpression(Sema &S, const NamedDecl *ND,
const Expr *ConstrExpr) {
static const Expr *
SubstituteConstraintExpression(Sema &S,
const Sema::TemplateCompareNewDeclInfo &DeclInfo,
const Expr *ConstrExpr) {
MultiLevelTemplateArgumentList MLTAL = S.getTemplateInstantiationArgs(
ND, /*Final=*/false, /*Innermost=*/nullptr,
DeclInfo.getDecl(), DeclInfo.getLexicalDeclContext(), /*Final=*/false,
/*Innermost=*/nullptr,
/*RelativeToPrimary=*/true,
/*Pattern=*/nullptr, /*ForConstraintInstantiation=*/true,
/*SkipForSpecialization*/ false);

if (MLTAL.getNumSubstitutedLevels() == 0)
return ConstrExpr;

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

Sema::InstantiatingTemplate Inst(
S, ND->getLocation(),
S, DeclInfo.getLocation(),
Sema::InstantiatingTemplate::ConstraintNormalization{},
const_cast<NamedDecl *>(ND), SourceRange{});

const_cast<NamedDecl *>(DeclInfo.getDecl()), SourceRange{});
if (Inst.isInvalid())
return nullptr;

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

bool Sema::AreConstraintExpressionsEqual(const NamedDecl *Old,
const Expr *OldConstr,
const NamedDecl *New,
const TemplateCompareNewDeclInfo &New,
const Expr *NewConstr) {
if (OldConstr == NewConstr)
return true;
// C++ [temp.constr.decl]p4
if (Old && New && Old != New &&
Old->getLexicalDeclContext() != New->getLexicalDeclContext()) {
if (Old && !New.isInvalid() && !New.ContainsDecl(Old) &&
Old->getLexicalDeclContext() != New.getLexicalDeclContext()) {
if (const Expr *SubstConstr =
SubstituteConstraintExpression(*this, Old, OldConstr))
OldConstr = SubstConstr;
Expand Down Expand Up @@ -1252,7 +1256,8 @@ static bool substituteParameterMappings(Sema &S, NormalizedConstraint &N,
TemplateArgumentList TAL{TemplateArgumentList::OnStack,
CSE->getTemplateArguments()};
MultiLevelTemplateArgumentList MLTAL = S.getTemplateInstantiationArgs(
CSE->getNamedConcept(), /*Final=*/false, &TAL,
CSE->getNamedConcept(), CSE->getNamedConcept()->getLexicalDeclContext(),
/*Final=*/false, &TAL,
/*RelativeToPrimary=*/true,
/*Pattern=*/nullptr,
/*ForConstraintInstantiation=*/true);
Expand Down
22 changes: 13 additions & 9 deletions clang/lib/Sema/SemaTemplate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1995,10 +1995,13 @@ DeclResult Sema::CheckClassTemplate(
// for a friend in a dependent context: the template parameter list itself
// could be dependent.
if (!(TUK == TUK_Friend && CurContext->isDependentContext()) &&
!TemplateParameterListsAreEqual(TemplateParams,
PrevClassTemplate->getTemplateParameters(),
/*Complain=*/true,
TPL_TemplateMatch))
!TemplateParameterListsAreEqual(
TemplateCompareNewDeclInfo(SemanticContext ? SemanticContext
: CurContext,
CurContext, KWLoc),
TemplateParams, PrevClassTemplate,
PrevClassTemplate->getTemplateParameters(), /*Complain=*/true,
TPL_TemplateMatch))
return true;

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

MultiLevelTemplateArgumentList MLTAL = getTemplateInstantiationArgs(
Template, /*Final=*/false, &StackTemplateArgs,
Template, NewContext, /*Final=*/false, &StackTemplateArgs,
/*RelativeToPrimary=*/true,
/*Pattern=*/nullptr,
/*ForConceptInstantiation=*/true);
Expand Down Expand Up @@ -8017,7 +8020,8 @@ Sema::BuildExpressionFromIntegralTemplateArgument(const TemplateArgument &Arg,

/// Match two template parameters within template parameter lists.
static bool MatchTemplateParameterKind(
Sema &S, NamedDecl *New, const NamedDecl *NewInstFrom, NamedDecl *Old,
Sema &S, NamedDecl *New,
const Sema::TemplateCompareNewDeclInfo &NewInstFrom, NamedDecl *Old,
const NamedDecl *OldInstFrom, bool Complain,
Sema::TemplateParameterListEqualKind Kind, SourceLocation TemplateArgLoc) {
// Check the actual kind (type, non-type, template).
Expand Down Expand Up @@ -8105,8 +8109,8 @@ static bool MatchTemplateParameterKind(
// For template template parameters, check the template parameter types.
// The template parameter lists of template template
// parameters must agree.
else if (TemplateTemplateParmDecl *OldTTP
= dyn_cast<TemplateTemplateParmDecl>(Old)) {
else if (TemplateTemplateParmDecl *OldTTP =
dyn_cast<TemplateTemplateParmDecl>(Old)) {
TemplateTemplateParmDecl *NewTTP = cast<TemplateTemplateParmDecl>(New);
if (!S.TemplateParameterListsAreEqual(
NewInstFrom, NewTTP->getTemplateParameters(), OldInstFrom,
Expand Down Expand Up @@ -8210,7 +8214,7 @@ void DiagnoseTemplateParameterListArityMismatch(Sema &S,
/// \returns True if the template parameter lists are equal, false
/// otherwise.
bool Sema::TemplateParameterListsAreEqual(
const NamedDecl *NewInstFrom, TemplateParameterList *New,
const TemplateCompareNewDeclInfo &NewInstFrom, TemplateParameterList *New,
const NamedDecl *OldInstFrom, TemplateParameterList *Old, bool Complain,
TemplateParameterListEqualKind Kind, SourceLocation TemplateArgLoc) {
if (Old->size() != New->size() && Kind != TPL_TemplateTemplateArgumentMatch) {
Expand Down
2 changes: 1 addition & 1 deletion clang/lib/Sema/SemaTemplateDeduction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2889,7 +2889,7 @@ CheckDeducedArgumentConstraints(Sema &S, TemplateDeclT *Template,
CanonicalDeducedArgs};

MultiLevelTemplateArgumentList MLTAL = S.getTemplateInstantiationArgs(
Template, /*Final=*/false,
Template, Template->getDeclContext(), /*Final=*/false,
/*InnerMost=*/NeedsReplacement ? nullptr : &DeducedTAL,
/*RelativeToPrimary=*/true, /*Pattern=*/
nullptr, /*ForConstraintInstantiation=*/true);
Expand Down
18 changes: 14 additions & 4 deletions clang/lib/Sema/SemaTemplateInstantiate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,10 @@ Response HandleGenericDeclContext(const Decl *CurDecl) {
/// \param ND the declaration for which we are computing template instantiation
/// arguments.
///
/// \param DC In the event we don't HAVE a declaration yet, we instead provide
/// the decl context where it will be created. In this case, the `Innermost`
/// should likely be provided. If ND is non-null, this is ignored.
///
/// \param Innermost if non-NULL, specifies a template argument list for the
/// template declaration passed as ND.
///
Expand All @@ -331,10 +335,11 @@ Response HandleGenericDeclContext(const Decl *CurDecl) {
/// arguments on an enclosing class template.

MultiLevelTemplateArgumentList Sema::getTemplateInstantiationArgs(
const NamedDecl *ND, bool Final, const TemplateArgumentList *Innermost,
bool RelativeToPrimary, const FunctionDecl *Pattern,
bool ForConstraintInstantiation, bool SkipForSpecialization) {
assert(ND && "Can't find arguments for a decl if one isn't provided");
const NamedDecl *ND, const DeclContext *DC, bool Final,
const TemplateArgumentList *Innermost, bool RelativeToPrimary,
const FunctionDecl *Pattern, bool ForConstraintInstantiation,
bool SkipForSpecialization) {
assert((ND || DC) && "Can't find arguments for a decl if one isn't provided");
// Accumulate the set of template argument lists in this structure.
MultiLevelTemplateArgumentList Result;

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

if (!ND)
CurDecl = Decl::castFromDeclContext(DC);

while (!CurDecl->isFileContextDecl()) {
Response R;
if (const auto *VarTemplSpec =
Expand All @@ -369,6 +377,8 @@ MultiLevelTemplateArgumentList Sema::getTemplateInstantiationArgs(
R = HandleImplicitConceptSpecializationDecl(CSD, Result);
} else if (const auto *FTD = dyn_cast<FunctionTemplateDecl>(CurDecl)) {
R = HandleFunctionTemplateDecl(FTD, Result);
} else if (const auto *CTD = dyn_cast<ClassTemplateDecl>(CurDecl)) {
R = Response::ChangeDecl(CTD->getLexicalDeclContext());
} else if (!isa<DeclContext>(CurDecl)) {
R = Response::DontClearRelativeToPrimaryNextDecl(CurDecl);
if (CurDecl->getDeclContext()->isTranslationUnit()) {
Expand Down
9 changes: 6 additions & 3 deletions clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4626,7 +4626,8 @@ bool Sema::InstantiateDefaultArgument(SourceLocation CallLoc, FunctionDecl *FD,
// template<typename T>
// A<T> Foo(int a = A<T>::FooImpl());
MultiLevelTemplateArgumentList TemplateArgs = getTemplateInstantiationArgs(
FD, /*Final=*/false, nullptr, /*RelativeToPrimary=*/true);
FD, FD->getLexicalDeclContext(), /*Final=*/false, nullptr,
/*RelativeToPrimary=*/true);

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

MultiLevelTemplateArgumentList TemplateArgs = getTemplateInstantiationArgs(
Decl, /*Final=*/false, nullptr, /*RelativeToPrimary*/ true);
Decl, Decl->getLexicalDeclContext(), /*Final=*/false, nullptr,
/*RelativeToPrimary*/ true);

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

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

0 comments on commit 98191d7

Please sign in to comment.