On Sat, Jan 24, 2026 at 04:09:44PM +0800, Jason Merrill wrote:
> On 1/24/26 12:26 PM, Nathaniel Shead wrote:
> > On Tue, Jan 13, 2026 at 10:45:03PM +0800, Jason Merrill wrote:
> > > On 1/9/26 2:29 PM, Nathaniel Shead wrote:
> > > > Bootstrapped and regtested on x86_64-pc-linux-gnu, OK for trunk? I'm not
> > > > 100% sure if this matches the wording in the standard, but it more
> > > > closely matches Clang's current behaviour and seems to fix a lot of
> > > > issues I've seen people have with GCC's current behaviour.
> > > > 
> > > > -- >8 --
> > > > 
> > > > Many expressions rely on standard library names being visible when used,
> > > > such as for structured bindings, typeid, coroutine traits, and so forth.
> > > > When these expressions occur in templates, and the instantiations occur
> > > > in a TU where those names are not visible, we currently error, even if
> > > > the name was visible in the TU the template was defined.
> > > > 
> > > > This is a poor user experience (made worse by fixit suggestions to add
> > > > an include in the module the template was defined, which wouldn't fix
> > > > the error anyway).  Instead, I think it seems reasonable (and probably
> > > > the intention of the wording?) that we should perform the lookup in the
> > > > instantiation context of the lookup.
> > > 
> > > It seems to me that the lookup should occur in the definition context of 
> > > the
> > > template, both binding the name and making the definition decl-reachable.
> > > The full instantiation path is more than we need.
> > 
> > I've had a bit of a look into this; would this mean perhaps extending
> > 'DEPENDENT_OPERATOR_TYPE' (or something like it) to save the results of
> > name lookup at definition time for all contexts that we currently do
> > 'lookup_qualified_name' somewhere if processing_template_decl, and then
> > at instantiation time supplementing the results with a second lookup and
> > using that set? Or is there another approach that you're thinking of
> > here?
> 
> Yes, long-term that seems like how it ought to work to handle order of
> declaration edge cases.
> 
> Easier should be to do something like this patch, but only looking at the
> originating module of the current instantiation rather than the full
> instantiation path.

So something like this?

Bootstrapped and regtested on x86_64-pc-linux-gnu, OK for trunk?

-- >8 --

Subject: [PATCH v2] c++/modules: Include instantiation origination for all
 name lookup [PR122609]

Many expressions rely on standard library names being visible when used,
such as for structured bindings, typeid, coroutine traits, and so forth.
When these expressions occur in templates, and the instantiations occur
in a TU where those names are not visible, we currently error, even if
the name was visible in the TU the template was defined.

This is a poor user experience (made worse by fixit suggestions to add
an include in the module the template was defined, which wouldn't fix
the error anyway).  It seems reasonable to also include declarations that
were visible at the point the instantiation originated.

When using 'import std' this should fix most such errors.  If using
traditional #includes to provide the standard library this may or may
not fix the error; in many cases we may still incorrectly discard the
relevant names (e.g. typeid in a template does not currently cause us to
consider std::type_info to be decl-reachable).

This also fixes the XFAIL in adl-12_b.C as we add_candidates now
properly considers names visible in the instantiation context of the
comparison.

        PR c++/122609
        PR c++/101140

gcc/cp/ChangeLog:

        * cp-tree.h (visible_from_instantiation_origination): Declare.
        * module.cc: (orig_decl_for_instantiation): New function.
        (path_of_instantiation): Use it.
        (visible_from_instantiation_origination): New function.
        * name-lookup.cc (name_lookup::search_namespace_only): Also find
        names visible at the point the instantiation originated.

gcc/testsuite/ChangeLog:

        * g++.dg/modules/adl-12_b.C: Remove XFAIL.
        * g++.dg/modules/inst-8_a.C: New test.
        * g++.dg/modules/inst-8_b.C: New test.
        * g++.dg/modules/inst-9_c.C: New test.
        * g++.dg/modules/inst-9_a.C: New test.
        * g++.dg/modules/inst-9_b.C: New test.
        * g++.dg/modules/inst-10_a.C: New test.
        * g++.dg/modules/inst-10_b.C: New test.
        * g++.dg/modules/inst-10_c.C: New test.

Signed-off-by: Nathaniel Shead <[email protected]>
---
 gcc/cp/cp-tree.h                         |  1 +
 gcc/cp/module.cc                         | 47 ++++++++++++++++++++----
 gcc/cp/name-lookup.cc                    | 18 +++++++--
 gcc/testsuite/g++.dg/modules/adl-12_b.C  |  7 +---
 gcc/testsuite/g++.dg/modules/inst-10_a.C |  9 +++++
 gcc/testsuite/g++.dg/modules/inst-10_b.C | 19 ++++++++++
 gcc/testsuite/g++.dg/modules/inst-10_c.C | 14 +++++++
 gcc/testsuite/g++.dg/modules/inst-8_a.C  | 27 ++++++++++++++
 gcc/testsuite/g++.dg/modules/inst-8_b.C  | 42 +++++++++++++++++++++
 gcc/testsuite/g++.dg/modules/inst-8_c.C  | 12 ++++++
 gcc/testsuite/g++.dg/modules/inst-9_a.C  | 37 +++++++++++++++++++
 gcc/testsuite/g++.dg/modules/inst-9_b.C  | 15 ++++++++
 12 files changed, 231 insertions(+), 17 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/modules/inst-10_a.C
 create mode 100644 gcc/testsuite/g++.dg/modules/inst-10_b.C
 create mode 100644 gcc/testsuite/g++.dg/modules/inst-10_c.C
 create mode 100644 gcc/testsuite/g++.dg/modules/inst-8_a.C
 create mode 100644 gcc/testsuite/g++.dg/modules/inst-8_b.C
 create mode 100644 gcc/testsuite/g++.dg/modules/inst-8_c.C
 create mode 100644 gcc/testsuite/g++.dg/modules/inst-9_a.C
 create mode 100644 gcc/testsuite/g++.dg/modules/inst-9_b.C

diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 90911bf03c7..2cc3b3b66f2 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -7951,6 +7951,7 @@ extern void *finish_module_processing (cpp_reader *);
 extern char const *module_name (unsigned, bool header_ok);
 extern bitmap get_import_bitmap ();
 extern bitmap visible_instantiation_path (bitmap *);
+extern bitmap visible_from_instantiation_origination (unsigned *);
 extern void module_begin_main_file (cpp_reader *, line_maps *,
                                    const line_map_ordinary *);
 extern void module_preprocess_options (cpp_reader *);
diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
index c786519c1c0..e154d14708a 100644
--- a/gcc/cp/module.cc
+++ b/gcc/cp/module.cc
@@ -21564,6 +21564,23 @@ get_import_bitmap ()
   return this_module ()->imports;
 }
 
+/* Get the original decl for an instantiation at TINST, or NULL_TREE
+   if we're not an instantiation.  */
+
+static tree
+orig_decl_for_instantiation (tinst_level *tinst)
+{
+  if (!tinst || TREE_CODE (tinst->tldcl) == TEMPLATE_FOR_STMT)
+    return NULL_TREE;
+
+  tree decl = tinst->tldcl;
+  if (TREE_CODE (decl) == TREE_LIST)
+    decl = TREE_PURPOSE (decl);
+  if (TYPE_P (decl))
+    decl = TYPE_NAME (decl);
+  return decl;
+}
+
 /* Return the visible imports and path of instantiation for an
    instantiation at TINST.  If TINST is nullptr, we're not in an
    instantiation, and thus will return the visible imports of the
@@ -21571,11 +21588,12 @@ get_import_bitmap ()
    the tinst level itself.  */
 
 static bitmap
-path_of_instantiation (tinst_level *tinst,  bitmap *path_map_p)
+path_of_instantiation (tinst_level *tinst, bitmap *path_map_p)
 {
   gcc_checking_assert (modules_p ());
 
-  if (!tinst || TREE_CODE (tinst->tldcl) == TEMPLATE_FOR_STMT)
+  tree decl = orig_decl_for_instantiation (tinst);
+  if (!decl)
     {
       gcc_assert (!tinst || !tinst->next);
       /* Not inside an instantiation, just the regular case.  */
@@ -21595,12 +21613,6 @@ path_of_instantiation (tinst_level *tinst,  bitmap 
*path_map_p)
          bitmap_set_bit (path_map, 0);
        }
 
-      tree decl = tinst->tldcl;
-      if (TREE_CODE (decl) == TREE_LIST)
-       decl = TREE_PURPOSE (decl);
-      if (TYPE_P (decl))
-       decl = TYPE_NAME (decl);
-
       if (unsigned mod = get_originating_module (decl))
        if (!bitmap_bit_p (path_map, mod))
          {
@@ -21644,6 +21656,25 @@ visible_instantiation_path (bitmap *path_map_p)
   return path_of_instantiation (current_instantiation (), path_map_p);
 }
 
+/* Returns the bitmap describing what modules were visible from the
+   module that the current instantiation originated from.  If we're
+   not an instantiation, returns NULL.  *MODULE_P is filled in with
+   the originating module of the definition for this instantiation.  */
+
+bitmap
+visible_from_instantiation_origination (unsigned *module_p)
+{
+  if (!modules_p ())
+    return NULL;
+
+  tree decl = orig_decl_for_instantiation (current_instantiation ());
+  if (!decl)
+    return NULL;
+
+  *module_p = get_originating_module (decl);
+  return (*modules)[*module_p]->imports;
+}
+
 /* We've just directly imported IMPORT.  Update our import/export
    bitmaps.  IS_EXPORT is true if we're reexporting the OTHER.  */
 
diff --git a/gcc/cp/name-lookup.cc b/gcc/cp/name-lookup.cc
index 1b3713dc40e..3b98e3c04cc 100644
--- a/gcc/cp/name-lookup.cc
+++ b/gcc/cp/name-lookup.cc
@@ -862,6 +862,14 @@ name_lookup::search_namespace_only (tree scope)
             the import bitmap.  Hence iterate over the former
             checking for bits set in the bitmap.  */
          bitmap imports = get_import_bitmap ();
+         /* FIXME: For instantiations, we also want to include any
+            declarations visible at the point it was defined, even
+            if not visible from the current TU; we approximate
+            this here, but a proper solution would involve caching
+            phase 1 lookup results (PR c++/122609).  */
+         unsigned orig_mod = 0;
+         bitmap orig_imp = visible_from_instantiation_origination (&orig_mod);
+
          binding_cluster *cluster = BINDING_VECTOR_CLUSTER_BASE (val);
          int marker = 0;
          int dup_detect = 0;
@@ -922,18 +930,19 @@ name_lookup::search_namespace_only (tree scope)
                  if (unsigned span = cluster->indices[jx].span)
                    do
                      if (bool (want & LOOK_want::ANY_REACHABLE)
-                         || bitmap_bit_p (imports, base))
+                         || bitmap_bit_p (imports, base)
+                         || (orig_imp && bitmap_bit_p (orig_imp, base)))
                        goto found;
                    while (++base, --span);
                continue;
 
              found:;
                /* Is it loaded?  */
+               unsigned mod = cluster->indices[jx].base;
                if (cluster->slots[jx].is_lazy ())
                  {
                    gcc_assert (cluster->indices[jx].span == 1);
-                   lazy_load_binding (cluster->indices[jx].base,
-                                      scope, name, &cluster->slots[jx]);
+                   lazy_load_binding (mod, scope, name, &cluster->slots[jx]);
                  }
                tree bind = cluster->slots[jx];
                if (!bind)
@@ -966,7 +975,8 @@ name_lookup::search_namespace_only (tree scope)
                        dup_detect |= dup;
                      }
 
-                   if (bool (want & LOOK_want::ANY_REACHABLE))
+                   if (bool (want & LOOK_want::ANY_REACHABLE)
+                       || mod == orig_mod)
                      {
                        type = STAT_TYPE (bind);
                        bind = STAT_DECL (bind);
diff --git a/gcc/testsuite/g++.dg/modules/adl-12_b.C 
b/gcc/testsuite/g++.dg/modules/adl-12_b.C
index cde8c35b20e..abe772d49c7 100644
--- a/gcc/testsuite/g++.dg/modules/adl-12_b.C
+++ b/gcc/testsuite/g++.dg/modules/adl-12_b.C
@@ -20,10 +20,7 @@ void test(Q::X x) {
 
 #if __cpp_impl_three_way_comparison >= 201907L
   rewrite_ops(0);  // OK
-
-  // This should error, but lookup_qualified_name in add_candidates
-  // doesn't look in the instantiation context of this call, so
-  // we don't see the operator!= and think we can validly rewrite.
-  rewrite_ops_error(0);  // { dg-error "required from here" "PR122609" { xfail 
*-*-* } }
+  rewrite_ops_error(0);  // { dg-message "required from here" "" { target 
c++20 } }
+  // { dg-prune-output "no match for" }
 #endif
 }
diff --git a/gcc/testsuite/g++.dg/modules/inst-10_a.C 
b/gcc/testsuite/g++.dg/modules/inst-10_a.C
new file mode 100644
index 00000000000..5cb7a26152f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/inst-10_a.C
@@ -0,0 +1,9 @@
+// PR c++/122609
+// { dg-do compile { target c++20 } }
+// { dg-additional-options "-fmodules" }
+// { dg-module-cmi K }
+
+export module K;
+export template <typename T> void use_without_import(T t) {
+  auto [x] = t;
+}
diff --git a/gcc/testsuite/g++.dg/modules/inst-10_b.C 
b/gcc/testsuite/g++.dg/modules/inst-10_b.C
new file mode 100644
index 00000000000..9279aedd8ea
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/inst-10_b.C
@@ -0,0 +1,19 @@
+// PR c++/122609
+// { dg-do compile { target c++20 } }
+// { dg-additional-options "-fmodules" }
+// { dg-module-cmi U }
+
+export module U;
+import K;
+
+namespace std {
+  template <typename T> struct tuple_size { static constexpr int value = 1; };
+  template <int I, typename T> struct tuple_element { using type = int; };
+};
+
+export struct S {};
+template <int I> int get(S) { return 123; };
+
+export template <typename T> void call_use_without_import(T t) {
+  use_without_import(t);
+}
diff --git a/gcc/testsuite/g++.dg/modules/inst-10_c.C 
b/gcc/testsuite/g++.dg/modules/inst-10_c.C
new file mode 100644
index 00000000000..f7e5f71056d
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/inst-10_c.C
@@ -0,0 +1,14 @@
+// PR c++/122609
+// { dg-do compile { target c++20 } }
+// { dg-additional-options "-fmodules" }
+
+import U;
+
+int main() {
+  // std::tuple_size and std::tuple_element aren't visible from
+  // use_without_import, and also aren't visible from here,
+  // so despite being visible on the instantiation path this is an error.
+
+  call_use_without_import(S{});  // { dg-message "required from here" }
+  // { dg-error {cannot decompose class type} "" { target *-*-* } 0 }
+}
diff --git a/gcc/testsuite/g++.dg/modules/inst-8_a.C 
b/gcc/testsuite/g++.dg/modules/inst-8_a.C
new file mode 100644
index 00000000000..1b0a47978cb
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/inst-8_a.C
@@ -0,0 +1,27 @@
+// PR c++/122609
+// { dg-do compile { target c++20 } }
+// { dg-additional-options "-fmodules" }
+// { dg-module-cmi std }
+
+module;
+#include <coroutine>
+#include <tuple>
+#include <typeinfo>
+export module std;
+
+// PR c++/101140
+export using ::operator new;
+export using ::operator delete;
+
+export namespace std {
+  using std::coroutine_handle;
+  using std::coroutine_traits;
+  using std::suspend_always;
+
+  using std::get;
+  using std::tuple;
+  using std::tuple_size;
+  using std::tuple_element;
+
+  using std::type_info;
+}
diff --git a/gcc/testsuite/g++.dg/modules/inst-8_b.C 
b/gcc/testsuite/g++.dg/modules/inst-8_b.C
new file mode 100644
index 00000000000..4e3b63da671
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/inst-8_b.C
@@ -0,0 +1,42 @@
+// PR c++/122609
+// { dg-do compile { target c++20 } }
+// { dg-additional-options "-fmodules" }
+// { dg-module-cmi M }
+
+export module M;
+import std;  // not exported
+
+export template <typename T> void structured_binding() {
+  std::tuple<T> t;
+  auto [x] = t;
+}
+
+export template <typename T> void operator_new() {
+  // PR c++/101140
+  T x;
+  new (&x) T;
+}
+
+export template <typename T> void use_typeid() {
+  const auto& id = typeid(T);
+}
+
+struct simple_promise;
+struct simple_coroutine : std::coroutine_handle<simple_promise> {
+  using promise_type = ::simple_promise;
+};
+struct simple_promise {
+  simple_coroutine get_return_object() { return { 
simple_coroutine::from_promise(*this) }; }
+  std::suspend_always initial_suspend() noexcept { return {}; }
+  std::suspend_always final_suspend() noexcept { return {}; }
+  void return_void() {}
+  void unhandled_exception() {}
+};
+template <typename T> simple_coroutine coroutine_impl() {
+  co_return;
+}
+export template <typename T> void coroutine() {
+  simple_coroutine sc = coroutine_impl<T>();
+  sc.resume();
+  sc.destroy();
+}
diff --git a/gcc/testsuite/g++.dg/modules/inst-8_c.C 
b/gcc/testsuite/g++.dg/modules/inst-8_c.C
new file mode 100644
index 00000000000..460232d5a87
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/inst-8_c.C
@@ -0,0 +1,12 @@
+// PR c++/122609
+// { dg-do compile { target c++20 } }
+// { dg-additional-options "-fmodules" }
+
+import M;
+
+int main() {
+  structured_binding<int>();
+  operator_new<int>();
+  use_typeid<int>();
+  coroutine<int>();
+}
diff --git a/gcc/testsuite/g++.dg/modules/inst-9_a.C 
b/gcc/testsuite/g++.dg/modules/inst-9_a.C
new file mode 100644
index 00000000000..cc60f195dbf
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/inst-9_a.C
@@ -0,0 +1,37 @@
+// PR c++/122609
+// PR c++/101140
+// { dg-additional-options "-fmodules -Wno-global-module -fdump-lang-module" }
+// { dg-module-cmi M }
+
+module;
+
+using size_t = decltype(sizeof(0));
+
+namespace std {
+  template <typename T> struct tuple_size;
+  template <size_t I, typename T> struct tuple_element;
+}
+
+struct G {};
+template <> struct std::tuple_size<G> { static constexpr size_t value = 1; };
+template <> struct std::tuple_element<0, G> { using type = int; };
+template <size_t I> int get(G) { return 123; }
+
+export module M;
+
+export using ::G;
+export template <typename T> void use_gmf(T t) {
+  // This should make std::tuple_size and std::tuple_element decl-reachable;
+  // additionally, this should make ::get reachable via ADL.
+  auto [x] = t;
+  // { dg-final { scan-lang-dump "Bindings '::std::tuple_size'" module { xfail 
*-*-* } } }
+  // { dg-final { scan-lang-dump "Bindings '::std::tuple_element'" module { 
xfail *-*-* } } }
+  // { dg-final { scan-lang-dump "Bindings '::get'" module { xfail *-*-* } } }
+}
+
+export template <typename T> void use_future_decl(T t) {
+  ::new (t) int;
+}
+
+export struct F {};
+void* operator new(size_t, F);  // not exported
diff --git a/gcc/testsuite/g++.dg/modules/inst-9_b.C 
b/gcc/testsuite/g++.dg/modules/inst-9_b.C
new file mode 100644
index 00000000000..748204e1fdd
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/inst-9_b.C
@@ -0,0 +1,15 @@
+// PR c++/122609
+// PR c++/101140
+// { dg-additional-options "-fmodules" }
+
+import M;
+
+int main() {
+  use_gmf(G{});  // { dg-bogus "" "PR122609" { xfail *-*-* } }
+  // { dg-prune-output "cannot decompose" }
+
+  // operator new is not visible from point of definition,
+  // and is also not visible from this TU (point of instantiation),
+  // so the call below should fail.
+  use_future_decl(F{});  // { dg-error "" "PR122609" { xfail *-*-* } }
+}
-- 
2.51.0

Reply via email to