[v2,1/3] c++: Introduce USING_DECLs for non-function usings [PR114683]

Message ID 668d3eb3.630a0220.063e.5a39@mx.google.com
State New
Headers
Series [v2,1/3] c++: Introduce USING_DECLs for non-function usings [PR114683] |

Commit Message

Nathaniel Shead July 9, 2024, 1:44 p.m. UTC
  On Mon, Jul 08, 2024 at 12:26:41PM -0400, Jason Merrill wrote:
> On 7/6/24 10:06 PM, Nathaniel Shead wrote:
> > Bootstrapped and regtested on x86_64-pc-linux-gnu, OK for trunk?
> > 
> > -- >8 --
> > 
> > With modules, a non-function using-declaration is not completely
> > interchangable with the declaration that it refers to; in particular,
> > such a using-declaration may be exported without revealing the name of
> > the entity it refers to.
> > 
> > This patch fixes this by building USING_DECLs for all using-declarations
> > that bind a non-function.  These new decls can than have purviewness and
> > exportingness attached to them without affecting the decl that they
> > refer to.
> > 
> > We do this for all such usings, not just usings that may be revealed in
> > a module; this way we can verify the change in representation against
> > the (more comprehensive) non-modules testsuites, and in a future patch
> > we can use the locations of these using-decls to enhance relevant
> > diagnostics.
> > 
> > Another possible approach would be to reuse OVERLOADs for this, as is
> > already done within add_binding_entity for modules.  I didn't do this
> > because lots of code (as well as the names of the accessors) makes
> > assumptions that OVERLOADs refer to function overload sets, and so
> > splitting this up reduced semantic burden and made it easier to avoid
> > unintentional changes.  This did mean that we need to move out the
> > definitions of ovl_iterator::{purview,exporting}_p, because the
> > structures for module decls are declared later on in cp-tree.h.
> > 
> > Building USING_DECLs changed a couple of code paths when adjusting
> > bindings; in particular, pushdecl recognises global using-declarations
> > as usings now, and so checks fall through to update_binding.  To not
> > regress g++.dg/lookup/linkage2.C the checks for 'extern' declarations no
> > longer were sufficient (they don't handle 'extern "C"'); but
> > duplicate_decls performed all the relevant checks anyway.
> > 
> > Otherwise in general we strip using-decls from all lookup_* functions
> > where necessary.  Over time for diagnostics purposes it would probably
> > be good to slowly revert this (especially e.g. lookup_elaborated_type
> > causes some diagnostic quality regressions here) but this patch doesn't
> > do so to minimise churn.
> > 
> > 	PR c++/114683
> > 
> > gcc/cp/ChangeLog:
> > 
> > 	* cp-tree.h (class ovl_iterator): Move definitions of purview_p
> > 	and exporting_p to name-lookup.cc.
> > 	* module.cc (depset::hash::add_binding_entity): Strip
> > 	using-decls, remove workarounds.
> > 	(enum ct_bind_flags): Remove unnecessary cbf_wrapped flag.
> > 	(module_state::write_cluster): Likewise.
> > 	(module_state::read_cluster): Build USING_DECL for non-function
> > 	usings.
> > 	* name-lookup.cc (get_fixed_binding_slot): Strip USING_DECLs.
> > 	(name_lookup::process_binding): Strip USING_DECLs.
> > 	(name_lookup::process_module_binding): Remove workaround.
> > 	(update_binding): Strip USING_DECLs, remove incorrect check for
> > 	non-extern variables.
> > 	(ovl_iterator::purview_p): Support USING_DECLs.
> > 	(ovl_iterator::exporting_p): Support USING_DECLs.
> > 	(walk_module_binding): Handle stat hack type.
> > 	(do_nonmember_using_decl): Strip USING_DECLs when comparing;
> > 	build USING_DECLs for non-function usings rather than binding
> > 	directly.
> > 	(get_namespace_binding): Strip USING_DECLs.
> > 	(lookup_name): Strip USING_DECLs.
> > 	(lookup_elaborated_type): Strip USING_DECLs.
> > 	* decl.cc (poplevel): Still support -Wunused for using-decls.
> > 	(lookup_and_check_tag): Remove unnecessary strip_using_decl.
> > 	* parser.cc (cp_parser_template_name): Likewise.
> > 	(cp_parser_nonclass_name): Likewise.
> > 	(cp_parser_class_name): Likewise.
> > 
> > gcc/testsuite/ChangeLog:
> > 
> > 	* g++.dg/lookup/using29.C: Update errors.
> > 	* g++.dg/lookup/using53.C: Update errors, add XFAILs.
> > 	* g++.dg/modules/using-22_b.C: Remove xfails.
> > 	* g++.dg/warn/Wunused-var-18.C: Update error location.
> > 	* g++.dg/lookup/using68.C: New test.
> > 	* g++.dg/modules/using-24_a.C: New test.
> > 	* g++.dg/modules/using-24_b.C: New test.
> > 	* g++.dg/modules/using-enum-4_a.C: New test.
> > 	* g++.dg/modules/using-enum-4_b.C: New test.
> > 	* g++.dg/modules/using-enum-4_c.C: New test.
> > 
> > Signed-off-by: Nathaniel Shead <nathanieloshead@gmail.com>
> > ---
> >   gcc/cp/cp-tree.h                              |  13 +-
> >   gcc/cp/decl.cc                                |  18 +-
> >   gcc/cp/module.cc                              |  78 +++++----
> >   gcc/cp/name-lookup.cc                         | 156 ++++++++++--------
> >   gcc/cp/parser.cc                              |   6 -
> >   gcc/testsuite/g++.dg/lookup/using29.C         |   6 +-
> >   gcc/testsuite/g++.dg/lookup/using53.C         |  20 ++-
> >   gcc/testsuite/g++.dg/lookup/using68.C         |   7 +
> >   gcc/testsuite/g++.dg/modules/using-22_b.C     |   6 +-
> >   gcc/testsuite/g++.dg/modules/using-24_a.C     |  15 ++
> >   gcc/testsuite/g++.dg/modules/using-24_b.C     |  12 ++
> >   gcc/testsuite/g++.dg/modules/using-enum-4_a.C |  13 ++
> >   gcc/testsuite/g++.dg/modules/using-enum-4_b.C |   7 +
> >   gcc/testsuite/g++.dg/modules/using-enum-4_c.C |  11 ++
> >   gcc/testsuite/g++.dg/warn/Wunused-var-18.C    |   4 +-
> >   15 files changed, 231 insertions(+), 141 deletions(-)
> >   create mode 100644 gcc/testsuite/g++.dg/lookup/using68.C
> >   create mode 100644 gcc/testsuite/g++.dg/modules/using-24_a.C
> >   create mode 100644 gcc/testsuite/g++.dg/modules/using-24_b.C
> >   create mode 100644 gcc/testsuite/g++.dg/modules/using-enum-4_a.C
> >   create mode 100644 gcc/testsuite/g++.dg/modules/using-enum-4_b.C
> >   create mode 100644 gcc/testsuite/g++.dg/modules/using-enum-4_c.C
> > 
> > diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> > index 4bb3e9c4989..8732b7dc71b 100644
> > --- a/gcc/cp/cp-tree.h
> > +++ b/gcc/cp/cp-tree.h
> > @@ -889,21 +889,12 @@ class ovl_iterator {
> >       return (TREE_CODE (ovl) == USING_DECL
> >   	    || (TREE_CODE (ovl) == OVERLOAD && OVL_USING_P (ovl)));
> >     }
> > -  /* Whether this using is in the module purview.  */
> > -  bool purview_p () const
> > -  {
> > -    return OVL_PURVIEW_P (get_using ());
> > -  }
> > -  /* Whether this using is being exported.  */
> > -  bool exporting_p () const
> > -  {
> > -    return OVL_EXPORT_P (get_using ());
> > -  }
> > -
> >     bool hidden_p () const
> >     {
> >       return TREE_CODE (ovl) == OVERLOAD && OVL_HIDDEN_P (ovl);
> >     }
> > +  bool purview_p () const;
> > +  bool exporting_p () const;
> >    public:
> >     tree remove_node (tree head)
> > diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
> > index 29616100cfe..5a823e2f94c 100644
> > --- a/gcc/cp/decl.cc
> > +++ b/gcc/cp/decl.cc
> > @@ -702,12 +702,13 @@ poplevel (int keep, int reverse, int functionbody)
> >   	/* There are cases where D itself is a TREE_LIST.  See in
> >   	   push_local_binding where the list of decls returned by
> >   	   getdecls is built.  */
> > -	decl = TREE_CODE (d) == TREE_LIST ? TREE_VALUE (d) : d;
> > +	tree udecl = TREE_CODE (d) == TREE_LIST ? TREE_VALUE (d) : d;
> > +	decl = strip_using_decl (udecl);
> >   	tree type = TREE_TYPE (decl);
> >   	if (VAR_P (decl)
> > -	    && (! TREE_USED (decl) || !DECL_READ_P (decl))
> > -	    && ! DECL_IN_SYSTEM_HEADER (decl)
> > +	    && (!TREE_USED (decl) || !DECL_READ_P (decl))
> > +	    && !DECL_IN_SYSTEM_HEADER (udecl)
> >   	    /* For structured bindings, consider only real variables, not
> >   	       subobjects.  */
> >   	    && (DECL_DECOMPOSITION_P (decl) ? DECL_DECOMP_IS_BASE (decl)
> > @@ -720,14 +721,14 @@ poplevel (int keep, int reverse, int functionbody)
> >   		|| lookup_attribute ("warn_unused",
> >   				     TYPE_ATTRIBUTES (TREE_TYPE (decl)))))
> >   	  {
> > -	    if (! TREE_USED (decl))
> > +	    if (!TREE_USED (decl))
> >   	      {
> >   		if (!DECL_NAME (decl) && DECL_DECOMPOSITION_P (decl))
> > -		  warning_at (DECL_SOURCE_LOCATION (decl),
> > +		  warning_at (DECL_SOURCE_LOCATION (udecl),
> >   			      OPT_Wunused_variable,
> >   			      "unused structured binding declaration");
> >   		else
> > -		  warning_at (DECL_SOURCE_LOCATION (decl),
> > +		  warning_at (DECL_SOURCE_LOCATION (udecl),
> >   			      OPT_Wunused_variable, "unused variable %qD", decl);
> >   		suppress_warning (decl, OPT_Wunused_variable);
> >   	      }
> 
> If we're changing this diagnostic to refer to the location of the using, we
> probably also want the message to talk about using?
> 

Right; I mostly was just doing the minimal to not regress the testcase,
but may as well do it properly.

> > @@ -737,11 +738,11 @@ poplevel (int keep, int reverse, int functionbody)
> >   		     && errorcount == unused_but_set_errorcount)
> >   	      {
> >   		if (!DECL_NAME (decl) && DECL_DECOMPOSITION_P (decl))
> > -		  warning_at (DECL_SOURCE_LOCATION (decl),
> > +		  warning_at (DECL_SOURCE_LOCATION (udecl),
> >   			      OPT_Wunused_but_set_variable, "structured "
> >   			      "binding declaration set but not used");
> >   		else
> > -		  warning_at (DECL_SOURCE_LOCATION (decl),
> > +		  warning_at (DECL_SOURCE_LOCATION (udecl),
> >   			      OPT_Wunused_but_set_variable,
> >   			      "variable %qD set but not used", decl);
> >   		unused_but_set_errorcount = errorcount;
> > @@ -16495,7 +16496,6 @@ lookup_and_check_tag (enum tag_types tag_code, tree name,
> >         /* First try ordinary name lookup, ignoring hidden class name
> >   	 injected via friend declaration.  */
> >         decl = lookup_name (name, LOOK_want::TYPE);
> > -      decl = strip_using_decl (decl);
> >         /* If that fails, the name will be placed in the smallest
> >   	 non-class, non-function-prototype scope according to 3.3.1/5.
> >   	 We may already have a hidden name declared as friend in this
> > diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
> > index dc5d046f04d..f5966fc8c1c 100644
> > --- a/gcc/cp/module.cc
> > +++ b/gcc/cp/module.cc
> > @@ -13125,6 +13125,7 @@ bool
> >   depset::hash::add_binding_entity (tree decl, WMB_Flags flags, void *data_)
> >   {
> >     auto data = static_cast <add_binding_data *> (data_);
> > +  decl = strip_using_decl (decl);
> >     if (!(TREE_CODE (decl) == NAMESPACE_DECL && !DECL_NAMESPACE_ALIAS (decl)))
> >       {
> > @@ -13159,18 +13160,14 @@ depset::hash::add_binding_entity (tree decl, WMB_Flags flags, void *data_)
> >   	/* Ignore NTTP objects.  */
> >   	return false;
> > -      bool unscoped_enum_const_p = false;
> >         if (!(flags & WMB_Using) && CP_DECL_CONTEXT (decl) != data->ns)
> >   	{
> > -	  /* A using that lost its wrapper or an unscoped enum
> > -	     constant.  */
> > -	  /* FIXME: Ensure that unscoped enums are differentiated from
> > -	     'using enum' declarations when PR c++/114683 is fixed.  */
> > -	  unscoped_enum_const_p = (TREE_CODE (decl) == CONST_DECL);
> > +	  /* An unscoped enum constant implicitly brought into the containing
> > +	     namespace.  We treat this like a using-decl.  */
> > +	  gcc_checking_assert (TREE_CODE (decl) == CONST_DECL);
> > +
> >   	  flags = WMB_Flags (flags | WMB_Using);
> > -	  if (DECL_MODULE_EXPORT_P (TREE_CODE (decl) == CONST_DECL
> > -				    ? TYPE_NAME (TREE_TYPE (decl))
> > -				    : STRIP_TEMPLATE (decl)))
> > +	  if (DECL_MODULE_EXPORT_P (TYPE_NAME (TREE_TYPE (decl))))
> >   	    flags = WMB_Flags (flags | WMB_Export);
> >   	}
> > @@ -13228,8 +13225,7 @@ depset::hash::add_binding_entity (tree decl, WMB_Flags flags, void *data_)
> >         if (flags & WMB_Using)
> >   	{
> >   	  decl = ovl_make (decl, NULL_TREE);
> > -	  if (!unscoped_enum_const_p)
> > -	    OVL_USING_P (decl) = true;
> > +	  OVL_USING_P (decl) = true;
> >   	  OVL_PURVIEW_P (decl) = true;
> >   	  if (flags & WMB_Export)
> >   	    OVL_EXPORT_P (decl) = true;
> > @@ -13253,12 +13249,8 @@ depset::hash::add_binding_entity (tree decl, WMB_Flags flags, void *data_)
> >         data->met_namespace = true;
> >         if (data->hash->add_namespace_entities (decl, data->partitions))
> >   	{
> > -	  /* It contains an exported thing, so it is exported.
> > -
> > -	     FIXME we have to set DECL_MODULE_PURVIEW_P instead of asserting
> > -	     that it is already set because of the c++/114683 issue with
> > -	     exported using-declarations; see do_nonmember_using_decl.  */
> > -	  DECL_MODULE_PURVIEW_P (decl) = true;
> > +	  /* It contains an exported thing, so it is exported.  */
> > +	  gcc_checking_assert (DECL_MODULE_PURVIEW_P (decl));
> >   	  DECL_MODULE_EXPORT_P (decl) = true;
> >   	}
> > @@ -14967,7 +14959,6 @@ enum ct_bind_flags
> >     cbf_export = 0x1,	/* An exported decl.  */
> >     cbf_hidden = 0x2,	/* A hidden (friend) decl.  */
> >     cbf_using = 0x4,	/* A using decl.  */
> > -  cbf_wrapped = 0x8,  	/* ... that is wrapped.  */
> >   };
> >   /* DEP belongs to a different cluster, seed it to prevent
> > @@ -15130,13 +15121,9 @@ module_state::write_cluster (elf_out *to, depset *scc[], unsigned size,
> >   		    if (!(TREE_CODE (bound) == CONST_DECL
> >   			  && UNSCOPED_ENUM_P (TREE_TYPE (bound))
> >   			  && decl == TYPE_NAME (TREE_TYPE (bound))))
> > -		      {
> > -			/* An unscope enumerator in its enumeration's
> > -			   scope is not a using.  */
> > -			flags |= cbf_using;
> > -			if (OVL_USING_P (ovl))
> > -			  flags |= cbf_wrapped;
> > -		      }
> > +		      /* An unscoped enumerator in its enumeration's
> > +			 scope is not a using.  */
> > +		      flags |= cbf_using;
> >   		    if (OVL_EXPORT_P (ovl))
> >   		      flags |= cbf_export;
> >   		  }
> > @@ -15303,13 +15290,46 @@ module_state::read_cluster (unsigned snum)
> >   		    /* Stat hack.  */
> >   		    if (type || !DECL_IMPLICIT_TYPEDEF_P (decl))
> >   		      sec.set_overrun ();
> > -		    type = decl;
> > +
> > +		    type = build_lang_decl_loc (UNKNOWN_LOCATION,
> > +						USING_DECL,
> > +						DECL_NAME (decl),
> > +						NULL_TREE);
> 
> Shouldn't this be conditional on cbf_using?
> 

Thanks, fixed.

> > +		    USING_DECL_DECLS (type) = decl;
> > +		    USING_DECL_SCOPE (type) = CP_DECL_CONTEXT (decl);
> > +		    DECL_CONTEXT (type) = ns;
> > +
> > +		    DECL_MODULE_PURVIEW_P (type) = true;
> > +		    if (flags & cbf_export)
> > +		      DECL_MODULE_EXPORT_P (type) = true;
> >   		  }
> >   		else
> >   		  {
> > -		    if (decls
> > -			|| (flags & (cbf_hidden | cbf_wrapped))
> > -			|| DECL_FUNCTION_TEMPLATE_P (decl))
> > +		    if ((flags & cbf_using) &&
> > +			!DECL_DECLARES_FUNCTION_P (decl))
> > +		      {
> > +			/* We should only see a single non-function using-decl
> > +			   for a binding; more than that would clash.  */
> > +			if (decls)
> > +			  sec.set_overrun ();
> > +
> > +			/* FIXME: Propagate the location of the using-decl
> > +			   for use in diagnostics.  */
> > +			decls = build_lang_decl_loc (UNKNOWN_LOCATION,
> > +						     USING_DECL,
> > +						     DECL_NAME (decl),
> > +						     NULL_TREE);
> > +			USING_DECL_DECLS (decls) = decl;
> > +			USING_DECL_SCOPE (decls) = CP_DECL_CONTEXT (decl);
> 
> Maybe comment that we don't record the actual scope of a using-declaration,
> we expect that this approximation is good enough.
> 

Done.

> > +			DECL_CONTEXT (decls) = ns;
> > +
> > +			DECL_MODULE_PURVIEW_P (decls) = true;
> > +			if (flags & cbf_export)
> > +			  DECL_MODULE_EXPORT_P (decls) = true;
> > +		      }
> > +		    else if (decls
> > +			     || (flags & (cbf_hidden | cbf_using))
> > +			     || DECL_FUNCTION_TEMPLATE_P (decl))
> >   		      {
> >   			decls = ovl_make (decl, decls);
> >   			if (flags & cbf_using)
> > diff --git a/gcc/cp/name-lookup.cc b/gcc/cp/name-lookup.cc
> > index b57893116eb..ddb4c0fe1c8 100644
> > --- a/gcc/cp/name-lookup.cc
> > +++ b/gcc/cp/name-lookup.cc
> > @@ -281,10 +281,11 @@ get_fixed_binding_slot (tree *slot, tree name, unsigned ix, int create)
> >   	  /* Propagate global & module entities to the global and
> >   	     partition slots.  */
> > -	  if (tree type = MAYBE_STAT_TYPE (orig))
> > +	  if (tree type = strip_using_decl (MAYBE_STAT_TYPE (orig)))
> >   	    init_global_partition (cluster, type);
> > -	  for (ovl_iterator iter (MAYBE_STAT_DECL (orig)); iter; ++iter)
> > +	  for (ovl_iterator iter (strip_using_decl (MAYBE_STAT_DECL (orig)));
> > +	       iter; ++iter)
> >   	    {
> >   	      tree decl = *iter;
> > @@ -767,6 +768,9 @@ name_lookup::process_binding (tree new_val, tree new_type)
> >         && (want & LOOK_want::TYPE_NAMESPACE) == LOOK_want::NAMESPACE)
> >       new_type = NULL_TREE;
> > +  new_val = strip_using_decl (new_val);
> > +  new_type = strip_using_decl (new_type);
> > +
> >     /* Do we really see a value? */
> >     if (new_val)
> >       switch (TREE_CODE (new_val))
> > @@ -827,17 +831,6 @@ name_lookup::process_module_binding (tree new_val, tree new_type,
> >         marker |= 2;
> >       }
> > -  /* add_binding_entity wraps decls brought in by 'using' in an OVERLOAD even
> > -     for non-functions; strip it now.
> > -     ??? Why isn't it represented with a USING_DECL?  Or do we want to use
> > -     OVERLOAD for using more widely to address 114683?  */
> > -  if (new_val && TREE_CODE (new_val) == OVERLOAD
> > -      && !DECL_DECLARES_FUNCTION_P (OVL_FUNCTION (new_val)))
> > -    {
> > -      gcc_checking_assert (OVL_USING_P (new_val) && !OVL_CHAIN (new_val));
> > -      new_val = OVL_FUNCTION (new_val);
> > -    }
> > -
> >     if (new_type || new_val)
> >       marker |= process_binding (new_val, new_type);
> > @@ -2998,6 +2991,7 @@ update_binding (cp_binding_level *level, cxx_binding *binding, tree *slot,
> >     if (old == error_mark_node)
> >       old = NULL_TREE;
> > +  old = strip_using_decl (old);
> >     if (DECL_IMPLICIT_TYPEDEF_P (decl))
> >       {
> > @@ -3102,11 +3096,7 @@ update_binding (cp_binding_level *level, cxx_binding *binding, tree *slot,
> >   	}
> >         else if (TREE_CODE (old) == VAR_DECL)
> >   	{
> > -	  /* There can be two block-scope declarations of the same
> > -	     variable, so long as they are `extern' declarations.  */
> > -	  if (!DECL_EXTERNAL (old) || !DECL_EXTERNAL (decl))
> > -	    goto conflict;
> > -	  else if (tree match = duplicate_decls (decl, old))
> > +	  if (tree match = duplicate_decls (decl, old))
> >   	    {
> >   	      gcc_checking_assert (!hide_value && !hiding);
> >   	      return match;
> > @@ -4205,6 +4195,28 @@ lookup_class_binding (tree klass, tree name)
> >     return found;
> >   }
> > +/* Whether this using is declared in the module purview.  */
> > +
> > +bool
> > +ovl_iterator::purview_p () const
> > +{
> > +  gcc_checking_assert (using_p ());
> > +  if (TREE_CODE (ovl) == USING_DECL)
> > +    return DECL_MODULE_PURVIEW_P (ovl);
> > +  return OVL_PURVIEW_P (ovl);
> > +}
> > +
> > +/* Whether this using is exported from this module.  */
> > +
> > +bool
> > +ovl_iterator::exporting_p () const
> > +{
> > +  gcc_checking_assert (using_p ());
> > +  if (TREE_CODE (ovl) == USING_DECL)
> > +    return DECL_MODULE_EXPORT_P (ovl);
> > +  return OVL_EXPORT_P (ovl);
> > +}
> > +
> >   /* Given a namespace-level binding BINDING, walk it, calling CALLBACK
> >      for all decls of the current module.  When partitions are involved,
> >      decls might be mentioned more than once.   Return the accumulation of
> > @@ -4215,8 +4227,6 @@ walk_module_binding (tree binding, bitmap partitions,
> >   		     bool (*callback) (tree decl, WMB_Flags, void *data),
> >   		     void *data)
> >   {
> > -  // FIXME: We don't quite deal with using decls naming stat hack
> > -  // type.
> >     tree current = binding;
> >     unsigned count = 0;
> > @@ -4229,6 +4239,14 @@ walk_module_binding (tree binding, bitmap partitions,
> >         WMB_Flags flags = WMB_None;
> >         if (STAT_TYPE_HIDDEN_P (current))
> >   	flags = WMB_Flags (flags | WMB_Hidden);
> > +      if (TREE_CODE (type) == USING_DECL)
> > +	{
> > +	  flags = WMB_Flags (flags | WMB_Using);
> > +	  if (DECL_MODULE_PURVIEW_P (type))
> > +	    flags = WMB_Flags (flags | WMB_Purview);
> > +	  if (DECL_MODULE_EXPORT_P (type))
> > +	    flags = WMB_Flags (flags | WMB_Export);
> > +	}
> >         count += callback (type, flags, data);
> >         decl_hidden = STAT_DECL_HIDDEN_P (current);
> >       }
> > @@ -4300,7 +4318,14 @@ walk_module_binding (tree binding, bitmap partitions,
> >   			  flags = WMB_Flags (flags | WMB_Dups);
> >   			if (STAT_TYPE_HIDDEN_P (bind))
> >   			  flags = WMB_Flags (flags | WMB_Hidden);
> > -
> > +			if (TREE_CODE (btype) == USING_DECL)
> > +			  {
> > +			    flags = WMB_Flags (flags | WMB_Using);
> > +			    if (DECL_MODULE_PURVIEW_P (btype))
> > +			      flags = WMB_Flags (flags | WMB_Purview);
> > +			    if (DECL_MODULE_EXPORT_P (btype))
> > +			      flags = WMB_Flags (flags | WMB_Export);
> > +			  }
> >   			count += callback (btype, flags, data);
> >   		      }
> >   		    bool part_hidden = STAT_DECL_HIDDEN_P (bind);
> > @@ -5201,7 +5226,7 @@ do_nonmember_using_decl (name_lookup &lookup, bool fn_scope_p,
> >     /* Shift the old and new bindings around so we're comparing class and
> >        enumeration names to each other.  */
> > -  if (value && DECL_IMPLICIT_TYPEDEF_P (value))
> > +  if (value && DECL_IMPLICIT_TYPEDEF_P (strip_using_decl (value)))
> >       {
> >         type = value;
> >         value = NULL_TREE;
> > @@ -5306,61 +5331,51 @@ do_nonmember_using_decl (name_lookup &lookup, bool fn_scope_p,
> >     else if (value
> >   	   /* Ignore anticipated builtins.  */
> >   	   && !anticipated_builtin_p (value)
> > -	   && (fn_scope_p || !decls_match (lookup.value, value)))
> > +	   && (fn_scope_p
> > +	       || !decls_match (lookup.value, strip_using_decl (value))))
> >       {
> >         diagnose_name_conflict (lookup.value, value);
> >         failed = true;
> >       }
> >     else if (insert_p)
> >       {
> > -      /* FIXME: Handle exporting declarations from a different scope
> > -	 without also marking those declarations as exported.
> > -	 This will require not just binding directly to the underlying
> > -	 value; see c++/114683 and c++/114685.  We allow the extra exports
> > -	 for now as this doesn't (currently) cause ICEs
> > -	 later down the line, but this should be revisited.  */
> > -      if (revealing_p)
> > -	{
> > -	  if (module_exporting_p ()
> > -	      && check_can_export_using_decl (lookup.value)
> > -	      && !DECL_MODULE_EXPORT_P (lookup.value))
> > -	    {
> > -	      /* We're redeclaring the same value, but this time as
> > -		 newly exported: make sure to mark it as such.  */
> > -	      if (TREE_CODE (lookup.value) == TEMPLATE_DECL)
> > -		DECL_MODULE_EXPORT_P (DECL_TEMPLATE_RESULT (lookup.value)) = true;
> > -	      DECL_MODULE_EXPORT_P (lookup.value) = true;
> > -	    }
> > -	  /* We're also revealing this declaration with a using-decl,
> > -	     so mark it as purview to ensure it's emitted.  */
> > -	  tree inner = STRIP_TEMPLATE (lookup.value);
> > -	  retrofit_lang_decl (inner);
> > -	  DECL_MODULE_PURVIEW_P (inner) = true;
> > -	}
> > -      value = lookup.value;
> > +      /* A using-decl does not necessarily have the same purview-ness or
> > +	 exporting as the declaration it reveals, so build a USING_DECL
> > +	 that we can attach this information to.  This also gives us a
> > +	 location for the using-decl that we can use in diagnostics.  */
> 
> For a using-decl in the same scope as the original decl, won't this replace
> it so only the using-decl is visible to lookup?  I had expected to omit the
> USING_DECL in that case.
> 

Yup it will; I think I'd originally done that so that more recent
(re-)declaration would be the one referred to by diagnostics, but on
retrospect that seems unhelpful; fixed.  (Though need to keep the
replacement for CONST_DECLs, because the modules handling otherwise only
handles them in the context of their containing enumeration type, which
isn't what we want here; I've added a new test for this as well.)

Here's a new version of the patch; bootstrapped and regtested on
x86_64-pc-linux-gnu (so far just modules.exp and dg.exp), OK for trunk
if full regtest passes?

-- >8 --

With modules, a non-function using-declaration is not completely
interchangable with the declaration that it refers to; in particular,
such a using-declaration may be exported without revealing the name of
the entity it refers to.

This patch fixes this by building USING_DECLs for all using-declarations
that bind a non-function from a different scope.  These new decls can
than have purviewness and exportingness attached to them without
affecting the decl that they refer to.

We do this for all such usings, not just usings that may be revealed in
a module; this way we can verify the change in representation against
the (more comprehensive) non-modules testsuites, and in a future patch
we can use the locations of these using-decls to enhance relevant
diagnostics.

Another possible approach would be to reuse OVERLOADs for this, as is
already done within add_binding_entity for modules.  I didn't do this
because lots of code (as well as the names of the accessors) makes
assumptions that OVERLOADs refer to function overload sets, and so
splitting this up reduced semantic burden and made it easier to avoid
unintentional changes.  This did mean that we need to move out the
definitions of ovl_iterator::{purview,exporting}_p, because the
structures for module decls are declared later on in cp-tree.h.

Building USING_DECLs changed a couple of code paths when adjusting
bindings; in particular, pushdecl recognises global using-declarations
as usings now, and so checks fall through to update_binding.  To not
regress g++.dg/lookup/linkage2.C the checks for 'extern' declarations no
longer were sufficient (they don't handle 'extern "C"'); but
duplicate_decls performed all the relevant checks anyway.

Otherwise in general we strip using-decls from all lookup_* functions
where necessary.  Over time for diagnostics purposes it would probably
be good to slowly revert this (especially e.g. lookup_elaborated_type
causes some diagnostic quality regressions here) but this patch doesn't
do so to minimise churn.

	PR c++/114683

gcc/cp/ChangeLog:

	* cp-tree.h (class ovl_iterator): Move definitions of purview_p
	and exporting_p to name-lookup.cc.
	* module.cc (depset::hash::add_binding_entity): Strip
	using-decls, remove workarounds.
	(enum ct_bind_flags): Remove unnecessary cbf_wrapped flag.
	(module_state::write_cluster): Likewise.
	(module_state::read_cluster): Build USING_DECL for non-function
	usings.
	* name-lookup.cc (get_fixed_binding_slot): Strip USING_DECLs.
	(name_lookup::process_binding): Strip USING_DECLs.
	(name_lookup::process_module_binding): Remove workaround.
	(update_binding): Strip USING_DECLs, remove incorrect check for
	non-extern variables.
	(ovl_iterator::purview_p): Support USING_DECLs.
	(ovl_iterator::exporting_p): Support USING_DECLs.
	(walk_module_binding): Handle stat hack type.
	(do_nonmember_using_decl): Strip USING_DECLs when comparing;
	build USING_DECLs for non-function usings in different scope
	rather than binding directly.
	(get_namespace_binding): Strip USING_DECLs.
	(lookup_name): Strip USING_DECLs.
	(lookup_elaborated_type): Strip USING_DECLs.
	* decl.cc (poplevel): Still support -Wunused for using-decls.
	(lookup_and_check_tag): Remove unnecessary strip_using_decl.
	* parser.cc (cp_parser_template_name): Likewise.
	(cp_parser_nonclass_name): Likewise.
	(cp_parser_class_name): Likewise.

gcc/testsuite/ChangeLog:

	* g++.dg/lookup/using29.C: Update errors.
	* g++.dg/lookup/using53.C: Update errors, add XFAILs.
	* g++.dg/modules/using-22_b.C: Remove xfails.
	* g++.dg/warn/Wunused-var-18.C: Update error, add check.
	* g++.dg/lookup/using68.C: New test.
	* g++.dg/modules/using-24_a.C: New test.
	* g++.dg/modules/using-24_b.C: New test.
	* g++.dg/modules/using-25_a.C: New test.
	* g++.dg/modules/using-25_b.C: New test.
	* g++.dg/modules/using-enum-4_a.C: New test.
	* g++.dg/modules/using-enum-4_b.C: New test.
	* g++.dg/modules/using-enum-4_c.C: New test.

Signed-off-by: Nathaniel Shead <nathanieloshead@gmail.com>
---
 gcc/cp/cp-tree.h                              |  13 +-
 gcc/cp/decl.cc                                |  16 +-
 gcc/cp/module.cc                              |  86 ++++++---
 gcc/cp/name-lookup.cc                         | 167 +++++++++++-------
 gcc/cp/parser.cc                              |   6 -
 gcc/testsuite/g++.dg/lookup/using29.C         |   6 +-
 gcc/testsuite/g++.dg/lookup/using53.C         |  20 ++-
 gcc/testsuite/g++.dg/lookup/using68.C         |   7 +
 gcc/testsuite/g++.dg/modules/using-22_b.C     |   6 +-
 gcc/testsuite/g++.dg/modules/using-24_a.C     |  15 ++
 gcc/testsuite/g++.dg/modules/using-24_b.C     |  12 ++
 gcc/testsuite/g++.dg/modules/using-25_a.C     |  15 ++
 gcc/testsuite/g++.dg/modules/using-25_b.C     |  17 ++
 gcc/testsuite/g++.dg/modules/using-enum-4_a.C |  13 ++
 gcc/testsuite/g++.dg/modules/using-enum-4_b.C |   7 +
 gcc/testsuite/g++.dg/modules/using-enum-4_c.C |  11 ++
 gcc/testsuite/g++.dg/warn/Wunused-var-18.C    |  11 +-
 17 files changed, 294 insertions(+), 134 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/lookup/using68.C
 create mode 100644 gcc/testsuite/g++.dg/modules/using-24_a.C
 create mode 100644 gcc/testsuite/g++.dg/modules/using-24_b.C
 create mode 100644 gcc/testsuite/g++.dg/modules/using-25_a.C
 create mode 100644 gcc/testsuite/g++.dg/modules/using-25_b.C
 create mode 100644 gcc/testsuite/g++.dg/modules/using-enum-4_a.C
 create mode 100644 gcc/testsuite/g++.dg/modules/using-enum-4_b.C
 create mode 100644 gcc/testsuite/g++.dg/modules/using-enum-4_c.C
  

Comments

Jason Merrill July 9, 2024, 9:43 p.m. UTC | #1
On 7/9/24 9:44 AM, Nathaniel Shead wrote:
> On Mon, Jul 08, 2024 at 12:26:41PM -0400, Jason Merrill wrote:
>> For a using-decl in the same scope as the original decl, won't this replace
>> it so only the using-decl is visible to lookup?  I had expected to omit the
>> USING_DECL in that case.
> 
> Yup it will; I think I'd originally done that so that more recent
> (re-)declaration would be the one referred to by diagnostics, but on
> retrospect that seems unhelpful; fixed.  (Though need to keep the
> replacement for CONST_DECLs, because the modules handling otherwise only
> handles them in the context of their containing enumeration type, which
> isn't what we want here; I've added a new test for this as well.)

Ah, using-25, sure.  I would think we could still tell the difference by 
comparing PURVIEW/EXPORT on the CONST_DECL to those of its type?

Or perhaps have add_binding_entity skip implicitly inserted enumerators, 
and instead insert them again when reading the enum, which should also 
save a bit of space.

Jason
  
Nathaniel Shead July 11, 2024, 10:22 a.m. UTC | #2
On Tue, Jul 09, 2024 at 05:43:59PM -0400, Jason Merrill wrote:
> On 7/9/24 9:44 AM, Nathaniel Shead wrote:
> > On Mon, Jul 08, 2024 at 12:26:41PM -0400, Jason Merrill wrote:
> > > For a using-decl in the same scope as the original decl, won't this replace
> > > it so only the using-decl is visible to lookup?  I had expected to omit the
> > > USING_DECL in that case.
> > 
> > Yup it will; I think I'd originally done that so that more recent
> > (re-)declaration would be the one referred to by diagnostics, but on
> > retrospect that seems unhelpful; fixed.  (Though need to keep the
> > replacement for CONST_DECLs, because the modules handling otherwise only
> > handles them in the context of their containing enumeration type, which
> > isn't what we want here; I've added a new test for this as well.)
> 
> Ah, using-25, sure.  I would think we could still tell the difference by
> comparing PURVIEW/EXPORT on the CONST_DECL to those of its type?
> 
> Or perhaps have add_binding_entity skip implicitly inserted enumerators, and
> instead insert them again when reading the enum, which should also save a
> bit of space.
> 
> Jason
> 

So maybe something like the following?

Bootstrapped and regtested on x86_64-pc-linux-gnu, can be applied either
incrementally on the previous patch or separately as you prefer.

-- >8 --

Subject: [PATCH] c++/modules: Avoid unnecessary wrapping for CONST_DECLs

Enumerators are only written when writing the type definition (at which
point they are all written); this will happen regardless of scoped vs
unscoped or whether the enum is explicitly exported.  All other cases
where an enumerator needs to be written (e.g. template parameters) they
are just a backreference to the type decl and the name of the value.

'add_binding_entity' needs to explicitly write the names of unscoped
enumerators so that lazy loading will trigger when the name is found by
name lookup; it does this by pretending that the enum declarations are
always usings so that it doesn't double-write definitions.  By also
checking if the enumerator was marked purview/exported we can use that
to override a non-purview/non-exported TYPE_DECL and ensure it's made
visible regardless.

When reading we should get the exported flag on the enumeration
constant, and so should properly create a binding for it.  We don't need
to do anything to handle importedness as that checking is skipped for
EK_USINGs.

Some other places assume that module information for a CONST_DECL
inherits module information from its containing type.  This includes:

- get_originating_module_decl, for determining if the name was imported
  or has module attachment; I don't /think/ this change should affect
  that, so I'm leaving this untouched.

- binding_cmp, for sorting by exportedness; since now an enumerator
  could be exported without the containing decl being exported, we need
  to handle this here too.

With all this in mind, we can avoid creating a new USING_DECL for a
same-scope using that reveals a CONST_DECL by ensuring that we
special-case CONST_DECLs with purview/exported flags appropriately.

gcc/cp/ChangeLog:

	* module.cc (depset::hash::add_binding_entity): Handle
	CONST_DECLs with different purview/exported from their enum.
	(binding_cmp): Likewise.
	(set_instantiating_module): Support CONST_DECLs.
	* name-lookup.cc (do_nonmember_using_decl): Don't special-case
	CONST_DECLs.

Signed-off-by: Nathaniel Shead <nathanieloshead@gmail.com>
---
 gcc/cp/module.cc      | 19 +++++++++++++++++--
 gcc/cp/name-lookup.cc |  6 ++----
 2 files changed, 19 insertions(+), 6 deletions(-)

diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
index 7187d251d1d..d385b422168 100644
--- a/gcc/cp/module.cc
+++ b/gcc/cp/module.cc
@@ -13129,7 +13129,10 @@ depset::hash::add_binding_entity (tree decl, WMB_Flags flags, void *data_)
       tree inner = decl;
 
       if (TREE_CODE (inner) == CONST_DECL
-	  && TREE_CODE (DECL_CONTEXT (inner)) == ENUMERAL_TYPE)
+	  && TREE_CODE (DECL_CONTEXT (inner)) == ENUMERAL_TYPE
+	  /* A using-decl could make a CONST_DECL purview for a non-purview
+	     enumeration.  */
+	  && (!DECL_LANG_SPECIFIC (inner) || !DECL_MODULE_PURVIEW_P (inner)))
 	inner = TYPE_NAME (DECL_CONTEXT (inner));
       else if (TREE_CODE (inner) == TEMPLATE_DECL)
 	inner = DECL_TEMPLATE_RESULT (inner);
@@ -13164,7 +13167,10 @@ depset::hash::add_binding_entity (tree decl, WMB_Flags flags, void *data_)
 	  gcc_checking_assert (TREE_CODE (decl) == CONST_DECL);
 
 	  flags = WMB_Flags (flags | WMB_Using);
-	  if (DECL_MODULE_EXPORT_P (TYPE_NAME (TREE_TYPE (decl))))
+	  if (DECL_MODULE_EXPORT_P (TYPE_NAME (TREE_TYPE (decl)))
+	      /* A using-decl can make an enum constant exported for a
+		 non-exported enumeration.  */
+	      || (DECL_LANG_SPECIFIC (decl) && DECL_MODULE_EXPORT_P (decl)))
 	    flags = WMB_Flags (flags | WMB_Export);
 	}
 
@@ -13736,6 +13742,10 @@ binding_cmp (const void *a_, const void *b_)
       a_export = OVL_EXPORT_P (a_ent);
       a_ent = OVL_FUNCTION (a_ent);
     }
+  else if (TREE_CODE (a_ent) == CONST_DECL
+	   && DECL_LANG_SPECIFIC (a_ent)
+	   && DECL_MODULE_EXPORT_P (a_ent))
+    a_export = true;
   else
     a_export = DECL_MODULE_EXPORT_P (TREE_CODE (a_ent) == CONST_DECL
 				     ? TYPE_NAME (TREE_TYPE (a_ent))
@@ -13748,6 +13758,10 @@ binding_cmp (const void *a_, const void *b_)
       b_export = OVL_EXPORT_P (b_ent);
       b_ent = OVL_FUNCTION (b_ent);
     }
+  else if (TREE_CODE (b_ent) == CONST_DECL
+	   && DECL_LANG_SPECIFIC (b_ent)
+	   && DECL_MODULE_EXPORT_P (b_ent))
+    b_export = true;
   else
     b_export = DECL_MODULE_EXPORT_P (TREE_CODE (b_ent) == CONST_DECL
 				     ? TYPE_NAME (TREE_TYPE (b_ent))
@@ -19230,6 +19244,7 @@ set_instantiating_module (tree decl)
 	      || TREE_CODE (decl) == TYPE_DECL
 	      || TREE_CODE (decl) == CONCEPT_DECL
 	      || TREE_CODE (decl) == TEMPLATE_DECL
+	      || TREE_CODE (decl) == CONST_DECL
 	      || (TREE_CODE (decl) == NAMESPACE_DECL
 		  && DECL_NAMESPACE_ALIAS (decl)));
 
diff --git a/gcc/cp/name-lookup.cc b/gcc/cp/name-lookup.cc
index 5b1f3bfd510..e5f8562105e 100644
--- a/gcc/cp/name-lookup.cc
+++ b/gcc/cp/name-lookup.cc
@@ -5345,10 +5345,8 @@ do_nonmember_using_decl (name_lookup &lookup, bool fn_scope_p,
 	 location for the using-decl that we can use in diagnostics.
 
 	 But this is unnecessary if we're just redeclaring the same decl;
-	 in that case we can just mark it purview or exported directly.
-	 Except for using-decls of enumerators, which are currently
-	 handled specially in modules code and so need to be wrapped.  */
-      if (value != lookup.value || TREE_CODE (value) == CONST_DECL)
+	 in that case we can just mark it purview or exported directly.  */
+      if (value != lookup.value)
 	{
 	  value = build_lang_decl (USING_DECL, lookup.name, NULL_TREE);
 	  USING_DECL_DECLS (value) = lookup.value;
  
Jason Merrill July 11, 2024, 3:31 p.m. UTC | #3
On 7/11/24 6:22 AM, Nathaniel Shead wrote:
> On Tue, Jul 09, 2024 at 05:43:59PM -0400, Jason Merrill wrote:
>> On 7/9/24 9:44 AM, Nathaniel Shead wrote:
>>> On Mon, Jul 08, 2024 at 12:26:41PM -0400, Jason Merrill wrote:
>>>> For a using-decl in the same scope as the original decl, won't this replace
>>>> it so only the using-decl is visible to lookup?  I had expected to omit the
>>>> USING_DECL in that case.
>>>
>>> Yup it will; I think I'd originally done that so that more recent
>>> (re-)declaration would be the one referred to by diagnostics, but on
>>> retrospect that seems unhelpful; fixed.  (Though need to keep the
>>> replacement for CONST_DECLs, because the modules handling otherwise only
>>> handles them in the context of their containing enumeration type, which
>>> isn't what we want here; I've added a new test for this as well.)
>>
>> Ah, using-25, sure.  I would think we could still tell the difference by
>> comparing PURVIEW/EXPORT on the CONST_DECL to those of its type?
>>
>> Or perhaps have add_binding_entity skip implicitly inserted enumerators, and
>> instead insert them again when reading the enum, which should also save a
>> bit of space.
>>
>> Jason
>>
> 
> So maybe something like the following?
> 
> Bootstrapped and regtested on x86_64-pc-linux-gnu, can be applied either
> incrementally on the previous patch or separately as you prefer.

Looks good. I think squash this patch into the previous one; the 
combined patch is OK.

> -- >8 --
> 
> Subject: [PATCH] c++/modules: Avoid unnecessary wrapping for CONST_DECLs
> 
> Enumerators are only written when writing the type definition (at which
> point they are all written); this will happen regardless of scoped vs
> unscoped or whether the enum is explicitly exported.  All other cases
> where an enumerator needs to be written (e.g. template parameters) they
> are just a backreference to the type decl and the name of the value.
> 
> 'add_binding_entity' needs to explicitly write the names of unscoped
> enumerators so that lazy loading will trigger when the name is found by
> name lookup; it does this by pretending that the enum declarations are
> always usings so that it doesn't double-write definitions.  By also
> checking if the enumerator was marked purview/exported we can use that
> to override a non-purview/non-exported TYPE_DECL and ensure it's made
> visible regardless.
> 
> When reading we should get the exported flag on the enumeration
> constant, and so should properly create a binding for it.  We don't need
> to do anything to handle importedness as that checking is skipped for
> EK_USINGs.
> 
> Some other places assume that module information for a CONST_DECL
> inherits module information from its containing type.  This includes:
> 
> - get_originating_module_decl, for determining if the name was imported
>    or has module attachment; I don't /think/ this change should affect
>    that, so I'm leaving this untouched.
> 
> - binding_cmp, for sorting by exportedness; since now an enumerator
>    could be exported without the containing decl being exported, we need
>    to handle this here too.
> 
> With all this in mind, we can avoid creating a new USING_DECL for a
> same-scope using that reveals a CONST_DECL by ensuring that we
> special-case CONST_DECLs with purview/exported flags appropriately.
> 
> gcc/cp/ChangeLog:
> 
> 	* module.cc (depset::hash::add_binding_entity): Handle
> 	CONST_DECLs with different purview/exported from their enum.
> 	(binding_cmp): Likewise.
> 	(set_instantiating_module): Support CONST_DECLs.
> 	* name-lookup.cc (do_nonmember_using_decl): Don't special-case
> 	CONST_DECLs.
> 
> Signed-off-by: Nathaniel Shead <nathanieloshead@gmail.com>
> ---
>   gcc/cp/module.cc      | 19 +++++++++++++++++--
>   gcc/cp/name-lookup.cc |  6 ++----
>   2 files changed, 19 insertions(+), 6 deletions(-)
> 
> diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
> index 7187d251d1d..d385b422168 100644
> --- a/gcc/cp/module.cc
> +++ b/gcc/cp/module.cc
> @@ -13129,7 +13129,10 @@ depset::hash::add_binding_entity (tree decl, WMB_Flags flags, void *data_)
>         tree inner = decl;
>   
>         if (TREE_CODE (inner) == CONST_DECL
> -	  && TREE_CODE (DECL_CONTEXT (inner)) == ENUMERAL_TYPE)
> +	  && TREE_CODE (DECL_CONTEXT (inner)) == ENUMERAL_TYPE
> +	  /* A using-decl could make a CONST_DECL purview for a non-purview
> +	     enumeration.  */
> +	  && (!DECL_LANG_SPECIFIC (inner) || !DECL_MODULE_PURVIEW_P (inner)))
>   	inner = TYPE_NAME (DECL_CONTEXT (inner));
>         else if (TREE_CODE (inner) == TEMPLATE_DECL)
>   	inner = DECL_TEMPLATE_RESULT (inner);
> @@ -13164,7 +13167,10 @@ depset::hash::add_binding_entity (tree decl, WMB_Flags flags, void *data_)
>   	  gcc_checking_assert (TREE_CODE (decl) == CONST_DECL);
>   
>   	  flags = WMB_Flags (flags | WMB_Using);
> -	  if (DECL_MODULE_EXPORT_P (TYPE_NAME (TREE_TYPE (decl))))
> +	  if (DECL_MODULE_EXPORT_P (TYPE_NAME (TREE_TYPE (decl)))
> +	      /* A using-decl can make an enum constant exported for a
> +		 non-exported enumeration.  */
> +	      || (DECL_LANG_SPECIFIC (decl) && DECL_MODULE_EXPORT_P (decl)))
>   	    flags = WMB_Flags (flags | WMB_Export);
>   	}
>   
> @@ -13736,6 +13742,10 @@ binding_cmp (const void *a_, const void *b_)
>         a_export = OVL_EXPORT_P (a_ent);
>         a_ent = OVL_FUNCTION (a_ent);
>       }
> +  else if (TREE_CODE (a_ent) == CONST_DECL
> +	   && DECL_LANG_SPECIFIC (a_ent)
> +	   && DECL_MODULE_EXPORT_P (a_ent))
> +    a_export = true;
>     else
>       a_export = DECL_MODULE_EXPORT_P (TREE_CODE (a_ent) == CONST_DECL
>   				     ? TYPE_NAME (TREE_TYPE (a_ent))
> @@ -13748,6 +13758,10 @@ binding_cmp (const void *a_, const void *b_)
>         b_export = OVL_EXPORT_P (b_ent);
>         b_ent = OVL_FUNCTION (b_ent);
>       }
> +  else if (TREE_CODE (b_ent) == CONST_DECL
> +	   && DECL_LANG_SPECIFIC (b_ent)
> +	   && DECL_MODULE_EXPORT_P (b_ent))
> +    b_export = true;
>     else
>       b_export = DECL_MODULE_EXPORT_P (TREE_CODE (b_ent) == CONST_DECL
>   				     ? TYPE_NAME (TREE_TYPE (b_ent))
> @@ -19230,6 +19244,7 @@ set_instantiating_module (tree decl)
>   	      || TREE_CODE (decl) == TYPE_DECL
>   	      || TREE_CODE (decl) == CONCEPT_DECL
>   	      || TREE_CODE (decl) == TEMPLATE_DECL
> +	      || TREE_CODE (decl) == CONST_DECL
>   	      || (TREE_CODE (decl) == NAMESPACE_DECL
>   		  && DECL_NAMESPACE_ALIAS (decl)));
>   
> diff --git a/gcc/cp/name-lookup.cc b/gcc/cp/name-lookup.cc
> index 5b1f3bfd510..e5f8562105e 100644
> --- a/gcc/cp/name-lookup.cc
> +++ b/gcc/cp/name-lookup.cc
> @@ -5345,10 +5345,8 @@ do_nonmember_using_decl (name_lookup &lookup, bool fn_scope_p,
>   	 location for the using-decl that we can use in diagnostics.
>   
>   	 But this is unnecessary if we're just redeclaring the same decl;
> -	 in that case we can just mark it purview or exported directly.
> -	 Except for using-decls of enumerators, which are currently
> -	 handled specially in modules code and so need to be wrapped.  */
> -      if (value != lookup.value || TREE_CODE (value) == CONST_DECL)
> +	 in that case we can just mark it purview or exported directly.  */
> +      if (value != lookup.value)
>   	{
>   	  value = build_lang_decl (USING_DECL, lookup.name, NULL_TREE);
>   	  USING_DECL_DECLS (value) = lookup.value;
  

Patch

diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 4bb3e9c4989..8732b7dc71b 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -889,21 +889,12 @@  class ovl_iterator {
     return (TREE_CODE (ovl) == USING_DECL
 	    || (TREE_CODE (ovl) == OVERLOAD && OVL_USING_P (ovl)));
   }
-  /* Whether this using is in the module purview.  */
-  bool purview_p () const
-  {
-    return OVL_PURVIEW_P (get_using ());
-  }
-  /* Whether this using is being exported.  */
-  bool exporting_p () const
-  {
-    return OVL_EXPORT_P (get_using ());
-  }
-  
   bool hidden_p () const
   {
     return TREE_CODE (ovl) == OVERLOAD && OVL_HIDDEN_P (ovl);
   }
+  bool purview_p () const;
+  bool exporting_p () const;
 
  public:
   tree remove_node (tree head)
diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
index 29616100cfe..09ae8da7acd 100644
--- a/gcc/cp/decl.cc
+++ b/gcc/cp/decl.cc
@@ -702,12 +702,13 @@  poplevel (int keep, int reverse, int functionbody)
 	/* There are cases where D itself is a TREE_LIST.  See in
 	   push_local_binding where the list of decls returned by
 	   getdecls is built.  */
-	decl = TREE_CODE (d) == TREE_LIST ? TREE_VALUE (d) : d;
+	tree udecl = TREE_CODE (d) == TREE_LIST ? TREE_VALUE (d) : d;
+	decl = strip_using_decl (udecl);
 
 	tree type = TREE_TYPE (decl);
 	if (VAR_P (decl)
-	    && (! TREE_USED (decl) || !DECL_READ_P (decl))
-	    && ! DECL_IN_SYSTEM_HEADER (decl)
+	    && (!TREE_USED (decl) || !DECL_READ_P (decl))
+	    && !DECL_IN_SYSTEM_HEADER (udecl)
 	    /* For structured bindings, consider only real variables, not
 	       subobjects.  */
 	    && (DECL_DECOMPOSITION_P (decl) ? DECL_DECOMP_IS_BASE (decl)
@@ -720,9 +721,13 @@  poplevel (int keep, int reverse, int functionbody)
 		|| lookup_attribute ("warn_unused",
 				     TYPE_ATTRIBUTES (TREE_TYPE (decl)))))
 	  {
-	    if (! TREE_USED (decl))
+	    if (!TREE_USED (decl))
 	      {
-		if (!DECL_NAME (decl) && DECL_DECOMPOSITION_P (decl))
+		if (TREE_CODE (udecl) == USING_DECL)
+		  warning_at (DECL_SOURCE_LOCATION (udecl),
+			      OPT_Wunused_variable,
+			      "unused using-declaration %qD", udecl);
+		else if (!DECL_NAME (decl) && DECL_DECOMPOSITION_P (decl))
 		  warning_at (DECL_SOURCE_LOCATION (decl),
 			      OPT_Wunused_variable,
 			      "unused structured binding declaration");
@@ -16495,7 +16500,6 @@  lookup_and_check_tag (enum tag_types tag_code, tree name,
       /* First try ordinary name lookup, ignoring hidden class name
 	 injected via friend declaration.  */
       decl = lookup_name (name, LOOK_want::TYPE);
-      decl = strip_using_decl (decl);
       /* If that fails, the name will be placed in the smallest
 	 non-class, non-function-prototype scope according to 3.3.1/5.
 	 We may already have a hidden name declared as friend in this
diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
index da180244e03..7187d251d1d 100644
--- a/gcc/cp/module.cc
+++ b/gcc/cp/module.cc
@@ -13122,6 +13122,7 @@  bool
 depset::hash::add_binding_entity (tree decl, WMB_Flags flags, void *data_)
 {
   auto data = static_cast <add_binding_data *> (data_);
+  decl = strip_using_decl (decl);
 
   if (!(TREE_CODE (decl) == NAMESPACE_DECL && !DECL_NAMESPACE_ALIAS (decl)))
     {
@@ -13156,18 +13157,14 @@  depset::hash::add_binding_entity (tree decl, WMB_Flags flags, void *data_)
 	/* Ignore NTTP objects.  */
 	return false;
 
-      bool unscoped_enum_const_p = false;
       if (!(flags & WMB_Using) && CP_DECL_CONTEXT (decl) != data->ns)
 	{
-	  /* A using that lost its wrapper or an unscoped enum
-	     constant.  */
-	  /* FIXME: Ensure that unscoped enums are differentiated from
-	     'using enum' declarations when PR c++/114683 is fixed.  */
-	  unscoped_enum_const_p = (TREE_CODE (decl) == CONST_DECL);
+	  /* An unscoped enum constant implicitly brought into the containing
+	     namespace.  We treat this like a using-decl.  */
+	  gcc_checking_assert (TREE_CODE (decl) == CONST_DECL);
+
 	  flags = WMB_Flags (flags | WMB_Using);
-	  if (DECL_MODULE_EXPORT_P (TREE_CODE (decl) == CONST_DECL
-				    ? TYPE_NAME (TREE_TYPE (decl))
-				    : STRIP_TEMPLATE (decl)))
+	  if (DECL_MODULE_EXPORT_P (TYPE_NAME (TREE_TYPE (decl))))
 	    flags = WMB_Flags (flags | WMB_Export);
 	}
 
@@ -13225,8 +13222,7 @@  depset::hash::add_binding_entity (tree decl, WMB_Flags flags, void *data_)
       if (flags & WMB_Using)
 	{
 	  decl = ovl_make (decl, NULL_TREE);
-	  if (!unscoped_enum_const_p)
-	    OVL_USING_P (decl) = true;
+	  OVL_USING_P (decl) = true;
 	  OVL_PURVIEW_P (decl) = true;
 	  if (flags & WMB_Export)
 	    OVL_EXPORT_P (decl) = true;
@@ -13250,12 +13246,8 @@  depset::hash::add_binding_entity (tree decl, WMB_Flags flags, void *data_)
       data->met_namespace = true;
       if (data->hash->add_namespace_entities (decl, data->partitions))
 	{
-	  /* It contains an exported thing, so it is exported.
-
-	     FIXME we have to set DECL_MODULE_PURVIEW_P instead of asserting
-	     that it is already set because of the c++/114683 issue with
-	     exported using-declarations; see do_nonmember_using_decl.  */
-	  DECL_MODULE_PURVIEW_P (decl) = true;
+	  /* It contains an exported thing, so it is exported.  */
+	  gcc_checking_assert (DECL_MODULE_PURVIEW_P (decl));
 	  DECL_MODULE_EXPORT_P (decl) = true;
 	}
 
@@ -14964,7 +14956,6 @@  enum ct_bind_flags
   cbf_export = 0x1,	/* An exported decl.  */
   cbf_hidden = 0x2,	/* A hidden (friend) decl.  */
   cbf_using = 0x4,	/* A using decl.  */
-  cbf_wrapped = 0x8,  	/* ... that is wrapped.  */
 };
 
 /* DEP belongs to a different cluster, seed it to prevent
@@ -15127,13 +15118,9 @@  module_state::write_cluster (elf_out *to, depset *scc[], unsigned size,
 		    if (!(TREE_CODE (bound) == CONST_DECL
 			  && UNSCOPED_ENUM_P (TREE_TYPE (bound))
 			  && decl == TYPE_NAME (TREE_TYPE (bound))))
-		      {
-			/* An unscope enumerator in its enumeration's
-			   scope is not a using.  */
-			flags |= cbf_using;
-			if (OVL_USING_P (ovl))
-			  flags |= cbf_wrapped;
-		      }
+		      /* An unscoped enumerator in its enumeration's
+			 scope is not a using.  */
+		      flags |= cbf_using;
 		    if (OVL_EXPORT_P (ovl))
 		      flags |= cbf_export;
 		  }
@@ -15300,13 +15287,54 @@  module_state::read_cluster (unsigned snum)
 		    /* Stat hack.  */
 		    if (type || !DECL_IMPLICIT_TYPEDEF_P (decl))
 		      sec.set_overrun ();
-		    type = decl;
+
+		    if (flags & cbf_using)
+		      {
+			type = build_lang_decl_loc (UNKNOWN_LOCATION,
+						    USING_DECL,
+						    DECL_NAME (decl),
+						    NULL_TREE);
+			USING_DECL_DECLS (type) = decl;
+			USING_DECL_SCOPE (type) = CP_DECL_CONTEXT (decl);
+			DECL_CONTEXT (type) = ns;
+
+			DECL_MODULE_PURVIEW_P (type) = true;
+			if (flags & cbf_export)
+			  DECL_MODULE_EXPORT_P (type) = true;
+		      }
+		    else
+		      type = decl;
 		  }
 		else
 		  {
-		    if (decls
-			|| (flags & (cbf_hidden | cbf_wrapped))
-			|| DECL_FUNCTION_TEMPLATE_P (decl))
+		    if ((flags & cbf_using) &&
+			!DECL_DECLARES_FUNCTION_P (decl))
+		      {
+			/* We should only see a single non-function using-decl
+			   for a binding; more than that would clash.  */
+			if (decls)
+			  sec.set_overrun ();
+
+			/* FIXME: Propagate the location of the using-decl
+			   for use in diagnostics.  */
+			decls = build_lang_decl_loc (UNKNOWN_LOCATION,
+						     USING_DECL,
+						     DECL_NAME (decl),
+						     NULL_TREE);
+			USING_DECL_DECLS (decls) = decl;
+			/* We don't currently record the actual scope of the
+			   using-declaration, but this approximation should
+			   generally be good enough.  */
+			USING_DECL_SCOPE (decls) = CP_DECL_CONTEXT (decl);
+			DECL_CONTEXT (decls) = ns;
+
+			DECL_MODULE_PURVIEW_P (decls) = true;
+			if (flags & cbf_export)
+			  DECL_MODULE_EXPORT_P (decls) = true;
+		      }
+		    else if (decls
+			     || (flags & (cbf_hidden | cbf_using))
+			     || DECL_FUNCTION_TEMPLATE_P (decl))
 		      {
 			decls = ovl_make (decl, decls);
 			if (flags & cbf_using)
diff --git a/gcc/cp/name-lookup.cc b/gcc/cp/name-lookup.cc
index b57893116eb..5b1f3bfd510 100644
--- a/gcc/cp/name-lookup.cc
+++ b/gcc/cp/name-lookup.cc
@@ -281,10 +281,11 @@  get_fixed_binding_slot (tree *slot, tree name, unsigned ix, int create)
 
 	  /* Propagate global & module entities to the global and
 	     partition slots.  */
-	  if (tree type = MAYBE_STAT_TYPE (orig))
+	  if (tree type = strip_using_decl (MAYBE_STAT_TYPE (orig)))
 	    init_global_partition (cluster, type);
 
-	  for (ovl_iterator iter (MAYBE_STAT_DECL (orig)); iter; ++iter)
+	  for (ovl_iterator iter (strip_using_decl (MAYBE_STAT_DECL (orig)));
+	       iter; ++iter)
 	    {
 	      tree decl = *iter;
 
@@ -767,6 +768,9 @@  name_lookup::process_binding (tree new_val, tree new_type)
       && (want & LOOK_want::TYPE_NAMESPACE) == LOOK_want::NAMESPACE)
     new_type = NULL_TREE;
 
+  new_val = strip_using_decl (new_val);
+  new_type = strip_using_decl (new_type);
+
   /* Do we really see a value? */
   if (new_val)
     switch (TREE_CODE (new_val))
@@ -827,17 +831,6 @@  name_lookup::process_module_binding (tree new_val, tree new_type,
       marker |= 2;
     }
 
-  /* add_binding_entity wraps decls brought in by 'using' in an OVERLOAD even
-     for non-functions; strip it now.
-     ??? Why isn't it represented with a USING_DECL?  Or do we want to use
-     OVERLOAD for using more widely to address 114683?  */
-  if (new_val && TREE_CODE (new_val) == OVERLOAD
-      && !DECL_DECLARES_FUNCTION_P (OVL_FUNCTION (new_val)))
-    {
-      gcc_checking_assert (OVL_USING_P (new_val) && !OVL_CHAIN (new_val));
-      new_val = OVL_FUNCTION (new_val);
-    }
-
   if (new_type || new_val)
     marker |= process_binding (new_val, new_type);
 
@@ -2998,6 +2991,7 @@  update_binding (cp_binding_level *level, cxx_binding *binding, tree *slot,
 
   if (old == error_mark_node)
     old = NULL_TREE;
+  old = strip_using_decl (old);
 
   if (DECL_IMPLICIT_TYPEDEF_P (decl))
     {
@@ -3102,11 +3096,7 @@  update_binding (cp_binding_level *level, cxx_binding *binding, tree *slot,
 	}
       else if (TREE_CODE (old) == VAR_DECL)
 	{
-	  /* There can be two block-scope declarations of the same
-	     variable, so long as they are `extern' declarations.  */
-	  if (!DECL_EXTERNAL (old) || !DECL_EXTERNAL (decl))
-	    goto conflict;
-	  else if (tree match = duplicate_decls (decl, old))
+	  if (tree match = duplicate_decls (decl, old))
 	    {
 	      gcc_checking_assert (!hide_value && !hiding);
 	      return match;
@@ -4205,6 +4195,28 @@  lookup_class_binding (tree klass, tree name)
   return found;
 }
 
+/* Whether this using is declared in the module purview.  */
+
+bool
+ovl_iterator::purview_p () const
+{
+  gcc_checking_assert (using_p ());
+  if (TREE_CODE (ovl) == USING_DECL)
+    return DECL_MODULE_PURVIEW_P (ovl);
+  return OVL_PURVIEW_P (ovl);
+}
+
+/* Whether this using is exported from this module.  */
+
+bool
+ovl_iterator::exporting_p () const
+{
+  gcc_checking_assert (using_p ());
+  if (TREE_CODE (ovl) == USING_DECL)
+    return DECL_MODULE_EXPORT_P (ovl);
+  return OVL_EXPORT_P (ovl);
+}
+
 /* Given a namespace-level binding BINDING, walk it, calling CALLBACK
    for all decls of the current module.  When partitions are involved,
    decls might be mentioned more than once.   Return the accumulation of
@@ -4215,8 +4227,6 @@  walk_module_binding (tree binding, bitmap partitions,
 		     bool (*callback) (tree decl, WMB_Flags, void *data),
 		     void *data)
 {
-  // FIXME: We don't quite deal with using decls naming stat hack
-  // type.
   tree current = binding;
   unsigned count = 0;
 
@@ -4229,6 +4239,14 @@  walk_module_binding (tree binding, bitmap partitions,
       WMB_Flags flags = WMB_None;
       if (STAT_TYPE_HIDDEN_P (current))
 	flags = WMB_Flags (flags | WMB_Hidden);
+      if (TREE_CODE (type) == USING_DECL)
+	{
+	  flags = WMB_Flags (flags | WMB_Using);
+	  if (DECL_MODULE_PURVIEW_P (type))
+	    flags = WMB_Flags (flags | WMB_Purview);
+	  if (DECL_MODULE_EXPORT_P (type))
+	    flags = WMB_Flags (flags | WMB_Export);
+	}
       count += callback (type, flags, data);
       decl_hidden = STAT_DECL_HIDDEN_P (current);
     }
@@ -4300,7 +4318,14 @@  walk_module_binding (tree binding, bitmap partitions,
 			  flags = WMB_Flags (flags | WMB_Dups);
 			if (STAT_TYPE_HIDDEN_P (bind))
 			  flags = WMB_Flags (flags | WMB_Hidden);
-
+			if (TREE_CODE (btype) == USING_DECL)
+			  {
+			    flags = WMB_Flags (flags | WMB_Using);
+			    if (DECL_MODULE_PURVIEW_P (btype))
+			      flags = WMB_Flags (flags | WMB_Purview);
+			    if (DECL_MODULE_EXPORT_P (btype))
+			      flags = WMB_Flags (flags | WMB_Export);
+			  }
 			count += callback (btype, flags, data);
 		      }
 		    bool part_hidden = STAT_DECL_HIDDEN_P (bind);
@@ -5201,7 +5226,7 @@  do_nonmember_using_decl (name_lookup &lookup, bool fn_scope_p,
 
   /* Shift the old and new bindings around so we're comparing class and
      enumeration names to each other.  */
-  if (value && DECL_IMPLICIT_TYPEDEF_P (value))
+  if (value && DECL_IMPLICIT_TYPEDEF_P (strip_using_decl (value)))
     {
       type = value;
       value = NULL_TREE;
@@ -5306,61 +5331,70 @@  do_nonmember_using_decl (name_lookup &lookup, bool fn_scope_p,
   else if (value
 	   /* Ignore anticipated builtins.  */
 	   && !anticipated_builtin_p (value)
-	   && (fn_scope_p || !decls_match (lookup.value, value)))
+	   && (fn_scope_p
+	       || !decls_match (lookup.value, strip_using_decl (value))))
     {
       diagnose_name_conflict (lookup.value, value);
       failed = true;
     }
   else if (insert_p)
     {
-      /* FIXME: Handle exporting declarations from a different scope
-	 without also marking those declarations as exported.
-	 This will require not just binding directly to the underlying
-	 value; see c++/114683 and c++/114685.  We allow the extra exports
-	 for now as this doesn't (currently) cause ICEs
-	 later down the line, but this should be revisited.  */
-      if (revealing_p)
+      /* A using-decl does not necessarily have the same purview-ness or
+	 exporting as the declaration it reveals, so build a USING_DECL
+	 that we can attach this information to.  This also gives us a
+	 location for the using-decl that we can use in diagnostics.
+
+	 But this is unnecessary if we're just redeclaring the same decl;
+	 in that case we can just mark it purview or exported directly.
+	 Except for using-decls of enumerators, which are currently
+	 handled specially in modules code and so need to be wrapped.  */
+      if (value != lookup.value || TREE_CODE (value) == CONST_DECL)
 	{
-	  if (module_exporting_p ()
-	      && check_can_export_using_decl (lookup.value)
-	      && !DECL_MODULE_EXPORT_P (lookup.value))
-	    {
-	      /* We're redeclaring the same value, but this time as
-		 newly exported: make sure to mark it as such.  */
-	      if (TREE_CODE (lookup.value) == TEMPLATE_DECL)
-		DECL_MODULE_EXPORT_P (DECL_TEMPLATE_RESULT (lookup.value)) = true;
-	      DECL_MODULE_EXPORT_P (lookup.value) = true;
-	    }
-	  /* We're also revealing this declaration with a using-decl,
-	     so mark it as purview to ensure it's emitted.  */
-	  tree inner = STRIP_TEMPLATE (lookup.value);
-	  retrofit_lang_decl (inner);
-	  DECL_MODULE_PURVIEW_P (inner) = true;
+	  value = build_lang_decl (USING_DECL, lookup.name, NULL_TREE);
+	  USING_DECL_DECLS (value) = lookup.value;
+	  USING_DECL_SCOPE (value) = CP_DECL_CONTEXT (lookup.value);
+	  DECL_CONTEXT (value) = current_scope ();
+	  DECL_MODULE_PURVIEW_P (value) = module_purview_p ();
+	}
+      else
+	set_instantiating_module (value);
+
+      if (revealing_p
+	  && module_exporting_p ()
+	  && check_can_export_using_decl (lookup.value))
+	{
+	  if (TREE_CODE (value) == TEMPLATE_DECL)
+	    DECL_MODULE_EXPORT_P (DECL_TEMPLATE_RESULT (value)) = true;
+	  DECL_MODULE_EXPORT_P (value) = true;
 	}
-      value = lookup.value;
     }
-  
+
   /* Now the type binding.  */
   if (lookup.type)
     {
-      if (type && !decls_match (lookup.type, type))
+      if (type && !decls_match (lookup.type, strip_using_decl (type)))
 	{
 	  diagnose_name_conflict (lookup.type, type);
 	  failed = true;
 	}
       else if (insert_p)
 	{
-	  if (revealing_p)
+	  /* As with revealing value bindings.  */
+	  if (type != lookup.type)
 	    {
-	      /* As with revealing value bindings.  */
-	      if (module_exporting_p ()
-		  && check_can_export_using_decl (lookup.type)
-		  && lookup.type == type)
-		DECL_MODULE_EXPORT_P (lookup.type) = true;
-	      retrofit_lang_decl (lookup.type);
-	      DECL_MODULE_PURVIEW_P (lookup.type) = true;
+	      type = build_lang_decl (USING_DECL, lookup.name, NULL_TREE);
+	      USING_DECL_DECLS (type) = lookup.type;
+	      USING_DECL_SCOPE (type) = CP_DECL_CONTEXT (lookup.type);
+	      DECL_CONTEXT (type) = current_scope ();
+	      DECL_MODULE_PURVIEW_P (type) = module_purview_p ();
 	    }
-	  type = lookup.type;
+	  else
+	    set_instantiating_module (type);
+
+	  if (revealing_p
+	      && module_exporting_p ()
+	      && check_can_export_using_decl (lookup.type))
+	    DECL_MODULE_EXPORT_P (type) = true;
 	}
     }
 
@@ -6217,7 +6251,7 @@  get_namespace_binding (tree ns, tree name)
       if (TREE_CODE (ret) == BINDING_VECTOR)
 	ret = BINDING_VECTOR_CLUSTER (ret, 0).slots[0];
       if (ret)
-	ret = MAYBE_STAT_DECL (ret);
+	ret = strip_using_decl (MAYBE_STAT_DECL (ret));
     }
 
   return ret;
@@ -8000,7 +8034,7 @@  lookup_name (tree name, LOOK_where where, LOOK_want want)
 
 	    if (binding)
 	      {
-		val = binding;
+		val = strip_using_decl (binding);
 		break;
 	      }
 	  }
@@ -8073,7 +8107,7 @@  lookup_elaborated_type (tree name, TAG_how how)
 	     typedef struct C {} C;
 	   correctly.  */
 
-	if (tree type = iter->type)
+	if (tree type = strip_using_decl (iter->type))
 	  {
 	    if (qualify_lookup (type, LOOK_want::TYPE)
 		&& (how != TAG_how::CURRENT_ONLY
@@ -8089,7 +8123,8 @@  lookup_elaborated_type (tree name, TAG_how how)
 	  }
 	else
 	  {
-	    if (qualify_lookup (iter->value, LOOK_want::TYPE)
+	    tree value = strip_using_decl (iter->value);
+	    if (qualify_lookup (value, LOOK_want::TYPE)
 		&& (how != TAG_how::CURRENT_ONLY
 		    || !INHERITED_VALUE_BINDING_P (iter)))
 	      {
@@ -8097,7 +8132,7 @@  lookup_elaborated_type (tree name, TAG_how how)
 		  /* It is no longer a hidden binding.  */
 		  HIDDEN_TYPE_BINDING_P (iter) = false;
 
-		return iter->value;
+		return value;
 	      }
 	  }
       }
@@ -8121,7 +8156,7 @@  lookup_elaborated_type (tree name, TAG_how how)
       if (bind)
 	{
 	  /* If this is the kind of thing we're looking for, we're done.  */
-	  if (tree type = MAYBE_STAT_TYPE (bind))
+	  if (tree type = strip_using_decl (MAYBE_STAT_TYPE (bind)))
 	    {
 	      if (how != TAG_how::HIDDEN_FRIEND)
 		/* No longer hidden.  */
@@ -8129,7 +8164,7 @@  lookup_elaborated_type (tree name, TAG_how how)
 	      
 	      return type;
 	    }
-	  else if (tree decl = MAYBE_STAT_DECL (bind))
+	  else if (tree decl = strip_using_decl (MAYBE_STAT_DECL (bind)))
 	    {
 	      if (qualify_lookup (decl, LOOK_want::TYPE))
 		{
@@ -8210,10 +8245,10 @@  lookup_elaborated_type (tree name, TAG_how how)
 		  }
 
 		if (type && qualify_lookup (type, LOOK_want::TYPE))
-		  return type;
+		  return strip_using_decl (type);
 
 		if (bind && qualify_lookup (bind, LOOK_want::TYPE))
-		  return bind;
+		  return strip_using_decl (bind);
 	      }
 
 	  if (!module_purview_p ())
diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
index 31ae9c2fb54..229a0b2c96e 100644
--- a/gcc/cp/parser.cc
+++ b/gcc/cp/parser.cc
@@ -19506,8 +19506,6 @@  cp_parser_template_name (cp_parser* parser,
 				/*ambiguous_decls=*/NULL,
 				token->location);
 
-  decl = strip_using_decl (decl);
-
   /* 13.3 [temp.names] A < is interpreted as the delimiter of a
     template-argument-list if it follows a name that is not a
     conversion-function-id and
@@ -21140,8 +21138,6 @@  cp_parser_nonclass_name (cp_parser* parser)
   /* Look up the type-name.  */
   type_decl = cp_parser_lookup_name_simple (parser, identifier, token->location);
 
-  type_decl = strip_using_decl (type_decl);
-
   if (TREE_CODE (type_decl) != TYPE_DECL
       && (objc_is_id (identifier) || objc_is_class_name (identifier)))
     {
@@ -26916,8 +26912,6 @@  cp_parser_class_name (cp_parser *parser,
 	decl = TYPE_NAME (decl);
     }
 
-  decl = strip_using_decl (decl);
-
   /* Check to see that it is really the name of a class.  */
   if (TREE_CODE (decl) == TEMPLATE_ID_EXPR
       && identifier_p (TREE_OPERAND (decl, 0))
diff --git a/gcc/testsuite/g++.dg/lookup/using29.C b/gcc/testsuite/g++.dg/lookup/using29.C
index 428021cf778..697a654aacb 100644
--- a/gcc/testsuite/g++.dg/lookup/using29.C
+++ b/gcc/testsuite/g++.dg/lookup/using29.C
@@ -51,7 +51,7 @@  struct I2 : H
 
 struct J
 {
-  struct type {};
+  struct type {}; // { dg-message "previous" }
 };
 
 struct K : J
@@ -62,8 +62,8 @@  struct K : J
 
 struct L : J
 {
-  using J::type; // { dg-message "previous" }
-  struct type {}; // { dg-error "conflicts" }
+  using J::type;
+  struct type {}; // { dg-error "redefinition" }
 };
 
 struct M
diff --git a/gcc/testsuite/g++.dg/lookup/using53.C b/gcc/testsuite/g++.dg/lookup/using53.C
index f9e59e66cea..e91829e939a 100644
--- a/gcc/testsuite/g++.dg/lookup/using53.C
+++ b/gcc/testsuite/g++.dg/lookup/using53.C
@@ -1,21 +1,23 @@ 
 // PR c++/20420
+// FIXME: These error messages aren't great, rather than multiple definition
+// we should talk about conflicting declarations with the using-decl.
 
 class B
 {
 protected:
-  enum E { E1, E2, E3 };
-  struct S { int i; E e; };
+  enum E { E1, E2, E3 };     // { dg-message "previous" }
+  struct S { int i; E e; };  // { dg-message "previous" }
 };
 
 class D : private B
 {
 public:
-  using B::E;       // { dg-message "previous" }
-  using B::S;       // { dg-message "previous" }
+  using B::E;
+  using B::S;
 
 private:
-  enum E {};        // { dg-error "conflicts" }
-  struct S {};      // { dg-error "conflicts" }
+  enum E {};        // { dg-error "multiple definition" }
+  struct S {};      // { dg-error "redefinition" }
 };
 
 template<typename T>
@@ -30,11 +32,11 @@  template<typename T>
 class DT : private BT<T>
 {
 public:
-  using BT<T>::E;   // { dg-message "previous" }
+  using BT<T>::E;   // { dg-message "previous" "PR c++/115806" { xfail *-*-* } }
   using BT<T>::S;   // { dg-message "previous" }
 
 private:
-  enum E {};        // { dg-error "conflicts" }
+  enum E {};        // { dg-error "conflicts" "PR c++/115806" { xfail *-*-* } }
   struct S {};      // { dg-error "conflicts" }
 };
 
@@ -50,5 +52,5 @@  void
 f ()
 {
   using N::i;
-  using N::i;       // { dg-error "redeclaration" }
+  using N::i;       // { dg-bogus "conflicts" "See P1787 (CWG36)" { xfail *-*-* } }
 }
diff --git a/gcc/testsuite/g++.dg/lookup/using68.C b/gcc/testsuite/g++.dg/lookup/using68.C
new file mode 100644
index 00000000000..6eb2be5c19c
--- /dev/null
+++ b/gcc/testsuite/g++.dg/lookup/using68.C
@@ -0,0 +1,7 @@ 
+// { dg-do compile }
+
+struct S {}; // { dg-message "previous" }
+void foo() {
+  using ::S;
+  struct S {};  // { dg-error "redefinition" }
+}
diff --git a/gcc/testsuite/g++.dg/modules/using-22_b.C b/gcc/testsuite/g++.dg/modules/using-22_b.C
index 0b66f4ad6b0..be122aa6ffa 100644
--- a/gcc/testsuite/g++.dg/modules/using-22_b.C
+++ b/gcc/testsuite/g++.dg/modules/using-22_b.C
@@ -7,7 +7,9 @@  int main()
   std::basic_string<char> s;
 
   // The inline namespace should not be exported, only the 'using' in std.
-  std::__cxx11::basic_string<char> s2; // { dg-error "has not been declared" "" { xfail *-*-* } }
+  std::__cxx11::basic_string<char> s2; // { dg-error "has not been declared" }
   // The non-exported using should also not be visible.
-  foo::basic_string<char> s3; // { dg-error "has not been declared" "" { xfail *-*-* } }
+  foo::basic_string<char> s3; // { dg-error "has not been declared" }
 }
+
+// { dg-prune-output "expected primary-expression" }
diff --git a/gcc/testsuite/g++.dg/modules/using-24_a.C b/gcc/testsuite/g++.dg/modules/using-24_a.C
new file mode 100644
index 00000000000..872f4fc8ab4
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/using-24_a.C
@@ -0,0 +1,15 @@ 
+// { dg-additional-options "-fmodules-ts -Wno-global-module" }
+// { dg-module-cmi M }
+
+module;
+
+namespace foo {
+  struct S {} S;
+}
+
+export module M;
+
+namespace bar {
+  export using foo::S;
+  export using X = struct foo::S;
+}
diff --git a/gcc/testsuite/g++.dg/modules/using-24_b.C b/gcc/testsuite/g++.dg/modules/using-24_b.C
new file mode 100644
index 00000000000..ea80b4cd667
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/using-24_b.C
@@ -0,0 +1,12 @@ 
+// { dg-additional-options "-fmodules-ts" }
+
+import M;
+namespace foo {}
+
+int main() {
+  struct bar::S a = bar::S;
+  bar::X b = bar::S;
+
+  struct foo::S c;  // { dg-error "does not name a type" }
+  auto d = foo::S;  // { dg-error "not a member" }
+}
diff --git a/gcc/testsuite/g++.dg/modules/using-25_a.C b/gcc/testsuite/g++.dg/modules/using-25_a.C
new file mode 100644
index 00000000000..bc7dc5b8363
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/using-25_a.C
@@ -0,0 +1,15 @@ 
+// { dg-additional-options "-fmodules-ts -Wno-global-module" }
+// { dg-module-cmi M }
+
+module;
+namespace A {
+  enum E1 { Exposed_1, Hidden_1 };
+  enum E2 { Exposed_2, Hidden_2 };
+}
+export module M;
+namespace A {
+  export using E1::Exposed_1;
+}
+namespace B {
+  export using A::E2::Exposed_2;
+}
diff --git a/gcc/testsuite/g++.dg/modules/using-25_b.C b/gcc/testsuite/g++.dg/modules/using-25_b.C
new file mode 100644
index 00000000000..41fdbd1d4cc
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/using-25_b.C
@@ -0,0 +1,17 @@ 
+// { dg-additional-options "-fmodules-ts" }
+
+import M;
+
+int main() {
+  // These have been exported by using-decls and should be OK
+  auto a = A::Exposed_1;
+  auto b = B::Exposed_2;
+
+  // But we shouldn't have exposed the type names
+  A::E1 c;  // { dg-error "not a member" }
+  B::E2 d;  // { dg-error "not a member" }
+
+  // We also shouldn't have exposed any other enumerators
+  auto e = A::Hidden_1;  // { dg-error "not a member" }
+  auto f = B::Hidden_2;  // { dg-error "not a member" }
+}
diff --git a/gcc/testsuite/g++.dg/modules/using-enum-4_a.C b/gcc/testsuite/g++.dg/modules/using-enum-4_a.C
new file mode 100644
index 00000000000..071f5ff797b
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/using-enum-4_a.C
@@ -0,0 +1,13 @@ 
+// PR c++/114683
+// { dg-do compile { target c++20 } }
+// { dg-additional-options "-fmodules-ts -Wno-global-module" }
+// { dg-module-cmi M:a }
+
+module;
+namespace foo {
+  enum class a { x, y, z };
+}
+export module M:a;
+namespace bar {
+  export using enum foo::a;
+}
diff --git a/gcc/testsuite/g++.dg/modules/using-enum-4_b.C b/gcc/testsuite/g++.dg/modules/using-enum-4_b.C
new file mode 100644
index 00000000000..38e6081bf58
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/using-enum-4_b.C
@@ -0,0 +1,7 @@ 
+// PR c++/114683
+// { dg-do compile { target c++20 } }
+// { dg-additional-options "-fmodules-ts" }
+// { dg-module-cmi M }
+
+export module M;
+export import :a;
diff --git a/gcc/testsuite/g++.dg/modules/using-enum-4_c.C b/gcc/testsuite/g++.dg/modules/using-enum-4_c.C
new file mode 100644
index 00000000000..4f5965c746a
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/using-enum-4_c.C
@@ -0,0 +1,11 @@ 
+// PR c++/114683
+// { dg-do compile { target c++20 } }
+// { dg-additional-options "-fmodules-ts" }
+
+import M;
+int main() {
+  auto y = bar::y;
+
+  // This should not be made visible.
+  auto z = foo::a::z;  // { dg-error "not been declared" }
+}
diff --git a/gcc/testsuite/g++.dg/warn/Wunused-var-18.C b/gcc/testsuite/g++.dg/warn/Wunused-var-18.C
index 0339663721c..71331ba9980 100644
--- a/gcc/testsuite/g++.dg/warn/Wunused-var-18.C
+++ b/gcc/testsuite/g++.dg/warn/Wunused-var-18.C
@@ -4,11 +4,18 @@ 
 
 namespace N
 {
-    int i; // { dg-warning "unused variable" }
+    int i;
 }
 
 void
 f ()
 {
-    using N::i;
+    using N::i; // { dg-warning "unused using" }
+}
+
+void
+g ()
+{
+    using N::i; // { dg-bogus "set but not used" }
+    i = 10;
 }