On Tue, Dec 16, 2025 at 09:27:25AM +0700, Jason Merrill wrote:
> On 12/16/25 7:35 AM, Marek Polacek wrote:
> > On Tue, Dec 09, 2025 at 08:38:26PM +0800, Jason Merrill wrote:
> > > On 12/6/25 4:19 AM, Marek Polacek wrote:
> > > > On Fri, Nov 28, 2025 at 08:25:05AM +0530, Jason Merrill wrote:
> > > > > On 11/15/25 6:04 AM, Marek Polacek wrote:
> > > > > > @@ -5514,6 +5582,15 @@ cxx_init_decl_processing (void)
> > > > > >      /* Create the `std' namespace.  */
> > > > > >      push_namespace (get_identifier ("std"));
> > > > > >      std_node = current_namespace;
> > > > > > +  if (flag_reflection)
> > > > > > +    {
> > > > > > +      /* Note that we haven't initialized void_type_node yet, so
> > > > > > +    std_meta_node will be initially typeless; its type will be
> > > > > > +    set a little later in init_reflection.  */
> > > > > > +      push_namespace (get_identifier ("meta"), /*inline*/false);
> > > > > > +      std_meta_node = current_namespace;
> > > > > > +      pop_namespace ();
> > > > > > +    }
> > > > > 
> > > > > ??? std_meta_node is a namespace, which are always typeless.
> > > > 
> > > > This was an ICE in is_std_substitution on:
> > > > 
> > > >      if (!(TYPE_LANG_SPECIFIC (type) && TYPE_TEMPLATE_INFO (type)))
> > > > 
> > > > where decl=namespace_decl meta and type was null due to the ordering
> > > > issue mentioned in the comment.
> > > > 
> > > > make_namespace uses void_type_node to build a namespace_decl, so all
> > > > other namespaces have a non-null type.
> > > 
> > > Why doesn't std_node have the same issue?
> > 
> > Because is_std_substitution has this early exit:
> > 
> >    if (!DECL_NAMESPACE_STD_P (CP_DECL_CONTEXT (decl)))
> >      return false;
> 
> So, basically by accident; is_std_substitution wants either a class or a
> class template, but is looking at the type of any decl.  That seems worth
> tightening up so we only look at the type of a class template. But not
> urgent.

OK, I'll do it later.
 
> > > Incidentally, why do we need to initialize std_meta_node here rather than 
> > > in
> > > init_reflection?
> > 
> > It seemed convenient since we just pushed into std:: which is where
> > meta should reside.
> 
> Sure, but if creating it here means we need to fix it up later perhaps it's
> cleaner to just create it later.  Fine either way.

Fair enough, changed in: 
<https://forge.sourceware.org/marek/gcc/commit/e6c0579c2b535c2f0ea2ed1596b6c6b0b0cc3e1d>

> > > > > > @@ -9808,6 +9918,20 @@ cp_finish_decl (tree decl, tree init, bool 
> > > > > > init_const_expr_p,
> > > > > >     record_types_used_by_current_var_decl (decl);
> > > > > >        }
> > > > > > +  /* CWG 3115: Every function of consteval-only type shall be an
> > > > > > +     immediate function.  */
> > > > > > +  if (TREE_CODE (decl) == FUNCTION_DECL
> > > > > > +      && !DECL_IMMEDIATE_FUNCTION_P (decl)
> > > > > > +      && consteval_only_p (decl)
> > > > > > +      /* But if the function can be escalated, merrily we roll 
> > > > > > along.  */
> > > > > > +      && !immediate_escalating_function_p (decl))
> > > > > > +    {
> > > > > > +      error_at (DECL_SOURCE_LOCATION (decl),
> > > > > > +           "function of consteval-only type must be declared %qs",
> > > > > > +           "consteval");
> > > > > > +      return;
> > > > > > +    }
> > > > > > @@ -20471,6 +20641,16 @@ finish_function (bool inline_p)
> > > > > >          unused_but_set_errorcount = errorcount;
> > > > > >        }
> > > > > > +  /* CWG 3115: Every function of consteval-only type shall be an 
> > > > > > immediate
> > > > > > +     function.  */
> > > > > > +  if (!DECL_IMMEDIATE_FUNCTION_P (fndecl)
> > > > > > +      && consteval_only_p (fndecl)
> > > > > > +      && !immediate_escalating_function_p (fndecl)
> > > > > > +      && !is_std_allocator_allocate (fndecl))
> > > > > > +    error_at (DECL_SOURCE_LOCATION (fndecl),
> > > > > > +         "function of consteval-only type must be declared %qs",
> > > > > > +         "consteval");
> > > > > 
> > > > > Do we need this in both places?
> > > > 
> > > > I think so, one is for fn decls, the other one for fn defs.
> > > 
> > > But every function definition is also a declaration.  And this condition
> > > seems like something you can check before finish_function.
> > > 
> > > Maybe in grokfndecl?  Though that won't help with instantiations, so 
> > > perhaps
> > > you need a separate check function to call from there and
> > > tsubst_function_decl?
> > 
> > Reworked in
> > <https://forge.sourceware.org/marek/gcc/commit/21332410abe133b726172248e44825e48bfd4b00>
> > 
> > I couldn't call the new function in tsubst_function_decl though -- it
> > caused too many errors on code such as:
> > 
> >    indirect_result_t<_Proj&, _Iter> operator*() const; // not defined
> > 
> > in bits/iterator_concepts.h, or
> > 
> >    _UninitDestroyGuard(const _UninitDestroyGuard&);
> > 
> > in bits/stl_uninitialized.h.
> 
> Why?

It complains that they're not declared consteval, which...they're not.
And they are not immediate-escalatable (defaulted special members of
consteval-only type should be though -- PR123404).
 
> > But it worked to call it in instantiate_body -- that is, when we
> > instantiate a fn def.
> > 
> > I had to add consteval to 4 std::meta::exception member fns.
> > (I think this needs an LWG issue.)
> > 
> > > > > > +       c = BINFO_INHERITANCE_CHAIN (c);
> > > > > > +     tpvis = type_visibility (BINFO_TYPE (c));
> > > > > > +     *walk_subtrees = 0;
> > > > > > +     break;
> > > > > > +   case REFLECT_DATA_MEMBER_SPEC:
> > > > > > +     /* For data member description determine visibility
> > > > > > +        from the type.  */
> > > > > > +     tpvis = type_visibility (TREE_VEC_ELT (r, 0));
> > > > > > +     *walk_subtrees = 0;
> > > > > > +     break;
> > > > > > +   case REFLECT_PARM:
> > > > > > +     /* For function parameter reflection determine visibility
> > > > > > +        based on parent_of.  */
> > > > > > +     tpvis = expr_visibility (DECL_CONTEXT (r));
> > > > > > +     *walk_subtrees = 0;
> > > > > > +     break;
> > > > > > +   case REFLECT_OBJECT:
> > > > > > +     c = get_base_address (r);
> > > > > > +     if ((VAR_P (c) && decl_function_context (c))
> > > > > > +         || TREE_CODE (c) == PARM_DECL)
> > > > > > +       {
> > > > > > +         /* Make reflect_object of block scope variables
> > > > > > +            subobjects local.  */
> > > > > > +         tpvis = VISIBILITY_ANON;
> > > > > > +         *walk_subtrees = 0;
> > > > > > +       }
> > > > > 
> > > > > We just stopped treating local declarations as VISIBILITY_ANON in
> > > > > r16-5024-gf062a6b7985fce -- for this to differ from that, we need more
> > > > > rationale.
> > > > 
> > > > E.g. in
> > > > 
> > > >     template <int N, decltype(^^::) I>
> > > >     void
> > > >     foo ()
> > > >     {
> > > >     }
> > > > 
> > > >     static void
> > > >     bar ()
> > > >     {
> > > >       int v = 42;
> > > >       foo <101, ^^v> (); // #1
> > > >     }
> > > > 
> > > > #1 shouldn't be exported.  If I follow r16-5024, decl_linkage will
> > > > give me lk_none for 'v', as it should (it's a var at block scope), but
> > > > type_visibility of its type says VISIBILITY_DEFAULT
> > > 
> > > v is TU-local under https://eel.is/c++draft/basic.link#15.1.2 because it's
> > > defined within TU-local bar().
> > > 
> > > So perhaps r16-5024 went too far one way, and this goes too far the other
> > > way: for a decl with no linkage, we need to refer to the linkage of the
> > > containing entity that does have a name with linkage.
> > 
> > So like this?  I haven't pushed this because I'm not sure that this
> > is what we want, although it seems OK to me -- it changes places with
> > a TODO in the testcase.  (I think this could be checked in post-merge.)
> 
> Agreed.
> 
> > diff --git a/gcc/cp/decl2.cc b/gcc/cp/decl2.cc
> > index 1e717b3801f..07ee5568efd 100644
> > --- a/gcc/cp/decl2.cc
> > +++ b/gcc/cp/decl2.cc
> > @@ -2965,8 +2965,13 @@ min_vis_expr_r (tree *tp, int *walk_subtrees, void 
> > *data)
> >       if ((VAR_P (r) && decl_function_context (r))
> 
> I think we want to drop the decl_function_context check here and make ^^var
> unconditionally expr_visibility (r)...
> 
> >           || TREE_CODE (r) == PARM_DECL)
> >         {
> > -         /* Block scope variables are local to the TU.  */
> > -         tpvis = VISIBILITY_ANON;
> > +         /* For _DECLs with no linkage refer to the linkage of the
> > +            containing entity that does have a name with linkage.
> > +            ??? The r16-5024 change should perhaps follow this logic.  */
> > +         if (decl_linkage (r) == lk_none)
> > +           tpvis = expr_visibility (DECL_CONTEXT (r));
> 
> ..., and make this DECL_CONTEXT change under case VAR_DECL (adjusting the
> r16-5024 change).

Okay, so like this (dg.exp passes)?  I still haven't pushed this.

diff --git a/gcc/cp/decl2.cc b/gcc/cp/decl2.cc
index 4d1e15f1d5e..c4647e56aad 100644
--- a/gcc/cp/decl2.cc
+++ b/gcc/cp/decl2.cc
@@ -2906,7 +2906,7 @@ min_vis_expr_r (tree *tp, int *walk_subtrees, void *data)
        }
     addressable:
       if (decl_linkage (t) == lk_none)
-       tpvis = type_visibility (TREE_TYPE (t));
+       tpvis = expr_visibility (DECL_CONTEXT (t));
       /* Decls that have had their visibility constrained will report
         as external linkage, but we still want to transitively constrain
         if we refer to them, so just check TREE_PUBLIC instead.  */
@@ -2961,11 +2961,20 @@ min_vis_expr_r (tree *tp, int *walk_subtrees, void 
*data)
              *walk_subtrees = 0;
              break;
            }
-         if ((VAR_P (r) && decl_function_context (r))
-             || TREE_CODE (r) == PARM_DECL)
+         /* For _DECLs with no linkage refer to the linkage of the
+            containing entity that does have a name with linkage.  */
+         if (VAR_P (r))
            {
-             /* Block scope variables are local to the TU.  */
-             tpvis = VISIBILITY_ANON;
+             tpvis = expr_visibility (r);
+             *walk_subtrees = 0;
+             break;
+           }
+         if (TREE_CODE (r) == PARM_DECL)
+           {
+             if (decl_linkage (r) == lk_none)
+               tpvis = expr_visibility (DECL_CONTEXT (r));
+             else
+               tpvis = VISIBILITY_ANON;
              *walk_subtrees = 0;
              break;
            }
diff --git a/gcc/testsuite/g++.dg/reflect/visibility1.C 
b/gcc/testsuite/g++.dg/reflect/visibility1.C
index c47817b9b4e..331e8353e4a 100644
--- a/gcc/testsuite/g++.dg/reflect/visibility1.C
+++ b/gcc/testsuite/g++.dg/reflect/visibility1.C
@@ -49,8 +49,8 @@ inline void
 qux (int x)
 {
   int v = 42;
-  foo <106, ^^x> ();                           // { dg-final { 
scan-assembler-not "\t.weak\t_Z3fooILi106EL" } } - var in inline fn - TODO, 
shall this be exported?
-  foo <107, ^^v> ();                           // { dg-final { 
scan-assembler-not "\t.weak\t_Z3fooILi107EL" } } - var in inline fn - TODO, 
shall this be exported?
+  foo <106, ^^x> ();                           // { dg-final { scan-assembler 
"\t.weak\t_Z3fooILi106EL" } } - var in inline fn
+  foo <107, ^^v> ();                           // { dg-final { scan-assembler 
"\t.weak\t_Z3fooILi107EL" } } - var in inline fn
 }
 
 template <int N>
@@ -58,8 +58,8 @@ void
 plugh (int x)
 {
   int v = 42;
-  foo <132, ^^x> ();                           // { dg-final { 
scan-assembler-not "\t.weak\t_Z3fooILi132EL" } } - var in public fn template 
instantiation - TODO, shall this be exported?
-  foo <133, ^^v> ();                           // { dg-final { 
scan-assembler-not "\t.weak\t_Z3fooILi133EL" } } - var in public fn template 
instantiation - TODO, shall this be exported?
+  foo <132, ^^x> ();                           // { dg-final { scan-assembler 
"\t.weak\t_Z3fooILi132EL" } } - var in public fn template instantiation
+  foo <133, ^^v> ();                           // { dg-final { scan-assembler 
"\t.weak\t_Z3fooILi133EL" } } - var in public fn template instantiation
   foo <134, parameters_of (parent_of (^^v))[0]> (); // { dg-final { 
scan-assembler "\t.weak\t_Z3fooILi134EL" { target *-*-linux* } } } - fn parm of 
public fn template instantiation
 }
 
@@ -80,8 +80,8 @@ void
 fred (int x)
 {
   int v = 42;
-  foo <138, ^^x> ();                           // { dg-final { 
scan-assembler-not "\t.weak\t_Z3fooILi138EL" } } - var in TU-local fn template 
instantiation
-  foo <139, ^^v> ();                           // { dg-final { 
scan-assembler-not "\t.weak\t_Z3fooILi139EL" } } - var in Tu-local fn template 
instantiation
+  foo <138, ^^x> ();                           // { dg-final { scan-assembler 
"\t.weak\t_Z3fooILi138EL" } } - var in TU-local fn template instantiation
+  foo <139, ^^v> ();                           // { dg-final { scan-assembler 
"\t.weak\t_Z3fooILi139EL" } } - var in Tu-local fn template instantiation
   foo <140, parameters_of (parent_of (^^v))[0]> (); // { dg-final { 
scan-assembler-not "\t.weak\t_Z3fooILi140EL" { xfail *-*-* } } } - fn parm of 
TU-local fn template instantiation - TODO, I think this shouldn't be exported 
and the mangling of these 3 doesn't include the template parameter
 }
 
@@ -108,8 +108,8 @@ xyzzy (int x)
   foo <122, ^^G <int>> ();                     // { dg-final { 
scan-assembler-not "\t.weak\t_Z3fooILi122EL" } } - specialization of TU-local 
template
   foo <123, ^^G <A>> ();                       // { dg-final { 
scan-assembler-not "\t.weak\t_Z3fooILi123EL" } } - specialization of TU-local 
template
   foo <124, ^^G <B>> ();                       // { dg-final { 
scan-assembler-not "\t.weak\t_Z3fooILi124EL" } } - specialization of TU-local 
template
-  foo <125, ^^x> ();                           // { dg-final { 
scan-assembler-not "\t.weak\t_Z3fooILi125EL" } } - var in public fn but 
non-comdat - TODO, shall this be exported?
-  foo <126, ^^v> ();                           // { dg-final { 
scan-assembler-not "\t.weak\t_Z3fooILi126EL" } } - var in public fn but 
non-comdat - TODO, shall this be exported?
+  foo <125, ^^x> ();                           // { dg-final { scan-assembler 
"\t.weak\t_Z3fooILi125EL" } } - var in public fn but non-comdat
+  foo <126, ^^v> ();                           // { dg-final { scan-assembler 
"\t.weak\t_Z3fooILi126EL" } } - var in public fn but non-comdat
   foo <127, std::meta::info {}> ();            // { dg-final { scan-assembler 
"\t.weak\t_Z3fooILi127EL" { target *-*-linux* } } } - null reflection
   foo <128, ^^b> ();                           // { dg-final { scan-assembler 
"\t.weak\t_Z3fooILi128EL" { target *-*-linux* } } } - public variable
   foo <129, ^^c> ();                           // { dg-final { 
scan-assembler-not "\t.weak\t_Z3fooILi129EL" } } - TU-local variable
 
> > @@ -80,8 +80,8 @@ void
> >   fred (int x)
> >   {
> >     int v = 42;
> > -  foo <138, ^^x> ();                               // { dg-final { 
> > scan-assembler-not "\t.weak\t_Z3fooILi138EL" } } - var in TU-local fn 
> > template instantiation
> > -  foo <139, ^^v> ();                               // { dg-final { 
> > scan-assembler-not "\t.weak\t_Z3fooILi139EL" } } - var in Tu-local fn 
> > template instantiation
> > +  foo <138, ^^x> ();                               // { dg-final { 
> > scan-assembler "\t.weak\t_Z3fooILi138EL" } } - var in TU-local fn template 
> > instantiation
> > +  foo <139, ^^v> ();                               // { dg-final { 
> > scan-assembler "\t.weak\t_Z3fooILi139EL" } } - var in Tu-local fn template 
> > instantiation
> 
> ??? Since the template is TU-local, the instantiations should be too,
> regardless of the visibility of the reflection.
 
So here DECL_CONTEXT of x is the fn decl fred which is lk_external and its
DECL_VISIBILITY is VISIBILITY_DEFAULT.

I'm confused.  fred is not static and isn't in namespace {}, why should
it be TU-local?  I think the comment is wrong.

> > > > > > +   /* This can happen for std::meta::info(^^int) where the cast 
> > > > > > has no
> > > > > > +      meaning.  */
> > > > > > +   if (REFLECTION_TYPE_P (type) && REFLECT_EXPR_P (op))
> > > > > > +     {
> > > > > > +       r = op;
> > > > > > +       break;
> > > > > > +     }
> > > > > 
> > > > > Why is this needed?  I'd expect the existing code to work fine for a 
> > > > > cast to
> > > > > the same type.
> > > > 
> > > > This is to handle
> > > > 
> > > >    using info = decltype(^^int);
> > > >    void
> > > >    g ()
> > > >    {
> > > >      constexpr auto r = info(^^int);
> > > >    }
> > > > 
> > > > An analogical test would be:
> > > > 
> > > >    using T = int;
> > > >    void
> > > >    g ()
> > > >    {
> > > >      constexpr auto i = T(42);
> > > >    }
> > > > 
> > > > which is handled by
> > > > 
> > > >      if (same_type_ignoring_tlq_and_bounds_p (type, TREE_TYPE (op)))
> > > >        r = op;
> > > > 
> > > > later in that function.  But with info(^^int) we don't get that far
> > > > because we do:
> > > > 
> > > >      if (op == oldop && tcode != UNARY_PLUS_EXPR)
> > > >        /* We didn't fold at the top so we could check for ptr-int
> > > >          conversion.  */
> > > >        return fold (t);
> > > > 
> > > > because op and oldop are the same REFLECT_EXPR, whereas with T(42)
> > > > op == 42 and oldop == NON_LVALUE_EXPR <42>.
> > > 
> > > But why is taking that return a problem?
> > 
> > So t is a NOP_EXPR: (info) ^^int and fold leaves that NOP_EXPR be.
> 
> Aha, because fold doesn't understand REFLECT_EXPR I guess.
> 
> So let's keep the added special handling but move it to just before the
> op==oldop code.

Done in
<https://forge.sourceware.org/marek/gcc/commit/1f6219c2033466f2d5ca118dde50b885a362de17>
 
> > > > > > +    {
> > > > > > +      /* We may have to instantiate; for instance, if we're 
> > > > > > dealing with
> > > > > > +    a variable template.  For &[: ^^S::x :], we have to create an
> > > > > > +    OFFSET_REF.  For a VAR_DECL, we need the 
> > > > > > convert_from_reference.  */
> > > > > > +      cp_unevaluated u;
> > > > > > +      /* CWG 3109 adjusted [class.protected] to say that checking 
> > > > > > access to
> > > > > > +    protected non-static members is disabled for members 
> > > > > > designated by a
> > > > > > +    splice-expression.  */
> > > > > > +      push_deferring_access_checks (dk_no_check);
> > > > > 
> > > > > I'm not sure where an access check would happen here, either?
> > > > > finish_id_expression_1 already does this push/pop for the FIELD_DECL 
> > > > > case.
> > > > 
> > > > This is for:
> > > > 
> > > >    class Base {
> > > >    private:
> > > >      int m;
> > > >    public:
> > > >      static constexpr auto rm = ^^m;
> > > >    };
> > > >    class Derived { static constexpr auto mp = &[:Base::rm:]; };
> > > > 
> > > > where in finish_id_expression_1 we don't go to the FIELD_DECL block,
> > > > but to the one above it with finish_qualified_id_expr because scope is
> > > > Base here.
> > > 
> > > I don't see how this applies; finish_id_expression_1 would be looking at
> > > Base::rm, which is a public static member.
> > 
> > Ah!  So cp_parser_splice_expression for [:Base::rm:] here calls
> > cp_parser_splice_specifier which parses Base::rm which produces
> > VCE<info>(rm) but then splice does cxx_constant_value which evaluates
> > it to field_decl m.  So then when we reach the finish_id_expression back
> > in cp_parser_splice_expression, we're dealing with m and not rm.
> > 
> > It's not clear to me if splice can avoid cxx_constant_value.  I don't
> > think so.
> 
> That all makes sense.  But again, finish_id_expression_1 already does the
> same push/pop for FIELD_DECL.  So the trouble comes because of
> 
> >       /* We don't have the parser scope here, so figure out the context.
> > In                                        struct S { static constexpr
> > int i = 42; };
> > constexpr auto r = ^^S::i;
> > int i = [: r :];
> > we need to pass down 'S'.  */
> >       tree ctx = DECL_P (t) ? DECL_CONTEXT (t) : NULL_TREE;
> 
> diverting us to finish_qualified_id_expr.  Why do we need this?  Do we still
> need it after the fixes to avoid repeating the lookup?

Looks like many testcases still need this.  E.g.,

  struct S {
    int m(this S) { return 42; }
  };
  auto p = &[: ^^S::m :];

crashes without the ctx: finish_id_expression_1 gets function_decl m and
goes to finish_class_member_access_expr -> lookup_member where we crash
because we don't have identifier_p.

  struct S {
    static constexpr int s_x = 11;
  };
  static_assert(&[:^^S::s_x:] == &S::s_x);

crashes without the ctx because we have a VAR_DECL but finish_id_expression_1
in one place expects only a CONST_DECL.

I've played with this more but then I ran into errors like
  invalid operands of types 'int*' and 'int S::*'
because without the ctx we failed to call build_offset_ref from
finish_qualified_id_expr.

So I guess we do need the context :/.

Marek

Reply via email to