On Tue, Nov 04, 2025 at 12:57:53PM +0300, Jason Merrill wrote:
> On 10/30/25 3:00 PM, Nathaniel Shead wrote:
> > One unfortunate side effect of this is that even with -pedantic-errors,
> > unless the user specifies '-Wtemplate-names-tu-local' when building the
> > module interface there will be no diagnostic at all from instantiating a
> > template that exposes global TU-local entities, either when building the
> > module or its importer.
> >
> > Would it be reasonable to attempt to change behaviour when
> > '-pedantic-errors' or '-Werror=expose-global-module-tu-local' is
> > specified and build TU_LOCAL_ENTITY nodes for such declarations in this
> > case? (Though I'm not sure how to detect the latter.) Or maybe even go
> > the other way around and always do this for template bodies unless
> > '-fpermissive' (or somesuch) is specified; thoughts?
> >
> > Otherwise bootstrapped and regtested on x86_64-pc-linux-gnu, OK for
> > trunk?
> >
> > -- >8 --
> >
> > A frequent issue with migrating to C++20 modules has been dealing with
> > third-party libraries with internal functions or data. This causes GCC
> > to currently refuse to build the module if any references to these
> > internal-linkage declarations escape into the module CMI.
> >
> > This can seem needlessly hostile, however, especially since we have the
> > capabilities to support this (to a degree) from header units, albeit
> > with the inherent ODR issues associated with their use. In aid of this,
> > this patch demotes the error to a pedwarn in various scenarios, by
> > treating some declarations as not being TU-local even if they otherwise
> > would have been.
> >
> > Effort has been made to not alter semantics of valid programs, and to
> > continue to diagnose cases that the standard says we must. In
> > particular, any code in the module purview is still a hard error, due to
> > the inherent issues with exposing TU-local entities, and the lack of any
> > migration requirements. One unfortunate side effect however is that
> > instantiations of templates referencing gmf TU-local entities will have
> > no diagnostics on the importing side at all.
> >
> > Because this patch is just to assist migration, we only deal with the
> > simplest (yet most common) cases: namespace scope functions and
> > variables. Types are hard to handle neatly as we risk getting thousands
> > of unhelpful warnings as we continue to walk the type body and find new
> > TU-local entities to complain about. Templates are also tricky because
> > it's hard to tell if an instantiation that occurred in the module
> > purview only refers to global module entities or if it's inadvertantly
> > exposing a purview entity as well. Neither of these are likely to occur
> > frequently in third-party code; if need be, this can be relaxed later as
> > well.
> >
> > Similarly, even in the GMF a constexpr variable with a TU-local value
> > will not be usable in constant expressions in the importer, and since we
> > cannot easily warn about this from the importer we continue to make this
> > an error in the module interface.
> >
> > @@ -15334,6 +15377,128 @@ template_has_explicit_inst (tree tmpl)
> > return false;
> > }
> > +/* Complain about DEP that exposes a TU-local entity.
> > +
> > + If STRICT, DEP only referenced entities from the GMF. Returns TRUE
> > + if we explained anything. */
> > +
> > +bool
> > +depset::hash::diagnose_bad_internal_ref (depset *dep, bool strict)
> > +{
> > + tree decl = dep->get_entity ();
> > +
> > + /* Don't need to walk if we're not going to be emitting
> > + any diagnostics anyway. */
> > + if (strict && !warning_enabled_at (DECL_SOURCE_LOCATION (decl),
> > + OPT_Wexpose_global_module_tu_local))
> > + return false;
> > +
> > + for (depset *rdep : dep->deps)
> > + if (!rdep->is_binding () && rdep->is_tu_local (strict)
> > + && !is_exposure_of_member_type (dep, rdep))
> > + {
> > + // FIXME:QOI Better location information? We're
> > + // losing, so it doesn't matter about efficiency
>
> Period at end of comment.
>
Fixed on my branch.
> > + tree exposed = rdep->get_entity ();
> > + auto_diagnostic_group d;
> > + if (strict)
> > + {
> > + /* Allow suppressing the warning from the point of declaration
> > + of the otherwise-exposed decl, for cases we know that
> > + exposures will never be 'bad'. */
> > + if (warning_enabled_at (DECL_SOURCE_LOCATION (exposed),
> > + OPT_Wexpose_global_module_tu_local)
> > + && pedwarn (DECL_SOURCE_LOCATION (decl),
> > + OPT_Wexpose_global_module_tu_local,
> > + "%qD exposes TU-local entity %qD", decl, exposed))
> > + {
> > + bool informed = is_tu_local_entity (exposed, /*explain=*/true);
> > + gcc_checking_assert (informed);
> > + return true;
> > + }
> > + }
> > + else
> > + {
> > + error_at (DECL_SOURCE_LOCATION (decl),
> > + "%qD exposes TU-local entity %qD", decl, exposed);
> > + bool informed = is_tu_local_entity (exposed, /*explain=*/true);
> > + gcc_checking_assert (informed);
> > + if (dep->is_tu_local (/*strict=*/true))
> > + inform (DECL_SOURCE_LOCATION (decl),
> > + "%qD is also TU-local but has been exposed elsewhere",
> > + decl);
>
> Is it worthwhile to warn in this case? We should certainly complain about
> the original exposure, but after that it's natural for internal things to
> refer to other internal things, this seems like it would be undesirable
> diagnostic cascade.
>
This is giving an explanation when an internal entity is an exposure,
but the error would have normally been silenced exactly because we
don't complain if a TU-local entity references another TU-local entity.
For instance, in:
module;
static inline int x = []{ return 1; }();
export module M;
using ::x;
the error here is complaining about the TU-local lambda type, exposed by
the 'using ::x' (TU-local because it inherited the linkage of 'x').
This is necessary because the exposure of 'x' is now only a warning (and
that warning could even have been disabled), and this additional
explanatory note attempts to describe that. I felt the note was useful
to explain why we're complaining about this despite [basic.link] p17
normally only applying to non-TU-local entities.
> > + return true;
> > + }
> > + }
> > +
> > + return false;
> > +}
> > +
> > @@ -15358,30 +15523,21 @@ depset::hash::finalize_dependencies ()
> > if (CHECKING_P)
> > for (depset *entity : dep->deps)
> > gcc_checking_assert (!entity->is_import ());
> > + continue;
> > }
> > - else if (dep->is_exposure () && !dep->is_tu_local ())
> > - {
> > - ok = false;
> > - bool explained = false;
> > - tree decl = dep->get_entity ();
> > - for (depset *rdep : dep->deps)
> > - if (!rdep->is_binding ()
> > - && rdep->is_tu_local ()
> > - && !is_exposure_of_member_type (dep, rdep))
> > - {
> > - // FIXME:QOI Better location information? We're
> > - // losing, so it doesn't matter about efficiency
>
> Period at end of comment.
>
> Jason
This was the original comment from the implementation that is getting
moved into the new 'diagnose_bad_internal_ref' function.
Thanks,
Nathaniel