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