Re: [PATCH] c++, v2: Handle argument merging push_local_extern_decl_alias [PR123825]

2026-01-30 Thread Jason Merrill

On 1/30/26 5:50 PM, Jakub Jelinek wrote:

On Fri, Jan 30, 2026 at 10:08:20AM +0800, Jason Merrill wrote:

+  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),
+   "% 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.


!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.


I'm not sure that is a good idea, having -freflection affect e.g. whether
indeterminate attribute will be diagnosed or not is just weird.

It could be guarded on if (cxx_dialect >= cxx26) for now, or it could
just do the if (flag_reflection) handling in the function and not the
attribute/indeterminate handling for now.

Note, some attribute "merging" is done already unconditionally, when
there is no existing decl at the namespace scope yet, we use copies of the
parameters (well, due to the bug the patch is also fixing first parameter
only) and so in that case the attributes on it are propagated.

And guess we should test whether annotations are or are not gathered
across the local externs and/or their parameters,
[[=1]] void foo ([[=2]] int);
void
bar ()
{
   [[=3]] extern void foo ([[=4]] int);
}
static_assert (annotations_of (^^foo).size () == 1);
static_assert (annotations_of (parameters_of (^^foo)[0]).size () == 2);

Though, e.g. the clang reflection branch returns 2 annotations for ^^foo
and doesn't support annotations on parameters yet.

CCing Daniel for his thoughts on this.

Jakub





Re: [PATCH] c++, v2: Handle argument merging push_local_extern_decl_alias [PR123825]

2026-01-30 Thread Jakub Jelinek
On Fri, Jan 30, 2026 at 10:08:20AM +0800, Jason Merrill wrote:
> > +  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),
> > +   "% 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.

!extern_alias || flag_reflection could be done basically by only calling
the new function if (flag_reflection) from name-lookup.cc.
I'm not sure that is a good idea, having -freflection affect e.g. whether
indeterminate attribute will be diagnosed or not is just weird.

It could be guarded on if (cxx_dialect >= cxx26) for now, or it could
just do the if (flag_reflection) handling in the function and not the
attribute/indeterminate handling for now.

Note, some attribute "merging" is done already unconditionally, when
there is no existing decl at the namespace scope yet, we use copies of the
parameters (well, due to the bug the patch is also fixing first parameter
only) and so in that case the attributes on it are propagated.

And guess we should test whether annotations are or are not gathered
across the local externs and/or their parameters,
[[=1]] void foo ([[=2]] int);
void
bar ()
{
  [[=3]] extern void foo ([[=4]] int);
}
static_assert (annotations_of (^^foo).size () == 1);
static_assert (annotations_of (parameters_of (^^foo)[0]).size () == 2);

Though, e.g. the clang reflection branch returns 2 annotations for ^^foo
and doesn't support annotations on parameters yet.

CCing Daniel for his thoughts on this.

Jakub



Re: [PATCH] c++, v2: Handle argument merging push_local_extern_decl_alias [PR123825]

2026-01-29 Thread Jason Merrill

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  

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.h2026-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),
+   "% 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
+ 

Re: [PATCH] c++, v2: Handle argument merging push_local_extern_decl_alias [PR123825]

2026-01-29 Thread Jakub Jelinek
On Wed, Jan 28, 2026 at 02:04:46PM +0100, 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?

Bootstrapped/regtested successfully on x86_64-linux and i686-linux.

> 2026-01-28  Jakub Jelinek  
> 
>   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.

Jakub



[PATCH] c++, v2: Handle argument merging push_local_extern_decl_alias [PR123825]

2026-01-28 Thread Jakub Jelinek
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  

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.h2026-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),
+   "% 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;
+ M