https://github.com/efriedma-quic updated https://github.com/llvm/llvm-project/pull/220469
>From 6b14eeaa8b58b0b3290bb2f6a4f52cb1fcdb595d Mon Sep 17 00:00:00 2001 From: Eli Friedman <[email protected]> Date: Tue, 1 Sep 2026 20:20:39 -0700 Subject: [PATCH 1/3] [clang] Fix handling of constexpr static data member template. [temp.expl.spec] says "An explicit specialization of a static data member of a template or an explicit specialization of a static data member template is a definition if the declaration includes an initializer." The code did not match this rule: it carved out an exception for constexpr variables. I'm not confident we have enough test coverage here to tell if this breaks anything else. --- clang/lib/AST/Decl.cpp | 2 -- .../CodeGenCXX/cxx1z-inline-variables.cpp | 16 ++++++++++ clang/test/SemaCXX/dllimport.cpp | 31 +++++++------------ .../test/SemaTemplate/class-template-spec.cpp | 26 ++++++++++++++-- 4 files changed, 51 insertions(+), 24 deletions(-) diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp index c9524dc82588a..718883cce10aa 100644 --- a/clang/lib/AST/Decl.cpp +++ b/clang/lib/AST/Decl.cpp @@ -2258,8 +2258,6 @@ VarDecl::isThisDeclarationADefinition(ASTContext &C) const { // a static data member template outside the containing class? if (isStaticDataMember()) { if (isOutOfLine() && - !(getCanonicalDecl()->isInline() && - getCanonicalDecl()->isConstexpr()) && (hasInit() || // If the first declaration is out-of-line, this may be an // instantiation of an out-of-line partial specialization of a variable diff --git a/clang/test/CodeGenCXX/cxx1z-inline-variables.cpp b/clang/test/CodeGenCXX/cxx1z-inline-variables.cpp index 9b1a6e4647e85..95011b6edf240 100644 --- a/clang/test/CodeGenCXX/cxx1z-inline-variables.cpp +++ b/clang/test/CodeGenCXX/cxx1z-inline-variables.cpp @@ -101,6 +101,22 @@ constexpr int Y<int>::a; const int &yib = Y<int>::b; // CHECK-NOT: @_ZN1YIiE1cE +namespace PR219796 { +template<typename> struct A; +template<> +struct A<void> { + template<int> static int value; +}; + +// CHECK: _ZN8PR2197961AIvE5valueILi101EEE = weak_odr constant i32 3 +template<> constexpr int A<void>::value<101> = 3; + +const int& f() +{ + return A<void>::template value<101>; +} +} + // CHECK-LABEL: define {{.*}}global_var_init // CHECK: call noundef i32 @_Z1fv diff --git a/clang/test/SemaCXX/dllimport.cpp b/clang/test/SemaCXX/dllimport.cpp index cecabe98cfb5c..c31ecbcb9c8de 100644 --- a/clang/test/SemaCXX/dllimport.cpp +++ b/clang/test/SemaCXX/dllimport.cpp @@ -595,9 +595,7 @@ struct ImportMembers { __declspec(dllimport) static const int StaticConstFieldEqualInit = 1; __declspec(dllimport) static const int StaticConstFieldBraceInit{1}; __declspec(dllimport) constexpr static int ConstexprField = 1; -#if __cplusplus < 201703L && !defined(MS) - // expected-note@+2{{attribute is here}} -#endif + // expected-note@+1{{attribute is here}} __declspec(dllimport) constexpr static int ConstexprFieldDef = 1; }; @@ -643,9 +641,7 @@ inline void ImportMembers::staticInlineDef() {} int ImportMembers::StaticFieldDef; // expected-error{{definition of dllimport static field not allowed}} const int ImportMembers::StaticConstFieldDef = 1; // expected-error{{definition of dllimport static field not allowed}} -#if __cplusplus < 201703L && !defined(MS) -// expected-error@+2{{definition of dllimport static field not allowed}} -#endif +// expected-error@+1{{definition of dllimport static field not allowed}} constexpr int ImportMembers::ConstexprFieldDef; @@ -682,10 +678,8 @@ __declspec(dllimport) void ImportMemberDefs::staticInlineDecl() {} __declspec(dllimport) int ImportMemberDefs::StaticField; // expected-error{{definition of dllimport static field not allowed}} expected-note{{attribute is here}} __declspec(dllimport) const int ImportMemberDefs::StaticConstField = 1; // expected-error{{definition of dllimport static field not allowed}} expected-note{{attribute is here}} -#if __cplusplus < 201703L && !defined(MS) -// expected-error@+3{{definition of dllimport static field not allowed}} -// expected-note@+2{{attribute is here}} -#endif +// expected-error@+2{{definition of dllimport static field not allowed}} +// expected-note@+1{{attribute is here}} __declspec(dllimport) constexpr int ImportMemberDefs::ConstexprField; @@ -900,6 +894,9 @@ struct ImportMemberTmpl { template<typename T> __declspec(dllimport) static const int StaticConstFieldEqualInit = 1; template<typename T> __declspec(dllimport) static const int StaticConstFieldBraceInit{1}; template<typename T> __declspec(dllimport) constexpr static int ConstexprField = 1; +#ifdef MS + // expected-note@+2{{attribute is here}} +#endif template<typename T> __declspec(dllimport) constexpr static int ConstexprFieldDef = 1; #endif // __has_feature(cxx_variable_templates) }; @@ -926,7 +923,7 @@ template<typename T> inline void ImportMemberTmpl::staticInlineDef() {} // expec template<typename T> int ImportMemberTmpl::StaticFieldDef; // expected-error{{definition of dllimport static field not allowed}} template<typename T> const int ImportMemberTmpl::StaticConstFieldDef = 1; // expected-error{{definition of dllimport static field not allowed}} #ifdef MS -template<typename T> constexpr int ImportMemberTmpl::ConstexprFieldDef; +template<typename T> constexpr int ImportMemberTmpl::ConstexprFieldDef; // expected-error{{definition of dllimport static field not allowed}} #endif #endif // __has_feature(cxx_variable_templates) @@ -1192,9 +1189,7 @@ struct ImportClassTmplMembers { __declspec(dllimport) static const int StaticConstFieldEqualInit = 1; __declspec(dllimport) static const int StaticConstFieldBraceInit{1}; __declspec(dllimport) constexpr static int ConstexprField = 1; -#if __cplusplus < 201703L && !defined(MS) - // expected-note@+2{{attribute is here}} -#endif + // expected-note@+1{{attribute is here}} __declspec(dllimport) constexpr static int ConstexprFieldDef = 1; }; @@ -1239,9 +1234,7 @@ template<typename T> void ImportClassTmplMembers<T>::staticInlineDecl() { template<typename T> int ImportClassTmplMembers<T>::StaticFieldDef; // expected-warning{{definition of dllimport static field}} template<typename T> const int ImportClassTmplMembers<T>::StaticConstFieldDef = 1; // expected-warning{{definition of dllimport static field}} -#if __cplusplus < 201703L && !defined(MS) -// expected-warning@+2{{definition of dllimport static field}} -#endif +// expected-warning@+1{{definition of dllimport static field}} template<typename T> constexpr int ImportClassTmplMembers<T>::ConstexprFieldDef; @@ -1380,7 +1373,7 @@ struct ImportClsTmplMemTmpl { template<typename U> __declspec(dllimport) static const int StaticConstFieldBraceInit{1}; template<typename U> __declspec(dllimport) constexpr static int ConstexprField = 1; #ifdef MS - template<typename U> __declspec(dllimport) constexpr static int ConstexprFieldDef = 1; + template<typename U> __declspec(dllimport) constexpr static int ConstexprFieldDef = 1; // expected-note{{attribute is here}} #endif #endif // __has_feature(cxx_variable_templates) }; @@ -1407,7 +1400,7 @@ template<typename T> template<typename U> inline void ImportClsTmplMemTmpl<T>::s template<typename T> template<typename U> int ImportClsTmplMemTmpl<T>::StaticFieldDef; // expected-warning{{definition of dllimport static field}} template<typename T> template<typename U> const int ImportClsTmplMemTmpl<T>::StaticConstFieldDef = 1; // expected-warning{{definition of dllimport static field}} #ifdef MS -template<typename T> template<typename U> constexpr int ImportClsTmplMemTmpl<T>::ConstexprFieldDef; +template<typename T> template<typename U> constexpr int ImportClsTmplMemTmpl<T>::ConstexprFieldDef; // expected-warning {{definition of dllimport static field}} #endif #endif // __has_feature(cxx_variable_templates) diff --git a/clang/test/SemaTemplate/class-template-spec.cpp b/clang/test/SemaTemplate/class-template-spec.cpp index e60763feb2e1f..4a2f7c3efdb89 100644 --- a/clang/test/SemaTemplate/class-template-spec.cpp +++ b/clang/test/SemaTemplate/class-template-spec.cpp @@ -1,8 +1,8 @@ -// RUN: %clang_cc1 -fsyntax-only -verify %s +// RUN: %clang_cc1 -fsyntax-only -verify=expected,cxx17orlater -std=c++17 %s // RUN: %clang_cc1 -fsyntax-only -verify -std=c++98 %s // RUN: %clang_cc1 -fsyntax-only -verify -std=c++11 %s // RUN: %clang_cc1 -fsyntax-only -triple x86_64-linux-gnu -verify=expected,cxx14 -std=c++14 %s -// RUN: %clang_cc1 -fsyntax-only -verify -std=c++26 %s +// RUN: %clang_cc1 -fsyntax-only -verify=expected,cxx17orlater -std=c++26 %s template<typename T, typename U = int> struct A; // expected-note {{template is declared here}} \ // expected-note{{explicitly specialized}} @@ -259,8 +259,9 @@ namespace VarTemplateNoMember { template<> template<typename U> constexpr int S<long>::foo; // In C++14, these are definitions, not declarations, so they get a // redefinition error. - // cxx14-error@+2{{redefinition of 'foo'}} + // cxx14-error@+3{{redefinition of 'foo'}} // cxx14-note@-4{{previous definition is here}} + // cxx17orlater-error@+1 {{must be initialized by a constant expression}} template<> template<typename U> constexpr int S<long>::foo; // cxx14-error@+2{{redefinition of 'foo'}} // cxx14-note@-2{{previous definition is here}} @@ -273,3 +274,22 @@ namespace VarTemplateNoMember { template<> template<typename U> constexpr int S<long>::foo; } // namespace VarTemplateNoMember #endif + +#if __cplusplus >= 201703L +namespace ConstexprVarTemplateRedeclared { +template<typename> struct A; +struct SS {}; +template<> +struct A<void> { + template<int> static inline SS value = {}; +}; + +template<> inline constexpr SS A<void>::value<101> = {}; +template<> inline constexpr SS A<void>::value<101>; + +const SS& f() +{ + return A<void>::value<101>; +} +} +#endif >From 2911115ab26d0cf16366b2e72933ae2299fe386f Mon Sep 17 00:00:00 2001 From: Eli Friedman <[email protected]> Date: Mon, 14 Sep 2026 16:43:52 -0700 Subject: [PATCH 2/3] Add back handling for C++17 out-of-line declarations. --- clang/lib/AST/Decl.cpp | 3 +++ clang/test/SemaCXX/dllimport.cpp | 31 +++++++++++++++++++------------ 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp index 718883cce10aa..3eca92255acfc 100644 --- a/clang/lib/AST/Decl.cpp +++ b/clang/lib/AST/Decl.cpp @@ -2258,6 +2258,9 @@ VarDecl::isThisDeclarationADefinition(ASTContext &C) const { // a static data member template outside the containing class? if (isStaticDataMember()) { if (isOutOfLine() && + !(getCanonicalDecl()->isInline() && + getCanonicalDecl()->isConstexpr() && + !getCanonicalDecl()->isOutOfLine()) && (hasInit() || // If the first declaration is out-of-line, this may be an // instantiation of an out-of-line partial specialization of a variable diff --git a/clang/test/SemaCXX/dllimport.cpp b/clang/test/SemaCXX/dllimport.cpp index c31ecbcb9c8de..cecabe98cfb5c 100644 --- a/clang/test/SemaCXX/dllimport.cpp +++ b/clang/test/SemaCXX/dllimport.cpp @@ -595,7 +595,9 @@ struct ImportMembers { __declspec(dllimport) static const int StaticConstFieldEqualInit = 1; __declspec(dllimport) static const int StaticConstFieldBraceInit{1}; __declspec(dllimport) constexpr static int ConstexprField = 1; - // expected-note@+1{{attribute is here}} +#if __cplusplus < 201703L && !defined(MS) + // expected-note@+2{{attribute is here}} +#endif __declspec(dllimport) constexpr static int ConstexprFieldDef = 1; }; @@ -641,7 +643,9 @@ inline void ImportMembers::staticInlineDef() {} int ImportMembers::StaticFieldDef; // expected-error{{definition of dllimport static field not allowed}} const int ImportMembers::StaticConstFieldDef = 1; // expected-error{{definition of dllimport static field not allowed}} -// expected-error@+1{{definition of dllimport static field not allowed}} +#if __cplusplus < 201703L && !defined(MS) +// expected-error@+2{{definition of dllimport static field not allowed}} +#endif constexpr int ImportMembers::ConstexprFieldDef; @@ -678,8 +682,10 @@ __declspec(dllimport) void ImportMemberDefs::staticInlineDecl() {} __declspec(dllimport) int ImportMemberDefs::StaticField; // expected-error{{definition of dllimport static field not allowed}} expected-note{{attribute is here}} __declspec(dllimport) const int ImportMemberDefs::StaticConstField = 1; // expected-error{{definition of dllimport static field not allowed}} expected-note{{attribute is here}} -// expected-error@+2{{definition of dllimport static field not allowed}} -// expected-note@+1{{attribute is here}} +#if __cplusplus < 201703L && !defined(MS) +// expected-error@+3{{definition of dllimport static field not allowed}} +// expected-note@+2{{attribute is here}} +#endif __declspec(dllimport) constexpr int ImportMemberDefs::ConstexprField; @@ -894,9 +900,6 @@ struct ImportMemberTmpl { template<typename T> __declspec(dllimport) static const int StaticConstFieldEqualInit = 1; template<typename T> __declspec(dllimport) static const int StaticConstFieldBraceInit{1}; template<typename T> __declspec(dllimport) constexpr static int ConstexprField = 1; -#ifdef MS - // expected-note@+2{{attribute is here}} -#endif template<typename T> __declspec(dllimport) constexpr static int ConstexprFieldDef = 1; #endif // __has_feature(cxx_variable_templates) }; @@ -923,7 +926,7 @@ template<typename T> inline void ImportMemberTmpl::staticInlineDef() {} // expec template<typename T> int ImportMemberTmpl::StaticFieldDef; // expected-error{{definition of dllimport static field not allowed}} template<typename T> const int ImportMemberTmpl::StaticConstFieldDef = 1; // expected-error{{definition of dllimport static field not allowed}} #ifdef MS -template<typename T> constexpr int ImportMemberTmpl::ConstexprFieldDef; // expected-error{{definition of dllimport static field not allowed}} +template<typename T> constexpr int ImportMemberTmpl::ConstexprFieldDef; #endif #endif // __has_feature(cxx_variable_templates) @@ -1189,7 +1192,9 @@ struct ImportClassTmplMembers { __declspec(dllimport) static const int StaticConstFieldEqualInit = 1; __declspec(dllimport) static const int StaticConstFieldBraceInit{1}; __declspec(dllimport) constexpr static int ConstexprField = 1; - // expected-note@+1{{attribute is here}} +#if __cplusplus < 201703L && !defined(MS) + // expected-note@+2{{attribute is here}} +#endif __declspec(dllimport) constexpr static int ConstexprFieldDef = 1; }; @@ -1234,7 +1239,9 @@ template<typename T> void ImportClassTmplMembers<T>::staticInlineDecl() { template<typename T> int ImportClassTmplMembers<T>::StaticFieldDef; // expected-warning{{definition of dllimport static field}} template<typename T> const int ImportClassTmplMembers<T>::StaticConstFieldDef = 1; // expected-warning{{definition of dllimport static field}} -// expected-warning@+1{{definition of dllimport static field}} +#if __cplusplus < 201703L && !defined(MS) +// expected-warning@+2{{definition of dllimport static field}} +#endif template<typename T> constexpr int ImportClassTmplMembers<T>::ConstexprFieldDef; @@ -1373,7 +1380,7 @@ struct ImportClsTmplMemTmpl { template<typename U> __declspec(dllimport) static const int StaticConstFieldBraceInit{1}; template<typename U> __declspec(dllimport) constexpr static int ConstexprField = 1; #ifdef MS - template<typename U> __declspec(dllimport) constexpr static int ConstexprFieldDef = 1; // expected-note{{attribute is here}} + template<typename U> __declspec(dllimport) constexpr static int ConstexprFieldDef = 1; #endif #endif // __has_feature(cxx_variable_templates) }; @@ -1400,7 +1407,7 @@ template<typename T> template<typename U> inline void ImportClsTmplMemTmpl<T>::s template<typename T> template<typename U> int ImportClsTmplMemTmpl<T>::StaticFieldDef; // expected-warning{{definition of dllimport static field}} template<typename T> template<typename U> const int ImportClsTmplMemTmpl<T>::StaticConstFieldDef = 1; // expected-warning{{definition of dllimport static field}} #ifdef MS -template<typename T> template<typename U> constexpr int ImportClsTmplMemTmpl<T>::ConstexprFieldDef; // expected-warning {{definition of dllimport static field}} +template<typename T> template<typename U> constexpr int ImportClsTmplMemTmpl<T>::ConstexprFieldDef; #endif #endif // __has_feature(cxx_variable_templates) >From 4663abda1c09b1e6ad6d244e5931b26b22a410d4 Mon Sep 17 00:00:00 2001 From: Eli Friedman <[email protected]> Date: Mon, 14 Sep 2026 16:52:05 -0700 Subject: [PATCH 3/3] Fixups. --- clang/docs/ReleaseNotes.md | 3 +++ clang/lib/AST/Decl.cpp | 3 +-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index 043a0ddae2a6c..93742675b4478 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -676,6 +676,9 @@ features cannot lower the translation-unit ABI level; class with an invalid non-static data member, such as one qualified with an address space. (#GH194605) +- Fixed an issue where an explicit specialization of a constexpr variable would + result in a link error. (#GH219796) + #### Bug Fixes to AST Handling - Fixed a non-deterministic ordering of unused local typedefs that made diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp index a0670a3c8280a..34f5d0abe5f74 100644 --- a/clang/lib/AST/Decl.cpp +++ b/clang/lib/AST/Decl.cpp @@ -2258,8 +2258,7 @@ VarDecl::isThisDeclarationADefinition(ASTContext &C) const { // a static data member template outside the containing class? if (isStaticDataMember()) { if (isOutOfLine() && - !(getCanonicalDecl()->isInline() && - getCanonicalDecl()->isConstexpr() && + !(getCanonicalDecl()->isInline() && getCanonicalDecl()->isConstexpr() && !getCanonicalDecl()->isOutOfLine()) && (hasInit() || // If the first declaration is out-of-line, this may be an _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
