[gcc r17-2993] C++: Handle OpenMP/OpenACC array sections [: / :] with C++26

Tobias Burnus via Gcc-cvs <[email protected]> Wed, 5 Aug 2026 20:38:49 +0000 (GMT)
Newsgroups gmane.comp.gcc.cvs
Message-ID <[email protected]>
https://gcc.gnu.org/g:8f43cf55107e34bc10ed0ee8461d0551bc7e3817

commit r17-2993-g8f43cf55107e34bc10ed0ee8461d0551bc7e3817
Author: Tobias Burnus <[email protected]>
Date:   Wed Aug 5 22:37:41 2026 +0200

    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.

Diff:
---
 gcc/cp/parser.cc                             | 121 ++++++++++++++++++++++-----
 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, 391 insertions(+), 26 deletions(-)

diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
index 85c5030acbb4..b1a1157afda4 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,
@@ -29190,7 +29232,10 @@ cp_parser_braced_list (cp_parser *parser, bool *non_constant_p /*=nullptr*/)
 }
 
 /* Consume tokens up to, but not including, the next non-nested closing `]'.
-   Returns true iff we found a closing `]'.  */
+   Returns true iff we found a closing `]'.
+   When OpenMP array sections are permitted, the open and close splice, '[:'
+   and ':]', are are treated as '[' and ']' - because an absent lower or size
+   value in '[ lower : size ]' might get parsed as open or closed splice.  */
 
 static bool
 cp_parser_skip_up_to_closing_square_bracket (cp_parser *parser)
@@ -29212,10 +29257,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 +41905,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 +41941,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 +41986,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 +41999,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 +42009,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 78bd21d1a6f1..9a2d678bc10d 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 000000000000..9c6ca229501e
--- /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 0303b55fa256..e778314f07e7 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 000000000000..170405fba875
--- /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 000000000000..2ece1ac9e610
--- /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 000000000000..80075e6dabfd
--- /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 000000000000..163560779835
--- /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" }
+  ;
+}