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

Reply via email to