[Patch][v2] C++: Handle OpenMP/OpenACC array sections [: / :] with C++26
Tobias Burnus <[email protected]> Wed, 5 Aug 2026 18:50:25 +0200
| Newsgroups | gmane.comp.gcc.patches |
|---|---|
| Message-ID | <[email protected]> |
Jason Merrill wrote: > On 7/31/26 7:40 AM, Tobias Burnus wrote: > In unchanged code: > >> if (for_offsetof) >> index = cp_parser_constant_expression (parser); >> else if (!parser->omp_array_section_p >> || cp_lexer_next_token_is_not (parser->lexer, CPP_COLON)) > Don't you need to check CPP_CLOSE_SPLICE here? Yes. — I thought I had all possible variants in as testcase, but I missed that one. I have now added many more testcases – including invalid ones. (Esp. '[ : : ]', i.e. a tailing ':' which becomes only valid for strided arrays; those are only valid in 'target update' and strides are still unsupported on mainline.) With the additional tests, one runs also in some other issues you raised. >> @@ -9469,6 +9502,9 @@ cp_parser_postfix_open_square_expression >> (cp_parser *parser, >> cp_parser_skip_to_closing_square_bracket (parser); > Not necessary, but I wonder if we want to do anything to > ...skip_to_...bracket to handle CPP_CLOSE_SPLICE? Good question. The problem is that there can be both split specifier and array section use like in '[ [: ... :] ]'. Depending where one is with the parser, one might want to handle ':]' on equal footing or not – And if ... fails, do we want to skip to the outer ']' or to the inner ':]'? I have now implemented a variant, which hopefully more often makes sense than not. >> @@ -41882,25 +41919,34 @@ cp_parser_omp_var_list_no_open (cp_parser >> *parser, enum omp_clause_code kind,> if (!colon) >> parser->colon_corrects_to_scope_p >> = saved_colon_corrects_to_scope_p; > Not relevant to this patch, but I wonder why most of the handling of > colon_corrects_to_scope_p in this function is conditional on whether > the caller passed a pointer to the 'colon' parameter? I think it is fine at the end of the function, but, here, it seems to make more sense to reset it unconditionally as it is also set unconditionally. Thus, I have done so. Thanks for the review! Any comments to the revised version? Tobias
omp-cxx26-v2.diff
(text/x-patch, 27.7 KB)
C++: Handle OpenMP/OpenACC array sections [: / :] with C++26
OpenMP and OpenACC have array sections of the form '[ lower : size ]' where
the lower bound and/or the size bound can be left out, i.e. '[:n]', '[idx:]',
and '[:]'. However, since C++26, '[:' and ':]' are parsed as CPP_OPEN_SPLICE
and CPP_CLOSE_SPLICE, respectively - to implement the splicer as added
for reflections (see https://wg21.link/P2996).
At least as currently specified in C++ and in OpenMP/OpenACC, there should
be no ambiguity between splicing and array sections. Therefore, this
commit just handles the new token types to process OpenMP/OpenACC array
sections as it did in C++ until C++23.
On the OpenMP specification side, this and related ':' issues are tracked
in Issue 4740 - and, on the OpenACC side, in Issue 557.
Note: This commit permits to revert the commit
r16-5330-gedc821b60ce94b OpenMP/OpenACC tests. vs C++26
that is planned to be done as follow up.
The added/modified testcases ensure that also '[:' etc. is tested before
the revert - and adds some '[ :' tests for C++29 for after the revert.
After the revert, the hope is that -std= < C++26 and >= C++26 will together
span the full testspace, even if with C++26 not all OPEN_SQUARE + COLON
cases are tested for.
gcc/cp/ChangeLog:
* parser.cc (cp_parser_postfix_expression,
cp_parser_postfix_open_square_expression):
cp_parser_skip_up_to_closing_square_bracket,
cp_parser_omp_var_list_no_open): Handle OpenMP/OpenACC array
sections that are lexed since C++26 as CPP_OPEN_SPLICE and
CPP_CLOSE_SPLICE.
gcc/testsuite/ChangeLog:
* c-c++-common/gomp/affinity-2.c: Also run with C++ > 23
and add some '[ :' and ': ]' tests.
* g++.dg/gomp/allocate-3.C: Remove 'dg-skip-if' for c++26.
* g++.dg/goacc/cache-4.C: New test.
* g++.dg/gomp/array-section-3.C: New test.
* g++.dg/gomp/array-section-4.C: New test.
* g++.dg/gomp/array-section-5.C: New test.
* g++.dg/gomp/array-section-6.C: New test.
gcc/cp/parser.cc | 116 ++++++++++++++++++++++-----
gcc/testsuite/c-c++-common/gomp/affinity-2.c | 12 ++-
gcc/testsuite/g++.dg/goacc/cache-4.C | 12 +++
gcc/testsuite/g++.dg/gomp/allocate-3.C | 4 -
gcc/testsuite/g++.dg/gomp/array-section-3.C | 44 ++++++++++
gcc/testsuite/g++.dg/gomp/array-section-4.C | 43 ++++++++++
gcc/testsuite/g++.dg/gomp/array-section-5.C | 96 ++++++++++++++++++++++
gcc/testsuite/g++.dg/gomp/array-section-6.C | 85 ++++++++++++++++++++
8 files changed, 387 insertions(+), 25 deletions(-)
diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
index 85c5030acbb..9c0984a3384 100644
--- a/gcc/cp/parser.cc
+++ b/gcc/cp/parser.cc
@@ -8968,6 +8968,22 @@ cp_parser_postfix_expression (cp_parser *parser, bool address_p, bool cast_p,
switch (token->type)
{
+ case CPP_OPEN_SPLICE:
+ if (!parser->omp_array_section_p)
+ goto default_case;
+ /* Parse '[: length]' array section. */
+ postfix_expression
+ = cp_parser_postfix_open_square_expression (parser,
+ postfix_expression,
+ false,
+ decltype_p);
+ postfix_expression.set_range (start_loc,
+ postfix_expression.get_location ());
+
+ idk = CP_ID_KIND_NONE;
+ is_member_access = false;
+ break;
+
case CPP_OPEN_SQUARE:
if (cp_next_tokens_can_be_std_attribute_p (parser))
{
@@ -9243,6 +9259,7 @@ cp_parser_postfix_expression (cp_parser *parser, bool address_p, bool cast_p,
break;
default:
+ default_case:
if (pidk_return != NULL)
* pidk_return = idk;
if (member_access_only_p)
@@ -9312,7 +9329,12 @@ cp_parser_parenthesized_expression_list_elt (cp_parser *parser, bool cast_p,
postfix-expression [ expression-list[opt] ] (C++23)
FOR_OFFSETOF is set if we're being called in that context, which
- changes how we deal with integer constant expressions. */
+ changes how we deal with integer constant expressions.
+
+ With parser->omp_array_section_p, it also handles OpenMP
+ array sections of the type [ index : length ] where both
+ index and length are optional. Note that an absent index
+ might be lexed as CPP_OPEN_SPLICE ('[:') since C++26. */
static tree
cp_parser_postfix_open_square_expression (cp_parser *parser,
@@ -9326,7 +9348,9 @@ cp_parser_postfix_open_square_expression (cp_parser *parser,
bool saved_greater_than_is_operator_p;
bool saved_colon_corrects_to_scope_p;
- /* Consume the `[' token. */
+ bool open_splice = cp_lexer_next_token_is (parser->lexer, CPP_OPEN_SPLICE);
+
+ /* Consume the `[' token - or with open_splice the '[:' token. */
cp_lexer_consume_token (parser->lexer);
saved_greater_than_is_operator_p = parser->greater_than_is_operator_p;
@@ -9336,6 +9360,9 @@ cp_parser_postfix_open_square_expression (cp_parser *parser,
if (parser->omp_array_section_p)
parser->colon_corrects_to_scope_p = false;
+ if (open_splice)
+ goto post_colon_parsing;
+
/* Parse the index expression. */
/* ??? For offsetof, there is a question of what to allow here. If
offsetof is not being used in an integral constant expression context,
@@ -9347,7 +9374,8 @@ cp_parser_postfix_open_square_expression (cp_parser *parser,
if (for_offsetof)
index = cp_parser_constant_expression (parser);
else if (!parser->omp_array_section_p
- || cp_lexer_next_token_is_not (parser->lexer, CPP_COLON))
+ || (cp_lexer_next_token_is_not (parser->lexer, CPP_COLON)
+ && cp_lexer_next_token_is_not (parser->lexer, CPP_CLOSE_SPLICE)))
{
if (cxx_dialect >= cxx23
&& cp_lexer_next_token_is (parser->lexer, CPP_CLOSE_SQUARE))
@@ -9414,6 +9442,7 @@ cp_parser_postfix_open_square_expression (cp_parser *parser,
/*warn_comma_p=*/warn_comma_subscript);
}
+post_colon_parsing:
parser->greater_than_is_operator_p = saved_greater_than_is_operator_p;
if (cxx_dialect >= cxx23
@@ -9425,12 +9454,25 @@ cp_parser_postfix_open_square_expression (cp_parser *parser,
"section");
index = error_mark_node;
}
+
if (parser->omp_array_section_p
- && cp_lexer_next_token_is (parser->lexer, CPP_COLON))
+ && (open_splice
+ || cp_lexer_next_token_is (parser->lexer, CPP_COLON)
+ || cp_lexer_next_token_is (parser->lexer, CPP_CLOSE_SPLICE)))
{
- cp_lexer_consume_token (parser->lexer);
tree length = NULL_TREE;
- if (cp_lexer_next_token_is_not (parser->lexer, CPP_CLOSE_SQUARE))
+ bool close_splice = cp_lexer_next_token_is (parser->lexer,
+ CPP_CLOSE_SPLICE);
+ if (!open_splice)
+ cp_lexer_consume_token (parser->lexer);
+ if (open_splice && close_splice)
+ {
+ cp_parser_required_error (parser, RT_CLOSE_SQUARE, /*keyword=*/false,
+ UNKNOWN_LOCATION);
+ length = error_mark_node;
+ }
+ else if (cp_lexer_next_token_is_not (parser->lexer, CPP_CLOSE_SQUARE)
+ && !close_splice)
{
if (cxx_dialect >= cxx23)
{
@@ -9469,7 +9511,7 @@ cp_parser_postfix_open_square_expression (cp_parser *parser,
cp_parser_skip_to_closing_square_bracket (parser);
return error_mark_node;
}
- else
+ else if (!close_splice)
cp_parser_require (parser, CPP_CLOSE_SQUARE, RT_CLOSE_SQUARE);
return grok_omp_array_section (input_location, postfix_expression, index,
@@ -29212,10 +29254,19 @@ cp_parser_skip_up_to_closing_square_bracket (cp_parser *parser)
/* If we've run out of tokens, then there is no closing `]'. */
return false;
+ case CPP_OPEN_SPLICE:
+ if (!parser->omp_array_section_p)
+ break;
+ /* FALLTHRU */
+
case CPP_OPEN_SQUARE:
++square_depth;
break;
+ case CPP_CLOSE_SPLICE:
+ if (!parser->omp_array_section_p)
+ break;
+ /* FALLTHRU */
case CPP_CLOSE_SQUARE:
if (!square_depth--)
return true;
@@ -41851,7 +41902,8 @@ cp_parser_omp_var_list_no_open (cp_parser *parser, enum omp_clause_code kind,
case OMP_CLAUSE__CACHE_:
/* The OpenACC cache directive explicitly only allows "array
elements or subarrays". */
- if (cp_lexer_peek_token (parser->lexer)->type != CPP_OPEN_SQUARE)
+ if (!cp_lexer_next_token_is (parser->lexer, CPP_OPEN_SQUARE)
+ && !cp_lexer_next_token_is (parser->lexer, CPP_OPEN_SPLICE))
{
error_at (token->location, "expected %<[%>");
decl = error_mark_node;
@@ -41886,25 +41938,44 @@ cp_parser_omp_var_list_no_open (cp_parser *parser, enum omp_clause_code kind,
case OMP_CLAUSE_HAS_DEVICE_ADDR:
array_section_p = false;
dims.truncate (0);
- while (cp_lexer_next_token_is (parser->lexer, CPP_OPEN_SQUARE))
+ while (cp_lexer_next_token_is (parser->lexer, CPP_OPEN_SQUARE)
+ || cp_lexer_next_token_is (parser->lexer, CPP_OPEN_SPLICE))
{
location_t loc = UNKNOWN_LOCATION;
tree low_bound = NULL_TREE, length = NULL_TREE;
bool no_colon = false;
-
+ bool open_splice = cp_lexer_next_token_is (parser->lexer,
+ CPP_OPEN_SPLICE);
parser->colon_corrects_to_scope_p = false;
cp_lexer_consume_token (parser->lexer);
- if (!cp_lexer_next_token_is (parser->lexer, CPP_COLON))
+ bool close_splice = cp_lexer_next_token_is (parser->lexer,
+ CPP_CLOSE_SPLICE);
+ if (open_splice && close_splice)
+ {
+ cp_parser_required_error (parser, RT_CLOSE_SQUARE,
+ /*keyword=*/false, loc);
+ if ((kind == OMP_CLAUSE_DEPEND
+ || kind == OMP_CLAUSE_AFFINITY)
+ && cp_parser_simulate_error (parser))
+ goto depend_lvalue;
+ goto skip_comma;
+ }
+ if (!open_splice
+ && !close_splice
+ && !cp_lexer_next_token_is (parser->lexer, CPP_COLON))
{
loc = cp_lexer_peek_token (parser->lexer)->location;
low_bound = cp_parser_expression (parser);
/* Later handling is not prepared to see through these. */
gcc_checking_assert (!location_wrapper_p (low_bound));
+ close_splice = cp_lexer_next_token_is (parser->lexer,
+ CPP_CLOSE_SPLICE);
}
- if (!colon)
- parser->colon_corrects_to_scope_p
- = saved_colon_corrects_to_scope_p;
- if (cp_lexer_next_token_is (parser->lexer, CPP_CLOSE_SQUARE))
+ parser->colon_corrects_to_scope_p
+ = saved_colon_corrects_to_scope_p;
+ if (!open_splice
+ && cp_lexer_next_token_is (parser->lexer,
+ CPP_CLOSE_SQUARE))
{
length = integer_one_node;
no_colon = true;
@@ -41912,7 +41983,9 @@ cp_parser_omp_var_list_no_open (cp_parser *parser, enum omp_clause_code kind,
else
{
/* Look for `:'. */
- if (!cp_parser_require (parser, CPP_COLON, RT_COLON))
+ if (!open_splice
+ && !close_splice
+ && !cp_parser_require (parser, CPP_COLON, RT_COLON))
{
if ((kind == OMP_CLAUSE_DEPEND || kind == OMP_CLAUSE_AFFINITY)
&& cp_parser_simulate_error (parser))
@@ -41923,8 +41996,9 @@ cp_parser_omp_var_list_no_open (cp_parser *parser, enum omp_clause_code kind,
cp_parser_commit_to_tentative_parse (parser);
else
array_section_p = true;
- if (!cp_lexer_next_token_is (parser->lexer,
- CPP_CLOSE_SQUARE))
+ if (!close_splice
+ && !cp_lexer_next_token_is (parser->lexer,
+ CPP_CLOSE_SQUARE))
{
length = cp_parser_expression (parser);
/* Later handling is not prepared to see through these. */
@@ -41932,8 +42006,10 @@ cp_parser_omp_var_list_no_open (cp_parser *parser, enum omp_clause_code kind,
}
}
/* Look for the closing `]'. */
- if (!cp_parser_require (parser, CPP_CLOSE_SQUARE,
- RT_CLOSE_SQUARE))
+ if (close_splice)
+ cp_lexer_consume_token (parser->lexer);
+ else if (!cp_parser_require (parser, CPP_CLOSE_SQUARE,
+ RT_CLOSE_SQUARE))
{
if ((kind == OMP_CLAUSE_DEPEND || kind == OMP_CLAUSE_AFFINITY)
&& cp_parser_simulate_error (parser))
diff --git a/gcc/testsuite/c-c++-common/gomp/affinity-2.c b/gcc/testsuite/c-c++-common/gomp/affinity-2.c
index 78bd21d1a6f..9a2d678bc10 100644
--- a/gcc/testsuite/c-c++-common/gomp/affinity-2.c
+++ b/gcc/testsuite/c-c++-common/gomp/affinity-2.c
@@ -1,4 +1,4 @@
-/* { dg-do compile { target { c || c++23_down } } } */
+/* { dg-do compile } */
/* { dg-options "-fopenmp" } */
extern int a[][10], a2[][10];
@@ -32,14 +32,24 @@ foo (int g[3][10], int h[4][8], int i[2][10], int j[][9],
;
#pragma omp task affinity( b[-1:]) /* { dg-error "negative low bound in array section" } */
;
+ #pragma omp task affinity( b[-1: ]) /* { dg-error "negative low bound in array section" } */
+ ;
#pragma omp task affinity( c[:-3][1:1]) /* { dg-error "negative length in array section" } */
;
+ #pragma omp task affinity( c[ :-3][1:1]) /* { dg-error "negative length in array section" } */
+ ;
+ #pragma omp task affinity( d[11: ]) /* { dg-error "low bound \[^\n\r]* above array section size" } */
+ ;
#pragma omp task affinity( d[11:]) /* { dg-error "low bound \[^\n\r]* above array section size" } */
;
#pragma omp task affinity( e[:11]) /* { dg-error "length \[^\n\r]* above array section size" } */
;
#pragma omp task affinity( f[1:10]) /* { dg-error "high bound \[^\n\r]* above array section size" } */
;
+ #pragma omp task affinity( g[: ][2:4]) /* { dg-error "for array function parameter length expression must be specified" } */
+ ;
+ #pragma omp task affinity( g[ :][2:4]) /* { dg-error "for array function parameter length expression must be specified" } */
+ ;
#pragma omp task affinity( g[:][2:4]) /* { dg-error "for array function parameter length expression must be specified" } */
;
#pragma omp task affinity( h[2:2][-1:]) /* { dg-error "negative low bound in array section" } */
diff --git a/gcc/testsuite/g++.dg/goacc/cache-4.C b/gcc/testsuite/g++.dg/goacc/cache-4.C
new file mode 100644
index 00000000000..9c6ca229501
--- /dev/null
+++ b/gcc/testsuite/g++.dg/goacc/cache-4.C
@@ -0,0 +1,12 @@
+int l[10];
+
+void
+foo ()
+{
+ #pragma acc cache(l) /* { dg-error "expected '\\\['" } */
+ ;
+ #pragma acc cache(l[:7.5f]) /* { dg-error "length \[^\n\r]* of array section does not have integral type" } */
+ ;
+ #pragma acc cache(l[ :7.5f]) /* { dg-error "length \[^\n\r]* of array section does not have integral type" } */
+ ;
+}
diff --git a/gcc/testsuite/g++.dg/gomp/allocate-3.C b/gcc/testsuite/g++.dg/gomp/allocate-3.C
index 0303b55fa25..e778314f07e 100644
--- a/gcc/testsuite/g++.dg/gomp/allocate-3.C
+++ b/gcc/testsuite/g++.dg/gomp/allocate-3.C
@@ -1,7 +1,3 @@
-// Array sections without spaces between [ and : or : and ] are incompatible
-// with C++26.
-// { dg-skip-if "array sections vs. C++26" { c++26 } }
-
template <typename T>
void
foo (T &x, T (&y)[4], T *&z, int &u, int (&v)[4], int *&w)
diff --git a/gcc/testsuite/g++.dg/gomp/array-section-3.C b/gcc/testsuite/g++.dg/gomp/array-section-3.C
new file mode 100644
index 00000000000..170405fba87
--- /dev/null
+++ b/gcc/testsuite/g++.dg/gomp/array-section-3.C
@@ -0,0 +1,44 @@
+// { dg-additional-options "-fdump-tree-original" }
+
+// Check that OpenMP array sections with [: and :] do not get mixed up with C++26's splice specifier,
+// used for reflection.
+//
+// See also OpenMP spec Issue 4740.
+
+void f() {
+ char a1[4], a2[4], a3[4], b[5];
+ char c1[4], c2[4], c3[4], c4[4];
+
+ #pragma omp target map(to: a1[:], a2[:2], a3[2:], b[1:2], c1[ : ], c2[ : 2], c3[2: ], c4[ :])
+ ;
+
+ #pragma omp task affinity(a1[:])
+ ;
+ #pragma omp task affinity(a2[:2])
+ ;
+ #pragma omp task affinity(a3[2:])
+ ;
+ #pragma omp task affinity(b[1:2])
+ ;
+ #pragma omp task affinity(c1[ : ])
+ ;
+ #pragma omp task affinity(c2[ : 2])
+ ;
+ #pragma omp task affinity(c3[2: ])
+ ;
+ #pragma omp task affinity(c4[ :])
+ ;
+
+}
+
+// { dg-final { scan-tree-dump "#pragma omp target.* map\\(to:c4\\\[0\\\] \\\[len: 4\\\]\\).* map\\(to:c3\\\[2\\\] \\\[len: 2\\\]\\).* map\\(to:c2\\\[0\\\] \\\[len: 2\\\]\\).* map\\(to:c1\\\[0\\\] \\\[len: 4\\\]\\).* map\\(to:b\\\[1\\\] \\\[len: 2\\\]\\).* map\\(to:a3\\\[2\\\] \\\[len: 2\\\]\\).* map\\(to:a2\\\[0\\\] \\\[len: 2\\\]\\).* map\\(to:a1\\\[0\\\] \\\[len: 4\\\]\\)" "original" } }
+
+// { dg-final { scan-tree-dump "#pragma omp task affinity\\(a1\\\[0\\\]\\)" "original" } }
+// { dg-final { scan-tree-dump "#pragma omp task affinity\\(a2\\\[0\\\]\\)" "original" } }
+// { dg-final { scan-tree-dump "#pragma omp task affinity\\(a3\\\[2\\\]\\)" "original" } }
+// { dg-final { scan-tree-dump "#pragma omp task affinity\\(b\\\[1\\\]\\)" "original" } }
+// { dg-final { scan-tree-dump "#pragma omp task affinity\\(c1\\\[0\\\]\\)" "original" } }
+// { dg-final { scan-tree-dump "#pragma omp task affinity\\(c2\\\[0\\\]\\)" "original" } }
+// { dg-final { scan-tree-dump "#pragma omp task affinity\\(c3\\\[2\\\]\\)" "original" } }
+// { dg-final { scan-tree-dump "#pragma omp task affinity\\(c4\\\[0\\\]\\)" "original" } }
+
diff --git a/gcc/testsuite/g++.dg/gomp/array-section-4.C b/gcc/testsuite/g++.dg/gomp/array-section-4.C
new file mode 100644
index 00000000000..2ece1ac9e61
--- /dev/null
+++ b/gcc/testsuite/g++.dg/gomp/array-section-4.C
@@ -0,0 +1,43 @@
+// { dg-do compile { target c++26 } }
+// { dg-additional-options "-freflection -fdump-tree-original" }
+
+void f()
+{
+ constexpr int a = 42;
+ constexpr auto b = ^^a;
+ int c[128];
+
+ #pragma omp target enter data map(to : c[[:b:]])
+
+ #pragma omp target enter data map(to : c[[:b:]:])
+
+ #pragma omp target enter data map(to : c[:[:b:]])
+
+ #pragma omp target enter data map(to : c[[:b:]:2])
+
+ #pragma omp target enter data map(to : c[2:[:b:]])
+
+
+ #pragma omp task affinity( c[[:b:]])
+ ;
+
+ #pragma omp task affinity( c[[:b:]:2])
+ ;
+ #pragma omp task affinity( c[2:[:b:]])
+ ;
+
+ #pragma omp task affinity( c[[:b:]:])
+ ;
+ #pragma omp task affinity( c[:[:b:]])
+ ;
+}
+
+// { dg-final { scan-tree-dump-times "#pragma omp target enter data map\\(to:c\\\[42\\\] \\\[len: 4\\\]\\)" 1 "original" } }
+// { dg-final { scan-tree-dump-times "#pragma omp target enter data map\\(to:c\\\[42\\\] \\\[len: 344\\\]\\)" 1 "original" } }
+// { dg-final { scan-tree-dump-times "#pragma omp target enter data map\\(to:c\\\[0\\\] \\\[len: 168\\\]\\)" 1 "original" } }
+// { dg-final { scan-tree-dump-times "#pragma omp target enter data map\\(to:c\\\[42\\\] \\\[len: 8\\\]\\)" 1 "original" } }
+// { dg-final { scan-tree-dump-times "#pragma omp target enter data map\\(to:c\\\[2\\\] \\\[len: 168\\\]\\)" 1 "original" } }
+
+// { dg-final { scan-tree-dump-times "#pragma omp task affinity\\(c\\\[42\\\]\\)" 3 "original" } }
+// { dg-final { scan-tree-dump-times "#pragma omp task affinity\\(c\\\[2\\\]\\)" 1 "original" } }
+// { dg-final { scan-tree-dump-times "#pragma omp task affinity\\(c\\\[0\\\]\\)" 1 "original" } }
diff --git a/gcc/testsuite/g++.dg/gomp/array-section-5.C b/gcc/testsuite/g++.dg/gomp/array-section-5.C
new file mode 100644
index 00000000000..80075e6dabf
--- /dev/null
+++ b/gcc/testsuite/g++.dg/gomp/array-section-5.C
@@ -0,0 +1,96 @@
+// { dg-do compile { target c++23_down } }
+
+// See 'array-section-6.C' for C++26 and higher
+// Same test but slightly different error messages.
+
+
+// Check that OpenMP array sections with [: and :] do not get mixed up with C++26's splice specifier,
+// used for reflection.
+//
+// See also OpenMP spec Issue 4740.
+
+
+// The following checks that an additional ':' is diagnosed.
+//
+// NOTE: When STRIDES are permitted (e.g. for target update) that's actually
+// valid but not yet implemented (at the time this test was written) - and
+// it is still not valid for 'target enter data' or affinity - as used here.
+
+void f() {
+ char a1[4], a2[4], a3[4], a4[5], a5[5], b;
+ char c1[4], c2[4], c3[4], c4[4], c5[4];
+
+ #pragma omp target enter data map(b) map(to: a1[::]) // { dg-error "52: expected id-expression before '\\\]' token" }
+ #pragma omp target enter data map(b) map(to: a2[: :]) // { dg-error "52: expected primary-expression before ':' token" }
+ #pragma omp target enter data map(b) map(to: a3[ ::]) // { dg-error "53: expected id-expression before '\\\]' token" }
+ #pragma omp target enter data map(b) map(to: a4[:: ]) // { dg-error "53: expected id-expression before '\\\]' token" }
+ #pragma omp target enter data map(b) map(to: a5[ :: ]) // { dg-error "54: expected id-expression before '\\\]' token" }
+
+ #pragma omp target enter data map(b) map(to: a1[1::])
+ // { dg-error "51: expected '\\\]' before '::' token" "" { target *-*-* } .-1 }
+ // { dg-error "51: expected '\\)' before '::' token" "" { target *-*-* } .-2 }
+ // { dg-error "53: expected an OpenMP clause before '\\\]' token" "" { target *-*-* } .-3 }
+
+ #pragma omp target enter data map(b) map(to: a2[1:: ])
+ // { dg-error "51: expected '\\\]' before '::' token" "" { target *-*-* } .-1 }
+ // { dg-error "51: expected '\\)' before '::' token" "" { target *-*-* } .-2 }
+ // { dg-error "54: expected an OpenMP clause before '\\\]' token" "" { target *-*-* } .-3 }
+ #pragma omp target enter data map(b) map(to: a3[:1:])
+ // { dg-error "52: expected '\\\]' before ':' token" "" { target *-*-* } .-1 }
+ // { dg-error "52: expected '\\)' before ':' token" "" { target *-*-* } .-2 }
+ // { dg-error "53: expected an OpenMP clause before '\\\]' token" "" { target *-*-* } .-3 }
+ #pragma omp target enter data map(b) map(to: a4[ :1: ])
+ // { dg-error "53: expected '\\\]' before ':' token" "" { target *-*-* } .-1 }
+ // { dg-error "53: expected '\\)' before ':' token" "" { target *-*-* } .-2 }
+ // { dg-error "55: expected an OpenMP clause before '\\\]' token" "" { target *-*-* } .-3 }
+ #pragma omp target enter data map(b) map(to: a5[ :1:])
+ // { dg-error "53: expected '\\\]' before ':' token" "" { target *-*-* } .-1 }
+ // { dg-error "53: expected '\\)' before ':' token" "" { target *-*-* } .-2 }
+ // { dg-error "54: expected an OpenMP clause before '\\\]' token" "" { target *-*-* } .-3 }
+
+
+ #pragma omp task affinity(c1[::]) // { dg-error "28: expected '\\)' before 'c1'" }
+ ;
+
+ #pragma omp task affinity(c2[: :])
+ ;
+ // { dg-error "33: expected primary-expression before ':' token" "" { target *-*-* } .-2 }
+ // { dg-error "32: expected '\\\]' before ':' token" "" { target *-*-* } .-3 }
+ // { dg-error "34: expected an OpenMP clause before '\\\]' token" "" { target *-*-* } .-4 }
+
+ #pragma omp task affinity(c3[ ::]) // { dg-error "28: expected '\\)' before 'c3'" }
+ ;
+
+ #pragma omp task affinity(c4[:: ]) // { dg-error "28: expected '\\)' before 'c4'" }
+ ;
+
+ #pragma omp task affinity(c5[ :: ]) // { dg-error "28: expected '\\)' before 'c5'" }
+ ;
+
+ #pragma omp task affinity(c1[1::])
+ ;
+ // { dg-error "32: expected '\\\]' before '::' token" "" { target *-*-* } .-2 }
+ // { dg-error "32: expected '\\)' before '::' token" "" { target *-*-* } .-3 }
+ // { dg-error "34: expected an OpenMP clause before '\\\]' token" "" { target *-*-* } .-4 }
+
+ #pragma omp task affinity(c2[1:: ])
+ ;
+ // { dg-error "32: expected '\\\]' before '::' token" "" { target *-*-* } .-2 }
+ // { dg-error "32: expected '\\)' before '::' token" "" { target *-*-* } .-3 }
+ // { dg-error "35: expected an OpenMP clause before '\\\]' token" "" { target *-*-* } .-4 }
+
+ #pragma omp task affinity(c3[:1:])
+ ;
+ // { dg-error "33: expected '\\\]' before ':' token" "" { target *-*-* } .-2 }
+ // { dg-error "34: expected an OpenMP clause before '\\\]' token" "" { target *-*-* } .-3 }
+
+ #pragma omp task affinity(c4[ :1: ])
+ ;
+ // { dg-error "34: expected '\\\]' before ':' token" "" { target *-*-* } .-2 }
+ // { dg-error "36: expected an OpenMP clause before '\\\]' token" "" { target *-*-* } .-3 }
+
+ #pragma omp task affinity(c5[ :1:])
+ ;
+ // { dg-error "34: expected '\\\]' before ':' token" "" { target *-*-* } .-2 }
+ // { dg-error "35: expected an OpenMP clause before '\\\]' token" "" { target *-*-* } .-3 }
+}
diff --git a/gcc/testsuite/g++.dg/gomp/array-section-6.C b/gcc/testsuite/g++.dg/gomp/array-section-6.C
new file mode 100644
index 00000000000..16356077983
--- /dev/null
+++ b/gcc/testsuite/g++.dg/gomp/array-section-6.C
@@ -0,0 +1,85 @@
+// { dg-do compile { target c++26 } }
+
+// See 'array-section-5.C' for C++23 and lower
+// Same test but slightly different error messages.
+
+
+// Check that OpenMP array sections with [: and :] do not get mixed up with C++26's splice specifier,
+// used for reflection.
+//
+// See also OpenMP spec Issue 4740.
+
+
+// The following checks that an additional ':' is diagnosed.
+//
+// NOTE: When STRIDES are permitted (e.g. for target update) that's actually
+// valid but not yet implemented (at the time this test was written) - and
+// it is still not valid for 'target enter data' or affinity - as used here.
+
+void f() {
+ char a1[4], a2[4], a3[4], a4[5], a5[5], b;
+ char c1[4], c2[4], c3[4], c4[4], c5[4];
+
+ #pragma omp target enter data map(b) map(to: a1[::]) // { dg-error "52: expected id-expression before '\\\]' token" }
+ #pragma omp target enter data map(b) map(to: a2[: :]) // { dg-error "51: expected '\\\]' before ':\\\]' token" }
+ #pragma omp target enter data map(b) map(to: a3[ ::]) // { dg-error "53: expected id-expression before '\\\]' token" }
+ #pragma omp target enter data map(b) map(to: a4[:: ]) // { dg-error "53: expected id-expression before '\\\]' token" }
+ #pragma omp target enter data map(b) map(to: a5[ :: ]) // { dg-error "54: expected id-expression before '\\\]' token" }
+
+ #pragma omp target enter data map(b) map(to: a1[1::])
+ // { dg-error "51: expected '\\\]' before '::' token" "" { target *-*-* } .-1 }
+ // { dg-error "51: expected '\\)' before '::' token" "" { target *-*-* } .-2 }
+ // { dg-error "53: expected an OpenMP clause before '\\\]' token" "" { target *-*-* } .-3 }
+
+ #pragma omp target enter data map(b) map(to: a2[1:: ])
+ // { dg-error "51: expected '\\\]' before '::' token" "" { target *-*-* } .-1 }
+ // { dg-error "51: expected '\\)' before '::' token" "" { target *-*-* } .-2 }
+ // { dg-error "54: expected an OpenMP clause before '\\\]' token" "" { target *-*-* } .-3 }
+
+ #pragma omp target enter data map(b) map(to: a3[:1:])
+ // { dg-error "52: expected '\\\]' before ':\\\]' token" "" { target *-*-* } .-1 }
+ // { dg-error "52: expected '\\)' before ':\\\]' token" "" { target *-*-* } .-2 }
+
+ #pragma omp target enter data map(b) map(to: a4[ :1: ])
+ // { dg-error "53: expected '\\\]' before ':' token" "" { target *-*-* } .-1 }
+ // { dg-error "53: expected '\\)' before ':' token" "" { target *-*-* } .-2 }
+ // { dg-error "55: expected an OpenMP clause before '\\\]' token" "" { target *-*-* } .-3 }
+
+ #pragma omp target enter data map(b) map(to: a5[ :1:])
+ // { dg-error "53: expected '\\\]' before ':\\\]' token" "" { target *-*-* } .-1 }
+ // { dg-error "53: expected '\\)' before ':\\\]' token" "" { target *-*-* } .-2 }
+
+ #pragma omp task affinity(c1[::]) // { dg-error "28: expected '\\)' before 'c1'" }
+ ;
+ #pragma omp task affinity(c2[: :]) // { dg-error "30: expected '\\)' before '\\\[:' token" }
+ ;
+ #pragma omp task affinity(c3[ ::]) // { dg-error "28: expected '\\)' before 'c3'" }
+ ;
+ #pragma omp task affinity(c4[:: ]) // { dg-error "28: expected '\\)' before 'c4'" }
+ ;
+ #pragma omp task affinity(c5[ :: ]) // { dg-error "28: expected '\\)' before 'c5'" }
+ ;
+
+ #pragma omp task affinity(c1[1::])
+ ;
+ // { dg-error "32: expected '\\\]' before '::' token" "" { target *-*-* } .-2 }
+ // { dg-error "32: expected '\\)' before '::' token" "" { target *-*-* } .-3 }
+ // { dg-error "34: expected an OpenMP clause before '\\\]' token" "" { target *-*-* } .-4 }
+
+ #pragma omp task affinity(c2[1:: ])
+ ;
+ // { dg-error "32: expected '\\\]' before '::' token" "" { target *-*-* } .-2 }
+ // { dg-error "32: expected '\\)' before '::' token" "" { target *-*-* } .-3 }
+ // { dg-error "35: expected an OpenMP clause before '\\\]' token" "" { target *-*-* } .-4 }
+
+ #pragma omp task affinity(c3[:1:]) // { dg-error "33: expected '\\\]' before ':\\\]' token" }
+ ;
+
+ #pragma omp task affinity(c4[ :1: ])
+ ;
+ // { dg-error "34: expected '\\\]' before ':' token" "" { target *-*-* } .-2 }
+ // { dg-error "36: expected an OpenMP clause before '\\\]' token" "" { target *-*-* } .-3 }
+
+ #pragma omp task affinity(c5[ :1:]) // { dg-error "34: expected '\\\]' before ':\\\]' token" }
+ ;
+}