On Wed, Jan 28, 2026 at 11:21:53AM +0800, Jason Merrill wrote: > 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.
Ah, fixed (by checking template_p and typename_p). We don't emit a -Wmissing-template-keyword warning but currently we say: note: add 'template' to denote a template although this may change if we downgrade the error to a pedwarn. > > 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. We have to make sure that in a template argument list we don't eat both > because the second > isn't spurious. I gave it a few minutes but my quick fix didn't work, so I'm leaving it for later. > > 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 Cool, I've added that reference to a comment in the patch. > > 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. 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. We should accept the following given [temp.names]/3: S<[:r:] < 43> s; To that effect, I'm checking !in_template_argument_list_p along with typename_p and template_p. The template_p check is tested in reflect/type8.C; the typename_p check is tested in reflect/parse3.C. 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. 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 "S<[:r:] >= 1>" is fine as per [temp.names]/6. 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. 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. New typename_p parameter. (cp_parser_splice_type_specifier): Adjust the call to cp_parser_splice_specifier. (cp_parser_splice_expression): Adjust the call to cp_parser_splice_specifier. Don't detect unparenthesized splice expressions here. (cp_parser_splice_scope_specifier): Adjust the call to cp_parser_splice_specifier. (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. * g++.dg/reflect/parse4.C: New test. * g++.dg/reflect/parse5.C: New test. --- gcc/cp/parser.cc | 132 ++++++++++++++++++-------- gcc/testsuite/g++.dg/reflect/parse1.C | 41 ++++++++ gcc/testsuite/g++.dg/reflect/parse2.C | 39 ++++++++ gcc/testsuite/g++.dg/reflect/parse3.C | 59 ++++++++++++ gcc/testsuite/g++.dg/reflect/parse4.C | 23 +++++ gcc/testsuite/g++.dg/reflect/parse5.C | 12 +++ 6 files changed, 266 insertions(+), 40 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 create mode 100644 gcc/testsuite/g++.dg/reflect/parse4.C create mode 100644 gcc/testsuite/g++.dg/reflect/parse5.C diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc index 2891856098c..27190b57cb3 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 @@ -6125,11 +6127,13 @@ cp_parser_next_tokens_can_start_splice_scope_spec_p (cp_parser *parser) splice-specifier < template-argument-list[opt] > TEMPLATE_P is true if we've parsed the leading template keyword. + TYPENAME_P is true if we've parsed the leading typename keyword or are + in a type-only context. TARGS_P is set to true if there is a splice-specialization-specifier. */ static cp_expr cp_parser_splice_specifier (cp_parser *parser, bool template_p = false, - bool *targs_p = nullptr) + bool typename_p = false, bool *targs_p = nullptr) { /* Get the location of the '[:'. */ location_t start_loc = cp_lexer_peek_token (parser->lexer)->location; @@ -6169,8 +6173,15 @@ 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. [temp.names]/3: A < is interpreted as the delimiter of + a template-argument-list if either + -- it follows a splice-specifier that either + -- appears in a type-only context or + -- is preceded by template or typename. */ + if (cp_parser_next_token_starts_template_argument_list_p (parser) + && (!parser->in_template_argument_list_p || template_p || typename_p)) { /* For member access splice-specialization-specifier, try to wrap non-dependent splice for function template into a BASELINK so @@ -6222,7 +6233,8 @@ cp_parser_splice_type_specifier (cp_parser *parser) if (cp_lexer_next_token_is_keyword (parser->lexer, RID_TYPENAME)) cp_lexer_consume_token (parser->lexer); - cp_expr expr = cp_parser_splice_specifier (parser); + cp_expr expr = cp_parser_splice_specifier (parser, /*template_p=*/false, + /*typename_p=*/true); const location_t loc = (expr != error_mark_node ? expr.get_location () : input_location); tree type = expr.get_value (); @@ -6271,7 +6283,8 @@ cp_parser_splice_expression (cp_parser *parser, bool template_p, parser->object_scope = NULL_TREE; parser->qualifying_scope = NULL_TREE; - cp_expr expr = cp_parser_splice_specifier (parser, template_p, &targs_p); + cp_expr expr = cp_parser_splice_specifier (parser, template_p, + /*typename_p=*/false, &targs_p); /* And don't leave the scopes set, either. */ parser->scope = NULL_TREE; @@ -6351,14 +6364,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)) @@ -6432,7 +6437,8 @@ 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); + cp_expr scope = cp_parser_splice_specifier (parser, template_p, typename_p, + &targs_p); const location_t loc = scope.get_location (); if (TREE_CODE (scope) == TYPE_DECL) scope = TREE_TYPE (scope); @@ -6476,28 +6482,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 +6506,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 +7408,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 +7421,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; } @@ -21930,9 +21945,27 @@ cp_parser_template_argument (cp_parser* parser) && cp_lexer_next_token_is (parser->lexer, CPP_OPEN_BRACE)) return cp_parser_braced_list (parser); + /* [temp.names]/6: The constant-expression of a template-argument + shall not be an unparenthesized splice-expression. */ + 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) @@ -37967,6 +38000,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..174d0a682db --- /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; +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> (); +} diff --git a/gcc/testsuite/g++.dg/reflect/parse4.C b/gcc/testsuite/g++.dg/reflect/parse4.C new file mode 100644 index 00000000000..24dd904e1f4 --- /dev/null +++ b/gcc/testsuite/g++.dg/reflect/parse4.C @@ -0,0 +1,23 @@ +// { dg-do compile { target c++26 } } +// { dg-additional-options "-freflection" } +// From [temp.names]. + +using size_t = decltype(sizeof(int)); +using info = decltype(^^void); + +struct X { + template<size_t> X* alloc(); + template<size_t> static X* adjust(); +}; +template<class T> void f(T* p) { + T* p1 = p->alloc<200>(); // { dg-error "expected" } + // { dg-warning "expected .template. keyword before dependent template name" "" { target *-*-* } .-1 } + T* p2 = p->template alloc<200>(); // OK, < starts template argument list + T::adjust<100>(); // { dg-error "expected" } + // { dg-warning "expected .template. keyword before dependent template name" "" { target *-*-* } .-1 } + T::template adjust<100>(); // OK, < starts template argument list + + static constexpr info r = ^^T::adjust; + T* p3 = [:r:]<200>(); // { dg-error "splice expression" } + T* p4 = template [:r:]<200>(); // OK, < starts template argument list +} diff --git a/gcc/testsuite/g++.dg/reflect/parse5.C b/gcc/testsuite/g++.dg/reflect/parse5.C new file mode 100644 index 00000000000..e5386b1958c --- /dev/null +++ b/gcc/testsuite/g++.dg/reflect/parse5.C @@ -0,0 +1,12 @@ +// { dg-do compile { target c++26 } } +// { dg-additional-options "-freflection" } +// From [temp.names]. + +using info = decltype(^^void); + +template<int> struct S { }; +constexpr int k = 5; +constexpr info r = ^^k; +S<[:r:]> s1; // { dg-error "unparenthesized splice|invalid" } +S<([:r:])> s2; // OK +S<[:r:] + 1> s3; // OK base-commit: 92f976fc2a87a867f70921b577f7c79acd214eaf -- 2.52.0
