On Sun, Jun 16, 2024 at 12:29:23PM +1000, Nathaniel Shead wrote:
> Bootstrapped and regtested on x86_64-pc-linux-gnu, OK for trunk?
> 
> This probably isn't the most efficient approach, since we need to do
> name lookup to find deduction guides for a type which will also
> potentially do a bunch of pointless lazy loading from imported modules,
> but I wasn't able to work out a better approach without completely
> reworking how deduction guides are stored and represented.  This way at
> least is correct (I believe), and we can maybe revisit this in the
> future.
> 
> -- >8 --
> 
> Deduction guides are represented as 'normal' functions currently, and
> have no special handling in modules.  However, this causes some issues;
> by [temp.deduct.guide] a deduction guide is not found by normal name
> lookup and instead all reachable deduction guides for a class template
> should be considered, but this does not happen currently.
> 
> To solve this, this patch ensures that all deduction guides are
> considered exported to ensure that they are always visible to importers
> if they are reachable.  Another alternative here would be to add a new
> kind of "all reachable" flag to name lookup, but that is complicated by
> some difficulties in handling GM entities; this may be a better way to
> go if more kinds of entities end up needing this handling, however.
> 
> Another issue here is that because deduction guides are "unrelated"
> functions, they will usually get discarded from the GMF, so this patch
> ensures that when finding dependencies, GMF deduction guides will also
> have bindings created.  We do this in find_dependencies so that we don't
> unnecessarily create bindings for GMF deduction guides that are never
> reached; for consistency we do this for *all* deduction guides, not just
> GM ones.
> 
> Finally, when merging deduction guides from multiple modules, the name
> lookup code may now return two-dimensional overload sets, so update
> callers to match.
> 
> As a small drive-by improvement this patch also updates the error pretty
> printing code to add a space before the '->' when printing a deduction
> guide, so we get 'S(int) -> S<int>' instead of 'S(int)-> S<int>'.
> 
>       PR c++/115231
> 
> gcc/cp/ChangeLog:
> 
>       * error.cc (dump_function_decl): Add a space before '->' when
>       printing deduction guides.
>       * module.cc (depset::hash::add_binding_entity): Skip deduction
>       guides here.
>       (depset::hash::add_deduction_guides): New.
>       (depset::hash::find_dependencies): Add deduction guide
>       dependencies for a class template.
>       (module_state::write_cluster): Always consider deduction guides
>       as exported.
>       * pt.cc (deduction_guides_for): Use 'lkp_iterator' instead of
>       'ovl_iterator'.
> 
> gcc/testsuite/ChangeLog:
> 
>       * g++.dg/modules/dguide-1_a.C: New test.
>       * g++.dg/modules/dguide-1_b.C: New test.
>       * g++.dg/modules/dguide-2_a.C: New test.
>       * g++.dg/modules/dguide-2_b.C: New test.
>       * g++.dg/modules/dguide-3_a.C: New test.
>       * g++.dg/modules/dguide-3_b.C: New test.
>       * g++.dg/modules/dguide-3_c.C: New test.
> 
> Signed-off-by: Nathaniel Shead <nathanielosh...@gmail.com>
> ---
>  gcc/cp/error.cc                           |  1 +
>  gcc/cp/module.cc                          | 60 +++++++++++++++++++++++
>  gcc/cp/pt.cc                              |  2 +-
>  gcc/testsuite/g++.dg/modules/dguide-1_a.C | 44 +++++++++++++++++
>  gcc/testsuite/g++.dg/modules/dguide-1_b.C | 20 ++++++++
>  gcc/testsuite/g++.dg/modules/dguide-2_a.C | 24 +++++++++
>  gcc/testsuite/g++.dg/modules/dguide-2_b.C | 19 +++++++
>  gcc/testsuite/g++.dg/modules/dguide-3_a.C | 10 ++++
>  gcc/testsuite/g++.dg/modules/dguide-3_b.C | 10 ++++
>  gcc/testsuite/g++.dg/modules/dguide-3_c.C | 22 +++++++++
>  10 files changed, 211 insertions(+), 1 deletion(-)
>  create mode 100644 gcc/testsuite/g++.dg/modules/dguide-1_a.C
>  create mode 100644 gcc/testsuite/g++.dg/modules/dguide-1_b.C
>  create mode 100644 gcc/testsuite/g++.dg/modules/dguide-2_a.C
>  create mode 100644 gcc/testsuite/g++.dg/modules/dguide-2_b.C
>  create mode 100644 gcc/testsuite/g++.dg/modules/dguide-3_a.C
>  create mode 100644 gcc/testsuite/g++.dg/modules/dguide-3_b.C
>  create mode 100644 gcc/testsuite/g++.dg/modules/dguide-3_c.C
> 
> diff --git a/gcc/cp/error.cc b/gcc/cp/error.cc
> index 171a352c85f..2fb5084320e 100644
> --- a/gcc/cp/error.cc
> +++ b/gcc/cp/error.cc
> @@ -1866,6 +1866,7 @@ dump_function_decl (cxx_pretty_printer *pp, tree t, int 
> flags)
>       dump_type_suffix (pp, ret, flags);
>        else if (deduction_guide_p (t))
>       {
> +       pp->set_padding (pp_before);
>         pp_cxx_ws_string (pp, "->");
>         dump_type (pp, TREE_TYPE (TREE_TYPE (t)), flags);
>       }
> diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
> index 6d6044af199..a2c9e151fd5 100644
> --- a/gcc/cp/module.cc
> +++ b/gcc/cp/module.cc
> @@ -2589,6 +2589,9 @@ public:
>      void add_partial_entities (vec<tree, va_gc> *);
>      void add_class_entities (vec<tree, va_gc> *);
>  
> +  private:
> +    void add_deduction_guides (tree decl);
> +
>    public:    
>      void find_dependencies (module_state *);
>      bool finalize_dependencies ();
> @@ -13127,6 +13130,11 @@ depset::hash::add_binding_entity (tree decl, 
> WMB_Flags flags, void *data_)
>      {
>        tree inner = decl;
>  
> +      if (deduction_guide_p (inner))
> +     /* Ignore deduction guides here, they'll be handled within
> +        find_dependencies for a class template.  */
> +     return false;
> +
>        if (TREE_CODE (inner) == CONST_DECL
>         && TREE_CODE (DECL_CONTEXT (inner)) == ENUMERAL_TYPE)
>       inner = TYPE_NAME (DECL_CONTEXT (inner));
> @@ -13590,6 +13598,50 @@ find_pending_key (tree decl, tree *decl_p = nullptr)
>    return ns;
>  }
>  
> +/* Creates bindings and dependencies for all deduction guides of
> +   the given class template DECL as needed.  */
> +
> +void
> +depset::hash::add_deduction_guides (tree decl)
> +{
> +  /* Alias templates never have deduction guides.  */
> +  if (DECL_ALIAS_TEMPLATE_P (decl))
> +    return;
> +
> +  /* We don't need to do anything for class-scope deduction guides,
> +     as they will be added as members anyway.  */
> +  if (!DECL_NAMESPACE_SCOPE_P (decl))
> +    return;
> +
> +  tree ns = CP_DECL_CONTEXT (decl);
> +  tree name = dguide_name (decl);
> +
> +  /* We always add all deduction guides with a given name at once,
> +     so if there's already a binding there's nothing to do.  */
> +  if (find_binding (ns, name))
> +    return;
> +
> +  tree guides = lookup_qualified_name (ns, name, LOOK_want::NORMAL,
> +                                    /*complain=*/false);
> +  if (guides == error_mark_node)
> +    return;
> +
> +  /* We have bindings to add.  */
> +  depset *binding = make_binding (ns, name);
> +  add_namespace_context (binding, ns);
> +
> +  depset **slot = binding_slot (ns, name, /*insert=*/true);
> +  *slot = binding;
> +
> +  for (lkp_iterator it (guides); it; ++it)
> +    {
> +      gcc_checking_assert (!TREE_VISITED (*it));
> +      depset *dep = make_dependency (*it, EK_FOR_BINDING);
> +      binding->deps.safe_push (dep);
> +      dep->deps.safe_push (binding);
> +    }
> +}
> +
>  /* Iteratively find dependencies.  During the walk we may find more
>     entries on the same binding that need walking.  */
>  
> @@ -13649,6 +13701,10 @@ depset::hash::find_dependencies (module_state 
> *module)
>               }
>             walker.end ();
>  
> +           if (!walker.is_key_order ()
> +               && DECL_CLASS_TEMPLATE_P (decl))
> +             add_deduction_guides (decl);
> +
>             if (!walker.is_key_order ()
>                 && TREE_CODE (decl) == TEMPLATE_DECL
>                 && !DECL_UNINSTANTIATED_TEMPLATE_FRIEND_P (decl))
> @@ -15145,6 +15201,10 @@ module_state::write_cluster (elf_out *to, depset 
> *scc[], unsigned size,
>                     flags |= cbf_hidden;
>                   else if (DECL_MODULE_EXPORT_P (STRIP_TEMPLATE (bound)))
>                     flags |= cbf_export;
> +                 else if (deduction_guide_p (bound))
> +                   /* Deduction guides are always exported so that they are
> +                      reachable whenever their class template is.  */
> +                   flags |= cbf_export;
>                 }
>  
>               gcc_checking_assert (DECL_P (bound));
> diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
> index 607753ae6b7..8e007a571a5 100644
> --- a/gcc/cp/pt.cc
> +++ b/gcc/cp/pt.cc
> @@ -30784,7 +30784,7 @@ deduction_guides_for (tree tmpl, bool &any_dguides_p, 
> tsubst_flags_t complain)
>    else
>      {
>        cands = ctor_deduction_guides_for (tmpl, complain);
> -      for (ovl_iterator it (guides); it; ++it)
> +      for (lkp_iterator it (guides); it; ++it)
>       cands = lookup_add (*it, cands);
>      }
>  
> diff --git a/gcc/testsuite/g++.dg/modules/dguide-1_a.C 
> b/gcc/testsuite/g++.dg/modules/dguide-1_a.C
> new file mode 100644
> index 00000000000..834e033eae3
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/modules/dguide-1_a.C
> @@ -0,0 +1,44 @@
> +// PR c++/115231
> +// { dg-additional-options "-fmodules-ts -Wno-global-module" }
> +// { dg-module-cmi M }
> +
> +module;
> +
> +template <typename T>
> +struct A {
> +  template <typename U> A(U);
> +};
> +
> +template <typename T> A(T) -> A<T>;
> +
> +export module M;
> +
> +// Exporting a GMF entity should make the deduction guides reachable.
> +export using ::A;
> +
> +
> +export template <typename T>
> +struct B {
> +  template <typename U> B(U);
> +};
> +
> +// Not exported, but should still be reachable by [temp.deduct.guide] p1.
> +B(int) -> B<double>;
> +
> +
> +// Class-scope deduction guides should be reachable as well, even if
> +// the class body was not exported.
> +export template <typename T> struct C;
> +
> +template <typename T>
> +struct C {
> +  template <typename U>
> +  struct I {
> +    template <typename V> I(V);
> +  };
> +
> +  I(int) -> I<int>;
> +
> +  template <typename P>
> +  I(const P*) -> I<P>;
> +};
> diff --git a/gcc/testsuite/g++.dg/modules/dguide-1_b.C 
> b/gcc/testsuite/g++.dg/modules/dguide-1_b.C
> new file mode 100644
> index 00000000000..97266986d8f
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/modules/dguide-1_b.C
> @@ -0,0 +1,20 @@
> +// PR c++/115231
> +// { dg-additional-options "-fmodules-ts" }
> +
> +import M;
> +
> +int main() {
> +  // Check that deduction guides are reachable,
> +  // and that they declared the right type.
> +  A a(1);
> +  A<int> a2 = a;
> +
> +  B b(2);
> +  B<double> b2 = b;
> +
> +  C<int>::I x(10);
> +  C<int>::I<int> x2 = x;
> +
> +  C<int>::I y("xyz");
> +  C<int>::I<char> y2 = y;
> +}
> diff --git a/gcc/testsuite/g++.dg/modules/dguide-2_a.C 
> b/gcc/testsuite/g++.dg/modules/dguide-2_a.C
> new file mode 100644
> index 00000000000..fcd6c579813
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/modules/dguide-2_a.C
> @@ -0,0 +1,24 @@
> +// PR c++/115231
> +// { dg-additional-options "-fmodules-ts -Wno-global-module" }
> +// { dg-module-cmi M }
> +
> +module;
> +
> +template <typename T>
> +struct A {
> +  template <typename U> A(U);
> +};
> +template <typename T> A(T) -> A<T>;
> +
> +export module M;
> +
> +template <typename T>
> +struct B {
> +  template <typename U> B(U);
> +};
> +B(int) -> B<int>;
> +
> +// Accessing deduction guides should be possible,
> +// even if we can't name the type directly.
> +export A<void> f();
> +export B<void> g();
> diff --git a/gcc/testsuite/g++.dg/modules/dguide-2_b.C 
> b/gcc/testsuite/g++.dg/modules/dguide-2_b.C
> new file mode 100644
> index 00000000000..ca31306aea3
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/modules/dguide-2_b.C
> @@ -0,0 +1,19 @@
> +// PR c++/115231
> +// { dg-additional-options "-fmodules-ts" }
> +
> +import M;
> +
> +template <typename>
> +struct U;
> +
> +template <template <typename> typename TT, typename Inner>
> +struct U<TT<Inner>> {
> +  void go() {
> +    TT t(10);
> +  }
> +};
> +
> +int main() {
> +  U<decltype(f())>{}.go();
> +  U<decltype(g())>{}.go();
> +}
> diff --git a/gcc/testsuite/g++.dg/modules/dguide-3_a.C 
> b/gcc/testsuite/g++.dg/modules/dguide-3_a.C
> new file mode 100644
> index 00000000000..33350ce9804
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/modules/dguide-3_a.C
> @@ -0,0 +1,10 @@
> +// { dg-additional-options "-fmodules-ts" }
> +// { dg-module-cmi A }
> +
> +export module A;
> +
> +extern "C++" {
> +  template <typename T> struct S;
> +  S(int) -> S<int>;
> +  S(double) -> S<double>;
> +}
> diff --git a/gcc/testsuite/g++.dg/modules/dguide-3_b.C 
> b/gcc/testsuite/g++.dg/modules/dguide-3_b.C
> new file mode 100644
> index 00000000000..d23696c74bd
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/modules/dguide-3_b.C
> @@ -0,0 +1,10 @@
> +// { dg-additional-options "-fmodules-ts" }
> +// { dg-module-cmi B }
> +
> +export module B;
> +
> +extern "C++" {
> +  template <typename T> struct S;
> +  S(int) -> S<int>;
> +  S(char) -> S<char>;
> +}
> diff --git a/gcc/testsuite/g++.dg/modules/dguide-3_c.C 
> b/gcc/testsuite/g++.dg/modules/dguide-3_c.C
> new file mode 100644
> index 00000000000..39ae8f4aa58
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/modules/dguide-3_c.C
> @@ -0,0 +1,22 @@
> +// { dg-additional-options "-fmodules-ts" }
> +// Test merging deduction guides.
> +
> +import A;
> +import B;
> +
> +template <typename T> struct S {
> +  template <typename U> S(U);
> +};
> +
> +int main() {
> +  S x(123);  // { dg-bogus "incomplete" "" { xfail *-*-* } }
> +  S<int> x2 = x;
> +
> +  S y('c');  // { dg-bogus "incomplete" "" { xfail *-*-* } }
> +  S<char> y2 = y;
> +
> +  S z(0.5);  // { dg-bogus "incomplete" "" { xfail *-*-* } }
> +  S<double> z2 = z;
> +}
> +
> +S(char) -> S<double>;  // { dg-error "ambiguating" }
> -- 
> 2.43.2
> 

I just realised I forgot to explain these xfails in the above commit:
this is because the return types declare a new "specialisation" that is
treated as a separate type on stream-in, rather than merging with the
existing template on instantiation.

A simplified example of this error is:

  // a.cpp
  export module A;
  extern "C++" template <typename T> struct S;
  export S<int> foo();

  // main.cpp
  import A;
  template <typename T> struct S {};
  int main() { foo(); }

I suspect this is related to PR c++/113814.  I'm pretty sure this should
be valid code ([temp.inst] p2 doesn't seem to require instantiation in A
because a function return type doesn't require a completely-defined
object type and completeness won't affect the semantics of the program)
but I haven't looked much into how to fix this yet.

Nathaniel

Reply via email to