diff mbox series

[v3] c++/modules: Handle instantiating already tsubsted template friend classes [PR115801]

Message ID 66b426d7.050a0220.2958fc.0959@mx.google.com
State New
Headers show
Series [v3] c++/modules: Handle instantiating already tsubsted template friend classes [PR115801] | expand

Commit Message

Nathaniel Shead Aug. 8, 2024, 2 a.m. UTC
On Wed, Aug 07, 2024 at 09:12:13PM -0400, Patrick Palka wrote:
> 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 <nathanieloshead@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.
> > 

Ah right, I see; a full version of the testcase I guess might be

  template<class T>
  struct A {
    template<class U> struct B;

    template<class U>
    struct C {
      template<class V> friend struct A::B;
    private:
      int x;
    };
  };

  template <class T>
  template <class U>
  struct A<T>::B {
    int foo(A<int*>::C<long> c) { return c.x; }
  };

  template struct A<int*>::C<long>;
  template struct A<int*>::B<long>;
  template struct A<double*>::B<long>;  // !!!

Which is currently correctly rejected by GCC but with my proposed change
is no longer rejected.

How does this patch look instead?  This passes dg.exp and modules.exp
still, still waiting for full regtest to complete.

-- >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 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 particular case to work.

We do need to be careful to continue to do name lookup to handle
templates from an outer current instantiation though; this patch adds a
new testcase for this as well.  This should not impact modules (because
exportingness will only affect namespace lookup).

	PR c++/115801

gcc/cp/ChangeLog:

	* pt.cc (tsubst_friend_class): Return the type immediately when
	no tsubsting or name lookup 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.
	* g++.dg/template/friend82.C: New test.

Signed-off-by: Nathaniel Shead <nathanieloshead@gmail.com>
Reviewed-by: Patrick Palka <ppalka@redhat.com>
Reviewed-by: Jason Merrill <jason@redhat.com>
---
 gcc/cp/pt.cc                                  |  8 ++++
 .../g++.dg/modules/tpl-friend-16_a.C          | 40 +++++++++++++++++++
 .../g++.dg/modules/tpl-friend-16_b.C          | 17 ++++++++
 gcc/testsuite/g++.dg/template/friend82.C      | 23 +++++++++++
 4 files changed, 88 insertions(+)
 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
 create mode 100644 gcc/testsuite/g++.dg/template/friend82.C

Comments

Jason Merrill Aug. 8, 2024, 3:01 a.m. UTC | #1
On 8/7/24 10:00 PM, Nathaniel Shead wrote:
> On Wed, Aug 07, 2024 at 09:12:13PM -0400, Patrick Palka wrote:
>> 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 <nathanieloshead@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.
>>>
> 
> Ah right, I see; a full version of the testcase I guess might be
> 
>    template<class T>
>    struct A {
>      template<class U> struct B;
> 
>      template<class U>
>      struct C {
>        template<class V> friend struct A::B;
>      private:
>        int x;
>      };
>    };
> 
>    template <class T>
>    template <class U>
>    struct A<T>::B {
>      int foo(A<int*>::C<long> c) { return c.x; }
>    };
> 
>    template struct A<int*>::C<long>;
>    template struct A<int*>::B<long>;
>    template struct A<double*>::B<long>;  // !!!
> 
> Which is currently correctly rejected by GCC but with my proposed change
> is no longer rejected.
> 
> How does this patch look instead?  This passes dg.exp and modules.exp
> still, still waiting for full regtest to complete.

OK.

> -- >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 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 particular case to work.
> 
> We do need to be careful to continue to do name lookup to handle
> templates from an outer current instantiation though; this patch adds a
> new testcase for this as well.  This should not impact modules (because
> exportingness will only affect namespace lookup).
> 
> 	PR c++/115801
> 
> gcc/cp/ChangeLog:
> 
> 	* pt.cc (tsubst_friend_class): Return the type immediately when
> 	no tsubsting or name lookup 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.
> 	* g++.dg/template/friend82.C: New test.
> 
> Signed-off-by: Nathaniel Shead <nathanieloshead@gmail.com>
> Reviewed-by: Patrick Palka <ppalka@redhat.com>
> Reviewed-by: Jason Merrill <jason@redhat.com>
> ---
>   gcc/cp/pt.cc                                  |  8 ++++
>   .../g++.dg/modules/tpl-friend-16_a.C          | 40 +++++++++++++++++++
>   .../g++.dg/modules/tpl-friend-16_b.C          | 17 ++++++++
>   gcc/testsuite/g++.dg/template/friend82.C      | 23 +++++++++++
>   4 files changed, 88 insertions(+)
>   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
>   create mode 100644 gcc/testsuite/g++.dg/template/friend82.C
> 
> diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
> index 2db59213c54..8abd092116b 100644
> --- a/gcc/cp/pt.cc
> +++ b/gcc/cp/pt.cc
> @@ -11732,6 +11732,14 @@ tsubst_friend_class (tree friend_tmpl, tree args)
>         return TREE_TYPE (tmpl);
>       }
>   
> +  if (TMPL_PARMS_DEPTH (DECL_TEMPLATE_PARMS (friend_tmpl)) == 1)
> +    /* The template has already been fully substituted, e.g. for
> +
> +	 template <typename> friend class ::C;
> +
> +       so we can just return it directly.  */
> +    return TREE_TYPE (friend_tmpl);
> +
>     tree context = CP_DECL_CONTEXT (friend_tmpl);
>     if (TREE_CODE (context) == NAMESPACE_DECL)
>       push_nested_namespace (context);
> 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" }
> diff --git a/gcc/testsuite/g++.dg/template/friend82.C b/gcc/testsuite/g++.dg/template/friend82.C
> new file mode 100644
> index 00000000000..28a057dd23e
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/template/friend82.C
> @@ -0,0 +1,23 @@
> +// { dg-do compile }
> +
> +template<class T>
> +struct A {
> +  template<class U> struct B;
> +
> +  template<class U>
> +  struct C {
> +    template<class V> friend struct A::B;
> +  private:
> +    int x;
> +  };
> +};
> +
> +template <class T>
> +template <class U>
> +struct A<T>::B {
> +  int foo(A<int*>::C<long> c) { return c.x; }  // { dg-error "private" }
> +};
> +
> +template struct A<int*>::C<long>;
> +template struct A<int*>::B<long>;  // { dg-bogus "" }
> +template struct A<double*>::B<long>;  // { dg-message "required from here" }
diff mbox series

Patch

diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index 2db59213c54..8abd092116b 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -11732,6 +11732,14 @@  tsubst_friend_class (tree friend_tmpl, tree args)
       return TREE_TYPE (tmpl);
     }
 
+  if (TMPL_PARMS_DEPTH (DECL_TEMPLATE_PARMS (friend_tmpl)) == 1)
+    /* The template has already been fully substituted, e.g. for
+
+	 template <typename> friend class ::C;
+
+       so we can just return it directly.  */
+    return TREE_TYPE (friend_tmpl);
+
   tree context = CP_DECL_CONTEXT (friend_tmpl);
   if (TREE_CODE (context) == NAMESPACE_DECL)
     push_nested_namespace (context);
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" }
diff --git a/gcc/testsuite/g++.dg/template/friend82.C b/gcc/testsuite/g++.dg/template/friend82.C
new file mode 100644
index 00000000000..28a057dd23e
--- /dev/null
+++ b/gcc/testsuite/g++.dg/template/friend82.C
@@ -0,0 +1,23 @@ 
+// { dg-do compile }
+
+template<class T>
+struct A {
+  template<class U> struct B;
+
+  template<class U>
+  struct C {
+    template<class V> friend struct A::B;
+  private:
+    int x;
+  };
+};
+
+template <class T>
+template <class U>
+struct A<T>::B {
+  int foo(A<int*>::C<long> c) { return c.x; }  // { dg-error "private" }
+};
+
+template struct A<int*>::C<long>;
+template struct A<int*>::B<long>;  // { dg-bogus "" }
+template struct A<double*>::B<long>;  // { dg-message "required from here" }