[clang] [clang] Decay types of function and constant template parameter packs (PR #132189)
via cfe-commits
cfe-commits at lists.llvm.org
Thu Mar 20 04:43:30 PDT 2025
https://github.com/offsetof created https://github.com/llvm/llvm-project/pull/132189
* Handle pack expansion types in `ASTContext::getAdjustedParameterType`, adjusting their pattern if necessary.
* Perform the usual constant template parameter type adjustments in `Sema::RebuildTemplateParamsInCurrentInstantiation`.
* Augment `Sema::CheckNonTypeTemplateParameterType` with a `Diagnose` parameter to perform the aforementioned adjustments without emitting redundant diagnostics.
>From d9a290d644fef450018d078b9c9a035897e24dc2 Mon Sep 17 00:00:00 2001
From: offsetof <offsetof at mailo.com>
Date: Thu, 20 Mar 2025 11:19:19 +0000
Subject: [PATCH] [clang] Decay types of function and constant template
parameter packs
* Handle pack expansion types in `ASTContext::getAdjustedParameterType`,
adjusting their pattern if necessary.
* Perform the usual constant template parameter type adjustments in
`Sema::RebuildTemplateParamsInCurrentInstantiation`.
* Augment `Sema::CheckNonTypeTemplateParameterType` with a `Diagnose`
parameter to perform the aforementioned adjustments without emitting
redundant diagnostics.
---
clang/docs/ReleaseNotes.rst | 3 +
clang/include/clang/AST/ASTContext.h | 3 +
clang/include/clang/Sema/Sema.h | 15 ++-
clang/lib/AST/ASTContext.cpp | 4 +
clang/lib/Sema/SemaTemplate.cpp | 60 +++++----
clang/test/AST/ast-dump-templates.cpp | 4 +-
.../test/CXX/temp/temp.decls/temp.mem/p1.cpp | 67 ++++++++++
clang/test/CXX/temp/temp.param/p7.cpp | 2 +-
.../SemaTemplate/dependent-type-decay.cpp | 123 ++++++++++++++++++
clang/test/SemaTemplate/pack-deduction.cpp | 5 +-
10 files changed, 250 insertions(+), 36 deletions(-)
create mode 100644 clang/test/SemaTemplate/dependent-type-decay.cpp
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 4258d0d72c950..28b05a6dd62e5 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -352,6 +352,9 @@ Bug Fixes to C++ Support
The issue has been addressed by propagating qualifiers during derived-to-base conversions in the AST. (#GH127824)
- Clang now emits the ``-Wunused-variable`` warning when some structured bindings are unused
and the ``[[maybe_unused]]`` attribute is not applied. (#GH125810)
+- Function parameter packs and constant (non-type) template parameter packs
+ declared with an array or function type are now correctly adjusted to be of
+ the corresponding decayed pointer type.
Bug Fixes to AST Handling
^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h
index f9a12260a6590..35835408e8575 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -2977,6 +2977,9 @@ class ASTContext : public RefCountedBase<ASTContext> {
/// This routine adjusts the given parameter type @p T to the actual
/// parameter type used by semantic analysis (C99 6.7.5.3p[7,8],
/// C++ [dcl.fct]p3). The adjusted parameter type is returned.
+ ///
+ /// If @p T is a pack expansion type, the adjustment is performed
+ /// on its pattern.
QualType getAdjustedParameterType(QualType T) const;
/// Retrieve the parameter type as adjusted for use in the signature
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 9724f0def743a..da7dd670120c9 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -11352,19 +11352,22 @@ class Sema final : public SemaBase {
NonTypeTemplateParmDecl *OrigConstrainedParm,
SourceLocation EllipsisLoc);
- /// Require the given type to be a structural type, and diagnose if it is not.
+ /// Require the given type to be a structural type,
+ /// and optionally diagnose if it is not.
///
- /// \return \c true if an error was produced.
- bool RequireStructuralType(QualType T, SourceLocation Loc);
+ /// \return \c true if the type is *not* a structural type.
+ bool RequireStructuralType(QualType T, SourceLocation Loc, bool Diagnose);
/// Check that the type of a non-type template parameter is
/// well-formed.
///
/// \returns the (possibly-promoted) parameter type if valid;
- /// otherwise, produces a diagnostic and returns a NULL type.
+ /// otherwise, returns a NULL type and optionally produces a diagnostic.
QualType CheckNonTypeTemplateParameterType(TypeSourceInfo *&TSI,
- SourceLocation Loc);
- QualType CheckNonTypeTemplateParameterType(QualType T, SourceLocation Loc);
+ SourceLocation Loc,
+ bool Diagnose = true);
+ QualType CheckNonTypeTemplateParameterType(QualType T, SourceLocation Loc,
+ bool Diagnose = true);
NamedDecl *ActOnNonTypeTemplateParameter(Scope *S, Declarator &D,
unsigned Depth, unsigned Position,
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index 68a02f3bbe1ec..2403eadc2043c 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -7672,6 +7672,10 @@ const ArrayType *ASTContext::getAsArrayType(QualType T) const {
}
QualType ASTContext::getAdjustedParameterType(QualType T) const {
+ if (auto *PET = T->getAs<PackExpansionType>())
+ return getPackExpansionType(getAdjustedParameterType(PET->getPattern()),
+ PET->getNumExpansions(),
+ /*ExpectPackInType=*/false);
if (getLangOpts().HLSL && T->isConstantArrayType())
return getArrayParameterType(T);
if (T->isArrayType() || T->isFunctionType())
diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp
index c3c993d51b79d..91f675006508b 100644
--- a/clang/lib/Sema/SemaTemplate.cpp
+++ b/clang/lib/Sema/SemaTemplate.cpp
@@ -1267,7 +1267,8 @@ bool Sema::AttachTypeConstraint(AutoTypeLoc TL,
}
QualType Sema::CheckNonTypeTemplateParameterType(TypeSourceInfo *&TSI,
- SourceLocation Loc) {
+ SourceLocation Loc,
+ bool Diagnose) {
if (TSI->getType()->isUndeducedType()) {
// C++17 [temp.dep.expr]p3:
// An id-expression is type-dependent if it contains
@@ -1277,19 +1278,27 @@ QualType Sema::CheckNonTypeTemplateParameterType(TypeSourceInfo *&TSI,
TSI = SubstAutoTypeSourceInfoDependent(TSI);
}
- return CheckNonTypeTemplateParameterType(TSI->getType(), Loc);
+ return CheckNonTypeTemplateParameterType(TSI->getType(), Loc, Diagnose);
}
-bool Sema::RequireStructuralType(QualType T, SourceLocation Loc) {
+bool Sema::RequireStructuralType(QualType T, SourceLocation Loc,
+ bool Diagnose) {
if (T->isDependentType())
return false;
- if (RequireCompleteType(Loc, T, diag::err_template_nontype_parm_incomplete))
+ if (Diagnose ? RequireCompleteType(Loc, T,
+ diag::err_template_nontype_parm_incomplete)
+ : !isCompleteType(Loc, T))
return true;
if (T->isStructuralType())
return false;
+ // If we're not emitting diagnostics, there's no need to figure out
+ // why exactly T is not a structural type.
+ if (!Diagnose)
+ return true;
+
// Structural types are required to be object types or lvalue references.
if (T->isRValueReferenceType()) {
Diag(Loc, diag::err_template_nontype_parm_rvalue_ref) << T;
@@ -1379,16 +1388,21 @@ bool Sema::RequireStructuralType(QualType T, SourceLocation Loc) {
return true;
}
-QualType Sema::CheckNonTypeTemplateParameterType(QualType T,
- SourceLocation Loc) {
+QualType Sema::CheckNonTypeTemplateParameterType(QualType T, SourceLocation Loc,
+ bool Diagnose) {
// We don't allow variably-modified types as the type of non-type template
// parameters.
if (T->isVariablyModifiedType()) {
- Diag(Loc, diag::err_variably_modified_nontype_template_param)
- << T;
+ if (Diagnose)
+ Diag(Loc, diag::err_variably_modified_nontype_template_param) << T;
return QualType();
}
+ // C++2c [temp.param] p10:
+ // A constant template parameter of type "array of T" or of
+ // function type T is adjusted to be of type "pointer to T".
+ T = Context.getAdjustedParameterType(T);
+
// C++ [temp.param]p4:
//
// A non-type template-parameter shall have one of the following
@@ -1411,14 +1425,6 @@ QualType Sema::CheckNonTypeTemplateParameterType(QualType T,
return T.getUnqualifiedType();
}
- // C++ [temp.param]p8:
- //
- // A non-type template-parameter of type "array of T" or
- // "function returning T" is adjusted to be of type "pointer to
- // T" or "pointer to function returning T", respectively.
- if (T->isArrayType() || T->isFunctionType())
- return Context.getDecayedType(T);
-
// If T is a dependent type, we can't do the check now, so we
// assume that it is well-formed. Note that stripping off the
// qualifiers here is not really correct if T turns out to be
@@ -1430,18 +1436,20 @@ QualType Sema::CheckNonTypeTemplateParameterType(QualType T,
// C++20 [temp.param]p6:
// -- a structural type
- if (RequireStructuralType(T, Loc))
+ if (RequireStructuralType(T, Loc, Diagnose))
return QualType();
if (!getLangOpts().CPlusPlus20) {
// FIXME: Consider allowing structural types as an extension in C++17. (In
// earlier language modes, the template argument evaluation rules are too
// inflexible.)
- Diag(Loc, diag::err_template_nontype_parm_bad_structural_type) << T;
+ if (Diagnose)
+ Diag(Loc, diag::err_template_nontype_parm_bad_structural_type) << T;
return QualType();
}
- Diag(Loc, diag::warn_cxx17_compat_template_nontype_parm_type) << T;
+ if (Diagnose)
+ Diag(Loc, diag::warn_cxx17_compat_template_nontype_parm_type) << T;
return T.getUnqualifiedType();
}
@@ -1518,7 +1526,7 @@ NamedDecl *Sema::ActOnNonTypeTemplateParameter(Scope *S, Declarator &D,
QualType T = CheckNonTypeTemplateParameterType(TInfo, D.getIdentifierLoc());
if (T.isNull()) {
- T = Context.IntTy; // Recover with an 'int' type.
+ T = TInfo->getType();
Invalid = true;
}
@@ -11080,9 +11088,7 @@ bool Sema::RebuildNestedNameSpecifierInCurrentInstantiation(CXXScopeSpec &SS) {
bool Sema::RebuildTemplateParamsInCurrentInstantiation(
TemplateParameterList *Params) {
- for (unsigned I = 0, N = Params->size(); I != N; ++I) {
- Decl *Param = Params->getParam(I);
-
+ for (Decl *Param : *Params) {
// There is nothing to rebuild in a type parameter.
if (isa<TemplateTypeParmDecl>(Param))
continue;
@@ -11116,8 +11122,14 @@ bool Sema::RebuildTemplateParamsInCurrentInstantiation(
}
if (NewTSI != NTTP->getTypeSourceInfo()) {
+ QualType NewT = CheckNonTypeTemplateParameterType(
+ NewTSI->getType(), NTTP->getLocation(), /*Diagnose=*/false);
+ if (NewT.isNull()) {
+ NewT = NewTSI->getType();
+ NTTP->setInvalidDecl();
+ }
NTTP->setTypeSourceInfo(NewTSI);
- NTTP->setType(NewTSI->getType());
+ NTTP->setType(NewT);
}
}
diff --git a/clang/test/AST/ast-dump-templates.cpp b/clang/test/AST/ast-dump-templates.cpp
index 2728dc151c3c5..807f88b1ae388 100644
--- a/clang/test/AST/ast-dump-templates.cpp
+++ b/clang/test/AST/ast-dump-templates.cpp
@@ -48,7 +48,7 @@ void baz() {
// CHECK2: template<> int bar<5, int>()
// CHECK1-LABEL: template <typename ...T> struct A {
-// CHECK1-NEXT: template <T ...x[3]> struct B {
+// CHECK1-NEXT: template <T *...x> struct B {
template <typename ...T> struct A {
template <T ...x[3]> struct B {};
};
@@ -3174,7 +3174,7 @@ struct pr126341<{1, 2}>;
// JSON-NEXT: },
// JSON-NEXT: "name": "x",
// JSON-NEXT: "type": {
-// JSON-NEXT: "qualType": "T[3]..."
+// JSON-NEXT: "qualType": "T *..."
// JSON-NEXT: },
// JSON-NEXT: "depth": 1,
// JSON-NEXT: "index": 0,
diff --git a/clang/test/CXX/temp/temp.decls/temp.mem/p1.cpp b/clang/test/CXX/temp/temp.decls/temp.mem/p1.cpp
index 4ec41521f9a3b..5adfb6be573b2 100644
--- a/clang/test/CXX/temp/temp.decls/temp.mem/p1.cpp
+++ b/clang/test/CXX/temp/temp.decls/temp.mem/p1.cpp
@@ -141,4 +141,71 @@ namespace OutOfLine {
template<>
template<typename U, A<int>::B V>
struct A<int>::C<U&, V> { }; // expected-error {{redefinition of 'C<U &, V>'}}
+
+
+ template<class T>
+ class X { // expected-note 2 {{X defined here}}
+ using A = int;
+ template<A> void a1();
+ template<A> static int a2;
+ template<A> class a3;
+ template<OutOfLine::X<T>::A> void a4();
+ template<OutOfLine::X<T>::A> static int a5;
+ template<OutOfLine::X<T>::A> class a6;
+
+ using B = int[];
+ template<B> void b1();
+ template<B> static int b2;
+ template<B> class b3;
+ template<OutOfLine::X<T>::B> void b4();
+ template<OutOfLine::X<T>::B> static int b5;
+ template<OutOfLine::X<T>::B> class b6;
+
+ using Bad = int&&;
+ template<Bad> void bad1(); // expected-error {{non-type template parameter has rvalue reference type}}
+ template<Bad> static int bad2; // expected-error {{non-type template parameter has rvalue reference type}}
+ template<Bad> class bad3; // expected-error {{non-type template parameter has rvalue reference type}}
+ template<OutOfLine::X<T>::Bad> void bad4(); // expected-error {{non-type template parameter has rvalue reference type}}
+ template<OutOfLine::X<T>::Bad> static int bad5; // expected-error {{non-type template parameter has rvalue reference type}}
+ template<OutOfLine::X<T>::Bad> class bad6; // expected-error {{non-type template parameter has rvalue reference type}}
+
+ template<const T> class Q;
+ template<const T...> class Qp;
+
+ template<int> void good();
+ };
+
+ template<class T> template<X<T>::A> void X<T>::a1() {}
+ template<class T> template<X<T>::A> int X<T>::a2 = 2;
+ template<class T> template<X<T>::A> class X<T>::a3 {};
+ template<class T> template<X<T>::A> void X<T>::a4() {}
+ template<class T> template<X<T>::A> int X<T>::a5 = 5;
+ template<class T> template<X<T>::A> class X<T>::a6 {};
+
+ template<class T> template<X<T>::B> void X<T>::b1() {}
+ template<class T> template<X<T>::B> int X<T>::b2 = 2;
+ template<class T> template<X<T>::B> class X<T>::b3 {};
+ template<class T> template<X<T>::B> void X<T>::b4() {}
+ template<class T> template<X<T>::B> int X<T>::b5 = 5;
+ template<class T> template<X<T>::B> class X<T>::b6 {};
+
+ template<class T> template<X<T>::Bad> void X<T>::bad1() {}
+ template<class T> template<X<T>::Bad> int X<T>::bad2 = 2;
+ template<class T> template<X<T>::Bad> class X<T>::bad3 {};
+ template<class T> template<X<T>::Bad> void X<T>::bad4() {}
+ template<class T> template<X<T>::Bad> int X<T>::bad5 = 5;
+ template<class T> template<X<T>::Bad> class X<T>::bad6 {};
+
+ template<class T> template<const T> class X<T>::Q {};
+ template<class T> template<const T...> class X<T>::Qp {};
+
+ template<class T>
+ template<X<T>::Bad>
+ void X<T>::good() {} // expected-error {{out-of-line definition of 'good' does not match any declaration in 'X<T>'}}
+
+ template<class> using RRef = int&&;
+
+ template<class T>
+ template<RRef<T>> // expected-error {{non-type template parameter has rvalue reference type 'RRef<T>' (aka 'int &&')}}
+ void X<T>::good() {} // expected-error {{out-of-line definition of 'good' does not match any declaration in 'X<T>'}}
}
diff --git a/clang/test/CXX/temp/temp.param/p7.cpp b/clang/test/CXX/temp/temp.param/p7.cpp
index f804306d7c577..dbd310fe6a1f4 100644
--- a/clang/test/CXX/temp/temp.param/p7.cpp
+++ b/clang/test/CXX/temp/temp.param/p7.cpp
@@ -123,4 +123,4 @@ template<MutableField> struct WithMutableField {}; // cxx17-error {{cannot have
template<typename T> struct BadExtType { T t; }; // cxx20-note 2{{has a non-static data member of non-structural type}}
template<BadExtType<_Atomic float> > struct AtomicFloatField; // cxx17-error {{cannot have type}} cxx20-error {{is not a structural type}}
-template<BadExtType<_Atomic int> > struct AtomicInt; // cxx17-error {{cannot have type}} cxx20-error {{is not a structural type}}
+template<BadExtType<_Atomic int> > struct AtomicIntField; // cxx17-error {{cannot have type}} cxx20-error {{is not a structural type}}
diff --git a/clang/test/SemaTemplate/dependent-type-decay.cpp b/clang/test/SemaTemplate/dependent-type-decay.cpp
new file mode 100644
index 0000000000000..dd1b04d982b2c
--- /dev/null
+++ b/clang/test/SemaTemplate/dependent-type-decay.cpp
@@ -0,0 +1,123 @@
+// RUN: %clang_cc1 %s -fsyntax-only -std=c++20 -verify
+
+void f(auto*) {} // expected-note {{previous definition is here}}
+void f(auto[]) {} // expected-error {{redefinition of 'f'}}
+
+void g(auto()) {} // expected-note {{previous definition is here}}
+void g(auto (*)()) {} // expected-error {{redefinition of 'g'}}
+
+void fp(auto*...) {} // expected-note {{previous definition is here}}
+void fp(auto... _[]) {} // expected-error {{redefinition of 'fp'}}
+
+void gp(auto...()) {} // expected-note {{previous definition is here}}
+void gp(auto (*..._)()) {} // expected-error {{redefinition of 'gp'}}
+
+
+template<int*> class A;
+template<int _[]> class A;
+
+template<int()> class B;
+template<int (*)()> class B;
+
+template<int*...> class Ap;
+template<int... _[]> class Ap;
+
+template<int...()> class Bp;
+template<int (*..._)()> class Bp;
+
+template<class T, class... Ts>
+class C {
+ template<T*> void f(); // expected-note {{previous declaration is here}}
+ template<T[]> void f(); // expected-error {{class member cannot be redeclared}}
+
+ template<T()> void g(); // expected-note {{previous declaration is here}}
+ template<T(*)()> void g(); // expected-error {{class member cannot be redeclared}}
+
+ template<Ts*...> void fp(); // expected-note {{previous declaration is here}}
+ template<Ts... _[]> void fp(); // expected-error {{class member cannot be redeclared}}
+
+ template<Ts...()> void gp(); // expected-note {{previous declaration is here}}
+ template<Ts(* ..._)()> void gp(); // expected-error {{class member cannot be redeclared}}
+
+ template<T[]> class X;
+ template<T()> class Y;
+
+ template<Ts... _[]> class Xp;
+ template<Ts...()> class Yp;
+};
+
+template<class T, class... Ts>
+template<T*>
+class C<T, Ts...>::X {};
+
+template<class T, class... Ts>
+template<T (*)()>
+class C<T, Ts...>::Y {};
+
+template<class T, class... Ts>
+template<Ts*...>
+class C<T, Ts...>::Xp {};
+
+template<class T, class... Ts>
+template<Ts (*..._)()>
+class C<T, Ts...>::Yp {};
+
+
+template<class...> class R;
+
+template<class T>
+R<T>* d0(T[]) { return 0; }
+R<int>* r0 = d0((int*)0);
+
+template<class T>
+R<T>* d1(T(T[])) { return 0; }
+R<int>* r1 = d1((int (*)(int*))0);
+
+template<class T>
+R<T>* d2(T(T(T))) { return 0; }
+R<int>* r2 = d2((int (*)(int (*)(int)))0);
+
+template<class... Ts>
+R<Ts...>* d0p(Ts... _[]) { return 0; }
+R<int, char>* r0p = d0p((int*)0, (char*)0);
+
+template<class... Ts>
+R<Ts...>* d1p(Ts...(Ts... _[])) { return 0; }
+R<int, char>* r1p = d1p((int (*)(int*, char*))0, (char (*)(int*, char*))0);
+
+template<class... Ts>
+R<Ts...>* d2p(Ts...(Ts...(Ts...))) { return 0; }
+R<int, char>* r2p = d2p(
+ (int (*)(int(int, char), char(int, char)))0,
+ (char (*)(int(int, char), char(int, char)))0);
+
+
+template<class T> concept Y = sizeof(T*) != 0;
+
+template<class T, class... Ts>
+struct S {
+ static int f() requires Y<int(T*)>;
+ static constexpr int f() requires Y<int(T[1])> && true {
+ return 1;
+ }
+
+ static int g() requires Y<int(T (*)())>;
+ static constexpr int g() requires Y<int(T())> && true {
+ return 2;
+ }
+
+ static int fp() requires Y<int(Ts*...)>;
+ static constexpr int fp() requires Y<int(Ts... _[1])> && true {
+ return 3;
+ }
+
+ static int gp() requires Y<int(Ts (*..._)(Ts...))>;
+ static constexpr int gp() requires Y<int(Ts...(Ts...))> && true {
+ return 4;
+ }
+};
+
+static_assert(S<char, short, int>::f() == 1);
+static_assert(S<char, short, int>::g() == 2);
+static_assert(S<char, short, int>::fp() == 3);
+static_assert(S<char, short, int>::gp() == 4);
diff --git a/clang/test/SemaTemplate/pack-deduction.cpp b/clang/test/SemaTemplate/pack-deduction.cpp
index b3104609994a4..af188def8991b 100644
--- a/clang/test/SemaTemplate/pack-deduction.cpp
+++ b/clang/test/SemaTemplate/pack-deduction.cpp
@@ -158,12 +158,11 @@ namespace partial_full_mix {
namespace substitution_vs_function_deduction {
template <typename... T> struct A {
template <typename... U> void f(void(*...)(T, U)); // expected-warning {{ISO C++11 requires a parenthesized pack declaration to have a name}}
- template <typename... U> void g(void...(T, U)); // expected-note {{could not match 'void (T, U)' against 'void (*)(int, int)'}}
+ template <typename... U> void g(void...(T, U));
};
void f(int, int) {
A<int>().f(f);
- // FIXME: We fail to decay the parameter to a pointer type.
- A<int>().g(f); // expected-error {{no match}}
+ A<int>().g(f);
}
}
More information about the cfe-commits
mailing list