[llvm-branch-commits] [clang] [clang] fix template argument conversion (PR #124386)
Matheus Izvekov via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Sat Jan 25 19:59:25 PST 2025
https://github.com/mizvekov updated https://github.com/llvm/llvm-project/pull/124386
>From 4f4e65818c2fd85814efc63f779026853820ad76 Mon Sep 17 00:00:00 2001
From: Matheus Izvekov <mizvekov at gmail.com>
Date: Fri, 24 Jan 2025 19:25:38 -0300
Subject: [PATCH] [clang] fix template argument conversion
Converted template arguments need to be converted again, if
the corresponding template parameter changed, as different
conversions might apply in that case.
---
clang/docs/ReleaseNotes.rst | 3 +
clang/include/clang/AST/Expr.h | 10 ++
clang/include/clang/Sema/Sema.h | 2 +
clang/lib/AST/TemplateBase.cpp | 12 +--
clang/lib/Sema/SemaTemplate.cpp | 145 +++++++++++++++++++---------
clang/lib/Sema/TreeTransform.h | 6 +-
clang/test/SemaTemplate/cwg2398.cpp | 20 ----
7 files changed, 122 insertions(+), 76 deletions(-)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index b89d055304f4a6..27574924a14a92 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -993,6 +993,9 @@ Bug Fixes to C++ Support
- Fix immediate escalation not propagating through inherited constructors. (#GH112677)
- Fixed assertions or false compiler diagnostics in the case of C++ modules for
lambda functions or inline friend functions defined inside templates (#GH122493).
+- Fix template argument checking so that converted template arguments are
+ converted again. This fixes some issues with partial ordering involving
+ template template parameters with non-type template parameters.
Bug Fixes to AST Handling
^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/clang/include/clang/AST/Expr.h b/clang/include/clang/AST/Expr.h
index 708c8656decbe0..919025bec4d6da 100644
--- a/clang/include/clang/AST/Expr.h
+++ b/clang/include/clang/AST/Expr.h
@@ -2262,6 +2262,16 @@ class UnaryOperator final
}
public:
+ enum OnStack_t { OnStack };
+ UnaryOperator(OnStack_t _, const ASTContext &Ctx, Expr *input, Opcode opc,
+ QualType type, ExprValueKind VK, ExprObjectKind OK,
+ SourceLocation l, bool CanOverflow,
+ FPOptionsOverride FPFeatures)
+ : UnaryOperator(Ctx, input, opc, type, VK, OK, l, CanOverflow,
+ FPFeatures) {
+ assert(!FPFeatures.requiresTrailingStorage());
+ }
+
static UnaryOperator *CreateEmpty(const ASTContext &C, bool hasFPFeatures);
static UnaryOperator *Create(const ASTContext &C, Expr *input, Opcode opc,
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 1ea7c62cb36f05..0a7782fe7cc7e7 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -11753,6 +11753,8 @@ class Sema final : public SemaBase {
/// If an error occurred, it returns ExprError(); otherwise, it
/// returns the converted template argument. \p ParamType is the
/// type of the non-type template parameter after it has been instantiated.
+ /// \p Arg is the input template argument expression to be converted.
+ /// this expression may not necessarily outlive the converted result.
ExprResult CheckTemplateArgument(NonTypeTemplateParmDecl *Param,
QualType InstantiatedParamType, Expr *Arg,
TemplateArgument &SugaredConverted,
diff --git a/clang/lib/AST/TemplateBase.cpp b/clang/lib/AST/TemplateBase.cpp
index 3625b6e435a556..0eef8f305fcb39 100644
--- a/clang/lib/AST/TemplateBase.cpp
+++ b/clang/lib/AST/TemplateBase.cpp
@@ -515,19 +515,17 @@ void TemplateArgument::print(const PrintingPolicy &Policy, raw_ostream &Out,
}
case Declaration: {
- NamedDecl *ND = getAsDecl();
+ ValueDecl *VD = getAsDecl();
if (getParamTypeForDecl()->isRecordType()) {
- if (auto *TPO = dyn_cast<TemplateParamObjectDecl>(ND)) {
+ if (auto *TPO = dyn_cast<TemplateParamObjectDecl>(VD)) {
TPO->getType().getUnqualifiedType().print(Out, Policy);
TPO->printAsInit(Out, Policy);
break;
}
}
- if (auto *VD = dyn_cast<ValueDecl>(ND)) {
- if (needsAmpersandOnTemplateArg(getParamTypeForDecl(), VD->getType()))
- Out << "&";
- }
- ND->printQualifiedName(Out);
+ if (needsAmpersandOnTemplateArg(getParamTypeForDecl(), VD->getType()))
+ Out << "&";
+ VD->printQualifiedName(Out);
break;
}
diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp
index 210df2836eeb07..7abe0ed4f1c133 100644
--- a/clang/lib/Sema/SemaTemplate.cpp
+++ b/clang/lib/Sema/SemaTemplate.cpp
@@ -5199,7 +5199,7 @@ convertTypeTemplateArgumentToTemplate(ASTContext &Context, TypeLoc TLoc) {
}
bool Sema::CheckTemplateArgument(
- NamedDecl *Param, TemplateArgumentLoc &Arg, NamedDecl *Template,
+ NamedDecl *Param, TemplateArgumentLoc &ArgLoc, NamedDecl *Template,
SourceLocation TemplateLoc, SourceLocation RAngleLoc,
unsigned ArgumentPackIndex,
SmallVectorImpl<TemplateArgument> &SugaredConverted,
@@ -5208,9 +5208,10 @@ bool Sema::CheckTemplateArgument(
bool PartialOrderingTTP, bool *MatchedPackOnParmToNonPackOnArg) {
// Check template type parameters.
if (TemplateTypeParmDecl *TTP = dyn_cast<TemplateTypeParmDecl>(Param))
- return CheckTemplateTypeArgument(TTP, Arg, SugaredConverted,
+ return CheckTemplateTypeArgument(TTP, ArgLoc, SugaredConverted,
CanonicalConverted);
+ const TemplateArgument &Arg = ArgLoc.getArgument();
// Check non-type template parameters.
if (NonTypeTemplateParmDecl *NTTP =dyn_cast<NonTypeTemplateParmDecl>(Param)) {
// Do substitution on the type of the non-type template parameter
@@ -5252,63 +5253,114 @@ bool Sema::CheckTemplateArgument(
return true;
}
- switch (Arg.getArgument().getKind()) {
- case TemplateArgument::Null:
- llvm_unreachable("Should never see a NULL template argument here");
-
- case TemplateArgument::Expression: {
- Expr *E = Arg.getArgument().getAsExpr();
+ auto checkExpr = [&](Expr *E) -> Expr * {
TemplateArgument SugaredResult, CanonicalResult;
unsigned CurSFINAEErrors = NumSFINAEErrors;
ExprResult Res =
CheckTemplateArgument(NTTP, NTTPType, E, SugaredResult,
CanonicalResult, PartialOrderingTTP, CTAK);
- if (Res.isInvalid())
- return true;
// If the current template argument causes an error, give up now.
- if (CurSFINAEErrors < NumSFINAEErrors)
- return true;
+ if (Res.isInvalid() || CurSFINAEErrors < NumSFINAEErrors)
+ return nullptr;
+ SugaredConverted.push_back(SugaredResult);
+ CanonicalConverted.push_back(CanonicalResult);
+ return Res.get();
+ };
+ switch (Arg.getKind()) {
+ case TemplateArgument::Null:
+ llvm_unreachable("Should never see a NULL template argument here");
+
+ case TemplateArgument::Expression: {
+ Expr *E = Arg.getAsExpr();
+ Expr *R = checkExpr(E);
+ if (!R)
+ return true;
// If the resulting expression is new, then use it in place of the
// old expression in the template argument.
- if (Res.get() != E) {
- TemplateArgument TA(Res.get());
- Arg = TemplateArgumentLoc(TA, Res.get());
+ if (R != E) {
+ TemplateArgument TA(R);
+ ArgLoc = TemplateArgumentLoc(TA, R);
}
-
- SugaredConverted.push_back(SugaredResult);
- CanonicalConverted.push_back(CanonicalResult);
break;
}
- case TemplateArgument::Declaration:
- case TemplateArgument::Integral:
+ // As for the converted NTTP kinds, they still might need another
+ // conversion, as the new corresponding parameter might be different.
+ // Ideally, we would always perform substitution starting with sugared types
+ // and never need these, as we would still have expressions. Since these are
+ // needed so rarely, it's probably a better tradeoff to just convert them
+ // back to expressions.
+ case TemplateArgument::Integral: {
+ IntegerLiteral ILE(Context, Arg.getAsIntegral(), Arg.getIntegralType(),
+ SourceLocation());
+ if (!checkExpr(&ILE))
+ return true;
+ break;
+ }
+ case TemplateArgument::Declaration: {
+ bool needsAddrOf = false;
+ ValueDecl *VD = Arg.getAsDecl();
+ QualType ParamType = Arg.getParamTypeForDecl(), DeclType = VD->getType();
+ QualType DeclRefType;
+ auto handlePtrType = [&](auto *P) {
+ QualType PointeeType = P->getPointeeType();
+ if (!Context.hasSameType(P->getPointeeType(), DeclType))
+ return;
+ needsAddrOf = true;
+ DeclRefType = PointeeType;
+ };
+ if (auto *P = ParamType->getAs<PointerType>())
+ handlePtrType(P);
+ else if (auto *P = ParamType->getAs<MemberPointerType>())
+ handlePtrType(P);
+ else
+ DeclRefType = ParamType->castAs<ReferenceType>()->getPointeeType();
+
+ DeclRefExpr DRE(Context, Arg.getAsDecl(),
+ /*RefersToEnclosingVariableOrCapture=*/false, DeclRefType,
+ VK_LValue, SourceLocation());
+ if (needsAddrOf) {
+ UnaryOperator UOE(UnaryOperator::OnStack, Context, &DRE, UO_AddrOf,
+ ParamType, VK_PRValue, OK_Ordinary, SourceLocation(),
+ /*CanOverflow=*/false, /*FPFeatures=*/{});
+ if (!checkExpr(&UOE))
+ return true;
+ } else {
+ if (!checkExpr(&DRE))
+ return true;
+ }
+ break;
+ }
+ case TemplateArgument::NullPtr: {
+ CXXNullPtrLiteralExpr NPLE(Arg.getNullPtrType(), SourceLocation());
+ if (!checkExpr(&NPLE))
+ return true;
+ break;
+ }
case TemplateArgument::StructuralValue:
- case TemplateArgument::NullPtr:
- // We've already checked this template argument, so just copy
- // it to the list of converted arguments.
- SugaredConverted.push_back(Arg.getArgument());
- CanonicalConverted.push_back(
- Context.getCanonicalTemplateArgument(Arg.getArgument()));
+ // FIXME: Is it even possible to reach here?
+ // If this is actually used, this needs to convert the argument again.
+ SugaredConverted.push_back(Arg);
+ CanonicalConverted.push_back(Context.getCanonicalTemplateArgument(Arg));
break;
case TemplateArgument::Template:
case TemplateArgument::TemplateExpansion:
// We were given a template template argument. It may not be ill-formed;
// see below.
- if (DependentTemplateName *DTN
- = Arg.getArgument().getAsTemplateOrTemplatePattern()
- .getAsDependentTemplateName()) {
+ if (DependentTemplateName *DTN = Arg.getAsTemplateOrTemplatePattern()
+ .getAsDependentTemplateName()) {
// We have a template argument such as \c T::template X, which we
// parsed as a template template argument. However, since we now
// know that we need a non-type template argument, convert this
// template name into an expression.
DeclarationNameInfo NameInfo(DTN->getIdentifier(),
- Arg.getTemplateNameLoc());
+ ArgLoc.getTemplateNameLoc());
CXXScopeSpec SS;
- SS.Adopt(Arg.getTemplateQualifierLoc());
+ SS.Adopt(ArgLoc.getTemplateQualifierLoc());
// FIXME: the template-template arg was a DependentTemplateName,
// so it was provided with a template keyword. However, its source
// location is not stored in the template argument structure.
@@ -5319,8 +5371,8 @@ bool Sema::CheckTemplateArgument(
// If we parsed the template argument as a pack expansion, create a
// pack expansion expression.
- if (Arg.getArgument().getKind() == TemplateArgument::TemplateExpansion){
- E = ActOnPackExpansion(E.get(), Arg.getTemplateEllipsisLoc());
+ if (Arg.getKind() == TemplateArgument::TemplateExpansion) {
+ E = ActOnPackExpansion(E.get(), ArgLoc.getTemplateEllipsisLoc());
if (E.isInvalid())
return true;
}
@@ -5340,8 +5392,8 @@ bool Sema::CheckTemplateArgument(
// We have a template argument that actually does refer to a class
// template, alias template, or template template parameter, and
// therefore cannot be a non-type template argument.
- Diag(Arg.getLocation(), diag::err_template_arg_must_be_expr)
- << Arg.getSourceRange();
+ Diag(ArgLoc.getLocation(), diag::err_template_arg_must_be_expr)
+ << ArgLoc.getSourceRange();
NoteTemplateParameterLocation(*Param);
return true;
@@ -5357,8 +5409,8 @@ bool Sema::CheckTemplateArgument(
//
// We warn specifically about this case, since it can be rather
// confusing for users.
- QualType T = Arg.getArgument().getAsType();
- SourceRange SR = Arg.getSourceRange();
+ QualType T = Arg.getAsType();
+ SourceRange SR = ArgLoc.getSourceRange();
if (T->isFunctionType())
Diag(SR.getBegin(), diag::err_template_arg_nontype_ambig) << SR << T;
else
@@ -5409,34 +5461,33 @@ bool Sema::CheckTemplateArgument(
// When [the injected-class-name] is used [...] as a template-argument for
// a template template-parameter [...] it refers to the class template
// itself.
- if (Arg.getArgument().getKind() == TemplateArgument::Type) {
+ if (Arg.getKind() == TemplateArgument::Type) {
TemplateArgumentLoc ConvertedArg = convertTypeTemplateArgumentToTemplate(
- Context, Arg.getTypeSourceInfo()->getTypeLoc());
+ Context, ArgLoc.getTypeSourceInfo()->getTypeLoc());
if (!ConvertedArg.getArgument().isNull())
- Arg = ConvertedArg;
+ ArgLoc = ConvertedArg;
}
- switch (Arg.getArgument().getKind()) {
+ switch (Arg.getKind()) {
case TemplateArgument::Null:
llvm_unreachable("Should never see a NULL template argument here");
case TemplateArgument::Template:
case TemplateArgument::TemplateExpansion:
- if (CheckTemplateTemplateArgument(TempParm, Params, Arg, PartialOrdering,
+ if (CheckTemplateTemplateArgument(TempParm, Params, ArgLoc, PartialOrdering,
MatchedPackOnParmToNonPackOnArg))
return true;
- SugaredConverted.push_back(Arg.getArgument());
- CanonicalConverted.push_back(
- Context.getCanonicalTemplateArgument(Arg.getArgument()));
+ SugaredConverted.push_back(Arg);
+ CanonicalConverted.push_back(Context.getCanonicalTemplateArgument(Arg));
break;
case TemplateArgument::Expression:
case TemplateArgument::Type:
// We have a template template parameter but the template
// argument does not refer to a template.
- Diag(Arg.getLocation(), diag::err_template_arg_must_be_template)
- << getLangOpts().CPlusPlus11;
+ Diag(ArgLoc.getLocation(), diag::err_template_arg_must_be_template)
+ << getLangOpts().CPlusPlus11;
return true;
case TemplateArgument::Declaration:
diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h
index 12680843a434a0..81e351af575a96 100644
--- a/clang/lib/Sema/TreeTransform.h
+++ b/clang/lib/Sema/TreeTransform.h
@@ -7335,8 +7335,10 @@ QualType TreeTransform<Derived>::TransformTemplateSpecializationType(
NewTemplateArgs))
return QualType();
- // FIXME: maybe don't rebuild if all the template arguments are the same.
-
+ // This needs to be rebuilt if either the arguments changed, or if the
+ // original template changed. If the template changed, and even if the
+ // arguments didn't change, these arguments might not correspond to their
+ // respective parameters, therefore needing conversions.
QualType Result =
getDerived().RebuildTemplateSpecializationType(Template,
TL.getTemplateNameLoc(),
diff --git a/clang/test/SemaTemplate/cwg2398.cpp b/clang/test/SemaTemplate/cwg2398.cpp
index 1d35b574176a7e..137b94ba2641de 100644
--- a/clang/test/SemaTemplate/cwg2398.cpp
+++ b/clang/test/SemaTemplate/cwg2398.cpp
@@ -663,58 +663,38 @@ namespace nttp_auto {
namespace nttp_partial_order {
namespace t1 {
- // FIXME: This should pick the second overload.
template<template<short> class TT1> void f(TT1<0>);
- // new-note at -1 {{here}}
template<template<int> class TT2> void f(TT2<0>) {}
- // new-note at -1 {{here}}
template<int> struct B {};
template void f<B>(B<0>);
- // new-error at -1 {{ambiguous}}
} // namespace t1
namespace t2 {
- // FIXME: This should pick the second overload.
struct A {} a;
template<template<A&> class TT1> void f(TT1<a>);
- // new-note at -1 {{here}}
template<template<const A&> class TT2> void f(TT2<a>) {}
- // new-note at -1 {{here}}
template<const A&> struct B {};
template void f<B>(B<a>);
- // new-error at -1 {{ambiguous}}
} // namespace t2
namespace t3 {
- // FIXME: This should pick the second overload.
enum A {};
template<template<A> class TT1> void f(TT1<{}>);
- // new-note at -1 {{here}}
template<template<int> class TT2> void f(TT2<{}>) {}
- // new-note at -1 {{here}}
template<int> struct B {};
template void f<B>(B<{}>);
- // new-error at -1 {{ambiguous}}
} // namespace t3
namespace t4 {
- // FIXME: This should pick the second overload.
struct A {} a;
template<template<A*> class TT1> void f(TT1<&a>);
- // new-note at -1 {{here}}
template<template<const A*> class TT2> void f(TT2<&a>) {}
- // new-note at -1 {{here}}
template<const A*> struct B {};
template void f<B>(B<&a>);
- // new-error at -1 {{ambiguous}}
} // namespace t4
namespace t5 {
- // FIXME: This should pick the second overload.
struct A { int m; };
template<template<int A::*> class TT1> void f(TT1<&A::m>);
- // new-note at -1 {{here}}
template<template<const int A::*> class TT2> void f(TT2<&A::m>) {}
- // new-note at -1 {{here}}
template<const int A::*> struct B {};
template void f<B>(B<&A::m>);
- // new-error at -1 {{ambiguous}}
} // namespace t5
namespace t6 {
// FIXME: This should pick the second overload.
More information about the llvm-branch-commits
mailing list