On 1/31/26 12:54 AM, Jakub Jelinek wrote:
On Fri, Jan 30, 2026 at 09:36:15PM +0800, Jason Merrill wrote:
!extern_alias || flag_reflection could be done basically by only calling
the new function if (flag_reflection) from name-lookup.cc.

Ah, I meant limiting only the one line

  +      DECL_ATTRIBUTES (oldarg) = DECL_ATTRIBUTES (newarg);

not anything else.

Ah, ok.  I was still worried about indeterminate attribute, but
if oldarg doesn't have that attribute and newarg does, then that is
exactly the case the earlier few lines error on and so it doesn't
really matter if we update DECL_ATTRIBUTES (oldarg) or not for that.

So here is what I'll test tonight.

2026-01-30  Jakub Jelinek  <[email protected]>

        PR c++/123825
        * cp-tree.h (merge_decl_arguments): Declare.
        * decl.cc (duplicate_decls): Outline DECL_ARGUMENTS handling
        into ...
        (merge_decl_arguments): ... new function.
        * name-lookup.cc (push_local_extern_decl_alias): Call
        merge_decl_arguments.  Don't copy just the first PARM_DECL when
        creating a new alias FUNCTION_DECL.

        * g++.dg/reflect/has_identifier3.C: New test.
        * g++.dg/reflect/identifier_of3.C: New test.
        * g++.dg/cpp26/attr-indeterminate5.C: New test.

--- gcc/cp/cp-tree.h.jj 2026-01-30 17:44:13.381013056 +0100
+++ gcc/cp/cp-tree.h    2026-01-30 17:46:14.796089898 +0100
@@ -7456,6 +7456,7 @@ extern int decls_match                            (tree, 
tree, b
  extern bool maybe_version_functions           (tree, tree);
  extern bool validate_constexpr_redeclaration  (tree, tree);
  extern bool merge_default_template_args               (tree, tree, bool);
+extern void merge_decl_arguments               (tree, tree, bool, bool, bool);
  extern tree duplicate_decls                   (tree, tree,
                                                 bool hiding = false,
                                                 bool was_hidden = false);
--- gcc/cp/decl.cc.jj   2026-01-30 17:44:13.385012989 +0100
+++ gcc/cp/decl.cc      2026-01-30 17:48:36.196564509 +0100
@@ -1781,6 +1781,95 @@ merge_default_template_args (tree new_pa
    return true;
  }
+/* Helper function for duplicate_decls and push_local_extern_decl_alias.
+   Merge parameter attributes and names between NEWDECL and OLDDECL.
+   NEW_DEFINES_FUNCTION and TYPES_MATCH argument like variables in
+   duplicate_decls, EXTERN_ALIAS false for duplicate_decls and true for
+   push_local_extern_decl_alias.  */
+
+void
+merge_decl_arguments (tree newdecl, tree olddecl, bool new_defines_function,
+                     bool types_match, bool extern_alias)
+{
+  tree oldarg, newarg;
+  for (oldarg = DECL_ARGUMENTS (olddecl), newarg = DECL_ARGUMENTS (newdecl);
+       oldarg && newarg;
+       oldarg = DECL_CHAIN (oldarg), newarg = DECL_CHAIN (newarg))
+    {
+      DECL_ATTRIBUTES (newarg)
+       = (*targetm.merge_decl_attributes) (oldarg, newarg);
+      if (lookup_attribute (NULL, "indeterminate", DECL_ATTRIBUTES (newarg))
+         && !lookup_attribute (NULL, "indeterminate",
+                               DECL_ATTRIBUTES (oldarg)))
+       {
+         auto_diagnostic_group d;
+         error_at (DECL_SOURCE_LOCATION (newarg),
+                   "%<indeterminate%> attribute not specified for parameter "
+                   "%qD on the first declaration of its function", newarg);
+         inform (DECL_SOURCE_LOCATION (oldarg), "earlier declaration");
+       }

Let's add a comment

/* ??? Should attributes propagate out from a block extern?  If so,
   we should do that for the function itself, not just parameters.  */

OK with that addition.

+      if (!extern_alias || flag_reflection)
+       DECL_ATTRIBUTES (oldarg) = DECL_ATTRIBUTES (newarg);
+      /* Merge names for std::meta::has_identifier and
+        std::meta::{,u8}identifier_of purposes.  If they are different and
+        both oldarg and newarg are named, add flag to force that
+        std::meta::has_identifier returns false.  If one is named and one is
+        unnamed, if neither is a olddecl nor newdecl is definition, propagate
+        DECL_NAME to both.  Otherwise stash the old name into "old parm name"
+        artificial attribute.  */
+      if (flag_reflection && DECL_NAME (oldarg) != DECL_NAME (newarg))
+       {
+         if (DECL_NAME (oldarg) && DECL_NAME (newarg))
+           {
+             /* Different names.  */
+             MULTIPLE_NAMES_PARM_P (oldarg) = 1;
+             MULTIPLE_NAMES_PARM_P (newarg) = 1;
+           }
+         else if (!new_defines_function
+                  && types_match
+                  && DECL_INITIAL (olddecl) == NULL_TREE)
+           {
+             /* For 2 non-definitions with matching types, one is named and
+                one unnamed, propagate name to both.  */
+             if (DECL_NAME (oldarg))
+               DECL_NAME (newarg) = DECL_NAME (oldarg);
+             else
+               DECL_NAME (oldarg) = DECL_NAME (newarg);
+           }
+         /* Depending on which PARM_DECL we'll keep, look at the other
+            PARM_DECL's name.  */
+         else if (tree name = ((new_defines_function || !types_match)
+                               ? DECL_NAME (oldarg) : DECL_NAME (newarg)))
+           {
+             tree opn = lookup_attribute ("old parm name",
+                                          DECL_ATTRIBUTES (oldarg));
+             if (opn)
+               {
+                 if (TREE_VALUE (TREE_VALUE (opn)) == name)
+                   /* Name already in "old parm name" attribute.  */;
+                 else
+                   {
+                     /* Different names.  */
+                     MULTIPLE_NAMES_PARM_P (oldarg) = 1;
+                     MULTIPLE_NAMES_PARM_P (newarg) = 1;
+                   }
+               }
+             else
+               {
+                 /* Save name into attribute.  */
+                 DECL_ATTRIBUTES (newarg)
+                   = tree_cons (get_identifier ("old parm name"),
+                                tree_cons (NULL_TREE, name, NULL_TREE),
+                                DECL_ATTRIBUTES (newarg));
+                 DECL_ATTRIBUTES (oldarg) = DECL_ATTRIBUTES (newarg);
+               }
+           }
+         else if (extern_alias)
+           DECL_NAME (newarg) = DECL_NAME (oldarg);
+       }
+    }
+}
+
  /* If NEWDECL is a redeclaration of OLDDECL, merge the declarations.
     If the redeclaration is invalid, a diagnostic is issued, and the
     error_mark_node is returned.  Otherwise, OLDDECL is returned.
@@ -3038,88 +3127,8 @@ duplicate_decls (tree newdecl, tree oldd
if (TREE_CODE (newdecl) == FUNCTION_DECL)
      {
-      tree parm;
-
-      /* Merge parameter attributes. */
-      tree oldarg, newarg;
-      for (oldarg = DECL_ARGUMENTS (olddecl),
-          newarg = DECL_ARGUMENTS (newdecl);
-          oldarg && newarg;
-          oldarg = DECL_CHAIN (oldarg), newarg = DECL_CHAIN (newarg))
-       {
-          DECL_ATTRIBUTES (newarg)
-           = (*targetm.merge_decl_attributes) (oldarg, newarg);
-         if (lookup_attribute (NULL, "indeterminate",
-                               DECL_ATTRIBUTES (newarg))
-             && !lookup_attribute (NULL, "indeterminate",
-                                   DECL_ATTRIBUTES (oldarg)))
-           {
-             auto_diagnostic_group d;
-             error_at (DECL_SOURCE_LOCATION (newarg),
-                       "%<indeterminate%> attribute not specified "
-                       "for parameter %qD on the first declaration of "
-                       "its function", newarg);
-             inform (DECL_SOURCE_LOCATION (oldarg),
-                     "earlier declaration");
-           }
-          DECL_ATTRIBUTES (oldarg) = DECL_ATTRIBUTES (newarg);
-         /* Merge names for std::meta::has_identifier and
-            std::meta::{,u8}identifier_of purposes.  If they are different
-            and both oldarg and newarg are named, add flag to force that
-            std::meta::has_identifier returns false.  If one is named and
-            one is unnamed, if neither is a olddecl nor newdecl is definition,
-            propagate DECL_NAME to both.  Otherwise stash the old name into
-            "old parm name" artificial attribute.  */
-         if (flag_reflection && DECL_NAME (oldarg) != DECL_NAME (newarg))
-           {
-             if (DECL_NAME (oldarg) && DECL_NAME (newarg))
-               {
-                 /* Different names.  */
-                 MULTIPLE_NAMES_PARM_P (oldarg) = 1;
-                 MULTIPLE_NAMES_PARM_P (newarg) = 1;
-               }
-             else if (!new_defines_function
-                      && types_match
-                      && DECL_INITIAL (olddecl) == NULL_TREE)
-               {
-                 /* For 2 non-definitions with matching types,
-                    one is named and one unnamed, propagate name
-                    to both.  */
-                 if (DECL_NAME (oldarg))
-                   DECL_NAME (newarg) = DECL_NAME (oldarg);
-                 else
-                   DECL_NAME (oldarg) = DECL_NAME (newarg);
-               }
-             /* Depending on which PARM_DECL we'll keep, look at the other
-                PARM_DECL's name.  */
-             else if (tree name = ((new_defines_function || !types_match)
-                                   ? DECL_NAME (oldarg) : DECL_NAME (newarg)))
-               {
-                 tree opn = lookup_attribute ("old parm name",
-                                              DECL_ATTRIBUTES (oldarg));
-                 if (opn)
-                   {
-                     if (TREE_VALUE (TREE_VALUE (opn)) == name)
-                       /* Name already in "old parm name" attribute.  */;
-                     else
-                       {
-                         /* Different names.  */
-                         MULTIPLE_NAMES_PARM_P (oldarg) = 1;
-                         MULTIPLE_NAMES_PARM_P (newarg) = 1;
-                       }
-                   }
-                 else
-                   {
-                     /* Save name into attribute.  */
-                     DECL_ATTRIBUTES (newarg)
-                       = tree_cons (get_identifier ("old parm name"),
-                                    tree_cons (NULL_TREE, name, NULL_TREE),
-                                    DECL_ATTRIBUTES (newarg));
-                     DECL_ATTRIBUTES (oldarg) = DECL_ATTRIBUTES (newarg);
-                   }
-               }
-           }
-       }
+      merge_decl_arguments (newdecl, olddecl, new_defines_function,
+                           types_match, false);
if (DECL_TEMPLATE_INSTANTIATION (olddecl)
          && !DECL_TEMPLATE_INSTANTIATION (newdecl))
@@ -3198,7 +3207,7 @@ duplicate_decls (tree newdecl, tree oldd
        DECL_ABSTRACT_P (newdecl) = DECL_ABSTRACT_P (olddecl);
/* Update newdecl's parms to point at olddecl. */
-      for (parm = DECL_ARGUMENTS (newdecl); parm;
+      for (tree parm = DECL_ARGUMENTS (newdecl); parm;
           parm = DECL_CHAIN (parm))
        DECL_CONTEXT (parm) = olddecl;
--- gcc/cp/name-lookup.cc.jj 2026-01-30 17:44:13.387012955 +0100
+++ gcc/cp/name-lookup.cc       2026-01-30 17:46:14.798724206 +0100
@@ -3736,6 +3736,8 @@ push_local_extern_decl_alias (tree decl)
              alias = *iter;
              if (!validate_constexpr_redeclaration (alias, decl))
                return;
+             if (TREE_CODE (decl) == FUNCTION_DECL)
+               merge_decl_arguments (decl, alias, false, true, true);
              break;
            }
@@ -3749,7 +3751,9 @@ push_local_extern_decl_alias (tree decl)
              for (tree *chain = &DECL_ARGUMENTS (alias);
                   *chain; chain = &DECL_CHAIN (*chain))
                {
+                 tree next = DECL_CHAIN (*chain);
                  *chain = copy_decl (*chain);
+                 DECL_CHAIN (*chain) = next;
                  DECL_CONTEXT (*chain) = alias;
                }
--- gcc/testsuite/g++.dg/reflect/has_identifier3.C.jj 2026-01-30 17:46:14.799227375 +0100
+++ gcc/testsuite/g++.dg/reflect/has_identifier3.C      2026-01-30 
17:46:14.799227375 +0100
@@ -0,0 +1,23 @@
+// PR c++/123825
+// { dg-do compile { target c++26 } }
+// { dg-additional-options "-freflection" }
+// Test std::meta::has_identifier.
+
+#include <meta>
+
+void fun (int);
+constexpr std::meta::info r = parameters_of (^^fun)[0];
+static_assert (!has_identifier (r));
+
+void fun (int x);
+static_assert (has_identifier (r));
+
+void fun (int x);
+static_assert (has_identifier (r));
+
+void
+poison ()
+{
+  void fun (int y);
+}
+static_assert (!has_identifier (r));
--- gcc/testsuite/g++.dg/reflect/identifier_of3.C.jj    2026-01-30 
17:46:14.799329100 +0100
+++ gcc/testsuite/g++.dg/reflect/identifier_of3.C       2026-01-30 
17:46:14.799329100 +0100
@@ -0,0 +1,73 @@
+// PR c++/123825
+// { dg-do compile { target c++26 } }
+// { dg-additional-options "-freflection" }
+// Test std::meta::identifier_of.
+
+#include <meta>
+
+void foo (int, int x, int y, int z);
+
+int
+bar (int, int x, int y, int z, int)
+{
+  return x + y + z;
+}
+
+constexpr auto foo1 = parameters_of (^^foo)[0];
+constexpr auto foo2 = parameters_of (^^foo)[1];
+constexpr auto foo3 = parameters_of (^^foo)[2];
+constexpr auto foo4 = parameters_of (^^foo)[3];
+constexpr auto bar1 = parameters_of (^^bar)[0];
+constexpr auto bar2 = parameters_of (^^bar)[1];
+constexpr auto bar3 = parameters_of (^^bar)[2];
+constexpr auto bar4 = parameters_of (^^bar)[3];
+constexpr auto bar5 = parameters_of (^^bar)[4];
+static_assert (!has_identifier (foo1));
+static_assert (identifier_of (foo2) == std::string_view ("x"));
+static_assert (identifier_of (foo3) == std::string_view ("y"));
+static_assert (identifier_of (foo4) == std::string_view ("z"));
+static_assert (!has_identifier (bar1));
+static_assert (identifier_of (bar2) == std::string_view ("x"));
+static_assert (identifier_of (bar3) == std::string_view ("y"));
+static_assert (identifier_of (bar4) == std::string_view ("z"));
+static_assert (!has_identifier (bar5));
+
+void
+baz ()
+{
+  void foo (int w, int, int v, int z);
+  int bar (int, int, int v, int z, int u);
+  void qux (int, int x, int y, int z);
+  constexpr auto qux1 = parameters_of (^^qux)[0];
+  constexpr auto qux2 = parameters_of (^^qux)[1];
+  constexpr auto qux3 = parameters_of (^^qux)[2];
+  constexpr auto qux4 = parameters_of (^^qux)[3];
+  static_assert (!has_identifier (qux1));
+  static_assert (identifier_of (qux2) == std::string_view ("x"));
+  static_assert (identifier_of (qux3) == std::string_view ("y"));
+  static_assert (identifier_of (qux4) == std::string_view ("z"));
+}
+
+static_assert (identifier_of (foo1) == std::string_view ("w"));
+static_assert (identifier_of (foo2) == std::string_view ("x"));
+static_assert (!has_identifier (foo3));
+static_assert (identifier_of (foo4) == std::string_view ("z"));
+static_assert (!has_identifier (bar1));
+static_assert (identifier_of (bar2) == std::string_view ("x"));
+static_assert (!has_identifier (bar3));
+static_assert (identifier_of (bar4) == std::string_view ("z"));
+static_assert (identifier_of (bar5) == std::string_view ("u"));
+
+void
+fred ()
+{
+  void qux (int w, int, int v, int z);
+  constexpr auto qux1 = parameters_of (^^qux)[0];
+  constexpr auto qux2 = parameters_of (^^qux)[1];
+  constexpr auto qux3 = parameters_of (^^qux)[2];
+  constexpr auto qux4 = parameters_of (^^qux)[3];
+  static_assert (identifier_of (qux1) == std::string_view ("w"));
+  static_assert (identifier_of (qux2) == std::string_view ("x"));
+  static_assert (!has_identifier (qux3));
+  static_assert (identifier_of (qux4) == std::string_view ("z"));
+}
--- gcc/testsuite/g++.dg/cpp26/attr-indeterminate5.C.jj 2026-01-30 
17:46:14.799475225 +0100
+++ gcc/testsuite/g++.dg/cpp26/attr-indeterminate5.C    2026-01-30 
17:46:14.799475225 +0100
@@ -0,0 +1,23 @@
+// C++ 26 P2795R5 - Erroneous behaviour for uninitialized reads
+// { dg-do compile { target c++11 } }
+// { dg-skip-if "" { c++26 } { "-ftrivial-auto-var-init=*" } { "" } }
+
+struct S { S (); S (const S &); ~S (); int s; };
+void foo (S s);                                                        // { dg-message 
"earlier declaration" }
+void bar (S s [[indeterminate]]);
+void baz (S s [[indeterminate]]);
+
+void
+fred ()
+{
+  void foo (S t [[indeterminate]]);                            // { dg-error 
"'indeterminate' attribute not specified for parameter 't' on the first declaration 
of its function" }
+  void bar (S t [[indeterminate]]);
+  void baz (S t);
+  void qux (S t);                                              // { dg-message 
"earlier declaration" }
+  void corge (S t [[indeterminate]]);
+  void garply (S t [[indeterminate]]);
+}
+
+void qux (S u [[indeterminate]]);                              // { dg-error 
"'indeterminate' attribute not specified for parameter 'u' on the first declaration 
of its function" }
+void corge (S u [[indeterminate]]);
+void garply (S u);


        Jakub


Reply via email to