On Wed, 7 Aug 2024, Patrick Palka wrote:

> On Thu, 8 Aug 2024, Nathaniel Shead wrote:
> 
> > On Wed, Aug 07, 2024 at 01:44:31PM -0400, Jason Merrill wrote:
> > > On 8/6/24 2:35 AM, Nathaniel Shead wrote:
> > > > Bootstrapped and regtested on x86_64-pc-linux-gnu, OK for trunk?
> > > > 
> > > > Another potential approach would be to go searching for this unexported
> > > > type and load it, either with a new LOOK_want::ANY_REACHABLE flag or by
> > > > expanding on the lookup_imported_temploid_friend hack.  I'm still not
> > > > exactly sure how name lookup for template friends is supposed to behave
> > > > though, specifically in terms of when and where they should conflict
> > > > with other entities with the same name.
> > > 
> > > CWG2588 tried to clarify this in https://eel.is/c++draft/basic#link-4.8 --
> > > if there's a matching reachable declaration, the friend refers to it even 
> > > if
> > > it isn't visible to name lookup.
> > > 
> > > It seems like an oversight that the new second bullet refers specifically 
> > > to
> > > functions, it seems natural for it to apply to classes as well.
> > > 
> > > So, they correspond but do not conflict because they declare the same
> > > entity.
> > > 
> > 
> > Right, makes sense.  OK, I'll work on filling out our testcases to make
> > sure that we cover everything under that interpretation and potentially
> > come back to making an ANY_REACHABLE flag for this.
> > 
> > > > The relevant paragraphs seem to be https://eel.is/c++draft/temp.friend#2
> > > > and/or https://eel.is/c++draft/dcl.meaning.general#2.2.2, in addition to
> > > > the usual rules in [basic.link] and [basic.scope.scope], but how these
> > > > all are supposed to interact isn't super clear to me right now.
> > > > 
> > > > Additionally I wonder if maybe the better approach long-term would be to
> > > > focus on getting textual redefinitions working first, and then reuse
> > > > whatever logic we build for that to handle template friends rather than
> > > > relying on finding these hidden 'mergeable' slots first.
> > > 
> > > I have a WIP patch to allow textual redefinitions by teaching
> > > duplicate_decls that it's OK to redefine an imported GM entity, so
> > > check_module_override works.
> > > 
> > > My current plan is to then just token-skip the bodies.  This won't 
> > > diagnose
> > > ODR problems, but our module merging doesn't do that consistently either.
> > > 
> > > > @@ -11800,6 +11800,15 @@ tsubst_friend_class (tree friend_tmpl, tree 
> > > > args)
> > > >             input_location = saved_input_location;
> > > >         }
> > > >       }
> > > > +  else if (TMPL_PARMS_DEPTH (DECL_TEMPLATE_PARMS (friend_tmpl))
> > > > +          <= TMPL_ARGS_DEPTH (args))
> > > 
> > > This condition seems impossible normally; it's only satisfied in this
> > > testcase because friend_tmpl doesn't actually represent the friend
> > > declaration, it's already the named class template.  So the substitution 
> > > in
> > > the next else fails because it was done already.
> > > 
> > > If this condition is true, we could set tmpl = friend_tmpl earlier, and 
> > > skip
> > > doing name lookup at all.
> > > 
> > > It's interesting that the previous if does the same depth comparison, and
> > > that dates back to 2002; I wonder why it was needed then?

I reckon the depth comparison in the previous if is equivalent to:

 if (DECL_UNINSTANTIATED_TEMPLATE_FRIEND_P (friend_tmpl))

But unfortunately we can't skip doing name lookup in that case due to
the mentioned example :/

> > > 
> > > Jason
> > > 
> > 
> > Ah right, I see.  I think the depth comparison in the previous if
> > actually is for exactly the same reason, just for the normal case when
> > the template *is* found by name lookup, e.g. 
> > 
> >   template <typename> struct A {};
> >   template <typename> struct B {
> >     template <typename> friend struct ::A;
> >   };
> > 
> > This is g++.dg/template/friend5.  Here's an updated patch which is so
> > far very lightly tested, OK for trunk if full bootstrap+regtest
> > succeeds?
> > 
> > -- >8 --
> > 
> > With modules it may be the case that a template friend class provided
> > with a qualified name is not found by name lookup at instantiation time,
> > due to the class not being exported from its module.  This causes issues
> > in tsubst_friend_class which did not handle this case.
> > 
> > This is a more general issue, in fact, caused by the named friend class
> > not actually requiring tsubsting.  This was already worked around for
> > the "found by name lookup" case (g++.dg/template/friend5.C), but it
> > looks like there's no need to do name lookup at all for this to work.
> > 
> >     PR c++/115801
> > 
> > gcc/cp/ChangeLog:
> > 
> >     * pt.cc (tsubst_friend_class): Return the type directly when no
> >     tsubsting is required.
> > 
> > gcc/testsuite/ChangeLog:
> > 
> >     * g++.dg/modules/tpl-friend-16_a.C: New test.
> >     * g++.dg/modules/tpl-friend-16_b.C: New test.
> > 
> > Signed-off-by: Nathaniel Shead <nathanielosh...@gmail.com>
> > ---
> >  gcc/cp/pt.cc                                  | 39 ++++++++++--------
> >  .../g++.dg/modules/tpl-friend-16_a.C          | 40 +++++++++++++++++++
> >  .../g++.dg/modules/tpl-friend-16_b.C          | 17 ++++++++
> >  3 files changed, 79 insertions(+), 17 deletions(-)
> >  create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-16_a.C
> >  create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-16_b.C
> > 
> > diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
> > index 2db59213c54..ea00577fd37 100644
> > --- a/gcc/cp/pt.cc
> > +++ b/gcc/cp/pt.cc
> > @@ -11732,6 +11732,15 @@ tsubst_friend_class (tree friend_tmpl, tree args)
> >        return TREE_TYPE (tmpl);
> >      }
> >  
> > +  if (TMPL_PARMS_DEPTH (DECL_TEMPLATE_PARMS (friend_tmpl))
> > +      <= TMPL_ARGS_DEPTH (args))
> > +    /* The template has already been subsituted, e.g. for
> > +
> > +    template <typename> friend class ::C;
> > +
> > +       so we just need to return it directly.  */
> > +    return TREE_TYPE (friend_tmpl);
> 
> IIUC I don't think this would do the right thing for a template friend
> declaration that directly names a template from an outer current
> instantiation:
> 
>     template<class T>
>     struct A {
>       template<class U> struct B;
> 
>       template<class U>
>       struct C {
>         template<class V> friend class A::B;
>       };
>     };
> 
>     template struct A<int*>::C<long>;
> 
> Here TMPL_PARMS_DEPTH (DECL_TEMPLATE_PARMS (friend_tmpl)) is 2, and
> so is TMPL_ARGS_DEPTH (args), so we'd exit early and return a fully
> dependent TEMPLATE_DECL A<T>::B, but what we want to return is
> A<int*>::B.
> 
> It should always be safe to exit early when
>   TMPL_PARMS_DEPTH (DECL_TEMPLATE_PARMS (friend_tmpl)) == 1
> though, and that should cover the most common case.
> 
> > +
> >    tree context = CP_DECL_CONTEXT (friend_tmpl);
> >    if (TREE_CODE (context) == NAMESPACE_DECL)
> >      push_nested_namespace (context);
> > @@ -11764,23 +11773,19 @@ tsubst_friend_class (tree friend_tmpl, tree args)
> >        compatible with the attachment of the friend template.  */
> >     module_may_redeclare (tmpl, friend_tmpl);
> >  
> > -      if (TMPL_PARMS_DEPTH (DECL_TEMPLATE_PARMS (friend_tmpl))
> > -     > TMPL_ARGS_DEPTH (args))
> > -   {
> > -     tree parms = tsubst_template_parms (DECL_TEMPLATE_PARMS (friend_tmpl),
> > -                                         args, tf_warning_or_error);
> > -     tsubst_each_template_parm_constraints (parms, args,
> > -                                            tf_warning_or_error);
> > -          location_t saved_input_location = input_location;
> > -          input_location = DECL_SOURCE_LOCATION (friend_tmpl);
> > -     tree cons = get_constraints (friend_tmpl);
> > -     ++processing_template_decl;
> > -     cons = tsubst_constraint_info (cons, args, tf_warning_or_error,
> > -                                    DECL_FRIEND_CONTEXT (friend_tmpl));
> > -     --processing_template_decl;
> > -          redeclare_class_template (TREE_TYPE (tmpl), parms, cons);
> > -          input_location = saved_input_location;
> > -   }
> > +      tree parms = tsubst_template_parms (DECL_TEMPLATE_PARMS 
> > (friend_tmpl),
> > +                                     args, tf_warning_or_error);
> > +      tsubst_each_template_parm_constraints (parms, args,
> > +                                        tf_warning_or_error);
> > +      location_t saved_input_location = input_location;
> > +      input_location = DECL_SOURCE_LOCATION (friend_tmpl);
> > +      tree cons = get_constraints (friend_tmpl);
> > +      ++processing_template_decl;
> > +      cons = tsubst_constraint_info (cons, args, tf_warning_or_error,
> > +                                DECL_FRIEND_CONTEXT (friend_tmpl));
> > +      --processing_template_decl;
> > +      redeclare_class_template (TREE_TYPE (tmpl), parms, cons);
> > +      input_location = saved_input_location;
> >      }
> >    else
> >      {
> > diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-16_a.C 
> > b/gcc/testsuite/g++.dg/modules/tpl-friend-16_a.C
> > new file mode 100644
> > index 00000000000..e1cdcd98e1e
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/modules/tpl-friend-16_a.C
> > @@ -0,0 +1,40 @@
> > +// PR c++/115801
> > +// { dg-additional-options "-fmodules-ts -Wno-global-module" }
> > +// { dg-module-cmi test }
> > +
> > +module;
> > +
> > +template <typename T> struct GMF;
> > +template <typename T> struct GMF_Hidden {
> > +  int go() { GMF<T> gmf; return gmf.x; }
> > +};
> > +
> > +template <typename T> struct GMF {
> > +private:
> > +  template <typename> friend struct ::GMF_Hidden;
> > +  int x = 1;
> > +};
> > +
> > +template <typename T> int test_gmf() {
> > +  GMF_Hidden<T> h; return h.go();
> > +}
> > +
> > +export module test;
> > +
> > +export using ::GMF;
> > +export using ::test_gmf;
> > +
> > +export template <typename> struct Attached;
> > +template <typename T> struct Attached_Hidden {
> > +  int go() { Attached<T> attached; return attached.x; }
> > +};
> > +
> > +template <typename T> struct Attached {
> > +private:
> > +  template <typename> friend struct ::Attached_Hidden;
> > +  int x = 2;
> > +};
> > +
> > +export template <typename T> int test_attached() {
> > +  Attached_Hidden<T> h; return h.go();
> > +}
> > diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-16_b.C 
> > b/gcc/testsuite/g++.dg/modules/tpl-friend-16_b.C
> > new file mode 100644
> > index 00000000000..d3484ab19b1
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/modules/tpl-friend-16_b.C
> > @@ -0,0 +1,17 @@
> > +// PR c++/115801
> > +// { dg-additional-options "-fmodules-ts" }
> > +
> > +import test;
> > +
> > +int main() {
> > +  GMF<int> gmf;
> > +  Attached<int> attached;
> > +
> > +  int a = test_gmf<double>();
> > +  int b = test_attached<double>();
> > +
> > +  GMF_Hidden<int> gmf_hidden;  // { dg-error "not declared" }
> > +  Attached_Hidden<int> attached_hidden;  // { dg-error "not declared" }
> > +}
> > +
> > +// { dg-prune-output "expected primary-expression" }
> > -- 
> > 2.43.2
> > 
> > 
> 

Reply via email to