On 1/28/26 10:50 AM, Marek Polacek wrote:
Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?

-- >8 --
This patch fixes the problem that when cp_parser_splice_specifier sees
:]<, it always thinks it's a template-id.  Consequently, this should compile
but does not:

   int i;
   constexpr auto r = ^^i;
   bool b = [:r:] < 42;

because we think that a template argument list follows the splice.
Fixed by using the new cp_parser_next_token_starts_template_argument_list_p
which checks that we see the whole <...>.

But this turned out to be more complicated when I considered
splice-specialization-specifiers in a template-argument-list.  With this
patch we reject

   S<[:r:] < 43> s;

but I think that's fine given the footnote in [temp.names]: "A > that
encloses [...] the template-arguments of a subsequent template-id or
splice-specialization-specifier, is considered nested for the purpose
of this description."  (We can't just check !in_template_argument_list_p
because [:x:]<> can be valid in a template argument list.)

I think that's wrong under https://eel.is/c++draft/temp.names#3.1 since the splice is not in a type-only context and is not preceded by template or typename.

We should accept the above and not assume that the < starts a template-argument-list. It would still be helpful to check whether it could do so for -Wmissing-template-keyword.

I think that cp_parser_skip_entire_template_parameter_list has a bug
because it doesn't correctly handle >> in a template argument list;
see the xfail in parse3.C.

Sounds right.

I also realized that my code to detect unparenthesized splice
expressions as template arguments is wrong.  [expr.prim.splice] says that

   constexpr auto i = e<[:^^h:]>;

is ill-formed, but I think "S<[:r:] >= 1>" is fine.

Agreed, the normative citation is https://eel.is/c++draft/temp#names-6

So I moved the
checking to cp_parser_template_argument while making sure that we
only complain when the splice-expression is the whole template-argument.

Sounds good.

This patch also fixes 123640.

        PR c++/123823
        PR c++/123640

gcc/cp/ChangeLog:

        * parser.cc (cp_parser_splice_specifier): Use
        cp_parser_next_token_starts_template_argument_list_p instead of
        checking CPP_LESS.
        (cp_parser_splice_expression): Don't detect unparenthesized splice
        expressions here.
        (cp_parser_skip_entire_splice_expr): New, broken out of...
        (cp_parser_splice_spec_is_nns_p): ...this.
        (cp_parser_id_expression): Use
        cp_parser_next_token_starts_template_argument_list_p.
        (cp_parser_template_argument): Detect unparenthesized splice
        expressions here.
        (cp_parser_next_token_starts_template_argument_list_p): New.

gcc/testsuite/ChangeLog:

        * g++.dg/reflect/parse1.C: New test.
        * g++.dg/reflect/parse2.C: New test.
        * g++.dg/reflect/parse3.C: New test.
---
  gcc/cp/parser.cc                      | 116 ++++++++++++++++++--------
  gcc/testsuite/g++.dg/reflect/parse1.C |  41 +++++++++
  gcc/testsuite/g++.dg/reflect/parse2.C |  39 +++++++++
  gcc/testsuite/g++.dg/reflect/parse3.C |  59 +++++++++++++
  4 files changed, 219 insertions(+), 36 deletions(-)
  create mode 100644 gcc/testsuite/g++.dg/reflect/parse1.C
  create mode 100644 gcc/testsuite/g++.dg/reflect/parse2.C
  create mode 100644 gcc/testsuite/g++.dg/reflect/parse3.C

diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
index 6e310f2c0fd..5d1ec3b2220 100644
--- a/gcc/cp/parser.cc
+++ b/gcc/cp/parser.cc
@@ -3136,6 +3136,8 @@ static bool cp_parser_next_token_ends_template_argument_p
    (cp_parser *);
  static bool cp_parser_nth_token_starts_template_argument_list_p
    (cp_parser *, size_t);
+static bool cp_parser_next_token_starts_template_argument_list_p
+  (cp_parser *, cp_token ** = nullptr);
  static enum tag_types cp_parser_token_is_class_key
    (cp_token *);
  static enum tag_types cp_parser_token_is_type_parameter_key
@@ -6169,8 +6171,10 @@ cp_parser_splice_specifier (cp_parser *parser, bool 
template_p = false,
    /* Get the reflected operand.  */
    expr = splice (expr);
- /* If the next token is a '<', it's a splice-specialization-specifier. */
-  if (cp_lexer_next_token_is (parser->lexer, CPP_LESS))
+  /* If the next token is a '<', it could be a splice-specialization-specifier.
+     But we need to handle "[:r:] < 42" where the '<' doesn't start a template
+     argument list.  */
+  if (cp_parser_next_token_starts_template_argument_list_p (parser))
      {
        /* For member access splice-specialization-specifier, try to wrap
         non-dependent splice for function template into a BASELINK so
@@ -6351,14 +6355,6 @@ cp_parser_splice_expression (cp_parser *parser, bool 
template_p,
        return error_mark_node;
      }
- if (parser->in_template_argument_list_p
-      && !parser->greater_than_is_operator_p)
-    {
-      error_at (loc, "unparenthesized splice expression cannot be used as "
-               "a template argument");
-      return error_mark_node;
-    }
-
    /* Make sure this splice-expression produces an expression.  */
    if (!check_splice_expr (loc, expr.get_start (), t, address_p,
                          member_access_p, /*complain=*/true))
@@ -6476,28 +6472,23 @@ cp_parser_splice_scope_specifier (cp_parser *parser, 
bool typename_p,
    return scope;
  }
-/* 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.  */
+/* Skip the whole splice-expression: the optional typename/template,
+   [:...:], and also the <...>, if present.  Return true if we skipped
+   successfully.  */
static bool
-cp_parser_splice_spec_is_nns_p (cp_parser *parser)
+cp_parser_skip_entire_splice_expr (cp_parser *parser)
  {
-  /* ??? 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);
-
    if (cp_lexer_next_token_is_keyword (parser->lexer, RID_TYPENAME)
        || cp_lexer_next_token_is_keyword (parser->lexer, RID_TEMPLATE))
      cp_lexer_consume_token (parser->lexer);
- bool ok = false;
+  if (cp_lexer_next_token_is_not (parser->lexer, CPP_OPEN_SPLICE))
+    return false;
+
    size_t n = cp_parser_skip_balanced_tokens (parser, 1);
    if (n != 1)
      {
-      ok = true;
        /* Consume tokens up to the ':]' (including).  */
        for (n = n - 1; n; --n)
        cp_lexer_consume_token (parser->lexer);
@@ -6505,11 +6496,30 @@ cp_parser_splice_spec_is_nns_p (cp_parser *parser)
        /* Consume the whole '<....>', if present.  */
        if (cp_lexer_next_token_is (parser->lexer, CPP_LESS)
          && !cp_parser_skip_entire_template_parameter_list (parser))
-       ok = false;
+       return false;
- ok = ok && cp_lexer_next_token_is (parser->lexer, CPP_SCOPE);
+      return true;
      }
+ return false;
+}
+
+/* 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)
+{
+  /* ??? 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);
+
+  const bool ok = (cp_parser_skip_entire_splice_expr (parser)
+                  && cp_lexer_next_token_is (parser->lexer, CPP_SCOPE));
+
    /* Roll back the tokens we skipped.  */
    cp_lexer_rollback_tokens (parser->lexer);
@@ -7388,6 +7398,7 @@ cp_parser_id_expression (cp_parser *parser,
                                     optional_p);
      }
+ cp_token *next;
    if (id && TREE_CODE (id) == IDENTIFIER_NODE
        && warn_missing_template_keyword
        && !template_keyword_p
@@ -7400,19 +7411,13 @@ cp_parser_id_expression (cp_parser *parser,
        /* Don't confuse an ill-formed constructor declarator for a missing
         template keyword in a return type.  */
        && !(declarator_p && constructor_name_p (id, scope))
-      && cp_parser_nth_token_starts_template_argument_list_p (parser, 1)
        && warning_enabled_at (token->location,
-                            OPT_Wmissing_template_keyword))
-    {
-      saved_token_sentinel toks (parser->lexer, STS_ROLLBACK);
-      if (cp_parser_skip_entire_template_parameter_list (parser)
-         /* An operator after the > suggests that the > ends a
-            template-id; a name or literal suggests that the > is an
-            operator.  */
-         && (cp_lexer_peek_token (parser->lexer)->type
-             <= CPP_LAST_PUNCTUATOR))
-       missing_template_diag (token->location);
-    }
+                            OPT_Wmissing_template_keyword)
+      && cp_parser_next_token_starts_template_argument_list_p (parser, &next)
+      /* An operator after the > suggests that the > ends a template-id;
+        a name or literal suggests that the > is an operator.  */
+      && (next->type <= CPP_LAST_PUNCTUATOR))
+    missing_template_diag (token->location);
return id;
  }
@@ -21919,9 +21924,29 @@ cp_parser_template_argument (cp_parser* parser)
          && cp_lexer_next_token_is (parser->lexer, CPP_OPEN_BRACE))
        return cp_parser_braced_list (parser);
+ /* As per [expr.prim.splice], detect an unparenthesized
+        splice-expression used as a template argument.  But we have to
+        make sure that the splice is the whole template argument, not
+        just a subexpression of it.  */
+      cp_token *next = nullptr;
+      if (flag_reflection)
+       {
+         saved_token_sentinel toks (parser->lexer, STS_ROLLBACK);
+         if (cp_parser_skip_entire_splice_expr (parser))
+           next = cp_lexer_peek_token (parser->lexer);
+       }
+
        /* With C++17 generalized non-type template arguments we need to handle
         lvalue constant expressions, too.  */
        argument = cp_parser_assignment_expression (parser);
+      if (UNLIKELY (cp_lexer_peek_token (parser->lexer) == next)
+         && argument != error_mark_node)
+       {
+         loc = cp_lexer_peek_token (parser->lexer)->location;
+         error_at (loc, "unparenthesized splice expression cannot be used "
+                   "as a template argument");
+         return error_mark_node;
+       }
      }
if (!maybe_type_id)
@@ -37956,6 +37981,25 @@ cp_parser_nth_token_starts_template_argument_list_p 
(cp_parser * parser,
    return false;
  }
+/* Return true if the next token is a "<" and we can successfully find the
+   final ">"; i.e., the next tokens seem like a valid template-argument-list.
+   If NEXT is non-null, set it to the token following the "<...>".  */
+
+static bool
+cp_parser_next_token_starts_template_argument_list_p (cp_parser *parser,
+                                                     cp_token **next)
+{
+  saved_token_sentinel toks (parser->lexer, STS_ROLLBACK);
+  if (cp_parser_nth_token_starts_template_argument_list_p (parser, 1)
+      && cp_parser_skip_entire_template_parameter_list (parser))
+    {
+      if (next)
+       *next = cp_lexer_peek_token (parser->lexer);
+      return true;
+    }
+  return false;
+}
+
  /* Returns the kind of tag indicated by TOKEN, if it is a class-key,
     or none_type otherwise.  */
diff --git a/gcc/testsuite/g++.dg/reflect/parse1.C b/gcc/testsuite/g++.dg/reflect/parse1.C
new file mode 100644
index 00000000000..6a746fc8428
--- /dev/null
+++ b/gcc/testsuite/g++.dg/reflect/parse1.C
@@ -0,0 +1,41 @@
+// PR c++/123823
+// { dg-do compile { target c++26 } }
+// { dg-additional-options "-freflection" }
+
+constexpr int i = 42;
+constexpr auto r = ^^i;
+static_assert ([:r:] < 43);
+static_assert ([:r:] <= 43);
+static_assert ([:r:] > 41);
+static_assert ([:r:] >= 41);
+static_assert ([:r:] == 42);
+static_assert ([:r:] != 41);
+
+static_assert (43 > [:r:]);
+static_assert (43 >= [:r:]);
+static_assert (41 < [:r:]);
+static_assert (41 <= [:r:]);
+static_assert (42 == [:r:]);
+static_assert (41 != [:r:]);
+
+static_assert ([:r:] < 86 >> 1);
+
+template<bool>
+struct S;
+template<>
+struct S<true> { };
+
+S<[:r:] < 43> s1; // { dg-error ".i. is not a template|invalid" }
+S<[:r:] <= 43> s2;
+// [temp.names]/4 -> need the ().
+S<([:r:] > 41)> s3;
+S<[:r:] >= 41> s4;
+S<[:r:] == 42> s5;
+S<[:r:] != 41> s6;
+
+S<(43 > [:r:])> s7;
+S<43 >= [:r:]> s8;
+S<41 < [:r:]> s9;
+S<41 <= [:r:]> s10;
+S<42 == [:r:]> s11;
+S<41 != [:r:]> s12;
diff --git a/gcc/testsuite/g++.dg/reflect/parse2.C 
b/gcc/testsuite/g++.dg/reflect/parse2.C
new file mode 100644
index 00000000000..504f87d92cc
--- /dev/null
+++ b/gcc/testsuite/g++.dg/reflect/parse2.C
@@ -0,0 +1,39 @@
+// PR c++/123640
+// { dg-do compile { target c++26 } }
+// { dg-additional-options "-freflection" }
+
+#include <meta>
+
+template<class T>
+constexpr std::size_t field_count {
+  std::meta::nonstatic_data_members_of(^^T, 
std::meta::access_context::unchecked()).size()
+};
+
+template<std::size_t index, class T>
+constexpr std::meta::info field_at {
+  std::meta::nonstatic_data_members_of(^^T, 
std::meta::access_context::unchecked())[index]
+};
+
+struct S {
+  int a, b, c;
+  constexpr bool operator<(this auto&&l, auto&&r) noexcept
+  {
+    using T = std::remove_cvref_t<decltype (l)>;
+    static constexpr auto N{field_count<T>};
+    if constexpr (N == 0)
+      return false;
+    else
+      {
+       template for (constexpr auto i : std::make_index_sequence<N - 1>{}) // { dg-bogus 
"constant" "" { xfail *-*-* } }
+         if (l.[:field_at<i,T>:] != r.[:field_at<i,T>:]) [[likely]]
+           return l.[:field_at<i,T>:] < r.[:field_at<i,T>:];
+       return l.[:field_at<N - 1, T>:] < r.[:field_at<N - 1, T>:];
+      }
+  }
+};
+
+int
+main ()
+{
+  return S{1,2,3} < S{1,3,2};
+}
diff --git a/gcc/testsuite/g++.dg/reflect/parse3.C 
b/gcc/testsuite/g++.dg/reflect/parse3.C
new file mode 100644
index 00000000000..b2bb1d12bb3
--- /dev/null
+++ b/gcc/testsuite/g++.dg/reflect/parse3.C
@@ -0,0 +1,59 @@
+// PR c++/123823
+// { dg-do compile { target c++26 } }
+// { dg-additional-options "-freflection" }
+
+constexpr int i = 0;
+constexpr auto r = ^^i;
+
+template<auto U, auto>
+constexpr int S2 = [:U:];
+
+constexpr auto a1 = S2<[:^^r:],  // { dg-error "unparenthesized splice" }
+                       [:^^r:]>; // { dg-error "unparenthesized 
splice|invalid" }
+constexpr auto a2 = S2<([:^^r:]),
+                       [:^^r:]>; // { dg-error "unparenthesized 
splice|invalid" }
+constexpr auto a3 = S2<[:^^r:],  // { dg-error "unparenthesized splice" }
+                       ([:^^r:])>; // { dg-error "invalid" }
+constexpr auto a4 = S2<([:^^r:]),
+                       ([:^^r:])>;
+
+template<int>
+struct S { };
+
+template<typename>
+struct R { };
+
+template<typename T>
+constexpr int fn (T) { return 42; }
+
+constexpr int foo (int) { return 42; };
+
+S<[: ^^foo :](0)> s0;
+S<template [: ^^fn :](1)> s1;
+S<template [: ^^fn :](1) < 43> s2;
+S<(template [: ^^fn :](1) > 43)> s3;
+
+template<int N>
+constexpr auto var = N;
+S<[: ^^var<1> :]> s4; // { dg-error "unparenthesized splice|invalid" }
+S<([: ^^var<1> :])> s5;
+
+template<typename T>
+struct C {
+  static constexpr T t{};
+};
+
+template<typename T>
+void
+f ()
+{
+  S<template [: ^^C :]<T>::t>();
+  R<typename [: ^^C :]<int> >();
+  R<typename [: ^^C :]<int>>(); // { dg-bogus "invalid" "" { xfail *-*-* } }
+}
+
+void
+g ()
+{
+  f<int> ();
+}

base-commit: 04ec3d4712519e32f85c034ff4237fcd5f9fbe39

Reply via email to