On Fri, Dec 19, 2025 at 10:26:58AM +0700, Jason Merrill wrote:
> On 12/18/25 12:43 AM, Marek Polacek wrote:
> > On Wed, Dec 17, 2025 at 11:42:41AM -0500, Marek Polacek wrote:
> > @@ -6320,18 +6343,27 @@ cp_parser_splice_expression (cp_parser *parser, 
> > bool template_p,
> >       {
> >         /* Grab the unresolved expression then.  */
> >         t = unresolved;
> > -      if (DECL_P (t))
> > -   /* We cannot forget what context we came from, so build up
> > -      a SCOPE_REF.  */
> > -   t = build_qualified_name (/*type=*/NULL_TREE, CP_DECL_CONTEXT (t),
> > -                             DECL_NAME (t), /*template_p=*/false);
> > -      else if (OVL_P (t))
> > -   t = OVL_NAME (t);
> > -      gcc_assert (TREE_CODE (t) == SCOPE_REF
> > -             || BASELINK_P (t)
> > -             || TREE_CODE (t) == SPLICE_EXPR
> > -             || TREE_CODE (t) == TEMPLATE_ID_EXPR
> > -             || TREE_CODE (t) == TREE_BINFO);
> > +      if (TREE_CODE (t) == FIELD_DECL
> > +     || VAR_P (t)
> > +     || TREE_CODE (t) == CONST_DECL
> > +     || TREE_CODE (t) == FUNCTION_DECL
> > +     || DECL_FUNCTION_TEMPLATE_P (OVL_FIRST (t)))
> > +   ;
> > +      else
> > +   {
> > +     if (DECL_P (t))
> > +       /* We cannot forget what context we came from, so build up
> > +          a SCOPE_REF.  */
> > +       t = build_qualified_name (/*type=*/NULL_TREE, CP_DECL_CONTEXT (t),
> > +                                 DECL_NAME (t), /*template_p=*/false);
> 
> What decls still get here?

This, and...

> > +     else if (OVL_P (t))
> > +       t = OVL_NAME (t);
> 
> Or here?  Seems like OVL_NAME will never produce something that satisfies
> the assert just below.

...this is no longer present: removed in
<https://forge.sourceware.org/marek/gcc/commit/59af6f17962d951e98ee17a62e12f80079797e40>

> > @@ -19325,8 +19287,7 @@ cp_parser_decltype_expr (cp_parser *parser,
> >         /* [dcl.type.decltype] "if E is an unparenthesized 
> > splice-expression,
> >      decltype(E) is the type of the entity, object, or value designated
> >      by the splice-specifier of E"  */
> > -      if (cp_lexer_next_token_is (parser->lexer, CPP_OPEN_SPLICE)
> > -     && !cp_parser_splice_spec_is_nns_p (parser))
> > +      if (cp_parser_nth_token_starts_splice_without_nns_p (parser, 1))
> >     {
> >       cp_id_kind idk;
> >       expr = cp_parser_splice_expression (parser, /*template_p=*/false,
> > @@ -22279,30 +22240,25 @@ cp_parser_type_specifier (cp_parser* parser,
> >         /* Fall through.  */
> >       case RID_TYPENAME:
> > -      {
> > -   /* If we see 'typename [:', this could be a typename-specifier.
> > -      But if there's no '::' after the '[:x:]' then it is probably
> 
> Maybe drop "probably"?  Can it be anything else?

I don't think that it can be anything else in a valid program.  I'm
going to drop the "probably" in the patch below.

> > -      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;
> > -      }
> > +      /* 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_parser_nth_token_starts_splice_without_nns_p (parser, 2))
> > +   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;
> >       case RID_CONST:
> >         ds = ds_const;
> > @@ -24389,8 +24345,7 @@ cp_parser_namespace_alias_definition (cp_parser* 
> > parser)
> >         return;
> >       }
> >     cp_parser_require (parser, CPP_EQ, RT_EQ);
> > -  if (cp_lexer_next_token_is (parser->lexer, CPP_OPEN_SPLICE)
> > -      && !cp_parser_splice_spec_is_nns_p (parser))
> > +  if (cp_parser_nth_token_starts_splice_without_nns_p (parser, 1))
> >       namespace_specifier = cp_parser_splice_specifier (parser);
> >     else
> >       /* Look for the qualified-namespace-specifier.  */
> > @@ -24879,8 +24834,7 @@ cp_parser_using_directive (cp_parser* parser)
> >     cp_parser_require_keyword (parser, RID_USING, RT_USING);
> >     /* And the `namespace' keyword.  */
> >     cp_parser_require_keyword (parser, RID_NAMESPACE, RT_NAMESPACE);
> > -  if (cp_lexer_next_token_is (parser->lexer, CPP_OPEN_SPLICE)
> > -      && !cp_parser_splice_spec_is_nns_p (parser))
> > +  if (cp_parser_nth_token_starts_splice_without_nns_p (parser, 1))
> >       namespace_decl = cp_parser_splice_specifier (parser);
> >     else
> >       {
> > @@ -27653,15 +27607,25 @@ cp_parser_declarator_id (cp_parser* parser, bool 
> > optional_p)
> >      If IS_TRAILING_RETURN is true, we are in a trailing-return-type,
> >      i.e. we've just seen "->".
> > +   If TYPE_ALIAS_P is non-null, we set it to true or false depending
> > +   on if the type-id is a type alias or just a type.  E.g., given
> > +
> > +     template<typename> struct S {};
> > +     template<typename T> using A = const S<T>;
> > +
> > +   ^^A<int> is a type alias, but ^^const A<int>, ^^A<int> const, or 
> > ^^A<int>*
> > +   are just types, not type aliases.
> > +
> >      Returns the TYPE specified.  */
> >   static tree
> >   cp_parser_type_id_1 (cp_parser *parser, cp_parser_flags flags,
> >                  bool is_template_arg, bool is_trailing_return,
> > -                location_t *type_location)
> > +                location_t *type_location, bool *type_alias_p)
> >   {
> >     cp_decl_specifier_seq type_specifier_seq;
> >     cp_declarator *abstract_declarator;
> > +  cp_token *next = nullptr;
> >     /* Parse the type-specifier-seq.  */
> >     cp_parser_type_specifier_seq (parser, flags,
> > @@ -27671,6 +27635,17 @@ cp_parser_type_id_1 (cp_parser *parser, 
> > cp_parser_flags flags,
> >     if (type_location)
> >       *type_location = type_specifier_seq.locations[ds_type_spec];
> > +  /* If there is just ds_type_spec specified, this could be a type alias.  
> > */
> > +  if (type_alias_p && is_typedef_decl (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)
> > +   next = cp_lexer_peek_token (parser->lexer);
> > +    }
> 
> Can we drop this (and the declaration of next) and...
> 
> >     if (is_template_arg && type_specifier_seq.type
> >         && TREE_CODE (type_specifier_seq.type) == TEMPLATE_TYPE_PARM
> >         && CLASS_PLACEHOLDER_TEMPLATE (type_specifier_seq.type))
> > @@ -27698,6 +27673,11 @@ cp_parser_type_id_1 (cp_parser *parser, 
> > cp_parser_flags flags,
> >     if (!cp_parser_parse_definitely (parser))
> >       abstract_declarator = NULL;
> > +  /* If we found * or & and similar after the type-specifier, it's not
> > +     a type alias.  */
> > +  if (type_alias_p)
> > +    *type_alias_p = cp_lexer_peek_token (parser->lexer) == next;
> 
> ...check !abstract_declarator here instead of next?  (And check
> is_typedef/type_specifier_seq here instead of above)

Patch posted:
<https://gcc.gnu.org/pipermail/gcc-patches/2026-January/706571.html>

> > +static tree
> > +tsubst_splice_expr (tree t, tree args, tsubst_flags_t complain, tree 
> > in_decl)
> > +{
> > +  tree op = tsubst_expr (TREE_OPERAND (t, 0), args, complain, in_decl);
> > +  if (op == error_mark_node)
> > +    return error_mark_node;
> > +  op = splice (op);
> > +  if (dependent_splice_p (op))
> > +    {
> > +      if (SPLICE_EXPR_EXPRESSION_P (t))
> > +   SET_SPLICE_EXPR_EXPRESSION_P (op);
> > +      if (SPLICE_EXPR_MEMBER_ACCESS_P (t))
> > +   SET_SPLICE_EXPR_MEMBER_ACCESS_P (op, true);
> > +      if (SPLICE_EXPR_ADDRESS_P (t))
> > +   SET_SPLICE_EXPR_ADDRESS_P (op, true);
> > +      return op;
> > +    }
> > +  if (SPLICE_EXPR_EXPRESSION_P (t)
> > +      && !check_splice_expr (input_location, UNKNOWN_LOCATION, op,
> > +                        SPLICE_EXPR_ADDRESS_P (t),
> > +                        SPLICE_EXPR_MEMBER_ACCESS_P (t),
> > +                        (complain & tf_error)))
> > +    return error_mark_node;
> > +  if (outer_automatic_var_p (op))
> > +    op = process_outer_var_ref (op, complain);
> > +  /* Like in cp_parser_splice_expression, for foo.[: bar :]
> > +     cp_parser_postfix_dot_deref_expression wants to see only
> > +     certain kind of entities.  */
> > +  if (SPLICE_EXPR_MEMBER_ACCESS_P (t))
> > +    {
> > +      if (TREE_CODE (op) == FIELD_DECL
> > +     || VAR_P (op)
> > +     || TREE_CODE (op) == CONST_DECL
> > +     || TREE_CODE (op) == FUNCTION_DECL
> > +     || DECL_FUNCTION_TEMPLATE_P (OVL_FIRST (op))
> > +     || variable_template_p (op))
> 
> Why is variable_template_p here but not in cp_parser_splice_expression?

Added into cp_parser_splice_expression in:
<https://forge.sourceware.org/marek/gcc/commit/59af6f17962d951e98ee17a62e12f80079797e40>

> > +   ;
> > +      else
> > +   {
> > +     if (DECL_P (op))
> > +       /* If we are called from tsubst_expr/SCOPE_REF, we'll build
> > +          the SCOPE_REF there.  */
> > +       op = DECL_NAME (op);
> > +     else if (OVL_P (op))
> > +       op = OVL_NAME (op);
> 
> As in cp_parser_splice_expression, what cases do these handle?

Removed in
<https://forge.sourceware.org/marek/gcc/commit/59af6f17962d951e98ee17a62e12f80079797e40>

> > @@ -1350,6 +1362,7 @@ eval_is_expected_access (tree r, reflect_kind kind, 
> > tree expected_access)
> >       {
> >         gcc_assert (TREE_CODE (r) == TREE_BINFO);
> >         tree c = r;
> > +      /* Looping needed for multiple virtual inheritance.  */
> >         while (BINFO_INHERITANCE_CHAIN (c))
> >     c = BINFO_INHERITANCE_CHAIN (c);
> 
> Since this comes up so many times, how about adding a function to return the
> derived type in an inheritance reflection?

We now have direct_base_derived_binfo.

> > @@ -8491,6 +8572,26 @@ check_splice_expr (location_t loc, location_t 
> > start_loc, tree t,
> >         return false;
> >       }
> > +  /* [expr.prim.splice]/2: "The expression is ill-formed if S [the 
> > construct
> > +     designated by splice-specifier] is
> > +     -- a local entity such that there is a lambda scope that intervenes
> > +     between the expression and the point at which S was introduced"
> > +     This also checks ODR violations (reflect/odr1.C).  */
> > +  if (outer_automatic_var_p (t)
> > +      && process_outer_var_ref (t, tf_none) == error_mark_node)
> 
> This still seems not quite right; it doesn't say that any outer automatic
> variable is ill-formed, only if there's a lambda.  Of course, any actual use
> is still ill-formed under the normal rules, so I guess this is close enough.
> 
> But if we're in a capturing lambda, we also need to check that
> process_outer_var_ref doesn't return a capture.

Patch posted:
<https://gcc.gnu.org/pipermail/gcc-patches/2026-January/706566.html>

> Apart from that small change, really getting lambdas right should probably
> involve refactoring process_outer_var_ref and also fixing 112926, and happen
> after the merge.

Sounds like GCC 17 project.  Maybe I can take that since I worked on P2036R3
a few months ago.

> > +    {
> > +      /* Not letting process_outer_var_ref emit the error so that we can
> > +    say "in a splice expression".  */
> > +      if (complain_p)
> > +   {
> > +     auto_diagnostic_group d;
> > +     error_at (loc, "use of local variable with automatic storage from "
> > +               "containing function in a splice expression");
> > +     inform (DECL_SOURCE_LOCATION (t), "%q#D declared here", t);
> > +   }
> > +      return false;
> > +    }
> > +
> >     /* If we had a reflect_kind here, we could just check for
> >        REFLECT_ANNOTATION and be done with it.  But we don't have it yet 
> > (TODO),
> >        so do it the suboptimal way.  */
> > @@ -8552,7 +8653,7 @@ tree
> >   reflection_mangle_prefix (tree refl, char prefix[3])
> >   {
> >     tree h = REFLECT_EXPR_HANDLE (refl);
> > -  reflect_kind kind = static_cast<reflect_kind>(REFLECT_EXPR_KIND (refl));
> > +  reflect_kind kind = REFLECT_EXPR_KIND (refl);
> >     if (h == unknown_type_node)
> >       {
> >         strcpy (prefix, "nu");
> > diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
> > index 2d08bda923a..5b645aa5568 100644
> > --- a/gcc/cp/semantics.cc
> > +++ b/gcc/cp/semantics.cc
> > @@ -4529,7 +4529,7 @@ baselink_for_fns (tree fns)
> >   /* Returns true iff we are currently parsing a lambda-declarator.  */
> > -static bool
> > +bool
> >   parsing_lambda_declarator ()
> >   {
> >     cp_binding_level *b = current_binding_level;
> > @@ -13686,16 +13686,13 @@ fold_builtin_is_string_literal (location_t loc, 
> > int nargs, tree *args)
> >     STRIP_NOPS (arg);
> >     while (TREE_CODE (arg) == POINTER_PLUS_EXPR)
> >       {
> > -      if (TREE_CODE (TREE_OPERAND (arg, 1)) != INTEGER_CST)
> > -   return boolean_false_node;
> >         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)
> > +  if (TREE_CODE (arg) == ARRAY_REF)
> >       arg = TREE_OPERAND (arg, 0);
> >     if (TREE_CODE (arg) != STRING_CST)
> >       return boolean_false_node;
> > diff --git a/gcc/cp/tree.cc b/gcc/cp/tree.cc
> > index 5fb797e527d..e69b1643777 100644
> > --- a/gcc/cp/tree.cc
> > +++ b/gcc/cp/tree.cc
> > @@ -5844,14 +5844,34 @@ handle_annotation_attribute (tree *node, tree 
> > ARG_UNUSED (name),
> >         *no_add_attrs = true;
> >         return NULL_TREE;
> >       }
> > -  if (!type_dependent_expression_p (TREE_VALUE (args))
> > -      && !structural_type_p (TREE_TYPE (TREE_VALUE (args))))
> > +  if (!type_dependent_expression_p (TREE_VALUE (args)))
> >       {
> > -      auto_diagnostic_group d;
> > -      error ("annotation does not have structural type");
> > -      structural_type_p (TREE_TYPE (TREE_VALUE (args)), true);
> > -      *no_add_attrs = true;
> > -      return NULL_TREE;
> > +      if (!structural_type_p (TREE_TYPE (TREE_VALUE (args))))
> > +   {
> > +     auto_diagnostic_group d;
> > +     error ("annotation does not have structural type");
> > +     structural_type_p (TREE_TYPE (TREE_VALUE (args)), true);
> > +     *no_add_attrs = true;
> > +     return NULL_TREE;
> > +   }
> > +      if (CLASS_TYPE_P (TREE_TYPE (TREE_VALUE (args))))
> > +   {
> > +     tree arg = make_tree_vec (1);
> > +     tree type = TREE_TYPE (TREE_VALUE (args));
> > +     tree ctype
> > +       = cp_build_qualified_type (type, cp_type_quals (type)
> > +                                        | TYPE_QUAL_CONST);
> > +     TREE_VEC_ELT (arg, 0)
> > +       = cp_build_reference_type (ctype, /*rval=*/false);
> 
> Maybe build_stub_type would be useful?

Patch posted:
<https://gcc.gnu.org/pipermail/gcc-patches/2026-January/706570.html>
and committed.

> > +     if (!is_xible (INIT_EXPR, type, arg))
> > +       {
> > +         auto_diagnostic_group d;
> > +         error ("annotation does not have copy constructible type");
> > +         is_xible (INIT_EXPR, type, arg, /*explain=*/true);
> > +         *no_add_attrs = true;
> > +         return NULL_TREE;
> > +       }
> > +   }
> >       }
> >     if (!processing_template_decl)
> >       {
> > @@ -6787,6 +6807,14 @@ maybe_adjust_arg_pos_for_attribute (const_tree 
> > fndecl)
> >     return n > 0 ? n - 1 : 0;
> >   }
> > +/* True if ATTR is annotation.  */
> > +
> > +bool
> > +annotation_p (tree attr)
> > +{
> > +  return is_attribute_p ("annotation ", get_attribute_name (attr));
> 
> Should this also check the namespace?  Well, perhaps that's not important
> for a name that a user can't write.

No change here.

> > +}
> > +
> >   
> >   /* Release memory we no longer need after parsing.  */
> >   void
> > diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc
> > index 3f471b47876..beee605d0b7 100644
> > --- a/gcc/cp/typeck.cc
> > +++ b/gcc/cp/typeck.cc
> > @@ -3443,9 +3443,9 @@ complain_about_unrecognized_member (tree access_path, 
> > tree name,
> >   /* This function is called by the parser to process a class member
> >      access expression of the form OBJECT.NAME.  NAME is a node used by
> > -   the parser to represent a name; it is not yet a DECL.  It may,
> > -   however, be a BASELINK where the BASELINK_FUNCTIONS is a
> > -   TEMPLATE_ID_EXPR.  Templates must be looked up by the parser, and
> > +   the parser to represent a name; it is not yet a DECL, except when
> > +   SPLICE_P.  It may, however, be a BASELINK where the BASELINK_FUNCTIONS
> > +   is a TEMPLATE_ID_EXPR.  Templates must be looked up by the parser, and
> >      there is no reason to do the lookup twice, so the parser keeps the
> >      BASELINK.  TEMPLATE_P is true iff NAME was explicitly declared to
> >      be a template via the use of the "A::template B" syntax.  SPLICE_P
> > @@ -3498,8 +3498,10 @@ finish_class_member_access_expr (cp_expr object, 
> > tree name, bool template_p,
> >           && dependent_splice_p (TREE_OPERAND (name, 1))))
> >     {
> >     dependent:
> > -     return build_min_nt_loc (UNKNOWN_LOCATION, COMPONENT_REF,
> > +     expr = build_min_nt_loc (UNKNOWN_LOCATION, COMPONENT_REF,
> >                                orig_object, orig_name, NULL_TREE);
> > +     COMPONENT_REF_SPLICE_P (expr) = splice_p;
> > +     return expr;
> >     }
> >       }
> >     else if (c_dialect_objc ()
> > @@ -3530,11 +3532,6 @@ 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);
> > -
> >     if (BASELINK_P (name))
> >       /* A member function that has already been looked up.  */
> >       member = name;
> > @@ -3574,6 +3571,13 @@ finish_class_member_access_expr (cp_expr object, 
> > tree name, bool template_p,
> >           return error_mark_node;
> >         }
> >     }
> > +      else if (splice_p
> > +          && (TREE_CODE (name) == FIELD_DECL
> > +              || VAR_P (name)
> > +              || TREE_CODE (name) == CONST_DECL
> > +              || TREE_CODE (name) == FUNCTION_DECL
> > +              || DECL_FUNCTION_TEMPLATE_P (OVL_FIRST (name))))
> 
> You can't just add these to the "already looked up" case above that was just
> BASELINK?  (And perhaps do without splice_p?)  It seems awkward to add a
> bunch of exceptions to the lookup code for something that's already looked
> up, probably better to add any needed checking (e.g. for dependent
> attributes) to the already-looked-up case.

I see what you mean.  But we need to compute access_path for the
build_baselink call in a splice_p block.  I suppose I could factor out
the whole if (scope) block and then move the splice_p code to the
"already looked up" case above.  Does that sound reasonable?

> > diff --git a/gcc/testsuite/g++.dg/reflect/dep5.C 
> > b/gcc/testsuite/g++.dg/reflect/dep5.C
> > index c8d84b51c05..4a118de1b70 100644
> > --- a/gcc/testsuite/g++.dg/reflect/dep5.C
> > +++ b/gcc/testsuite/g++.dg/reflect/dep5.C
> > @@ -18,8 +18,8 @@ f ()
> >     static_assert ([: ^^T :]::x == 42);
> >     typename [: ^^T :]::type a = 42;
> >     [: ^^T :]::fn (42);
> > -  [: ^^T :]::template tfn<([: ^^T :])>();  // { dg-error ".S. is not 
> > usable in a splice expression" }
> > -  auto x = [: ^^T :]::template var<([: ^^T :])>;  // { dg-error ".S. is 
> > not usable in a splice expression" }
> > +  [: ^^T :]::template tfn<([: ^^T :])>();  // { dg-error "expected a 
> > reflection of an expression instead of type .S." }
> > +  auto x = [: ^^T :]::template var<([: ^^T :])>;  // { dg-error "expected 
> > a reflection of an expression instead of type .S." }
> 
> This rephrasing seems like an improvement, can we do the same with the other
> "not usable" diagnostics?

Patch posted:
<https://gcc.gnu.org/pipermail/gcc-patches/2026-February/708160.html>

> >   }
> >   void
> > diff --git a/gcc/testsuite/g++.dg/reflect/diag1.C 
> > b/gcc/testsuite/g++.dg/reflect/diag1.C
> > index 3792c949bc5..2d34623a8ac 100644
> > --- a/gcc/testsuite/g++.dg/reflect/diag1.C
> > +++ b/gcc/testsuite/g++.dg/reflect/diag1.C
> > @@ -14,12 +14,12 @@ void
> >   f ()
> >   {
> >     S s;
> > -  s.[: ^^S::tfn :](42); // { dg-error "reflection not usable in a splice 
> > expression" }
> > +  s.[: ^^S::tfn :](42); // { dg-error "reflection .S::tfn. not usable in a 
> > splice expression" }
> >   // { dg-message "add .template. to denote a template" "" { target *-*-* } 
> > .-1 }
> >     s.template [: ^^S::tfn :](42);
> >     constexpr auto r = ^^fortytwo;
> > -  constexpr int i1 = [:r:]<int>; // { dg-error "reflection not usable in a 
> > splice expression with template arguments" }
> > +  constexpr int i1 = [:r:]<int>; // { dg-error "reflection .fortytwo<int>. 
> > not usable in a splice expression with template arguments" }
> 
> This diagnostic is pretty confusing (the reflection is not of
> fortytwo<int>), it should only suggest the missing 'template' keyword. And
> in this case since we already decide the <> are template arguments the
> diagnostic could be a pedwarn.

Patch posted:
<https://gcc.gnu.org/pipermail/gcc-patches/2026-February/708160.html>

> Though also, cp_parser_splice_specifier seems to be too eagerly concluding
> that :]< is a template-id; what about
> 
> int i;
> constexpr auto r = ^^i;
> bool b = [:r:] < 42;
> 
> ?  The missing_template_diag code seems relevant.

Patch posted:
<https://gcc.gnu.org/pipermail/gcc-patches/2026-January/706989.html>

> >   // { dg-message "add .template. to denote a template" "" { target *-*-* } 
> > .-1 }
> >     constexpr int i2 = template [:r:]<int>;
> >   }
> > diff --git a/gcc/testsuite/g++.dg/reflect/enumerators_of1.C 
> > b/gcc/testsuite/g++.dg/reflect/enumerators_of1.C
> > index 6896fa97230..17141185afa 100644
> > --- a/gcc/testsuite/g++.dg/reflect/enumerators_of1.C
> > +++ b/gcc/testsuite/g++.dg/reflect/enumerators_of1.C
> > @@ -104,7 +104,7 @@ qux ()
> >     // We certainly during instantiation don't differentiate between
> >     // forward enum declarations and later definitions.
> 
> Yeah, it would be pretty unfriendly to require us to represent such a
> useless distinction.

No change.

> > diff --git a/gcc/testsuite/g++.dg/reflect/extract1.C 
> > b/gcc/testsuite/g++.dg/reflect/extract1.C
> > index c3550cb8bd2..6eaf9ffe4b9 100644
> > --- a/gcc/testsuite/g++.dg/reflect/extract1.C
> > +++ b/gcc/testsuite/g++.dg/reflect/extract1.C
> > @@ -25,11 +25,11 @@ constexpr auto rA = reflect_constant ('A');
> >   static_assert (extract<char>(rA) == 'A');
> >   static_assert (extract<const char>(rA) == 'A');
> >   constexpr auto rPIf = reflect_constant (3.14f);
> > -static_assert (extract<float>(rPIf) == 3.14f);
> > -static_assert (extract<const float>(rPIf) == 3.14f);
> > +static_assert (extract<float>(rPIf) == float(3.14f));
> > +static_assert (extract<const float>(rPIf) == float(3.14f));
> >   constexpr auto rPI = reflect_constant (3.14);
> > -static_assert (extract<double>(rPI) == 3.14);
> > -static_assert (extract<const double>(rPI) == 3.14);
> > +static_assert (extract<double>(rPI) == double(3.14));
> > +static_assert (extract<const double>(rPI) == double(3.14));
> >   constexpr auto r666 = reflect_constant (666L);
> >   static_assert (extract<long int>(r666) == 666L);
> >   static_assert (extract<const long int>(r666) == 666L);
> > @@ -42,10 +42,16 @@ static_assert (extract<const signed char>(constant_of 
> > (^^sc)) == -1);
> >   constexpr unsigned char uc = 255;
> >   static_assert (extract<unsigned char>(constant_of (^^uc)) == 255);
> >   static_assert (extract<const unsigned char>(constant_of (^^uc)) == 255);
> > -constexpr const int *p = nullptr;
> > +constexpr int *p = nullptr;
> >   constexpr info rp = ^^p;
> > +static_assert (extract<int *>(rp) == nullptr);
> > +static_assert (extract<int * const>(rp) == nullptr);
> >   static_assert (extract<const int *>(rp) == nullptr);
> >   static_assert (extract<const int *const>(rp) == nullptr);
> > +constexpr const int *cp = nullptr;
> > +constexpr info rcp = ^^cp;
> > +static_assert (extract<const int *>(rcp) == nullptr);
> > +static_assert (extract<const int *const>(rcp) == nullptr);
> >   constexpr void (*pfn0)() = nullptr;
> >   constexpr void (*pfn1)(int) = nullptr;
> >   constexpr info rpfn0 = ^^pfn0;
> > @@ -138,6 +144,9 @@ static_assert (roundtrip ([] {}));
> >   struct B {
> >     static constexpr int k = 42;
> > +  int m;
> > +  int* p;
> > +
> >     int fn ();
> >     int fn2 () noexcept;
> >     static int fn3 ();
> > @@ -162,3 +171,13 @@ constexpr auto a4 = extract<int (*)() 
> > noexcept>(^^B::fn4);
> >   constexpr auto a5 = extract<int (*)(B)>(^^B::fn5);
> >   constexpr auto a6 = extract<int (B::*)(int) &>(^^B::fn6);
> >   constexpr auto a7 = extract<int (B::*)(int) &&>(^^B::fn7);
> > +
> > +constexpr auto a8 = extract<int B::*>(^^B::m);
> > +constexpr auto a9 = extract<int const B::*>(^^B::m);
> > +constexpr auto a10 = extract<int* B::*>(^^B::p);
> > +constexpr auto a11 = extract<int* const B::*>(^^B::p);
> > +constexpr auto a12 = extract<int const* const B::*>(^^B::p);
> > +
> > +// FIXME removing noexcept should be allowed
> 
> Please open a BZ if this isn't fixed before merge.  And in general xfail
> rather than comment or #if 0.

Fixed by:
<https://gcc.gnu.org/pipermail/gcc-patches/2026-January/706546.html>

> > --- a/gcc/testsuite/g++.dg/reflect/member1.C
> > +++ b/gcc/testsuite/g++.dg/reflect/member1.C
> > @@ -55,9 +55,9 @@ f ()
> >     sp->template [: ^^S::tfn :](42);
> >     s.template [: ^^S::tfn :]<int>(42);
> >     sp->template [: ^^S::tfn :]<int>(42);
> > -  s.[: ^^S::var :]<int> = 1; // { dg-error "reflection not usable in a 
> > splice expression with template arguments" }
> > +  s.[: ^^S::var :]<int> = 1; // { dg-error "reflection .var<int>. not 
> > usable in a splice expression with template arguments" }
> >     s.template [: ^^S::var :]<int> = 1;
> > -  sp->[: ^^S::var :]<int> = 1; // { dg-error "reflection not usable in a 
> > splice expression with template arguments" }
> > +  sp->[: ^^S::var :]<int> = 1; // { dg-error "reflection .var<int>. not 
> > usable in a splice expression with template arguments" }
> >     sp->template [: ^^S::var :]<int> = 1;
> >     s.[: ^^S::b :].[: ^^B::a :].val;
> >     sp->[: ^^S::b :].[: ^^B::a :].val;
> > @@ -74,9 +74,9 @@ f ()
> >     cp->template [: ^^C<int>::tfn :](42);
> >     c.template [: ^^C<int>::tfn :]<int>(42);
> >     cp->template [: ^^C<int>::tfn :]<int>(42);
> > -  c.[: ^^C<int>::var :]<int> = 1; // { dg-error "reflection not usable in 
> > a splice expression with template arguments" }
> > +  c.[: ^^C<int>::var :]<int> = 1; // { dg-error "reflection .var<int>. not 
> > usable in a splice expression with template arguments" }
> >     c.template [: ^^C<int>::var :]<int> = 1;
> > -  cp->[: ^^C<int>::var :]<int> = 1; // { dg-error "reflection not usable 
> > in a splice expression with template arguments" }
> > +  cp->[: ^^C<int>::var :]<int> = 1; // { dg-error "reflection .var<int>. 
> > not usable in a splice expression with template arguments" }
> >     cp->template [: ^^C<int>::var :]<int> = 1;
> >     c.[: ^^C<int>::b :].[: ^^B::a :].val;
> >     cp->[: ^^C<int>::b :].[: ^^B::a :].val;
> > @@ -85,14 +85,15 @@ f ()
> >     [: ^^S :]::template N<int> n1;
> >     [: ^^C<int> :]::template N<int> n2;
> > -  [: ^^C :]<int>::template N<int> n3;
> > +  [: ^^C :]<int>::template N<int> n3;  // { dg-error "missing 
> > .template.|expected" }
> > +  typename [: ^^C :]<int>::template N<int> n4;
> >     s->[: ^^S::x :] = 2; // { dg-error "non-pointer type" }
> >     sp.[: ^^S::x :] = 2; // { dg-error "which is of pointer type" }
> >     c.[: ^^C<char>::x :] = 1; // { dg-error "is not a base of" }
> >     cp->[: ^^C<char>::x :] = 1; // { dg-error "is not a base of" }
> > -  s.template [: ^^S::N :].t;  // { dg-error "reflection not usable in a 
> > template splice" }
> > +  s.template [: ^^S::N :].t;  // { dg-error "reflection .S::N. not usable 
> > in a template splice" }
> 
> But S::N is a template, the problem is the missing targs.

Adding the targs wouldn't make this valid, because we expect a reflection
of an expression.  We now say:

  expected a reflection of a function template instead of class template 'S::N'

> >     S::template [: ^^S::N<int> :] e1;  // { dg-error "expected 
> > unqualified-id" }
> >     C<int>::template [: ^^S::N<int> :] e2;  // { dg-error "expected 
> > unqualified-id" }
>
> This should clarify that the problem is using the 'template' keyword with a
> splice of a specialization.

I didn't change this, sorry.  I think the current error is fine -- this
code would still be invalid even without 'template'.  The problem is that
we arrive at the

  qualified-id:
    nested-name-specifier template[opt] unqualified-id 

production but unqualified-id doesn't expand to a splice (only after ~). 
So we error in cp_parser_unqualified_id because the next token is
CPP_OPEN_SPLICE which we don't handle there.

Marek

Reply via email to