On Fri, Dec 05, 2025 at 10:18:34PM +0530, Jason Merrill wrote:
> > +static tree
> > +cp_parser_reflection_name (cp_parser *parser)
> > +{
> > +  /* Look for the optional `::' operator.  */
> > +  bool global_scope_p
> > +    = (cp_parser_global_scope_opt (parser,
> > +                              /*current_scope_valid_p=*/false)
> > +       != NULL_TREE);
> > +  /* And the optional nested-name-specifier.  */
> > +  bool nested_name_specifier_p
> > +    = (cp_parser_nested_name_specifier_opt (parser,
> > +                                       /*typename_keyword_p=*/false,
> > +                                       /*check_dependency_p=*/true,
> > +                                       /*type_p=*/false,
> > +                                       /*is_declaration=*/false,
> > +                                       /*template_keyword_p=*/false,
> > +                                       global_scope_p)
> > +       != NULL_TREE);
> > +  /* Look for the optional `template' keyword.  */
> > +  if (cp_parser_optional_template_keyword (parser)
> > +      && !global_scope_p
> > +      && !nested_name_specifier_p)
> > +    /* Only "template identifier" isn't valid.  */
> > +    cp_parser_simulate_error (parser);
> 
> This could be a cp_parser_error without a comment?

Done.  Though I think we'll never see that error because here we're
always parsing tentatively.

<https://forge.sourceware.org/marek/gcc/commit/b362fee1eff8f0ad289ccb1b54330386bb5bf18b>

> > +  /* We don't know what this might be.  Try and see what works.  */
> > +  cp_parser_parse_tentatively (parser);
> > +  tree t = cp_parser_reflection_name (parser);
> > +  tree talias = NULL_TREE;
> > +  cp_token *next = NULL;
> > +  if (TREE_CODE (t) == TYPE_DECL && !cp_parser_error_occurred (parser))
> > +    {
> > +      /* Need to call cp_parser_type_id, because say
> > +    using A = int;
> > +    ^^A &
> > +    should parse the type id rather than reflection-name.
> > +    Though, remember the TYPE_DECL and next token in that case
> > +    if it is a type alias and if cp_parser_type_id parses the
> > +    same tokens, don't strip_typedefs.  */
> > +      if (is_typedef_decl (t))
> > +   {
> > +     talias = TREE_TYPE (t);
> > +     next = cp_lexer_peek_token (parser->lexer);
> > +   }
> > +      cp_parser_simulate_error (parser);
> > +    }
> > +  if (cp_parser_parse_definitely (parser))
> > +    return get_reflection (loc, t);
> 
> It seems like we avoid returning too early for ^^tmpl<args> somewhat
> accidentally, because looking for a nested-name-specifier turns the
> template-id into a CPP_TEMPLATE_ID.  That could use a comment.

Comment added.

> > +  /* Nope.  Well then, maybe it's a type-id.  */
> > +  cp_parser_parse_tentatively (parser);
> > +
> > +  /* Unfortunately we need to distinguish in
> > +     template <typename> struct cls_tmpl {};
> > +     template <typename T> using cls_tmpl_alias = const cls_tmpl <T>;
> > +
> > +     ^^cls_tmpl_alias <int> which is a type alias from
> > +     ^^const cls_tmpl_alias <int>
> > +     ^^cls_tmpl_alias <int> const
> > +     ^^cls_tmpl_alias <int> *
> > +     etc. which are just types, not type aliases.  Parse tentatively
> > +     type specifiers and check that there is just ds_type_spec specified
> > +     and it is a specialization of a template alias, in that case later
> > +     on if cp_parser_type_id parses the same tokens don't strip typedefs.  
> > */
> > +  if (!talias)
> > +    {
> > +      cp_decl_specifier_seq type_specifier_seq;
> > +
> > +      /* Parse the type-specifier-seq.  */
> > +      cp_parser_type_specifier_seq (parser, CP_PARSER_FLAGS_NONE,
> > +                               /*is_declaration=*/false, false,
> > +                               &type_specifier_seq);
> > +      if (is_typedef_decl (type_specifier_seq.type)
> > +     && TYPE_ALIAS_TEMPLATE_INFO (TREE_TYPE (type_specifier_seq.type)))
> > +   {
> > +     int i;
> > +     for (i = ds_first; i < ds_last; ++i)
> > +       if (i != ds_type_spec && type_specifier_seq.locations[i])
> > +         break;
> > +     if (i == ds_last)
> > +       {
> > +         talias = type_specifier_seq.type;
> > +         next = cp_lexer_peek_token (parser->lexer);
> > +       }
> > +   }
> > +      cp_parser_abort_tentative_parse (parser);
> > +      cp_parser_parse_tentatively (parser);
> > +    }
> > +
> > +  t = cp_parser_type_id (parser);
> > +  if (cp_parser_parse_definitely (parser))
> > +    {
> > +      if (TYPE_P (t))
> > +   {
> > +     /* With using A = int; ^^A is a type alias but
> > +        ^^const A or ^^A & or ^^A const is not.
> > +        With template <typename T> using B = C <T>;
> > +        ^^B <int> is a type alias though.  */
> > +     if (talias == NULL_TREE
> > +         || cp_lexer_peek_token (parser->lexer) != next)
> > +       t = strip_typedefs (t);
> > +   }
> > +      return get_reflection (loc, t);
> > +    }
> 
> I think it would be simpler to handle more of this in cp_parser_type_id_1,
> which already handles other special cases, rather than parsing the
> type-specifiers twice?

I took a crack at this in
<https://forge.sourceware.org/marek/gcc/commit/e2a9ca89a628d54897ffe30054191b0e14dfd1c4>
and it seemed to work.

> >      case RID_TYPENAME:
> > -      /* Look for an elaborated-type-specifier.  */
> > -      type_spec
> > -   = (cp_parser_elaborated_type_specifier
> > -      (parser,
> > -       decl_spec_seq_has_spec_p (decl_specs, ds_friend),
> > -       is_declaration));
> > -      if (decl_specs)
> > -   cp_parser_set_decl_spec_type (decl_specs,
> > -                                 type_spec,
> > -                                 token,
> > -                                 /*type_definition_p=*/false);
> > -      return type_spec;
> > +      {
> > +   /* If we see 'typename [:', this could be a typename-specifier.
> > +      But if there's no '::' after the '[:x:]' then it is probably
> > +      a simple-type-specifier.  */
> > +   if (keyword == RID_TYPENAME
> > +       && cp_lexer_peek_nth_token (parser->lexer, 2)->type
> > +           == CPP_OPEN_SPLICE
> > +       && !cp_parser_splice_spec_is_nns_p (parser))
> > +     break;
> > +
> > +   /* Look for an elaborated-type-specifier.  */
> > +   type_spec
> > +     = (cp_parser_elaborated_type_specifier
> > +        (parser,
> > +         decl_spec_seq_has_spec_p (decl_specs, ds_friend),
> > +         is_declaration));
> > +
> > +   if (decl_specs)
> > +     cp_parser_set_decl_spec_type (decl_specs,
> > +                                   type_spec,
> > +                                   token,
> > +                                   /*type_definition_p=*/false);
> > +   return type_spec;
> > +      }
> 
> I don't see the need for the added braces / indentation change?

Fixed in
<https://forge.sourceware.org/marek/gcc/commit/4843052d8217ab414bd2a276e8ca5dbce19e8e4f>

I suppose at some point I added a decl there and then removed it
but the {} stayed.

> > @@ -30467,7 +31339,15 @@ cp_parser_base_specifier (cp_parser* parser)
> >    if (cp_lexer_next_token_is_keyword (parser->lexer, RID_TYPENAME))
> >      {
> >        token = cp_lexer_peek_token (parser->lexer);
> > -      if (!processing_template_decl)
> > +      if (cp_parser_next_tokens_start_splice_type_spec_p (parser, true))
> > +   {
> > +     /* If typename is followed by [:, don't diagnose it just yet,
> > +        but defer it depending on whether it is splice-scope-specifier
> > +        or splice-type-specifier.  */
> 
> If we're deferring this case, how about deferring the diagnostic in general
> rather than duplicating it?

Done unless I goofed it up:
<https://forge.sourceware.org/marek/gcc/commit/969ac7978bbf860b5655378c7af3f802b9fcc25a>

> > +     typename_token = token;
> > +     splice_token = cp_lexer_peek_nth_token (parser->lexer, 2);
> > +   }
> > +      else if (!processing_template_decl)
> >     error_at (token->location,
> >               "keyword %<typename%> not allowed outside of templates");
> >        else
> > @@ -30501,10 +31381,31 @@ cp_parser_base_specifier (cp_parser* parser)
> >    class_scope_p = (parser->scope && TYPE_P (parser->scope));
> >    template_p = class_scope_p && cp_parser_optional_template_keyword 
> > (parser);
> > +  if (typename_token && cp_lexer_peek_token (parser->lexer) != 
> > splice_token)
> > +    {
> > +      /* Emit deferred diagnostics for invalid typename keyword if
> > +    cp_parser_nested_name_specifier_opt parsed splice-scope-specifier.  */
> > +      if (!processing_template_decl)
> > +   error_at (typename_token->location,
> > +             "keyword %<typename%> not allowed outside of templates");
> Pre-existig issue, but this is not true anymore.  For instance, this is
> fine:
> 
> struct A { struct B {}; };
> typename A::B b;
> 
> > +      else
> > +   error_at (typename_token->location,
> > +             "keyword %<typename%> not allowed in this context "
> > +             "(the base class is implicitly a type)");
> 
> ...so let's only give this error.

Happy to, but I think it should be done outside the Reflection
patch.  parse/typename6.C checks the first error.
 
> > @@ -17278,6 +17365,39 @@ tsubst (tree t, tree args, tsubst_flags_t 
> > complain, tree in_decl)
> >     if (f == error_mark_node)
> >       return error_mark_node;
> > +   /* We had [:X:]:: which was substituted into a NAMESPACE_DECL and not
> > +      a type as is expected here.  */
> > +   if (TREE_CODE (ctx) == NAMESPACE_DECL)
> 
> This comment reads like this is going to be an error, let's drop "as is
> expected here".

Removed.
 
> > @@ -17478,6 +17601,9 @@ tsubst (tree t, tree args, tsubst_flags_t complain, 
> > tree in_decl)
> >  static tree
> >  tsubst_scope (tree t, tree args, tsubst_flags_t complain, tree in_decl)
> >  {
> > +  /* This could be a dependent namespace.  */
> > +  if (TREE_CODE (t) == NAMESPACE_DECL)
> > +    return tsubst_expr (t, args, complain | tf_qualifying_scope, in_decl);
> >    gcc_checking_assert (TYPE_P (t));
> >    return tsubst (t, args, complain | tf_qualifying_scope, in_decl);
> >  }
> 
> Why doesn't tsubst handle NAMESPACE_DECL?  A namespace isn't an expression,
> so tsubst_expr seems wrong.  More context below.

NAMESPACE_DECL used to be handled by tsubst_copy and since r14-4796 by
tsubst_expr.

Moved case NAMESPACE_DECL into tsubst_decl here:
<https://forge.sourceware.org/marek/gcc/commit/950ad2a58b10542fb41978f2c657235f292e49a2>
 
> > @@ -20943,6 +21068,38 @@ tsubst_expr (tree t, tree args, tsubst_flags_t 
> > complain, tree in_decl)
> >         RETURN (r);
> >       }
> > +   /* Normally, we only expect a template function at this point.  But
> > +      for [:X:]<arg>, we don't really know what [:X:] means until we
> > +      substitute it.  */
> > +   if (DECL_TYPE_TEMPLATE_P (templ)
> > +       || DECL_TEMPLATE_TEMPLATE_PARM_P (templ))
> > +     {
> > +       tree op = TREE_OPERAND (t, 0);
> > +       gcc_assert (TREE_CODE (op) == SPLICE_EXPR);
> > +       tree r = finish_template_type (templ, targs,
> > +                                      /*entering_scope=*/false);
> > +       if (TREE_CODE (r) == TYPE_DECL)
> > +         r = TREE_TYPE (r);
> > +       if (SPLICE_EXPR_EXPRESSION_P (op)
> > +           && !check_splice_expr (input_location, UNKNOWN_LOCATION, r,
> > +                                  SPLICE_EXPR_ADDRESS_P (op),
> > +                                  SPLICE_EXPR_MEMBER_ACCESS_P (op),
> > +                                  /*complain_p=*/false))
> > +         {
> > +           if (complain & tf_error)
> > +             {
> > +               auto_diagnostic_group d;
> > +               error_at (cp_expr_loc_or_input_loc (t),
> > +                         "%qE is not usable in a splice expression", r);
> > +               if (TYPE_P (r))
> > +                 inform (input_location, "add %<typename%> to denote a "
> > +                         "type outside a type-only context");
> > +             }
> > +           RETURN (error_mark_node);
> > +         }
> > +       RETURN (r);
> 
> How can we get here such that it isn't an error?
> <tests>
> aha, in
> 
>  typename [: R :]<int> c1;
> 
> the type is SPLICE_SCOPE where SPLICE_SCOPE_EXPR is [:R:]<int>, and we pass
> it to tsubst_expr.
> 
> But the comment is wrong; we do know what [:R:] means, we know it needs to
> be a type because it's a SPLICE_SCOPE, so we shouldn't have needed to pass
> it to tsubst_expr in the first place.

Yes: every valid usage of [:R:] is coming from tsubst_splice_scope, so we
know it needs to expand to a type/scope.

> Can we instead add SPLICE_EXPR and TEMPLATE_ID_EXPR cases to tsubst for the
> type cases, as well as moving the NAMESPACE_DECL case there?

Hopefully done in
<https://forge.sourceware.org/marek/gcc/commit/0726f7476b187bccd4a113a80900fb8125486313>

> > +    case REFLECT_EXPR:
> > +      {
> > +   tree h = REFLECT_EXPR_HANDLE (t);
> > +   auto kind = static_cast<reflect_kind> (REFLECT_EXPR_KIND (t));
> 
> I'd expect the cast to be part of the REFLECT_EXPR_KIND macro, if it needs
> to be anywhere.

Done.  I had to add SET_REFLECT_EXPR_KIND to avoid
"lvalue required as left operand of assignment" for 
REFLECT_EXPR_KIND (t) = kind;

<https://forge.sourceware.org/marek/gcc/commit/874f82184d22e861b4bc2adc8198785aa7ee422e>
 
> > @@ -29318,6 +29567,27 @@ value_dependent_expression_p (tree expression)
> >         if (value_dependent_expression_p (op))
> >           return true;
> >       }
> > +   if (flag_reflection && !fn && CALL_EXPR_FN (expression))
> > +     {
> > +       fn = MAYBE_BASELINK_FUNCTIONS (CALL_EXPR_FN (expression));
> > +       if (fn && TREE_CODE (fn) != FUNCTION_DECL)
> > +         fn = NULL_TREE;
> > +     }
> > +   /* [meta.reflection.access.context]/8: An invocation of current that
> > +      appears at a program point P is value-dependent if eval-point(P)
> > +      is enclosed by a scope corresponding to a templated entity.  */
> > +   if (flag_reflection
> > +       && fn
> > +       && metafunction_p (fn)
> > +       && id_equal (DECL_NAME (fn), "current")
> > +       && DECL_CLASS_SCOPE_P (fn)
> > +       && TYPE_NAME (DECL_CONTEXT (fn))
> > +       && TREE_CODE (TYPE_NAME (DECL_CONTEXT (fn))) == TYPE_DECL
> > +       && DECL_NAME (TYPE_NAME (DECL_CONTEXT (fn)))
> > +       && id_equal (DECL_NAME (TYPE_NAME (DECL_CONTEXT (fn))),
> > +                    "access_context"))
> > +     return true;
> 
> The last four && could be just
> 
> && id_equal (TYPE_IDENTIFIER (DECL_CONTEXT (fn)), "access_context")

Done in
<https://forge.sourceware.org/marek/gcc/commit/e459ba68dae8d61e35bef52b78ecaba0778cb0d0>

> > +fold_builtin_is_string_literal (location_t loc, int nargs, tree *args)
> > +{
> > +  /* Unless users call the builtin directly, the following 3 checks should 
> > be
> > +     ensured from std::is_string_literal overloads.  */
> > +  if (nargs != 1)
> > +    {
> > +      error_at (loc, "%<__builtin_is_string_literal%> needs a single "
> > +           "argument");
> > +      return boolean_false_node;
> > +    }
> > +  tree arg = args[0];
> > +  if (error_operand_p (arg))
> > +    return boolean_false_node;
> > +  if (!TYPE_PTR_P (TREE_TYPE (arg))
> > +      || !TYPE_READONLY (TREE_TYPE (TREE_TYPE (arg)))
> > +      || TYPE_VOLATILE (TREE_TYPE (TREE_TYPE (arg))))
> > +    {
> > +    arg_invalid:
> > +      error_at (loc, "%<__builtin_is_string_literal%> "
> > +                "argument is not %<const char*%>, %<const wchar_t*%>, "
> > +                "%<const char8_t*%>, %<const char16_t*%> or "
> > +                "%<const char32_t*%>");
> > +      return boolean_false_node;
> > +    }
> > +  tree chart = TYPE_MAIN_VARIANT (TREE_TYPE (TREE_TYPE (arg)));
> > +  if (chart != char_type_node
> > +      && chart != wchar_type_node
> > +      && chart != char8_type_node
> > +      && chart != char16_type_node
> > +      && chart != char32_type_node)
> > +    goto arg_invalid;
> > +
> > +  STRIP_NOPS (arg);
> > +  while (TREE_CODE (arg) == POINTER_PLUS_EXPR)
> > +    {
> > +      if (TREE_CODE (TREE_OPERAND (arg, 1)) != INTEGER_CST)
> > +   return boolean_false_node;
> 
> Why check the offset?

Looks like it's not necessary.

> > +      arg = TREE_OPERAND (arg, 0);
> > +      STRIP_NOPS (arg);
> > +    }
> > +  if (TREE_CODE (arg) != ADDR_EXPR)
> > +    return boolean_false_node;
> > +  arg = TREE_OPERAND (arg, 0);
> > +  if (TREE_CODE (arg) == ARRAY_REF
> > +      && TREE_CODE (TREE_OPERAND (arg, 1)) == INTEGER_CST)
> 
> Likewise.

This one too.  Removed in
<https://forge.sourceware.org/marek/gcc/commit/17dd91f9e858c30681a0fb8cd2f1b745960e9376>

> > @@ -3437,9 +3444,24 @@ finish_class_member_access_expr (cp_expr object, 
> > tree name, bool template_p,
> >        return error_mark_node;
> >      }
> > +  /* For OBJECT.[:S::fn:] the BASELINK can be inside a SCOPE_REF.
> > +     This happens, but, until Reflection, not for a class member access.  
> > */
> > +  if (TREE_CODE (name) == SCOPE_REF && BASELINK_P (TREE_OPERAND (name, 1)))
> > +    name = TREE_OPERAND (name, 1);
> 
> Why does this happen?  Why isn't the SCOPE_REF folded into the BASELINK?

Happens in:

  struct S {
    void foo () {}
  };

  template <typename T>
  void qux ()
  {
    S s {};
    s.[:(T) ^^S::foo:] ();
  }

  void
  corge ()
  {
    qux <decltype (^^int)> ();
  }

In cp_parser_postfix_dot_deref_expression:
* cp_parser_splice_expression gives us a SPLICE_EXPR (~it's dependent)
* since parser->scope is S, we create a SCOPE_REF in build_qualified_name
* need to tsubst, so tsubst_expr/SCOPE_REF does tsubst_scope + tsubst_name,
  then build_qualified_name creates a new SCOPE_REF
* tsubst_expr calls finish_class_member_access_expr with that SCOPE_REF

Does this have to change?

> > @@ -3520,10 +3542,15 @@ finish_class_member_access_expr (cp_expr object, 
> > tree name, bool template_p,
> >          one copy of the data member that is shared by all the objects of
> >          the class.  So NAME can be unambiguously referred to even if
> >          there are multiple indirect base classes containing NAME.  */
> > -     const base_access ba = [scope, name] ()
> > +     const base_access ba = [scope, name, splice_p] ()
> >         {
> >           if (identifier_p (name))
> >             {
> > +             /* [class.access.base]/5: A member m is accessible at the
> > +                point R when designated in class N if
> > +                -- m is designated by a splice-expression  */
> > +             if (splice_p)
> > +               return ba_unique;
> 
> It being accessible doesn't mean it's unique.  But I assume this is already
> being reworked to avoid repeating the lookup.

As of this writing this code is the same, but the class member access
handling is being overhauled so maybe we'll be able to get rid of the
repeated lookup.
 
> > +/* The DIE for C++26 'decltype(^^int)' fundamental type.  */
> > +static GTY(()) dw_die_ref meta_type_die;
> 
> Why do we need this?
> 
> I don't think the current auto handling is a great model to copy.
> 
> I wonder about, in is_base_type and gen_type_die_with_usage, just handling
> everything past END_OF_BASE_TREE_CODES like LANG_TYPE?

Resolved in
<https://forge.sourceware.org/marek/gcc/commit/06e3ccc259570c2e2c7ab4e997d38326e8afc53d>

> OK, that's everything in patch 1/9.

Thanks.  There is another email I have to respond to:
<https://gcc.gnu.org/pipermail/gcc-patches/2025-December/703238.html>
before I can post v2.

Marek

Reply via email to