On 1/28/26 9:04 PM, Jakub Jelinek wrote:
On Wed, Jan 28, 2026 at 11:35:58AM +0800, Jason Merrill wrote:
Can we share code with duplicate_decls?  The problem you're seeing seems to
be due to the different code paths.

So like this?

2026-01-28  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-28 12:27:52.252582912 +0100
+++ gcc/cp/cp-tree.h    2026-01-28 13:45:08.246443065 +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-28 09:34:32.750243398 +0100
+++ gcc/cp/decl.cc      2026-01-28 13:44:57.963618703 +0100
@@ -1781,6 +1781,94 @@ 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");
+       }
+      DECL_ATTRIBUTES (oldarg) = DECL_ATTRIBUTES (newarg);

Hmm, I'm uncertain about unconditionally copying attributes from a local extern to a namespace-scope declaration, and am having trouble finding normative wording about what gets merged between declarations in different scopes for the same entity. Default arguments don't, but they are properties of the declaration rather than the entity, while attributes are described as applying to the entity. So I guess this is reasonable.

But since it's a change from existing behavior, let's limit it to !extern_alias || flag_reflection for GCC 16, and make it unconditional for GCC 17.

OK with that adjustment.

+      /* 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 +3126,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 +3206,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-28 09:34:32.754243330 +0100
+++ gcc/cp/name-lookup.cc       2026-01-28 13:45:28.123103551 +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-28 13:21:26.904852201 +0100
+++ gcc/testsuite/g++.dg/reflect/has_identifier3.C      2026-01-28 
13:21:26.904852201 +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-28 
13:21:26.904943481 +0100
+++ gcc/testsuite/g++.dg/reflect/identifier_of3.C       2026-01-28 
13:21:26.904943481 +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-28 
13:21:26.905084472 +0100
+++ gcc/testsuite/g++.dg/cpp26/attr-indeterminate5.C    2026-01-28 
13:21:26.905084472 +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