Hi! Apparently in clang __clang__ is a predefined macro (predefined to 1). Because of that, clang doesn't have alternate spelling of its scoped attributes as [[__clang__::__something__]] but [[_Clang::__something__]] The former isn't accepted even with -U__clang__. For GNU attributes they do handle [[__gnu__::__something__]] like GCC does (if they support a particular attribute).
So, I'm afraid if we want to stay compatible with this mess, we need to do the same thing. The following patch does that. It emits a warning when one attempts to use the __clang__ attribute namespace suggesting to use _Clang instead (clang++ does that too), but in __has_cpp_attribute/__has_attribute doesn't do that, just ensures that 0 is returned in that case (i.e. it is not canonicalized in that case), just canonicalizes _Clang to clang. Ok for trunk if this passes full bootstrap/regtest? 2026-07-14 Jakub Jelinek <[email protected]> PR c++/120635 gcc/ * doc/extend.texi: Document variant spelling of standard attributes and that alternate namespace spelling for clang namespace is _Clang rather than __clang__. For no_specializations use _Clang:: rather than __clang__::. gcc/c-family/ * c-lex.cc (c_common_has_attribute): For __clang__ namespace don't call canonicalize_attr_name and for _Clang use clang instead. gcc/cp/ * parser.cc (cp_parser_std_attribute): Add canonicalize_attr_ns_name lambda and use it instead of canonicalize_attr_name for namespaces. Emit a warning on __clang__::. (cp_parser_std_attribute_spec): Emit a warning on using __clang__:. gcc/testsuite/ * g++.dg/cpp0x/attr-trivial_abi8.C: Add further tests. * g++.dg/cpp0x/attr-trivial_abi11.C: New test. * g++.dg/ext/attr-no_specializations1.C: Use _Clang:: instead of __clang__. * g++.dg/ext/attr-no_specializations2.C: Likewise. * g++.dg/ext/attr-no_specializations6.C: Likewise. * g++.dg/ext/attr-no_specializations7.C: Likewise. * g++.dg/ext/attr-no_specializations8.C: Likewise. * g++.dg/ext/attr-no_specializations10.C: Add further tests. * g++.dg/ext/attr-no_specializations12.C: New test. --- gcc/doc/extend.texi.jj 2026-07-13 18:37:33.605239738 +0200 +++ gcc/doc/extend.texi 2026-07-14 19:08:44.872456333 +0200 @@ -1821,9 +1821,15 @@ entity is ignored with a warning. Every GNU-specific attribute has an alternate name that is prefixed and suffixed by two underscores; for example, the alternate name of -@code{weak} is @code{__weak__}. These alternate names are useful in -library header files in case they are included into files that have +@code{weak} is @code{__weak__}. These alternate names are useful in library +header files in case they are included into files that have redefined the base name of the attribute as a preprocessor macro. +Similarly for every supported standard or scoped attribute, for example, the +variant spellings of @code{[[gnu::weak]]} are @code{[[__gnu__::__weak__]]}, +@code{[[gnu::__weak__]]} or @code{[[__gnu__::weak]]}. The only exception +is that alternate spelling of the @code{clang} scoped attribute namespace +is @code{_Clang} rather than @code{__clang__} for compatibility with that +compiler. GCC also accepts the keyword @code{__attribute} as a synonym for @code{__attribute__}. @@ -31689,7 +31695,7 @@ struct __attribute__ ((__no_specializati template <typename T, typename U> [[clang::no_specializations]] int b = 1; template <typename T> -[[__clang__::__no_specializations__ ("foo cannot be specialized")]] int +[[_Clang::__no_specializations__ ("foo cannot be specialized")]] int foo () @{ return 42; @} template <> struct A <int> @{@}; // Error template <typename T> int b <T, T> = 2; // Error --- gcc/c-family/c-lex.cc.jj 2026-07-13 18:37:33.594239874 +0200 +++ gcc/c-family/c-lex.cc 2026-07-14 19:23:39.933351779 +0200 @@ -355,7 +355,6 @@ c_common_has_attribute (cpp_reader *pfil { attr_name = get_identifier ((const char *) cpp_token_as_text (pfile, token)); - attr_name = canonicalize_attr_name (attr_name); bool have_scope = false; int idx = 0; const cpp_token *nxt_token; @@ -390,6 +389,13 @@ c_common_has_attribute (cpp_reader *pfil if (nxt_token->type == CPP_NAME) { tree attr_ns = attr_name; + /* In clang, __clang__ is predefined macro, and the supported + alternate namespace is _Clang rather than __clang__ because + of that. Don't handle ___Clang__ that way though. */ + if (id_equal (attr_ns, "_Clang")) + attr_ns = get_identifier ("clang"); + else if (!id_equal (attr_ns, "__clang__")) + attr_ns = canonicalize_attr_name (attr_ns); tree attr_id = get_identifier ((const char *) cpp_token_as_text (pfile, nxt_token)); @@ -424,6 +430,7 @@ c_common_has_attribute (cpp_reader *pfil } else { + attr_name = canonicalize_attr_name (attr_name); /* Some standard attributes need special handling. */ if (c_dialect_cxx ()) { --- gcc/cp/parser.cc.jj 2026-07-13 18:37:33.599239812 +0200 +++ gcc/cp/parser.cc 2026-07-14 19:33:09.077283191 +0200 @@ -33298,6 +33298,7 @@ cp_parser_std_attribute (cp_parser *pars /* First, parse name of the attribute, a.k.a attribute-token. */ token = cp_lexer_peek_token (parser->lexer); + location_t id_loc = token->location; if (token->type == CPP_NAME) attr_id = token->u.value; else if (token->type == CPP_KEYWORD) @@ -33311,6 +33312,16 @@ cp_parser_std_attribute (cp_parser *pars cp_lexer_consume_token (parser->lexer); token = cp_lexer_peek_token (parser->lexer); + auto canonicalize_attr_ns_name = [] (tree attr_ns) { + /* In clang, __clang__ is predefined macro, and the supported alternate + namespace is _Clang rather than __clang__ because of that. + Don't handle ___Clang__ that way though. */ + if (id_equal (attr_ns, "_Clang")) + attr_ns = get_identifier ("clang"); + else + attr_ns = canonicalize_attr_name (attr_ns); + return attr_ns; + }; if (token->type == CPP_SCOPE) { /* We are seeing a scoped attribute token. */ @@ -33321,6 +33332,11 @@ cp_parser_std_attribute (cp_parser *pars "with scoped attribute token"); attr_ns = attr_id; + if (id_equal (attr_ns, "__clang__")) + warning_at (id_loc, OPT_Wattributes, + "alternate attribute namespace for %<clang%> is " + "%<_Clang%> rather than %<__clang__%>"); + token = cp_lexer_peek_token (parser->lexer); if (token->type == CPP_NAME) attr_id = token->u.value; @@ -33336,7 +33352,7 @@ cp_parser_std_attribute (cp_parser *pars } cp_lexer_consume_token (parser->lexer); - attr_ns = canonicalize_attr_name (attr_ns); + attr_ns = canonicalize_attr_ns_name (attr_ns); attr_id = canonicalize_attr_name (attr_id); attribute = build_tree_list (build_tree_list (attr_ns, attr_id), NULL_TREE); @@ -33344,7 +33360,7 @@ cp_parser_std_attribute (cp_parser *pars } else if (attr_ns) { - attr_ns = canonicalize_attr_name (attr_ns); + attr_ns = canonicalize_attr_ns_name (attr_ns); attr_id = canonicalize_attr_name (attr_id); attribute = build_tree_list (build_tree_list (attr_ns, attr_id), NULL_TREE); @@ -34097,7 +34113,6 @@ cp_parser_std_attribute_spec (cp_parser && cp_lexer_peek_nth_token (parser->lexer, 2)->type == CPP_OPEN_SQUARE) { tree attr_ns = NULL_TREE; - tree attr_name = NULL_TREE; cp_lexer_consume_token (parser->lexer); cp_lexer_consume_token (parser->lexer); @@ -34117,12 +34132,6 @@ cp_parser_std_attribute_spec (cp_parser return attributes; } - if (token->type == CPP_NAME) - { - attr_name = token->u.value; - attr_name = canonicalize_attr_name (attr_name); - } - if (cp_lexer_next_token_is_keyword (parser->lexer, RID_USING)) { token = cp_lexer_peek_nth_token (parser->lexer, 2); @@ -34141,6 +34150,11 @@ cp_parser_std_attribute_spec (cp_parser "attribute using prefix only available " "with %<-std=c++17%> or %<-std=gnu++17%>"); + if (id_equal (attr_ns, "__clang__")) + warning_at (token->location, OPT_Wattributes, + "alternate attribute namespace for %<clang%> is " + "%<_Clang%> rather than %<__clang__%>"); + cp_lexer_consume_token (parser->lexer); cp_lexer_consume_token (parser->lexer); cp_lexer_consume_token (parser->lexer); --- gcc/testsuite/g++.dg/cpp0x/attr-trivial_abi8.C.jj 2026-07-10 09:13:29.716984740 +0200 +++ gcc/testsuite/g++.dg/cpp0x/attr-trivial_abi8.C 2026-07-14 19:40:32.829768137 +0200 @@ -19,3 +19,28 @@ #if __has_attribute (trivial_abi) != 1 #error #endif +#if __has_cpp_attribute (_Clang::__trivial_abi__) != 1 +#error +#endif +#if __has_cpp_attribute (__gnu__::__trivial_abi__) != 0 +#error +#endif +#if __has_cpp_attribute (__trivial_abi__) != 0 +#error This fails for now +// { dg-bogus "This fails for now" "" { xfail *-*-* } .-1 } +#endif +#if __has_attribute (_Clang::__trivial_abi__) != 1 +#error +#endif +#if __has_attribute (__gnu__::__trivial_abi__) != 0 +#error +#endif +#if __has_attribute (__trivial_abi__) != 1 +#error +#endif +#if __has_cpp_attribute (__clang__::__trivial_abi__) != 0 +#error +#endif +#if __has_attribute (__clang__::__trivial_abi__) != 0 +#error +#endif --- gcc/testsuite/g++.dg/cpp0x/attr-trivial_abi11.C.jj 2026-07-14 19:37:01.965388812 +0200 +++ gcc/testsuite/g++.dg/cpp0x/attr-trivial_abi11.C 2026-07-14 19:40:17.261961617 +0200 @@ -0,0 +1,11 @@ +// { dg-do compile { target c++11 } } +// Test basic functionality of [[clang::trivial_abi]] attribute + +struct [[_Clang::__trivial_abi__]] A { int a; ~A () {} }; +#if __cplusplus >= 201703L +struct [[using _Clang:__trivial_abi__]] B { int b; ~B () {} }; +#endif +struct [[__clang__::__trivial_abi__]] C { int c; ~C () {} }; // { dg-warning "alternate attribute namespace for 'clang' is '_Clang' rather than '__clang__'" } +#if __cplusplus >= 201703L +struct [[using __clang__:__trivial_abi__]] D { int d; ~D () {} }; // { dg-warning "alternate attribute namespace for 'clang' is '_Clang' rather than '__clang__'" "" { target c++17 } } +#endif --- gcc/testsuite/g++.dg/ext/attr-no_specializations1.C.jj 2026-07-13 18:37:33.609239688 +0200 +++ gcc/testsuite/g++.dg/ext/attr-no_specializations1.C 2026-07-14 19:09:45.302708017 +0200 @@ -8,7 +8,7 @@ template <typename T, typename U> [[clang::no_specializations]] int b = 1; // { dg-message "declared 'clang::no_specializations' here" "" { target c++14 } } #endif template <typename T, typename U> -[[__clang__::__no_specializations__]] int foo () { return 0; } // { dg-message "declared 'clang::no_specializations' here" } +[[_Clang::__no_specializations__]] int foo () { return 0; } // { dg-message "declared 'clang::no_specializations' here" } template <typename T> struct B { struct [[clang::no_specializations]] C {}; // { dg-warning "'no_specializations' attribute only applies to class, function or variable templates" } --- gcc/testsuite/g++.dg/ext/attr-no_specializations2.C.jj 2026-07-13 18:37:33.609239688 +0200 +++ gcc/testsuite/g++.dg/ext/attr-no_specializations2.C 2026-07-14 19:09:50.652641766 +0200 @@ -5,7 +5,7 @@ template <typename T, typename U> union [[clang::no_specializations ("reason1")]] A {}; // { dg-message "declared 'clang::no_specializations' here" } #if __cpp_variable_templates >= 201304 template <typename T, typename U> -[[__clang__::__no_specializations__ ("reason2")]] int b = 1; // { dg-message "declared 'clang::no_specializations' here" "" { target c++14 } } +[[_Clang::__no_specializations__ ("reason2")]] int b = 1; // { dg-message "declared 'clang::no_specializations' here" "" { target c++14 } } #endif template <typename T, typename U> [[clang::no_specializations ("reason3")]] int foo () { return 0; } // { dg-message "declared 'clang::no_specializations' here" } --- gcc/testsuite/g++.dg/ext/attr-no_specializations6.C.jj 2026-07-13 18:37:33.609239688 +0200 +++ gcc/testsuite/g++.dg/ext/attr-no_specializations6.C 2026-07-14 19:09:57.254560014 +0200 @@ -9,7 +9,7 @@ template <typename T, typename U> [[clang::no_specializations]] constexpr int b = 1; // { dg-message "declared 'clang::no_specializations' here" "" { target c++14 } } #endif template <typename T, typename U> -[[__clang__::__no_specializations__]] int foo () { return 0; } // { dg-message "declared 'clang::no_specializations' here" } +[[_Clang::__no_specializations__]] int foo () { return 0; } // { dg-message "declared 'clang::no_specializations' here" } template <typename T> struct D {}; template <> @@ -35,7 +35,7 @@ template <typename T, typename U> [[clang::no_specializations ("my other reason")]] int c = 1; // { dg-message "declared 'clang::no_specializations' here" "" { target c++14 } } #endif template <typename T, typename U> -[[__clang__::__no_specializations__ ("bar cannot be specialized")]] int bar () { return 0; } // { dg-message "declared 'clang::no_specializations' here" } +[[_Clang::__no_specializations__ ("bar cannot be specialized")]] int bar () { return 0; } // { dg-message "declared 'clang::no_specializations' here" } template <> struct B <int, int> { int a; }; // { dg-error "'struct B<int, int>' cannot be specialized: 'this is why'" } template <typename T> --- gcc/testsuite/g++.dg/ext/attr-no_specializations7.C.jj 2026-07-13 18:37:33.609239688 +0200 +++ gcc/testsuite/g++.dg/ext/attr-no_specializations7.C 2026-07-14 19:10:02.487495214 +0200 @@ -9,7 +9,7 @@ template <typename T, typename U> [[clang::no_specializations]] constexpr int b = 1; // { dg-bogus "declared 'clang::no_specializations' here" } #endif template <typename T, typename U> -[[__clang__::__no_specializations__]] int foo () { return 0; } // { dg-bogus "declared 'clang::no_specializations' here" } +[[_Clang::__no_specializations__]] int foo () { return 0; } // { dg-bogus "declared 'clang::no_specializations' here" } template <typename T> struct D {}; template <> @@ -35,7 +35,7 @@ template <typename T, typename U> [[clang::no_specializations ("my other reason")]] int c = 1; // { dg-bogus "declared 'clang::no_specializations' here" } #endif template <typename T, typename U> -[[__clang__::__no_specializations__ ("bar cannot be specialized")]] int bar () { return 0; } // { dg-bogus "declared 'clang::no_specializations' here" } +[[_Clang::__no_specializations__ ("bar cannot be specialized")]] int bar () { return 0; } // { dg-bogus "declared 'clang::no_specializations' here" } template <> struct B <int, int> { int a; }; // { dg-bogus "'struct B<int, int>' cannot be specialized: 'this is why'" } template <typename T> --- gcc/testsuite/g++.dg/ext/attr-no_specializations8.C.jj 2026-07-13 18:37:33.609239688 +0200 +++ gcc/testsuite/g++.dg/ext/attr-no_specializations8.C 2026-07-14 19:10:06.950439951 +0200 @@ -10,7 +10,7 @@ template <typename T, typename U> [[clang::no_specializations ("my other reason")]] int c = 1; // { dg-message "declared 'clang::no_specializations' here" "" { target c++14 } } #endif template <typename T, typename U> -[[__clang__::__no_specializations__ ("bar cannot be specialized")]] int bar () { return 0; } // { dg-message "declared 'clang::no_specializations' here" } +[[_Clang::__no_specializations__ ("bar cannot be specialized")]] int bar () { return 0; } // { dg-message "declared 'clang::no_specializations' here" } template <typename T> struct D {}; template <> --- gcc/testsuite/g++.dg/ext/attr-no_specializations10.C.jj 2026-07-13 18:37:33.609239688 +0200 +++ gcc/testsuite/g++.dg/ext/attr-no_specializations10.C 2026-07-14 19:25:54.545681090 +0200 @@ -20,3 +20,28 @@ #if __has_attribute (no_specializations) != 1 #error #endif +#if __has_cpp_attribute (_Clang::__no_specializations__) != 1 +#error +#endif +#if __has_cpp_attribute (__gnu__::__no_specializations__) != 0 +#error +#endif +#if __has_cpp_attribute (__no_specializations__) != 0 +#error This fails for now +// { dg-bogus "This fails for now" "" { xfail *-*-* } .-1 } +#endif +#if __has_attribute (_Clang::__no_specializations__) != 1 +#error +#endif +#if __has_attribute (__gnu__::__no_specializations__) != 0 +#error +#endif +#if __has_attribute (__no_specializations__) != 1 +#error +#endif +#if __has_cpp_attribute (__clang__::__no_specializations__) != 0 +#error +#endif +#if __has_attribute (__clang__::__no_specializations__) != 0 +#error +#endif --- gcc/testsuite/g++.dg/ext/attr-no_specializations12.C.jj 2026-07-14 19:26:59.630873310 +0200 +++ gcc/testsuite/g++.dg/ext/attr-no_specializations12.C 2026-07-14 19:35:04.325850859 +0200 @@ -0,0 +1,15 @@ +// PR c++/120635 +// { dg-do compile { target c++17 } } + +template <typename T, typename U> +struct [[using _Clang:__no_specializations__]] A {}; +template <typename T, typename U> +struct [[using _Clang:__no_specializations__ ("foo")]] B {}; +template <typename T, typename U> +struct [[__clang__::__no_specializations__]] C {}; // { dg-warning "alternate attribute namespace for 'clang' is '_Clang' rather than '__clang__'" } +template <typename T, typename U> +struct [[__clang__::__no_specializations__ ("foo")]] D {}; // { dg-warning "alternate attribute namespace for 'clang' is '_Clang' rather than '__clang__'" } +template <typename T, typename U> +struct [[using __clang__:__no_specializations__]] E {}; // { dg-warning "alternate attribute namespace for 'clang' is '_Clang' rather than '__clang__'" } +template <typename T, typename U> +struct [[using __clang__:__no_specializations__ ("foo")]] F {}; // { dg-warning "alternate attribute namespace for 'clang' is '_Clang' rather than '__clang__'" } Jakub
