On 11/29/25 7:04 PM, Jakub Jelinek wrote:
On Sat, Nov 29, 2025 at 06:37:03PM +0530, Jason Merrill wrote:
Unfortunately it is not.  ATTR_ID here is just the attribute name, so
lookup_attribute_spec attempts to find it in the gnu namespace rather than
in the "internal " namespace in which it is present, and fails.

In order to make it work I guess is_late_template_attribute would need to pass
TREE_PURPOSE (attr) instead of name to attribute_takes_identifier_p,
similarly tsubst_attribute pass TREE_PURPOSE (t) instead of
get_attribute_name (t), and attribute_takes_identifier_p would then need
to do if (TREE_CODE (attr_id) == TREE_LIST) attr_id = TREE_VALUE (attr_id);
for the targetm.* call at the end (which still expects just IDENTIFIER_NODE.
Similarly maybe is_late_template_attribute should again call
lookup_attribute_spec on TREE_PURPOSE (attr) rather than name and then the
if (annotation_p (attr)) return true; stuff could be done after the
if (!spec) return false; checking.

Sounds good, perhaps as a separate patch.

Ok, will do a separate patch for that.

But the current behavior of merge_attributes reverses the order of the a2
list while leaving the a1 list in the same order.  This behavior seems too
chaotic to be worth trying to preserve; keeping both lists in order seems
strictly better.

Ok, will change.

So, shall it use attribute_value_equal instead?

Yes, that seems like it would be an improvement, perhaps along with the
is_late_template_attribute cleanup or by itself.

Ok, another incremental patch.

Why does this need to loop?

+           c = BINFO_INHERITANCE_CHAIN (c);

The current patchset records direct base relationship reflection
as REFLECT_BASE with a TREE_BINFO as REFLECT_EXPR_HANDLE.
That looked to me best as it is an existing tree, instead of having
to create say a TREE_VEC with 2 elements or TREE_LIST, where one
operand of that would be the derived class and the other the base
class.  The base class is immediately accessible from the TREE_BINFO,
it is BINFO_TYPE (REFLECT_EXPR_HANDLE (t)).  The derived class is
usually BINFO_TYPE (BINFO_INHERITANCE_CHAIN (REFLECT_EXPR_HANDLE (t))),
but not always, sometimes there is longer BINFO_INHERITANCE_CHAIN
chain before reaching the final one (which has NULL
BINFO_INHERITANCE_CHAIN).

Sure, I'm looking for a comment about when there can be a longer chain. Is
it when a direct virtual base is a primary base of a non-virtual base?

E.g. g++.dg/reflect/bases_of2.C
struct A {};
struct B : virtual A {};
struct C : virtual A {};
struct D : virtual B, virtual C {};
struct E : virtual D {};
struct F : virtual D, virtual E {};

The (F, D) direct base relationship TREE_BINFO has
BINFO_INHERITANCE_CHAIN (BINFO_INHERITANCE_CHAIN (base_binfo)) non-NULL and
only BINFO_TYPE of that is F.

Sure, the added comment could just mention multiple virtual inheritance.

And some further feedback:

+cp_parser_reflect_expression (cp_parser *parser)
+{
+  if (!flag_reflection)
+    {
+      error_at (cp_lexer_peek_token (parser->lexer)->location,
+               "reflection is only available with %<-freflection%>");
+      return error_mark_node;
+    }
+
+  /* Consume the '^^'.  */
+  cp_lexer_consume_token (parser->lexer);
+
+  /* Get the location of the operand.  */
+  const location_t loc = cp_lexer_peek_token (parser->lexer)->location;
+
+  auto s = make_temp_override (cp_preserve_using_decl, true);

This flag seems kind of a kludge; since we only need it to apply to a single lookup, I wonder how difficult it would be to use a LOOK_want flag instead?

Failing that, setting the flag should at least move into cp_parser_reflection_name, it seems dangerous to have it set while parsing template arguments.

+       /* 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.

@@ -10658,8 +10815,11 @@ cxx_eval_outermost_constant_expr (tree t, bool 
allow_non_constant,
     }
/* Check that immediate invocation does not return an expression referencing
-     any immediate function decls.  */
-  if (!non_constant_p && cxx_dialect >= cxx20)
+     any immediate function decls.  But allow
+       consteval int fn () { return 42; }
+       constexpr auto r = ^^fn;
+     which is OK to do.  */
+  if (!non_constant_p && cxx_dialect >= cxx20 && !REFLECT_EXPR_P (r))
     if (tree immediate_fndecl
        = cp_walk_tree_without_duplicates (&r, find_immediate_fndecl,
                                           NULL))

This change seems redundant with the one in find_immediate_fndecl.

  tree ctx = decl_namespace_context (fndecl);
  if (!DECL_NAMESPACE_STD_META_P (ctx))
    return false;

This pattern seems to be the only use of DECL_NAMESPACE_STD_META_P, how about dropping that macro and instead defining decl_in_std_meta_p?

+/* Return true iff the next tokens start a splice-type-specifier.
+   If REQUIRE_TYPENAME_P, we only return true if there is a preceding
+   typename keyword.  */
+
+static bool
+cp_parser_next_tokens_start_splice_type_spec_p (cp_parser *parser,
+                                               bool require_typename_p)
+{
+  if (cp_lexer_next_token_is (parser->lexer, CPP_OPEN_SPLICE))
+    return !require_typename_p;
+  return (cp_lexer_next_token_is_keyword (parser->lexer, RID_TYPENAME)
+         && cp_lexer_nth_token_is (parser->lexer, 2, CPP_OPEN_SPLICE));
+}

Doesn't this need to handle the optional typename template [: case?

+/* Return true iff the next tokens start a splice-scope-specifier.  */
+
+static bool
+cp_parser_next_tokens_start_splice_scope_spec_p (cp_parser *parser)
+{
+  if (cp_lexer_next_token_is (parser->lexer, CPP_OPEN_SPLICE))
+    return true;
+  return (cp_lexer_next_token_is_keyword (parser->lexer, RID_TEMPLATE)
+         && cp_lexer_nth_token_is (parser->lexer, 2, CPP_OPEN_SPLICE));
+}

Since these tokens could also start a splice-expression, maybe this should be called "...can_start..."?

+static tree
+cp_parser_splice_type_specifier (cp_parser *parser)
+{
+  /* Consume the optional typename.  */
+  if (cp_lexer_next_token_is_keyword (parser->lexer, RID_TYPENAME))
+    cp_lexer_consume_token (parser->lexer);
+
+  tree type = cp_parser_splice_specifier (parser);
+
+  if (TREE_CODE (type) == TYPE_DECL)
+    type = TREE_TYPE (type);
+
+  /* When we see [:T:] or [:T:]<arg> we don't know what it'll turn out
+     to be.  */
+  if (dependent_splice_p (type))
+    return make_splice_scope (type, /*type_p=*/true);
+
+  if (!valid_splice_type_p (type))
+    {
+      cp_parser_error (parser, "reflection not usable in a splice type");
+      type = NULL_TREE;
+    }

It seems unfortunate not to print what the problematic reflection is here; maybe we can use a regular error (if !simulate) instead of cp_parser_error?

+cp_parser_splice_expression (cp_parser *parser, bool template_p,
+                            bool address_p, bool template_arg_p,
+                            bool member_access_p, cp_id_kind *idk)
+{
+  bool targs_p = false;
+
+  /* [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 (member_access_p)
+    push_deferring_access_checks (dk_no_check);
+
+  cp_expr expr = cp_parser_splice_specifier (parser, template_p, &targs_p);
+
+  if (member_access_p)
+    pop_deferring_access_checks ();

I don't understand messing with access around parsing the splice; where is the access check that would be affected?

+  if (template_p)
+    {
+      /* [expr.prim.splice] For a splice-expression of the form template
+        splice-specifier, the splice-specifier shall designate a function
+        template.  */
+      if (!targs_p)
+       {
+         if (!really_overloaded_fn (t))
+           {
+             auto_diagnostic_group d;
+             error_at (loc, "reflection not usable in a template splice");
+             inform (loc, "only function templates are allowed here");
+             return error_mark_node;
+           }
+       }
+      /* [expr.prim.splice] For a splice-expression of the form
+        template splice-specialization-specifier, the splice-specifier of the
+        splice-specialization-specifier shall designate a template.  */
+      else
+       {
+         if (really_overloaded_fn (t)
+             || get_template_info (t)
+             || TREE_CODE (t) == TEMPLATE_ID_EXPR)
+           /* OK */;
+         else
+           {
+             auto_diagnostic_group d;
+             error_at (loc, "reflection not usable in a template splice");
+             inform (loc, "only templates are allowed here");
+             return error_mark_node;
+           }
+       }
+    }
+  else if (/* No 'template' but there were template arguments?  */
+          targs_p
+          /* No 'template' but the splice-specifier designates a template?  */
+          || really_overloaded_fn (t))
+    {
+      auto_diagnostic_group d;
+      if (targs_p)
+       error_at (loc, "reflection not usable in a splice expression with "
+                 "template arguments");
+      else
+       error_at (loc, "reflection not usable in a splice expression");
+      location_t sloc = expr.get_start ();
+      rich_location richloc (line_table, sloc);
+      richloc.add_fixit_insert_before (sloc, "template ");
+      inform (&richloc, "add %<template%> to denote a template");
+      return error_mark_node;

These "reflection not usable" errors also need to print the reflection.

+  /* [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"  */
+  // TODO This should be moved to check_splice_expr.

TODO

+  if (outer_automatic_var_p (t))
+    {
+      auto_diagnostic_group d;
+      error_at (loc, "cannot splice local entity %qD for which there is an "
+               "intervening lambda expression", t);
+      inform (DECL_SOURCE_LOCATION (t), "%qD declared here", t);
+      return error_mark_node;
+    }

Also, outer_automatic_var_p doesn't necessarily mean there are any lambdas involved; we could be in a local class member function.

+  /* When doing foo.[: bar :], cp_parser_postfix_dot_deref_expression wants
+     to see an identifier or a TEMPLATE_ID_EXPR, if we have something like
+     s.template [: ^^S::var :]<int> where S::var is a variable template.  */
+  if (member_access_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);

It seems wrong to throw away the result of our earlier name lookup, can we instead improve ...dot_deref... to handle these forms as arguments?

+    {
+      /* 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.

+cp_parser_splice_scope_specifier (cp_parser *parser, bool typename_p,
+                                 bool template_p)
+{
+  bool targs_p = false;
+  cp_expr scope = cp_parser_splice_specifier (parser, template_p, &targs_p);
+  location_t loc = scope.get_location ();
+  if (TREE_CODE (scope) == TYPE_DECL)
+    scope = TREE_TYPE (scope);
+
+  if (template_p && !targs_p)
+    {
+      error_at (loc, "extra %<template%> in a scope splice");
+      return error_mark_node;
+    }
+  /* [expr.prim.id.qual] The template may only be omitted from the
+     form template(opt) splice-specialization-specifier :: when the
+     splice-specialization-specifier is preceded by typename.  */
+  if (targs_p && !typename_p)
+    {
+      // TODO add error
+    }

TODO

+  if (!valid_splice_scope_p (scope))
+    {
+      auto_diagnostic_group d;
+      error_at (loc, "reflection not usable in a splice scope");
+      if (TYPE_P (scope))
+       inform (loc, "%qT is not a class, namespace, or enumeration",
+               tree (scope));

Let's print the reflection even if it isn't a type.

+/* Skip tokens until a non-nested closing CLOSE_TOKEN is the next
+   token, or there are no more tokens.  Return true in the first case,
+   false otherwise.  */
+
+// TODO: use instead of cp_parser_skip_to_closing_brace and
+// cp_parser_skip_up_to_closing_square_bracket

Or perhaps you could enhance/use cp_parser_skip_balanced_tokens here instead of adding a new skip function?

+/* We know the next token is '[:' (optionally preceded by a template or
+   typename) and we are wondering if a '::' follows right after the
+   closing ':]', or after the possible '<...>' after the ':]'.  Return
+   true if yes, false otherwise.  */
+
+static bool
+cp_parser_splice_spec_is_nns_p (cp_parser *parser)

All the callers first establish that we're looking at a splice and then call this function; I'd think we could reduce that to a single call to cp_parser_nth_token_starts_splice_nns_p?

+  /* ??? It'd be nice to use saved_token_sentinel, but its rollback
+     uses cp_lexer_previous_token, but we may be the first token in the
+     file so there are no previous tokens.  Sigh.  */
+  cp_lexer_save_tokens (parser->lexer);

That sounds pretty simple to fix?

@ -7098,6 +7577,20 @@ cp_parser_unqualified_id (cp_parser* parser,
            if (cp_parser_parse_definitely (parser))
              done = true;
          }
+       /* Allow r.~typename [:R:].  */
+       else if (!done
+                && cp_parser_next_tokens_start_splice_type_spec_p
+                    (parser, /*require_typename_p=*/true))
+         {
+           parser->scope = object_scope;
+           parser->object_scope = NULL_TREE;
+           parser->qualifying_scope = NULL_TREE;
+           type_decl = cp_parser_splice_type_specifier (parser);
+           if (!type_decl)
+             return error_mark_node;
+           /* We don't have a TYPE_DECL, so return early.  */
+           return build_min_nt_loc (loc, BIT_NOT_EXPR, type_decl);

The comment seems to belong one line higher.

@@ -9037,15 +9565,32 @@ cp_parser_postfix_dot_deref_expression (cp_parser 
*parser,
         ordinary class member access expression, rather than a
         pseudo-destructor-name.  */
       bool template_p;
+      bool template_keyword_p = cp_parser_optional_template_keyword (parser);
       cp_token *token = cp_lexer_peek_token (parser->lexer);
-      /* Parse the id-expression.  */
-      name = (cp_parser_id_expression
-             (parser,
-              cp_parser_optional_template_keyword (parser),
-              /*check_dependency_p=*/true,
-              &template_p,
-              /*declarator_p=*/false,
-              /*optional_p=*/false));
+      /* See if there was this->[:R:].  Note that this->[: ^^S :]::i;
+        is not a splice-expression.  */

"Note that [in] this->.... [the rhs] is not a splice-expression."

+          [:^^B::fn:]()  // do not disable virtual dispatch
+          [:^^B:]::fn()  // disable virtual dispatch
+
+        so we check SPLICE_P.  */
+      if (parser->scope && !splice_p)
        *idk = CP_ID_KIND_QUALIFIED;

Hmm, it seems wrong for parser->scope to still be set in the former case.

@@ -9087,13 +9641,17 @@ cp_parser_postfix_dot_deref_expression (cp_parser 
*parser,
              parser->qualifying_scope = NULL_TREE;
              parser->object_scope = NULL_TREE;
            }
-         if (parser->scope && name && BASELINK_P (name))
+         if ((parser->scope || splice_p) && name && BASELINK_P (name))
            adjust_result_of_qualified_name_lookup
-             (name, parser->scope, scope);
+             (name,
+              /* For obj->[:^^R:] we won't have parser->scope, but we still
+                 have to perform this adjustment.  */
+              (splice_p ? BINFO_TYPE (BASELINK_BINFO (name)) : parser->scope),
+              scope);

I think BASELINK_ACCESS_BINFO would be more accurate.

Still more to come...

Jason

Reply via email to