https://gcc.gnu.org/g:5d9de7136b30b104606e85fc4284a25a8063a0c0
commit r16-7389-g5d9de7136b30b104606e85fc4284a25a8063a0c0 Author: Marek Polacek <[email protected]> Date: Mon Jan 26 14:51:02 2026 -0500 c++/reflection: splice parsing fixes [PR123823] 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 implementing [temp.names]/3 better: only attempt to parse a template argument list if we saw template or typename. As an extension, also parse a template argument list if the splice yielded a TEMPLATE_DECL -- in that case, chances are that the user simply forgot to specify 'template'. In that case we'll suggest adding 'template' in cp_parser_splice_expression. We should accept the following given [temp.names]/3: S<[:r:] < 43> s; and we should also accept: [:r:] < 42 > 0; 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): New typename_p parameter. Use cp_parser_nth_token_starts_template_argument_list_p instead of checking CPP_LESS. Check for typename/template/TEMPLATE_DECL before parsing a template-id. (cp_parser_splice_type_specifier): Adjust the call to cp_parser_splice_specifier. (cp_parser_splice_expression): Don't detect unparenthesized splice expressions here. Adjust the call to cp_parser_splice_specifier. (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_template_id): Call pop_deferring_access_checks. (cp_parser_template_argument): Detect unparenthesized splice expressions here. gcc/testsuite/ChangeLog: * g++.dg/reflect/crash6.C: Adjust expected diagnostic. * g++.dg/reflect/expr3.C: Likewise. Test more cases. * g++.dg/reflect/splice4.C: Adjust expected diagnostic. * g++.dg/reflect/error12.C: New test. * 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. * g++.dg/reflect/parse6.C: New test. Reviewed-by: Jason Merrill <[email protected]> Diff: --- gcc/cp/parser.cc | 96 ++++++++++++++++++++++++---------- gcc/testsuite/g++.dg/reflect/crash6.C | 2 +- gcc/testsuite/g++.dg/reflect/error12.C | 22 ++++++++ gcc/testsuite/g++.dg/reflect/expr3.C | 10 ++-- gcc/testsuite/g++.dg/reflect/parse1.C | 43 +++++++++++++++ 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 +++++ gcc/testsuite/g++.dg/reflect/parse6.C | 16 ++++++ gcc/testsuite/g++.dg/reflect/splice4.C | 4 +- 11 files changed, 292 insertions(+), 34 deletions(-) diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc index 5f1d028de0cc..986605e0cdc1 100644 --- a/gcc/cp/parser.cc +++ b/gcc/cp/parser.cc @@ -6125,11 +6125,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 +6171,18 @@ 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_nth_token_starts_template_argument_list_p (parser, 1) + /* As a courtesy to the user, if there is a < after a template + name, parse the construct as an s-s-s and warn about the missing + 'template'; it can't be anything else. */ + && (template_p || typename_p || TREE_CODE (expr) == TEMPLATE_DECL)) { /* For member access splice-specialization-specifier, try to wrap non-dependent splice for function template into a BASELINK so @@ -6222,7 +6234,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 +6284,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 +6365,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 +6438,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 +6483,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 +6507,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); @@ -21270,6 +21291,7 @@ cp_parser_template_id (cp_parser *parser, error_at (token->location, "%qT is not a template", templ); else error_at (token->location, "%qE is not a template", templ); + pop_deferring_access_checks (); return error_mark_node; } else @@ -21950,9 +21972,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) diff --git a/gcc/testsuite/g++.dg/reflect/crash6.C b/gcc/testsuite/g++.dg/reflect/crash6.C index 13ce48afcba1..a455a61cd65b 100644 --- a/gcc/testsuite/g++.dg/reflect/crash6.C +++ b/gcc/testsuite/g++.dg/reflect/crash6.C @@ -11,7 +11,7 @@ void f () { [:R:] r; // { dg-error "expected" } - [:R:]<int> r; // { dg-error "reflection .\\\[: R :\\\]<int>. not usable in a splice expression" } + [:R:]<int> r; // { dg-error "expected" } } void diff --git a/gcc/testsuite/g++.dg/reflect/error12.C b/gcc/testsuite/g++.dg/reflect/error12.C new file mode 100644 index 000000000000..7eb787f010d6 --- /dev/null +++ b/gcc/testsuite/g++.dg/reflect/error12.C @@ -0,0 +1,22 @@ +// PR c++/123823 +// { dg-do compile { target c++26 } } +// { dg-additional-options "-freflection" } + +constexpr int i = 42; +constexpr auto r = ^^i; +constexpr bool b0 = template [:r:] < 0 > 0; // { dg-error "not a template" } +constexpr bool b1 = template [:r:] < 0 < 0; // { dg-error "not a template|expected" } +constexpr bool b2 = template [:r:] < 43; // { dg-error "not a template|expected" } +constexpr bool b3 = template [:r:] <= 43; // { dg-error "template splice" } +constexpr bool b4 = template [:r:] > 41; // { dg-error "template splice" } +constexpr bool b5 = template [:r:] >= 41; // { dg-error "template splice" } +constexpr bool b6 = template [:r:] == 42; // { dg-error "template splice" } +constexpr bool b7 = template [:r:] != 41; // { dg-error "template splice" } + +template<bool> struct S { }; +S<template [:r:] < 43> s1; // { dg-error "not a template|invalid" } +S<template [:r:] <= 43> s2; // { dg-error "template splice|invalid" } +S<(template [:r:] > 41)> s3; // { dg-error "template splice|invalid" } +S<template [:r:] >= 41> s4; // { dg-error "template splice|invalid" } +S<template [:r:] == 42> s5; // { dg-error "template splice|invalid" } +S<template[:r:] != 41> s6; // { dg-error "template splice|invalid" } diff --git a/gcc/testsuite/g++.dg/reflect/expr3.C b/gcc/testsuite/g++.dg/reflect/expr3.C index 27295f1875a0..de7cffe1a562 100644 --- a/gcc/testsuite/g++.dg/reflect/expr3.C +++ b/gcc/testsuite/g++.dg/reflect/expr3.C @@ -31,15 +31,19 @@ g () int i6 = template [: ^^foo :](42); int i7 = [: ^^foo<int> :](42); int i8 = template [: ^^foo<int> :](42); // { dg-error "reflection .foo<int>. not usable in a template splice" } - int i9 = [: ^^foo :]<int>(42); // { dg-error "reflection .foo<int>. not usable in a splice expression with template arguments" } + int i9 = [: ^^foo :]<int>(42); // { dg-error "reflection .foo. not usable in a splice expression|expected" } int i10 = template [: ^^foo :]<int>(42); int i11 = template [: ^^bar :]<int>(42); // { dg-error "no matching function for call" } int i12 = [: ^^two :]<int>; // { dg-error "reflection .two<int>. not usable in a splice expression with template arguments" } int i13 = template [: ^^two :]<int>; [: ^^ST :]<int> c1; // { dg-error "reflection .ST<int>. not usable in a splice expression with template arguments" } - [: ^^S :]<int> c2; // { dg-error "not a template|reflection not usable in a splice expression with template arguments" } - [: ^^bar :]<int>(); // { dg-error "reflection .bar<int>. not usable in a splice expression with template arguments" } + typename [: ^^ST :]<int> c2; + template [: ^^ST :]<int> c3; // { dg-error "expected a reflection of an expression" } + [: ^^S :]<int> c4; // { dg-error "expected a reflection of an expression|expected primary-expression" } + template [: ^^S :]<int> c5; // { dg-error ".S. is not a template" } + typename [: ^^S :]<int> c6; // { dg-error ".S. is not a template|expected" } + [: ^^bar :]<int>(); // { dg-error "expected" } auto x1 = [: ^^ST :]<int>{}; // { dg-error "reflection .ST<int>. not usable in a splice expression with template arguments" } auto x2 = template [: ^^ST :]<int>{}; // { dg-error "expected a reflection of an expression" } diff --git a/gcc/testsuite/g++.dg/reflect/parse1.C b/gcc/testsuite/g++.dg/reflect/parse1.C new file mode 100644 index 000000000000..e66721465cfb --- /dev/null +++ b/gcc/testsuite/g++.dg/reflect/parse1.C @@ -0,0 +1,43 @@ +// 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); +static_assert ([:r:] < 43 > 0); +static_assert (!([:r:] < 42 > 0)); + +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 000000000000..504f87d92ccf --- /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 000000000000..c114a45475b8 --- /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>>(); +} + +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 000000000000..d4ac04f660ca --- /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 "expected" } + 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 000000000000..e5386b1958c8 --- /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 diff --git a/gcc/testsuite/g++.dg/reflect/parse6.C b/gcc/testsuite/g++.dg/reflect/parse6.C new file mode 100644 index 000000000000..ca9250f1710a --- /dev/null +++ b/gcc/testsuite/g++.dg/reflect/parse6.C @@ -0,0 +1,16 @@ +// { dg-do compile { target c++26 } } +// { dg-additional-options "-freflection" } + +using info = decltype(^^::); + +template <info R> +bool f() +{ + return [: R :]<42>0; +} + +int i; +int main() +{ + f<^^i>(); +} diff --git a/gcc/testsuite/g++.dg/reflect/splice4.C b/gcc/testsuite/g++.dg/reflect/splice4.C index 29567d2df35c..01ce764d6650 100644 --- a/gcc/testsuite/g++.dg/reflect/splice4.C +++ b/gcc/testsuite/g++.dg/reflect/splice4.C @@ -12,6 +12,6 @@ namespace M static_assert (template [: ^^M::foo :] <42> () == 42); static_assert (template [: members_of (^^M, std::meta::access_context::unchecked ())[0] :] <43> () == 43); int a = [: ^^M::foo :] <44> (); -// { dg-error "reflection 'M::foo<44>' not usable in a splice expression with template arguments" "" { target *-*-* } .-1 } +// { dg-error "reflection 'M::foo' not usable in a splice expression|expected" "" { target *-*-* } .-1 } int b = [: members_of (^^M, std::meta::access_context::unchecked ())[0] :] <45> (); -// { dg-error "reflection 'M::foo<45>' not usable in a splice expression with template arguments" "" { target *-*-* } .-1 } +// { dg-error "reflection 'M::foo' not usable in a splice expression|expected" "" { target *-*-* } .-1 }
