c++, v4: Implement C++26 P2741R3 - user-generated static_assert messages [PR110348]

Message ID ZV3RVUdjeIKL0c6x@tucnak
State New
Headers
Series c++, v4: Implement C++26 P2741R3 - user-generated static_assert messages [PR110348] |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_gcc_build--master-aarch64 fail Patch failed to apply
linaro-tcwg-bot/tcwg_gcc_build--master-arm fail Patch failed to apply

Commit Message

Jakub Jelinek Nov. 22, 2023, 10 a.m. UTC
  On Tue, Nov 21, 2023 at 10:51:36PM -0500, Jason Merrill wrote:
> Actually, let's go back to the previous message, but change the tf_nones
> above to 'complain' so that we see those errors and then this explanation.
> Likewise with the conversion checks later in the function.

So like this?
Besides what you asked for I've separated the diagnostics for when size
member isn't found in lookup vs. when data isn't found, because it looked
weird to get 2 same errors e.g. in the udlit-error1.C case.

So far tested with
GXX_TESTSUITE_STDS=98,11,14,17,20,23,26 make check-g++ RUNTESTFLAGS="dg.exp='static_assert1.C feat-cxx26.C udlit-error1.C'"
Ok if it passes full bootstrap/regtest?

2023-11-22  Jakub Jelinek  <jakub@redhat.com>

	PR c++/110348
gcc/
	* doc/invoke.texi (-Wno-c++26-extensions): Document.
gcc/c-family/
	* c.opt (Wc++26-extensions): New option.
	* c-cppbuiltin.cc (c_cpp_builtins): For C++26 predefine
	__cpp_static_assert to 202306L rather than 201411L.
gcc/cp/
	* parser.cc: Implement C++26 P2741R3 - user-generated static_assert
	messages.
	(cp_parser_static_assert): Parse message argument as
	conditional-expression if it is not a pure string literal or
	several of them concatenated followed by closing paren.
	* semantics.cc (finish_static_assert): Handle message which is not
	STRING_CST.  For condition with bare parameter packs return early.
	* pt.cc (tsubst_expr) <case STATIC_ASSERT>: Also tsubst_expr
	message and make sure that if it wasn't originally STRING_CST, it
	isn't after tsubst_expr either.
gcc/testsuite/
	* g++.dg/cpp26/static_assert1.C: New test.
	* g++.dg/cpp26/feat-cxx26.C (__cpp_static_assert): Expect
	202306L rather than 201411L.
	* g++.dg/cpp0x/udlit-error1.C: Expect different diagnostics for
	static_assert with user-defined literal.



	Jakub
  

Comments

Jason Merrill Nov. 22, 2023, 9:53 p.m. UTC | #1
On 11/22/23 05:00, Jakub Jelinek wrote:
> On Tue, Nov 21, 2023 at 10:51:36PM -0500, Jason Merrill wrote:
>> Actually, let's go back to the previous message, but change the tf_nones
>> above to 'complain' so that we see those errors and then this explanation.
>> Likewise with the conversion checks later in the function.
> 
> So like this?
> Besides what you asked for I've separated the diagnostics for when size
> member isn't found in lookup vs. when data isn't found, because it looked
> weird to get 2 same errors e.g. in the udlit-error1.C case.
> 
> +      message_sz
> +	= finish_class_member_access_expr (message,
> +					   get_identifier ("size"),
> +					   false, complain);
> +      if (message_sz == error_mark_node)
> +	{
> +	  error_at (location, "%<static_assert%> message must be a string "
> +			      "literal or object with %<size%> and "
> +			      "%<data%> members");
> +	  return;
> +	}
> +      message_data
> +	= finish_class_member_access_expr (message,
> +					   get_identifier ("data"),
> +					   false, complain);
> +      if (message_data == error_mark_node)
> +	{
> +	  error_at (location, "%<static_assert%> message must be a string "
> +			      "literal or object with %<size%> and "
> +			      "%<data%> members");
> +	  return;
> +	}

I agree it's weird to get two of the same error, but maybe instead of 
duplicating the error, we could look up data only if size succeeded, and 
then error once if either failed?

OK with that change.

Jason
  
Jakub Jelinek Nov. 23, 2023, 8:32 a.m. UTC | #2
On Wed, Nov 22, 2023 at 04:53:48PM -0500, Jason Merrill wrote:
> I agree it's weird to get two of the same error, but maybe instead of
> duplicating the error, we could look up data only if size succeeded, and
> then error once if either failed?

Here is what I've committed after another bootstrap/regtest on x86_64-linux
and i686-linux.  Besides the above requested change I've tweaked 2 lines
in the test not to rely on a particular std::size_t exact type because
otherwise the test failed on i686-linux.  And accepting there only the
current
unsigned int
long unsigned int
long long unsinged int
unsigned __int20__ (or how exactly is this one spelled in diagnostics)
seems fragile.

Thanks a lot for the review of this (and sorry it took so long on my side
because I've missed the first review).

2023-11-23  Jakub Jelinek  <jakub@redhat.com>

	PR c++/110348
gcc/
	* doc/invoke.texi (-Wno-c++26-extensions): Document.
gcc/c-family/
	* c.opt (Wc++26-extensions): New option.
	* c-cppbuiltin.cc (c_cpp_builtins): For C++26 predefine
	__cpp_static_assert to 202306L rather than 201411L.
gcc/cp/
	* parser.cc: Implement C++26 P2741R3 - user-generated static_assert
	messages.
	(cp_parser_static_assert): Parse message argument as
	conditional-expression if it is not a pure string literal or
	several of them concatenated followed by closing paren.
	* semantics.cc (finish_static_assert): Handle message which is not
	STRING_CST.  For condition with bare parameter packs return early.
	* pt.cc (tsubst_expr) <case STATIC_ASSERT>: Also tsubst_expr
	message and make sure that if it wasn't originally STRING_CST, it
	isn't after tsubst_expr either.
gcc/testsuite/
	* g++.dg/cpp26/static_assert1.C: New test.
	* g++.dg/cpp26/feat-cxx26.C (__cpp_static_assert): Expect
	202306L rather than 201411L.
	* g++.dg/cpp0x/udlit-error1.C: Expect different diagnostics for
	static_assert with user-defined literal.

--- gcc/doc/invoke.texi.jj	2023-11-22 10:14:56.021376360 +0100
+++ gcc/doc/invoke.texi	2023-11-22 10:17:41.328065157 +0100
@@ -9107,6 +9107,13 @@ Do not warn about C++23 constructs in co
 an older C++ standard.  Even without this option, some C++23 constructs
 will only be diagnosed if @option{-Wpedantic} is used.
 
+@opindex Wc++26-extensions
+@opindex Wno-c++26-extensions
+@item -Wno-c++26-extensions @r{(C++ and Objective-C++ only)}
+Do not warn about C++26 constructs in code being compiled using
+an older C++ standard.  Even without this option, some C++26 constructs
+will only be diagnosed if @option{-Wpedantic} is used.
+
 @opindex Wcast-qual
 @opindex Wno-cast-qual
 @item -Wcast-qual
--- gcc/c-family/c.opt.jj	2023-11-22 10:14:55.963377171 +0100
+++ gcc/c-family/c.opt	2023-11-22 10:17:41.328065157 +0100
@@ -498,6 +498,10 @@ Wc++23-extensions
 C++ ObjC++ Var(warn_cxx23_extensions) Warning Init(1)
 Warn about C++23 constructs in code compiled with an older standard.
 
+Wc++26-extensions
+C++ ObjC++ Var(warn_cxx26_extensions) Warning Init(1)
+Warn about C++26 constructs in code compiled with an older standard.
+
 Wcast-function-type
 C ObjC C++ ObjC++ Var(warn_cast_function_type) Warning EnabledBy(Wextra)
 Warn about casts between incompatible function types.
--- gcc/c-family/c-cppbuiltin.cc.jj	2023-11-22 10:14:55.962377185 +0100
+++ gcc/c-family/c-cppbuiltin.cc	2023-11-22 10:17:41.329065143 +0100
@@ -1023,7 +1023,8 @@ c_cpp_builtins (cpp_reader *pfile)
 	{
 	  /* Set feature test macros for C++17.  */
 	  cpp_define (pfile, "__cpp_unicode_characters=201411L");
-	  cpp_define (pfile, "__cpp_static_assert=201411L");
+	  if (cxx_dialect <= cxx23)
+	    cpp_define (pfile, "__cpp_static_assert=201411L");
 	  cpp_define (pfile, "__cpp_namespace_attributes=201411L");
 	  cpp_define (pfile, "__cpp_enumerator_attributes=201411L");
 	  cpp_define (pfile, "__cpp_nested_namespace_definitions=201411L");
@@ -1086,6 +1087,7 @@ c_cpp_builtins (cpp_reader *pfile)
 	{
 	  /* Set feature test macros for C++26.  */
 	  cpp_define (pfile, "__cpp_constexpr=202306L");
+	  cpp_define (pfile, "__cpp_static_assert=202306L");
 	}
       if (flag_concepts)
         {
--- gcc/cp/parser.cc.jj	2023-11-22 10:14:55.969377087 +0100
+++ gcc/cp/parser.cc	2023-11-22 10:17:41.335065058 +0100
@@ -16616,6 +16616,7 @@ cp_parser_linkage_specification (cp_pars
    static_assert-declaration:
      static_assert ( constant-expression , string-literal ) ;
      static_assert ( constant-expression ) ; (C++17)
+     static_assert ( constant-expression, conditional-expression ) ; (C++26)
 
    If MEMBER_P, this static_assert is a class member.  */
 
@@ -16646,10 +16647,10 @@ cp_parser_static_assert (cp_parser *pars
 
   /* Parse the constant-expression.  Allow a non-constant expression
      here in order to give better diagnostics in finish_static_assert.  */
-  condition =
-    cp_parser_constant_expression (parser,
-                                   /*allow_non_constant_p=*/true,
-				   /*non_constant_p=*/nullptr);
+  condition
+    = cp_parser_constant_expression (parser,
+				     /*allow_non_constant_p=*/true,
+				     /*non_constant_p=*/nullptr);
 
   if (cp_lexer_peek_token (parser->lexer)->type == CPP_CLOSE_PAREN)
     {
@@ -16668,8 +16669,32 @@ cp_parser_static_assert (cp_parser *pars
       /* Parse the separating `,'.  */
       cp_parser_require (parser, CPP_COMMA, RT_COMMA);
 
-      /* Parse the string-literal message.  */
-      if (cxx_dialect >= cxx26)
+      /* Parse the message expression.  */
+      bool string_lit = true;
+      for (unsigned int i = 1; ; ++i)
+	{
+	  cp_token *tok = cp_lexer_peek_nth_token (parser->lexer, i);
+	  if (cp_parser_is_pure_string_literal (tok))
+	    continue;
+	  else if (tok->type == CPP_CLOSE_PAREN)
+	    break;
+	  string_lit = false;
+	  break;
+	}
+      if (!string_lit)
+	{
+	  location_t loc = cp_lexer_peek_token (parser->lexer)->location;
+	  if (cxx_dialect < cxx26)
+	    pedwarn (loc, OPT_Wc__26_extensions,
+		     "%<static_assert%> with non-string message only "
+		     "available with %<-std=c++2c%> or %<-std=gnu++2c%>");
+
+	  message = cp_parser_conditional_expression (parser);
+	  if (TREE_CODE (message) == STRING_CST)
+	    message = build1_loc (loc, PAREN_EXPR, TREE_TYPE (message),
+				  message);
+	}
+      else if (cxx_dialect >= cxx26)
 	message = cp_parser_unevaluated_string_literal (parser);
       else
 	message = cp_parser_string_literal (parser, /*translate=*/false,
--- gcc/cp/semantics.cc.jj	2023-11-22 11:30:08.019325101 +0100
+++ gcc/cp/semantics.cc	2023-11-22 22:58:25.194480633 +0100
@@ -11434,6 +11434,7 @@ finish_static_assert (tree condition, tr
 		      bool member_p, bool show_expr_p)
 {
   tsubst_flags_t complain = tf_warning_or_error;
+  tree message_sz = NULL_TREE, message_data = NULL_TREE;
 
   if (message == NULL_TREE
       || message == error_mark_node
@@ -11441,13 +11442,61 @@ finish_static_assert (tree condition, tr
       || condition == error_mark_node)
     return;
 
-  if (check_for_bare_parameter_packs (condition))
-    condition = error_mark_node;
+  if (check_for_bare_parameter_packs (condition)
+      || check_for_bare_parameter_packs (message))
+    return;
+
+  if (TREE_CODE (message) != STRING_CST
+      && !type_dependent_expression_p (message))
+    {
+      message_sz
+	= finish_class_member_access_expr (message,
+					   get_identifier ("size"),
+					   false, complain);
+      if (message_sz != error_mark_node)
+	message_data
+	  = finish_class_member_access_expr (message,
+					     get_identifier ("data"),
+					     false, complain);
+      if (message_sz == error_mark_node || message_data == error_mark_node)
+	{
+	  error_at (location, "%<static_assert%> message must be a string "
+			      "literal or object with %<size%> and "
+			      "%<data%> members");
+	  return;
+	}
+      releasing_vec size_args, data_args;
+      message_sz = finish_call_expr (message_sz, &size_args, false, false,
+				     complain);
+      message_data = finish_call_expr (message_data, &data_args, false, false,
+				       complain);
+      if (message_sz == error_mark_node || message_data == error_mark_node)
+	return;
+      message_sz = build_converted_constant_expr (size_type_node, message_sz,
+						  complain);
+      if (message_sz == error_mark_node)
+	{
+	  error_at (location, "%<static_assert%> message %<size()%> "
+			      "must be implicitly convertible to "
+			      "%<std::size_t%>");
+	  return;
+	}
+      message_data = build_converted_constant_expr (const_string_type_node,
+						    message_data, complain);
+      if (message_data == error_mark_node)
+	{
+	  error_at (location, "%<static_assert%> message %<data()%> "
+			      "must be implicitly convertible to "
+			      "%<const char*%>");
+	  return;
+	}
+    }
 
   /* Save the condition in case it was a concept check.  */
   tree orig_condition = condition;
 
-  if (instantiation_dependent_expression_p (condition))
+  if (instantiation_dependent_expression_p (condition)
+      || instantiation_dependent_expression_p (message))
     {
       /* We're in a template; build a STATIC_ASSERT and put it in
          the right place. */
@@ -11485,9 +11534,89 @@ finish_static_assert (tree condition, tr
 	  if (processing_template_decl)
 	    goto defer;
 
-	  int sz = TREE_INT_CST_LOW (TYPE_SIZE_UNIT
-				     (TREE_TYPE (TREE_TYPE (message))));
-	  int len = TREE_STRING_LENGTH (message) / sz - 1;
+	  int len;
+	  const char *msg = NULL;
+	  char *buf = NULL;
+	  if (message_sz && message_data)
+	    {
+	      tree msz = cxx_constant_value (message_sz, NULL_TREE, complain);
+	      if (!tree_fits_uhwi_p (msz))
+		{
+		  error_at (location,
+			    "%<static_assert%> message %<size()%> "
+			    "must be a constant expression");
+		  return;
+		}
+	      else if ((unsigned HOST_WIDE_INT) (int) tree_to_uhwi (msz)
+		       != tree_to_uhwi (msz))
+		{
+		  error_at (location,
+			    "%<static_assert%> message %<size()%> "
+			    "%qE too large", msz);
+		  return;
+		}
+	      len = tree_to_uhwi (msz);
+	      tree data = maybe_constant_value (message_data, NULL_TREE,
+						mce_true);
+	      if (!reduced_constant_expression_p (data))
+		data = NULL_TREE;
+	      if (len)
+		{
+		  if (data)
+		    msg = c_getstr (data);
+		  if (msg == NULL)
+		    buf = XNEWVEC (char, len);
+		  for (int i = 0; i < len; ++i)
+		    {
+		      tree t = message_data;
+		      if (i)
+			t = build2 (POINTER_PLUS_EXPR,
+				    TREE_TYPE (message_data), message_data,
+				    size_int (i));
+		      t = build1 (INDIRECT_REF, TREE_TYPE (TREE_TYPE (t)), t);
+		      tree t2 = cxx_constant_value (t, NULL_TREE, complain);
+		      if (!tree_fits_shwi_p (t2))
+			{
+			  error_at (location,
+				    "%<static_assert%> message %<data()[%d]%> "
+				    "must be a constant expression", i);
+			  return;
+			}
+		      if (msg == NULL)
+			buf[i] = tree_to_shwi (t2);
+		      /* If c_getstr worked, just verify the first and
+			 last characters using constant evaluation.  */
+		      else if (len > 2 && i == 0)
+			i = len - 2;
+		    }
+		  if (msg == NULL)
+		    msg = buf;
+		}
+	      else if (!data)
+		{
+		  /* We don't have any function to test whether some
+		     expression is a core constant expression.  So, instead
+		     test whether (message.data (), 0) is a constant
+		     expression.  */
+		  data = build2 (COMPOUND_EXPR, integer_type_node,
+				 message_data, integer_zero_node);
+		  tree t = cxx_constant_value (data, NULL_TREE, complain);
+		  if (!integer_zerop (t))
+		    {
+		      error_at (location,
+				"%<static_assert%> message %<data()%> "
+				"must be a core constant expression");
+		      return;
+		    }
+		}
+	    }
+	  else
+	    {
+	      tree eltype = TREE_TYPE (TREE_TYPE (message));
+	      int sz = TREE_INT_CST_LOW (TYPE_SIZE_UNIT (eltype));
+	      msg = TREE_STRING_POINTER (message);
+	      len = TREE_STRING_LENGTH (message) / sz - 1;
+	    }
 
 	  /* See if we can find which clause was failing (for logical AND).  */
 	  tree bad = find_failing_clause (NULL, orig_condition);
@@ -11497,12 +11626,13 @@ finish_static_assert (tree condition, tr
 
 	  auto_diagnostic_group d;
 
-          /* Report the error. */
+	  /* Report the error. */
 	  if (len == 0)
 	    error_at (cloc, "static assertion failed");
 	  else
-	    error_at (cloc, "static assertion failed: %s",
-		      TREE_STRING_POINTER (message));
+	    error_at (cloc, "static assertion failed: %.*s", len, msg);
+
+	  XDELETEVEC (buf);
 
 	  diagnose_failing_condition (bad, cloc, show_expr_p);
 	}
--- gcc/cp/pt.cc.jj	2023-11-22 10:14:55.992376766 +0100
+++ gcc/cp/pt.cc	2023-11-22 10:17:41.340064988 +0100
@@ -18701,15 +18701,20 @@ tsubst_stmt (tree t, tree args, tsubst_f
 
     case STATIC_ASSERT:
       {
-	tree condition;
+	tree condition, message;
 
 	++c_inhibit_evaluation_warnings;
 	condition = tsubst_expr (STATIC_ASSERT_CONDITION (t), args,
 				 complain, in_decl);
+	message = tsubst_expr (STATIC_ASSERT_MESSAGE (t), args,
+			       complain, in_decl);
+	if (TREE_CODE (STATIC_ASSERT_MESSAGE (t)) != STRING_CST
+	    && TREE_CODE (message) == STRING_CST)
+	  message = build1_loc (STATIC_ASSERT_SOURCE_LOCATION (t),
+				PAREN_EXPR, TREE_TYPE (message), message);
 	--c_inhibit_evaluation_warnings;
 
-        finish_static_assert (condition,
-                              STATIC_ASSERT_MESSAGE (t),
+	finish_static_assert (condition, message,
                               STATIC_ASSERT_SOURCE_LOCATION (t),
 			      /*member_p=*/false, /*show_expr_p=*/true);
       }
--- gcc/testsuite/g++.dg/cpp26/static_assert1.C.jj	2023-11-22 10:17:41.340064988 +0100
+++ gcc/testsuite/g++.dg/cpp26/static_assert1.C	2023-11-22 10:47:45.045848504 +0100
@@ -0,0 +1,309 @@
+// C++26 P2741R3 - user-generated static_assert messages
+// { dg-do compile { target c++11 } }
+// { dg-options "" }
+
+static_assert (true, "");
+static_assert (true, (""));	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+				// { dg-error "'static_assert' message must be a string literal or object with 'size' and 'data' members" "" { target *-*-* } .-1 }
+				// { dg-error "request for member 'size' in '\\\(\\\"\\\"\\\)', which is of non-class type 'const char \\\[1\\\]'" "" { target *-*-* } .-2 }
+static_assert (true, "" + 0);	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+				// { dg-error "'static_assert' message must be a string literal or object with 'size' and 'data' members" "" { target *-*-* } .-1 }
+				// { dg-error "request for member 'size' in '\\\(const char\\\*\\\)\\\"\\\"', which is of non-class type 'const char\\\*'" "" { target *-*-* } .-2 }
+static_assert (true, 0);	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+				// { dg-error "'static_assert' message must be a string literal or object with 'size' and 'data' members" "" { target *-*-* } .-1 }
+				// { dg-error "request for member 'size' in '0', which is of non-class type 'int'" "" { target *-*-* } .-2 }
+struct A {};
+static_assert (true, A {});	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+				// { dg-error "'static_assert' message must be a string literal or object with 'size' and 'data' members" "" { target *-*-* } .-1 }
+				// { dg-error "'struct A' has no member named 'size'" "" { target *-*-* } .-2 }
+struct B { int size; };
+static_assert (true, B {});	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+				// { dg-error "'static_assert' message must be a string literal or object with 'size' and 'data' members" "" { target *-*-* } .-1 }
+				// { dg-error "'struct B' has no member named 'data'" "" { target *-*-* } .-2 }
+struct C { constexpr int size () const { return 0; } };
+static_assert (true, C {});	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+				// { dg-error "'static_assert' message must be a string literal or object with 'size' and 'data' members" "" { target *-*-* } .-1 }
+				// { dg-error "'struct C' has no member named 'data'" "" { target *-*-* } .-2 }
+struct D { constexpr int size () const { return 0; } int data; };
+static_assert (true, D {});	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+				// { dg-error "'D\\\(\\\).D::data' cannot be used as a function" "" { target *-*-* } .-1 }
+struct E { int size = 0;
+	   constexpr const char *data () const { return ""; } };
+static_assert (true, E {});	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+				// { dg-error "'E\\\(\\\).E::size' cannot be used as a function" "" { target c++11_only } .-1 }
+				// { dg-error "'E\\\{0\\\}.E::size' cannot be used as a function" "" { target c++14 } .-2 }
+struct F { constexpr const char *size () const { return ""; }
+	   constexpr const char *data () const { return ""; } };
+static_assert (true, F {});	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+				// { dg-error "'static_assert' message 'size\\\(\\\)' must be implicitly convertible to 'std::size_t'" "" { target *-*-* } .-1 }
+				// { dg-error "could not convert 'F\\\(\\\).F::size\\\(\\\)' from 'const char\\\*' to '\[^']*'" "" { target *-*-* } .-2 }
+				// { dg-error "conversion from 'const char\\\*' to '\[^']*' in a converted constant expression" "" { target *-*-* } .-3 }
+struct G { constexpr long size () const { return 0; }
+	   constexpr float data () const { return 0.0f; } };
+static_assert (true, G {});	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+				// { dg-error "'static_assert' message 'data\\\(\\\)' must be implicitly convertible to 'const char\\\*'" "" { target *-*-* } .-1 }
+				// { dg-error "could not convert 'G\\\(\\\).G::data\\\(\\\)' from 'float' to 'const char\\\*'" "" { target *-*-* } .-2 }
+struct H { short size () const { return 0; }
+	   constexpr const char *data () const { return ""; } };
+static_assert (true, H {});	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+struct I { constexpr signed char size () const { return 0; }
+	   const char *data () const { return ""; } };
+static_assert (true, I {});	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+struct J { constexpr int size () const { return j ? throw 1 : 0; }	// { dg-error "expression '<throw-expression>' is not a constant expression" }
+	   constexpr const char *data () const { return ""; };
+	   constexpr J (int x) : j (x) {}
+	   int j; };
+static_assert (true, J (1));	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+static_assert (false, J (0));	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+				// { dg-error "static assertion failed" "" { target *-*-* } .-1 }
+static_assert (false, J (1));	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+				// { dg-error "'static_assert' message 'size\\\(\\\)' must be a constant expression" "" { target *-*-* } .-1 }
+struct K { constexpr operator int () { return 4; } };
+struct L { constexpr operator const char * () { return "test"; } };
+struct M { constexpr K size () const { return {}; }
+	   constexpr L data () const { return {}; } };
+static_assert (true, M {});	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+static_assert (false, M {});	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+				// { dg-error "static assertion failed: test" "" { target *-*-* } .-1 }
+#if  __cpp_constexpr_dynamic_alloc >= 201907L
+struct N { constexpr int size () const { return 3; }
+	   constexpr const char *data () const { return new char[3] { 'b', 'a', 'd' }; } }; // { dg-error "'\\\* N\\\(\\\).N::data\\\(\\\)' is not a constant expression because allocated storage has not been deallocated" "" { target c++20 } }
+static_assert (true, N {});	// { dg-warning "'static_assert' with non-string message only available with" "" { target { c++20 && c++23_down } } }
+static_assert (false, N {});	// { dg-warning "'static_assert' with non-string message only available with" "" { target { c++20 && c++23_down } } }
+				// { dg-error "'static_assert' message 'data\\\(\\\)\\\[0\\\]' must be a constant expression" "" { target c++20 } .-1 }
+#endif
+constexpr const char a[] = { 't', 'e', 's', 't' };
+struct O { constexpr int size () const { return 4; }
+	   constexpr const char *data () const { return a; } };
+static_assert (false, O {});	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+				// { dg-error "static assertion failed: test" "" { target *-*-* } .-1 }
+struct P { constexpr int size () const { return 4 - p; }
+	   constexpr const char *data () const { return &a[p]; }
+	   constexpr P (int x) : p (x) {}
+	   int p; };
+static_assert (false, P (0));	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+				// { dg-error "static assertion failed: test" "" { target *-*-* } .-1 }
+static_assert (false, P (2));	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+				// { dg-error "static assertion failed: st" "" { target *-*-* } .-1 }
+struct Q { constexpr int size () const { return 4 - q; }
+	   constexpr const char *data () const { return &"test"[q]; }
+	   constexpr Q (int x) : q (x) {}
+	   int q; };
+static_assert (false, Q (0));	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+				// { dg-error "static assertion failed: test" "" { target *-*-* } .-1 }
+static_assert (false, Q (1));	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+				// { dg-error "static assertion failed: est" "" { target *-*-* } .-1 }
+struct R { constexpr int size () const { return 4 - r; }
+	   constexpr const char *d () const { return "test"; }
+	   constexpr const char *data () const { return d () + r; }
+	   constexpr R (int x) : r (x) {}
+	   int r; };
+static_assert (false, R (0));	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+				// { dg-error "static assertion failed: test" "" { target *-*-* } .-1 }
+static_assert (false, R (2));	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+				// { dg-error "static assertion failed: st" "" { target *-*-* } .-1 }
+struct S { constexpr float size (float) const { return 42.0f; }
+	   constexpr int size (void * = nullptr) const { return 4; }
+	   constexpr double data (double) const { return 42.0; }
+	   constexpr const char *data (int = 0) const { return "test"; } };
+static_assert (true, S {});	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+static_assert (false, S {});	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+				// { dg-error "static assertion failed: test" "" { target *-*-* } .-1 }
+
+using size_t = decltype (sizeof (0));
+struct string_view {
+  size_t s;
+  const char *d;
+  constexpr string_view () : s (0), d (nullptr) {}
+  constexpr string_view (const char *p) : s (__builtin_strlen (p)), d (p) {}
+  constexpr string_view (size_t l, const char *p) : s (l), d (p) {}
+  constexpr size_t size () const noexcept { return s; }
+  constexpr const char *data () const noexcept { return d; }
+};
+static_assert (true, string_view{});				// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+static_assert (false, string_view ("test"));			// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+								// { dg-error "static assertion failed: test" "" { target *-*-* } .-1 }
+static_assert (false, string_view ("א"));			// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+								// { dg-error "static assertion failed: א" "" { target *-*-* } .-1 }
+static_assert (false, string_view (0, nullptr));		// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+								// { dg-error "static assertion failed" "" { target *-*-* } .-1 }
+static_assert (false, string_view (4, "testwithextrachars"));	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+								// { dg-error "static assertion failed: test" "" { target *-*-* } .-1 }
+static_assert (false, string_view (42, "test"));		// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+								// { dg-error "array subscript value '41' is outside the bounds of array type 'const char \\\[5\\\]'" "" { target *-*-* } .-1 }
+								// { dg-error "'static_assert' message 'data\\\(\\\)\\\[41\\\]' must be a constant expression" "" { target *-*-* } .-2 }
+
+template <typename T, size_t N>
+struct array {
+  constexpr size_t size () const { return N; }
+  constexpr const T *data () const { return a; }
+  const T a[N];
+};
+static_assert (true, array<char, 2> { 'O', 'K' });		// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+static_assert (true, array<wchar_t, 2> { L'O', L'K' });		// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+								// { dg-error "'static_assert' message 'data\\\(\\\)' must be implicitly convertible to 'const char\\\*'" "" { target *-*-* } .-1 }
+								// { dg-error "could not convert 'array<wchar_t, 2>{const wchar_t \\\[2\\\]{\[0-9]+, \[0-9]+}}.array<wchar_t, 2>::data\\\(\\\)' from 'const wchar_t\\\*' to 'const char\\\*'" "" { target *-*-* } .-2 }
+static_assert (false, array<char, 4> { 't', 'e', 's', 't' });	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+								// { dg-error "static assertion failed: test" "" { target *-*-* } .-1 }
+
+void
+foo ()
+{
+  constexpr auto a = array<char, 4> { 't', 'e', 's', 't' };
+  static_assert (false, a);					// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+}								// { dg-error "static assertion failed: test" "" { target *-*-* } .-1 }
+
+#if  __cpp_constexpr_dynamic_alloc >= 201907L
+struct T {
+  const char *d = init ();
+  constexpr int size () const { return 4; }
+  constexpr const char *data () const { return d; }
+  constexpr const char *init () const { return new char[4] { 't', 'e', 's', 't' }; }
+  constexpr ~T () { delete[] d; }
+};
+static_assert (false, T{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target { c++20 && c++23_down } } }
+					// { dg-error "static assertion failed: test" "" { target c++20 } .-1 }
+#endif
+struct U { constexpr operator const char * () const { return u; }
+	   char u[5] = "test"; };
+#if __cplusplus >= 201402L
+struct V { constexpr auto size () const { return K{}; }
+	   constexpr auto data () const { return U{}; } };
+static_assert (false, V{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target { c++14 && c++23_down } } }
+					// { dg-error "static assertion failed: test" "" { target c++14 } .-1 }
+#endif
+struct W { constexpr int size (int) const { return 4; }
+	   constexpr const char *data () const { return "test"; } };
+static_assert (true, W{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+					// { dg-error "no matching function for call to 'W::size\\\(\\\)'" "" { target *-*-* } .-1 }
+struct X { constexpr int size () const { return 4; }
+	   constexpr const char *data (int) const { return "test"; } };
+static_assert (true, X{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+					// { dg-error "no matching function for call to 'X::data\\\(\\\)'" "" { target *-*-* } .-1 }
+struct Y { constexpr int size () { return 4; }
+	   constexpr const char *data (int) { return "test"; } };
+static_assert (true, Y{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+					// { dg-error "no matching function for call to 'Y::data\\\(\\\)'" "" { target *-*-* } .-1 }
+#if __cpp_concepts >= 201907L
+struct Z { constexpr int size (auto...) const { return 4; }
+	   constexpr const char *data (auto...) const { return "test"; } };
+static_assert (false, Z{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target { c++20 && c++23_down } } }
+					// { dg-error "static assertion failed: test" "" { target c++20 } .-1 }
+#endif
+
+namespace NN
+{
+  template <typename T>
+  struct A {
+    constexpr int size () const = delete;
+    constexpr const char *data () const { return "test"; } };
+  static_assert (true, A<int>{});	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+					// { dg-error "use of deleted function 'constexpr int NN::A<T>::size\\\(\\\) const \\\[with T = int\\\]'" "" { target *-*-* } .-1 }
+#if __cpp_concepts >= 201907L
+  template <typename T>
+  struct B {
+    constexpr int size () const { return 4; }
+    constexpr const char *data () const requires false { return "test"; } };
+  static_assert (true, B<short>{});	// { dg-warning "'static_assert' with non-string message only available with" "" { target { c++20 && c++23_down } } }
+					// { dg-error "no matching function for call to 'NN::B<short int>::data\\\(\\\)'" "" { target c++20 } .-1 }
+#endif
+  class C {
+    constexpr int size () const = delete;
+    constexpr const char *data () const { return "test"; } };
+  static_assert (true, C{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+					// { dg-error "use of deleted function 'constexpr int NN::C::size\\\(\\\) const'" "" { target *-*-* } .-1 }
+					// { dg-error "'constexpr const char\\\* NN::C::data\\\(\\\) const' is private within this context" "" { target *-*-* } .-2 }
+#if __cplusplus >= 201402L
+  struct D {
+    constexpr int size () { return 4; }
+    constexpr int size () const { return 3; }
+    constexpr const char *data () { return "test"; }
+    constexpr const char *data () const { return "ehlo"; } };
+  static_assert (true, D{});	// { dg-warning "'static_assert' with non-string message only available with" "" { target { c++14 && c++23_down } } }
+  static_assert (false, D{});	// { dg-warning "'static_assert' with non-string message only available with" "" { target { c++14 && c++23_down } } }
+				// { dg-error "static assertion failed: test" "" { target c++14 } .-1 }
+#endif
+  struct E {
+    constexpr int size () const { return 4; }
+    constexpr const char *data () const { return "test"; } };
+  template <typename T>
+  struct F {
+    static_assert (false, T{});	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+  };				// { dg-error "static assertion failed: test" "" { target *-*-* } .-1 }
+  template <typename T>
+  struct G {
+    static_assert (false, T{});	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+  };				// { dg-error "'static_assert' message must be a string literal or object with 'size' and 'data' members" "" { target *-*-* } .-1 }
+				// { dg-error "request for member 'size' in '0', which is of non-class type 'long int'" "" { target *-*-* } .-2 }
+  F<E> fe;
+  G<long> gl;
+  constexpr E operator ""_myd (const char *, size_t) { return E{}; }
+  static_assert (false, "foo"_myd);	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+					// { dg-error "static assertion failed: test" "" { target *-*-* } .-1 }
+  constexpr E operator + (const char *, const E &) { return E{}; }
+  static_assert (false, "foo" + E{});	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+					// { dg-error "static assertion failed: test" "" { target *-*-* } .-1 }
+  struct H {
+    static constexpr int size () { return 7; }
+    static constexpr const char *data () { return "message"; } };
+  static_assert (true, H{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+  static_assert (false, H{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+					// { dg-error "static assertion failed: message" "" { target *-*-* } .-1 }
+  struct I {
+    static constexpr int size () { return 0; }
+    static constexpr const char *data () { return nullptr; } };
+  static_assert (true, I{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+  static_assert (false, I{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+					// { dg-error "static assertion failed" "" { target *-*-* } .-1 }
+#if __cplusplus >= 201402L
+  struct J {
+    static constexpr int size () { return 0; }
+    static constexpr const char *data (int x = 0) { if (x) return nullptr; else throw 1; } }; // { dg-error "expression '<throw-expression>' is not a constant expression" "" { target c++14 } }
+  static_assert (true, J{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target { c++14 && c++23_down } } }
+  static_assert (false, J{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target { c++14 && c++23_down } } }
+					// { dg-error "'static_assert' message 'data\\\(\\\)' must be a core constant expression" "" { target c++14 } .-1 }
+#endif
+#if __cpp_if_consteval >= 202106L
+  struct K {
+    static constexpr int size () { if consteval { return 4; } else { throw 1; } }
+    static constexpr const char *data () { return "test"; }
+  };
+  static_assert (true, K{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_only } }
+  static_assert (false, K{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_only } }
+					// { dg-error "static assertion failed: test" "" { target c++23 } .-1 }
+  struct L {
+    static constexpr int size () { return 4; }
+    static constexpr const char *data () { if consteval { return "test"; } else { throw 1; } }
+  };
+  static_assert (true, L{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_only } }
+  static_assert (false, L{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_only } }
+					// { dg-error "static assertion failed: test" "" { target c++23 } .-1 }
+  struct M {
+    static constexpr int size () { if consteval { throw 1; } else { return 4; } } // { dg-error "expression '<throw-expression>' is not a constant expression" "" { target c++23 } }
+    static constexpr const char *data () { return "test"; }
+  };
+  static_assert (true, M{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_only } }
+  static_assert (false, M{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_only } }
+					// { dg-error "'static_assert' message 'size\\\(\\\)' must be a constant expression" "" { target c++23 } .-1 }
+  struct N {
+    static constexpr int size () { return 4; }
+    static constexpr const char *data () { if consteval { throw 1; } else { return "test"; } } // { dg-error "expression '<throw-expression>' is not a constant expression" "" { target c++23 } }
+  };
+  static_assert (true, N{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_only } }
+  static_assert (false, N{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_only } }
+					// { dg-error "'static_assert' message 'data\\\(\\\)\\\[0\\\]' must be a constant expression" "" { target c++23 } .-1 }
+#endif
+  struct O { constexpr int operator () () const { return 12; } };
+  struct P { constexpr const char *operator () () const { return "another test"; } };
+  struct Q { O size; P data; };
+  static_assert (true, Q ());	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+  static_assert (false, Q {});	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+				// { dg-error "static assertion failed: another test" "" { target *-*-* } .-1 }
+  constexpr int get_size () { return 16; }
+  constexpr const char *get_data () { return "yet another test"; }
+  struct R { int (*size) () = NN::get_size;
+	     const char *(*data) () = NN::get_data; };
+  static_assert (true, R ());	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+  static_assert (false, R {});	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+				// { dg-error "static assertion failed: yet another test" "" { target *-*-* } .-1 }
+}
--- gcc/testsuite/g++.dg/cpp26/feat-cxx26.C.jj	2023-11-22 10:14:56.057375857 +0100
+++ gcc/testsuite/g++.dg/cpp26/feat-cxx26.C	2023-11-22 10:17:41.340064988 +0100
@@ -304,8 +304,8 @@
 
 #ifndef __cpp_static_assert
 #  error "__cpp_static_assert"
-#elif __cpp_static_assert != 201411
-#  error "__cpp_static_assert != 201411"
+#elif __cpp_static_assert != 202306
+#  error "__cpp_static_assert != 202306"
 #endif
 
 #ifndef __cpp_namespace_attributes
--- gcc/testsuite/g++.dg/cpp0x/udlit-error1.C.jj	2023-11-22 10:14:56.037376137 +0100
+++ gcc/testsuite/g++.dg/cpp0x/udlit-error1.C	2023-11-22 10:38:53.298282525 +0100
@@ -11,7 +11,9 @@ void operator""_x(const char *, decltype
 #pragma message "hi"_x	  // { dg-warning "string literal with user-defined suffix is invalid in this context" }
 
 extern "C"_x { void g(); } // { dg-error "before user-defined string literal" }
-static_assert(true, "foo"_x); // { dg-error "string literal with user-defined suffix is invalid in this context|expected" }
+static_assert(true, "foo"_x);	// { dg-error "'static_assert' with non-string message only available with" "" { target c++23_down } }
+				// { dg-error "'static_assert' message must be a string literal or object with 'size' and 'data' members" "" { target *-*-* } .-1 }
+				// { dg-error "invalid use of 'void'" "" { target *-*-* } .-2 }
 
 [[deprecated("oof"_x)]]	// { dg-error "string literal with user-defined suffix is invalid in this context" "" { target c++26 } }
 void


	Jakub
  
Jason Merrill Nov. 28, 2023, 4:31 p.m. UTC | #3
On 11/23/23 03:32, Jakub Jelinek wrote:
> On Wed, Nov 22, 2023 at 04:53:48PM -0500, Jason Merrill wrote:
>> I agree it's weird to get two of the same error, but maybe instead of
>> duplicating the error, we could look up data only if size succeeded, and
>> then error once if either failed?
> 
> Here is what I've committed after another bootstrap/regtest on x86_64-linux
> and i686-linux.  Besides the above requested change I've tweaked 2 lines
> in the test not to rely on a particular std::size_t exact type because
> otherwise the test failed on i686-linux.  And accepting there only the
> current
> unsigned int
> long unsigned int
> long long unsinged int
> unsigned __int20__ (or how exactly is this one spelled in diagnostics)
> seems fragile.
> 
> --- gcc/cp/semantics.cc.jj	2023-11-22 11:30:08.019325101 +0100
> +++ gcc/cp/semantics.cc	2023-11-22 22:58:25.194480633 +0100
> @@ -11485,9 +11534,89 @@ finish_static_assert (tree condition, tr
>   	  if (processing_template_decl)
>   	    goto defer;
>   
> -	  int sz = TREE_INT_CST_LOW (TYPE_SIZE_UNIT
> -				     (TREE_TYPE (TREE_TYPE (message))));
> -	  int len = TREE_STRING_LENGTH (message) / sz - 1;
> +	  int len;
> +	  const char *msg = NULL;
> +	  char *buf = NULL;
> +	  if (message_sz && message_data)
> +	    {
> +	      tree msz = cxx_constant_value (message_sz, NULL_TREE, complain);
> +	      if (!tree_fits_uhwi_p (msz))
> +		{
> +		  error_at (location,
> +			    "%<static_assert%> message %<size()%> "
> +			    "must be a constant expression");
> +		  return;
> +		}
> +	      else if ((unsigned HOST_WIDE_INT) (int) tree_to_uhwi (msz)
> +		       != tree_to_uhwi (msz))
> +		{
> +		  error_at (location,
> +			    "%<static_assert%> message %<size()%> "
> +			    "%qE too large", msz);
> +		  return;
> +		}
> +	      len = tree_to_uhwi (msz);
> +	      tree data = maybe_constant_value (message_data, NULL_TREE,
> +						mce_true);
> +	      if (!reduced_constant_expression_p (data))
> +		data = NULL_TREE;
> +	      if (len)
> +		{
> +		  if (data)
> +		    msg = c_getstr (data);
> +		  if (msg == NULL)
> +		    buf = XNEWVEC (char, len);

Jonathan pointed out elsewhere that this gets leaked if error return 
prevents us from getting to the XDELETEVEC.

> +		  for (int i = 0; i < len; ++i)
> +		    {
> +		      tree t = message_data;
> +		      if (i)
> +			t = build2 (POINTER_PLUS_EXPR,
> +				    TREE_TYPE (message_data), message_data,
> +				    size_int (i));
> +		      t = build1 (INDIRECT_REF, TREE_TYPE (TREE_TYPE (t)), t);
> +		      tree t2 = cxx_constant_value (t, NULL_TREE, complain);
> +		      if (!tree_fits_shwi_p (t2))
> +			{
> +			  error_at (location,
> +				    "%<static_assert%> message %<data()[%d]%> "
> +				    "must be a constant expression", i);
> +			  return;
> +			}
> +		      if (msg == NULL)
> +			buf[i] = tree_to_shwi (t2);
> +		      /* If c_getstr worked, just verify the first and
> +			 last characters using constant evaluation.  */
> +		      else if (len > 2 && i == 0)
> +			i = len - 2;
> +		    }
> +		  if (msg == NULL)
> +		    msg = buf;
> +		}
> +	      else if (!data)
> +		{
> +		  /* We don't have any function to test whether some
> +		     expression is a core constant expression.  So, instead
> +		     test whether (message.data (), 0) is a constant
> +		     expression.  */
> +		  data = build2 (COMPOUND_EXPR, integer_type_node,
> +				 message_data, integer_zero_node);
> +		  tree t = cxx_constant_value (data, NULL_TREE, complain);
> +		  if (!integer_zerop (t))
> +		    {
> +		      error_at (location,
> +				"%<static_assert%> message %<data()%> "
> +				"must be a core constant expression");
> +		      return;
> +		    }
> +		}
> +	    }
> +	  else
> +	    {
> +	      tree eltype = TREE_TYPE (TREE_TYPE (message));
> +	      int sz = TREE_INT_CST_LOW (TYPE_SIZE_UNIT (eltype));
> +	      msg = TREE_STRING_POINTER (message);
> +	      len = TREE_STRING_LENGTH (message) / sz - 1;
> +	    }
>   
>   	  /* See if we can find which clause was failing (for logical AND).  */
>   	  tree bad = find_failing_clause (NULL, orig_condition);
> @@ -11497,12 +11626,13 @@ finish_static_assert (tree condition, tr
>   
>   	  auto_diagnostic_group d;
>   
> -          /* Report the error. */
> +	  /* Report the error. */
>   	  if (len == 0)
>   	    error_at (cloc, "static assertion failed");
>   	  else
> -	    error_at (cloc, "static assertion failed: %s",
> -		      TREE_STRING_POINTER (message));
> +	    error_at (cloc, "static assertion failed: %.*s", len, msg);
> +
> +	  XDELETEVEC (buf);
>   
>   	  diagnose_failing_condition (bad, cloc, show_expr_p);
>   	}
  
Jakub Jelinek Nov. 28, 2023, 5:52 p.m. UTC | #4
On Tue, Nov 28, 2023 at 11:31:48AM -0500, Jason Merrill wrote:
> > +	      if (len)
> > +		{
> > +		  if (data)
> > +		    msg = c_getstr (data);
> > +		  if (msg == NULL)
> > +		    buf = XNEWVEC (char, len);
> 
> Jonathan pointed out elsewhere that this gets leaked if error return
> prevents us from getting to the XDELETEVEC.

Seems it is just one of the returns, so ok to just XDELETEVEC there,
or should I add some RAII for that?  The other error return after
this point is for !len case and so buf isn't allocated.

2023-11-28  Jakub Jelinek  <jakub@redhat.com>

	* semantics.cc (finish_static_assert): Free buf on error return.

--- gcc/cp/semantics.cc.jj	2023-11-25 10:28:27.778191561 +0100
+++ gcc/cp/semantics.cc	2023-11-28 18:50:00.094733919 +0100
@@ -11582,6 +11582,7 @@ finish_static_assert (tree condition, tr
 			  error_at (location,
 				    "%<static_assert%> message %<data()[%d]%> "
 				    "must be a constant expression", i);
+			  XDELETEVEC (buf);
 			  return;
 			}
 		      if (msg == NULL)


	Jakub
  
Jason Merrill Nov. 28, 2023, 9:33 p.m. UTC | #5
On 11/28/23 12:52, Jakub Jelinek wrote:
> On Tue, Nov 28, 2023 at 11:31:48AM -0500, Jason Merrill wrote:
>>> +	      if (len)
>>> +		{
>>> +		  if (data)
>>> +		    msg = c_getstr (data);
>>> +		  if (msg == NULL)
>>> +		    buf = XNEWVEC (char, len);
>>
>> Jonathan pointed out elsewhere that this gets leaked if error return
>> prevents us from getting to the XDELETEVEC.
> 
> Seems it is just one of the returns, so ok to just XDELETEVEC there,
> or should I add some RAII for that?  The other error return after
> this point is for !len case and so buf isn't allocated.

RAII is generally preferable, but this is sufficient.

> 2023-11-28  Jakub Jelinek  <jakub@redhat.com>
> 
> 	* semantics.cc (finish_static_assert): Free buf on error return.
> 
> --- gcc/cp/semantics.cc.jj	2023-11-25 10:28:27.778191561 +0100
> +++ gcc/cp/semantics.cc	2023-11-28 18:50:00.094733919 +0100
> @@ -11582,6 +11582,7 @@ finish_static_assert (tree condition, tr
>   			  error_at (location,
>   				    "%<static_assert%> message %<data()[%d]%> "
>   				    "must be a constant expression", i);
> +			  XDELETEVEC (buf);
>   			  return;
>   			}
>   		      if (msg == NULL)
> 
> 
> 	Jakub
>
  

Patch

--- gcc/doc/invoke.texi.jj	2023-11-22 10:14:56.021376360 +0100
+++ gcc/doc/invoke.texi	2023-11-22 10:17:41.328065157 +0100
@@ -9107,6 +9107,13 @@  Do not warn about C++23 constructs in co
 an older C++ standard.  Even without this option, some C++23 constructs
 will only be diagnosed if @option{-Wpedantic} is used.
 
+@opindex Wc++26-extensions
+@opindex Wno-c++26-extensions
+@item -Wno-c++26-extensions @r{(C++ and Objective-C++ only)}
+Do not warn about C++26 constructs in code being compiled using
+an older C++ standard.  Even without this option, some C++26 constructs
+will only be diagnosed if @option{-Wpedantic} is used.
+
 @opindex Wcast-qual
 @opindex Wno-cast-qual
 @item -Wcast-qual
--- gcc/c-family/c.opt.jj	2023-11-22 10:14:55.963377171 +0100
+++ gcc/c-family/c.opt	2023-11-22 10:17:41.328065157 +0100
@@ -498,6 +498,10 @@  Wc++23-extensions
 C++ ObjC++ Var(warn_cxx23_extensions) Warning Init(1)
 Warn about C++23 constructs in code compiled with an older standard.
 
+Wc++26-extensions
+C++ ObjC++ Var(warn_cxx26_extensions) Warning Init(1)
+Warn about C++26 constructs in code compiled with an older standard.
+
 Wcast-function-type
 C ObjC C++ ObjC++ Var(warn_cast_function_type) Warning EnabledBy(Wextra)
 Warn about casts between incompatible function types.
--- gcc/c-family/c-cppbuiltin.cc.jj	2023-11-22 10:14:55.962377185 +0100
+++ gcc/c-family/c-cppbuiltin.cc	2023-11-22 10:17:41.329065143 +0100
@@ -1023,7 +1023,8 @@  c_cpp_builtins (cpp_reader *pfile)
 	{
 	  /* Set feature test macros for C++17.  */
 	  cpp_define (pfile, "__cpp_unicode_characters=201411L");
-	  cpp_define (pfile, "__cpp_static_assert=201411L");
+	  if (cxx_dialect <= cxx23)
+	    cpp_define (pfile, "__cpp_static_assert=201411L");
 	  cpp_define (pfile, "__cpp_namespace_attributes=201411L");
 	  cpp_define (pfile, "__cpp_enumerator_attributes=201411L");
 	  cpp_define (pfile, "__cpp_nested_namespace_definitions=201411L");
@@ -1086,6 +1087,7 @@  c_cpp_builtins (cpp_reader *pfile)
 	{
 	  /* Set feature test macros for C++26.  */
 	  cpp_define (pfile, "__cpp_constexpr=202306L");
+	  cpp_define (pfile, "__cpp_static_assert=202306L");
 	}
       if (flag_concepts)
         {
--- gcc/cp/parser.cc.jj	2023-11-22 10:14:55.969377087 +0100
+++ gcc/cp/parser.cc	2023-11-22 10:17:41.335065058 +0100
@@ -16616,6 +16616,7 @@  cp_parser_linkage_specification (cp_pars
    static_assert-declaration:
      static_assert ( constant-expression , string-literal ) ;
      static_assert ( constant-expression ) ; (C++17)
+     static_assert ( constant-expression, conditional-expression ) ; (C++26)
 
    If MEMBER_P, this static_assert is a class member.  */
 
@@ -16646,10 +16647,10 @@  cp_parser_static_assert (cp_parser *pars
 
   /* Parse the constant-expression.  Allow a non-constant expression
      here in order to give better diagnostics in finish_static_assert.  */
-  condition =
-    cp_parser_constant_expression (parser,
-                                   /*allow_non_constant_p=*/true,
-				   /*non_constant_p=*/nullptr);
+  condition
+    = cp_parser_constant_expression (parser,
+				     /*allow_non_constant_p=*/true,
+				     /*non_constant_p=*/nullptr);
 
   if (cp_lexer_peek_token (parser->lexer)->type == CPP_CLOSE_PAREN)
     {
@@ -16668,8 +16669,32 @@  cp_parser_static_assert (cp_parser *pars
       /* Parse the separating `,'.  */
       cp_parser_require (parser, CPP_COMMA, RT_COMMA);
 
-      /* Parse the string-literal message.  */
-      if (cxx_dialect >= cxx26)
+      /* Parse the message expression.  */
+      bool string_lit = true;
+      for (unsigned int i = 1; ; ++i)
+	{
+	  cp_token *tok = cp_lexer_peek_nth_token (parser->lexer, i);
+	  if (cp_parser_is_pure_string_literal (tok))
+	    continue;
+	  else if (tok->type == CPP_CLOSE_PAREN)
+	    break;
+	  string_lit = false;
+	  break;
+	}
+      if (!string_lit)
+	{
+	  location_t loc = cp_lexer_peek_token (parser->lexer)->location;
+	  if (cxx_dialect < cxx26)
+	    pedwarn (loc, OPT_Wc__26_extensions,
+		     "%<static_assert%> with non-string message only "
+		     "available with %<-std=c++2c%> or %<-std=gnu++2c%>");
+
+	  message = cp_parser_conditional_expression (parser);
+	  if (TREE_CODE (message) == STRING_CST)
+	    message = build1_loc (loc, PAREN_EXPR, TREE_TYPE (message),
+				  message);
+	}
+      else if (cxx_dialect >= cxx26)
 	message = cp_parser_unevaluated_string_literal (parser);
       else
 	message = cp_parser_string_literal (parser, /*translate=*/false,
--- gcc/cp/semantics.cc.jj	2023-11-22 10:14:56.015376445 +0100
+++ gcc/cp/semantics.cc	2023-11-22 10:54:16.295379209 +0100
@@ -11434,6 +11434,7 @@  finish_static_assert (tree condition, tr
 		      bool member_p, bool show_expr_p)
 {
   tsubst_flags_t complain = tf_warning_or_error;
+  tree message_sz = NULL_TREE, message_data = NULL_TREE;
 
   if (message == NULL_TREE
       || message == error_mark_node
@@ -11441,13 +11442,67 @@  finish_static_assert (tree condition, tr
       || condition == error_mark_node)
     return;
 
-  if (check_for_bare_parameter_packs (condition))
-    condition = error_mark_node;
+  if (check_for_bare_parameter_packs (condition)
+      || check_for_bare_parameter_packs (message))
+    return;
+
+  if (TREE_CODE (message) != STRING_CST
+      && !type_dependent_expression_p (message))
+    {
+      message_sz
+	= finish_class_member_access_expr (message,
+					   get_identifier ("size"),
+					   false, complain);
+      if (message_sz == error_mark_node)
+	{
+	  error_at (location, "%<static_assert%> message must be a string "
+			      "literal or object with %<size%> and "
+			      "%<data%> members");
+	  return;
+	}
+      message_data
+	= finish_class_member_access_expr (message,
+					   get_identifier ("data"),
+					   false, complain);
+      if (message_data == error_mark_node)
+	{
+	  error_at (location, "%<static_assert%> message must be a string "
+			      "literal or object with %<size%> and "
+			      "%<data%> members");
+	  return;
+	}
+      releasing_vec size_args, data_args;
+      message_sz = finish_call_expr (message_sz, &size_args, false, false,
+				     complain);
+      message_data = finish_call_expr (message_data, &data_args, false, false,
+				       complain);
+      if (message_sz == error_mark_node || message_data == error_mark_node)
+	return;
+      message_sz = build_converted_constant_expr (size_type_node, message_sz,
+						  complain);
+      if (message_sz == error_mark_node)
+	{
+	  error_at (location, "%<static_assert%> message %<size()%> "
+			      "must be implicitly convertible to "
+			      "%<std::size_t%>");
+	  return;
+	}
+      message_data = build_converted_constant_expr (const_string_type_node,
+						    message_data, complain);
+      if (message_data == error_mark_node)
+	{
+	  error_at (location, "%<static_assert%> message %<data()%> "
+			      "must be implicitly convertible to "
+			      "%<const char*%>");
+	  return;
+	}
+    }
 
   /* Save the condition in case it was a concept check.  */
   tree orig_condition = condition;
 
-  if (instantiation_dependent_expression_p (condition))
+  if (instantiation_dependent_expression_p (condition)
+      || instantiation_dependent_expression_p (message))
     {
       /* We're in a template; build a STATIC_ASSERT and put it in
          the right place. */
@@ -11485,9 +11540,89 @@  finish_static_assert (tree condition, tr
 	  if (processing_template_decl)
 	    goto defer;
 
-	  int sz = TREE_INT_CST_LOW (TYPE_SIZE_UNIT
-				     (TREE_TYPE (TREE_TYPE (message))));
-	  int len = TREE_STRING_LENGTH (message) / sz - 1;
+	  int len;
+	  const char *msg = NULL;
+	  char *buf = NULL;
+	  if (message_sz && message_data)
+	    {
+	      tree msz = cxx_constant_value (message_sz, NULL_TREE, complain);
+	      if (!tree_fits_uhwi_p (msz))
+		{
+		  error_at (location,
+			    "%<static_assert%> message %<size()%> "
+			    "must be a constant expression");
+		  return;
+		}
+	      else if ((unsigned HOST_WIDE_INT) (int) tree_to_uhwi (msz)
+		       != tree_to_uhwi (msz))
+		{
+		  error_at (location,
+			    "%<static_assert%> message %<size()%> "
+			    "%qE too large", msz);
+		  return;
+		}
+	      len = tree_to_uhwi (msz);
+	      tree data = maybe_constant_value (message_data, NULL_TREE,
+						mce_true);
+	      if (!reduced_constant_expression_p (data))
+		data = NULL_TREE;
+	      if (len)
+		{
+		  if (data)
+		    msg = c_getstr (data);
+		  if (msg == NULL)
+		    buf = XNEWVEC (char, len);
+		  for (int i = 0; i < len; ++i)
+		    {
+		      tree t = message_data;
+		      if (i)
+			t = build2 (POINTER_PLUS_EXPR,
+				    TREE_TYPE (message_data), message_data,
+				    size_int (i));
+		      t = build1 (INDIRECT_REF, TREE_TYPE (TREE_TYPE (t)), t);
+		      tree t2 = cxx_constant_value (t, NULL_TREE, complain);
+		      if (!tree_fits_shwi_p (t2))
+			{
+			  error_at (location,
+				    "%<static_assert%> message %<data()[%d]%> "
+				    "must be a constant expression", i);
+			  return;
+			}
+		      if (msg == NULL)
+			buf[i] = tree_to_shwi (t2);
+		      /* If c_getstr worked, just verify the first and
+			 last characters using constant evaluation.  */
+		      else if (len > 2 && i == 0)
+			i = len - 2;
+		    }
+		  if (msg == NULL)
+		    msg = buf;
+		}
+	      else if (!data)
+		{
+		  /* We don't have any function to test whether some
+		     expression is a core constant expression.  So, instead
+		     test whether (message.data (), 0) is a constant
+		     expression.  */
+		  data = build2 (COMPOUND_EXPR, integer_type_node,
+				 message_data, integer_zero_node);
+		  tree t = cxx_constant_value (data, NULL_TREE, complain);
+		  if (!integer_zerop (t))
+		    {
+		      error_at (location,
+				"%<static_assert%> message %<data()%> "
+				"must be a core constant expression");
+		      return;
+		    }
+		}
+	    }
+	  else
+	    {
+	      tree eltype = TREE_TYPE (TREE_TYPE (message));
+	      int sz = TREE_INT_CST_LOW (TYPE_SIZE_UNIT (eltype));
+	      msg = TREE_STRING_POINTER (message);
+	      len = TREE_STRING_LENGTH (message) / sz - 1;
+	    }
 
 	  /* See if we can find which clause was failing (for logical AND).  */
 	  tree bad = find_failing_clause (NULL, orig_condition);
@@ -11497,12 +11632,13 @@  finish_static_assert (tree condition, tr
 
 	  auto_diagnostic_group d;
 
-          /* Report the error. */
+	  /* Report the error. */
 	  if (len == 0)
 	    error_at (cloc, "static assertion failed");
 	  else
-	    error_at (cloc, "static assertion failed: %s",
-		      TREE_STRING_POINTER (message));
+	    error_at (cloc, "static assertion failed: %.*s", len, msg);
+
+	  XDELETEVEC (buf);
 
 	  diagnose_failing_condition (bad, cloc, show_expr_p);
 	}
--- gcc/cp/pt.cc.jj	2023-11-22 10:14:55.992376766 +0100
+++ gcc/cp/pt.cc	2023-11-22 10:17:41.340064988 +0100
@@ -18701,15 +18701,20 @@  tsubst_stmt (tree t, tree args, tsubst_f
 
     case STATIC_ASSERT:
       {
-	tree condition;
+	tree condition, message;
 
 	++c_inhibit_evaluation_warnings;
 	condition = tsubst_expr (STATIC_ASSERT_CONDITION (t), args,
 				 complain, in_decl);
+	message = tsubst_expr (STATIC_ASSERT_MESSAGE (t), args,
+			       complain, in_decl);
+	if (TREE_CODE (STATIC_ASSERT_MESSAGE (t)) != STRING_CST
+	    && TREE_CODE (message) == STRING_CST)
+	  message = build1_loc (STATIC_ASSERT_SOURCE_LOCATION (t),
+				PAREN_EXPR, TREE_TYPE (message), message);
 	--c_inhibit_evaluation_warnings;
 
-        finish_static_assert (condition,
-                              STATIC_ASSERT_MESSAGE (t),
+	finish_static_assert (condition, message,
                               STATIC_ASSERT_SOURCE_LOCATION (t),
 			      /*member_p=*/false, /*show_expr_p=*/true);
       }
--- gcc/testsuite/g++.dg/cpp26/static_assert1.C.jj	2023-11-22 10:17:41.340064988 +0100
+++ gcc/testsuite/g++.dg/cpp26/static_assert1.C	2023-11-22 10:47:45.045848504 +0100
@@ -0,0 +1,309 @@ 
+// C++26 P2741R3 - user-generated static_assert messages
+// { dg-do compile { target c++11 } }
+// { dg-options "" }
+
+static_assert (true, "");
+static_assert (true, (""));	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+				// { dg-error "'static_assert' message must be a string literal or object with 'size' and 'data' members" "" { target *-*-* } .-1 }
+				// { dg-error "request for member 'size' in '\\\(\\\"\\\"\\\)', which is of non-class type 'const char \\\[1\\\]'" "" { target *-*-* } .-2 }
+static_assert (true, "" + 0);	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+				// { dg-error "'static_assert' message must be a string literal or object with 'size' and 'data' members" "" { target *-*-* } .-1 }
+				// { dg-error "request for member 'size' in '\\\(const char\\\*\\\)\\\"\\\"', which is of non-class type 'const char\\\*'" "" { target *-*-* } .-2 }
+static_assert (true, 0);	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+				// { dg-error "'static_assert' message must be a string literal or object with 'size' and 'data' members" "" { target *-*-* } .-1 }
+				// { dg-error "request for member 'size' in '0', which is of non-class type 'int'" "" { target *-*-* } .-2 }
+struct A {};
+static_assert (true, A {});	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+				// { dg-error "'static_assert' message must be a string literal or object with 'size' and 'data' members" "" { target *-*-* } .-1 }
+				// { dg-error "'struct A' has no member named 'size'" "" { target *-*-* } .-2 }
+struct B { int size; };
+static_assert (true, B {});	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+				// { dg-error "'static_assert' message must be a string literal or object with 'size' and 'data' members" "" { target *-*-* } .-1 }
+				// { dg-error "'struct B' has no member named 'data'" "" { target *-*-* } .-2 }
+struct C { constexpr int size () const { return 0; } };
+static_assert (true, C {});	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+				// { dg-error "'static_assert' message must be a string literal or object with 'size' and 'data' members" "" { target *-*-* } .-1 }
+				// { dg-error "'struct C' has no member named 'data'" "" { target *-*-* } .-2 }
+struct D { constexpr int size () const { return 0; } int data; };
+static_assert (true, D {});	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+				// { dg-error "'D\\\(\\\).D::data' cannot be used as a function" "" { target *-*-* } .-1 }
+struct E { int size = 0;
+	   constexpr const char *data () const { return ""; } };
+static_assert (true, E {});	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+				// { dg-error "'E\\\(\\\).E::size' cannot be used as a function" "" { target c++11_only } .-1 }
+				// { dg-error "'E\\\{0\\\}.E::size' cannot be used as a function" "" { target c++14 } .-2 }
+struct F { constexpr const char *size () const { return ""; }
+	   constexpr const char *data () const { return ""; } };
+static_assert (true, F {});	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+				// { dg-error "'static_assert' message 'size\\\(\\\)' must be implicitly convertible to 'std::size_t'" "" { target *-*-* } .-1 }
+				// { dg-error "could not convert 'F\\\(\\\).F::size\\\(\\\)' from 'const char\\\*' to 'long unsigned int'" "" { target *-*-* } .-2 }
+				// { dg-error "conversion from 'const char\\\*' to 'long unsigned int' in a converted constant expression" "" { target *-*-* } .-3 }
+struct G { constexpr long size () const { return 0; }
+	   constexpr float data () const { return 0.0f; } };
+static_assert (true, G {});	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+				// { dg-error "'static_assert' message 'data\\\(\\\)' must be implicitly convertible to 'const char\\\*'" "" { target *-*-* } .-1 }
+				// { dg-error "could not convert 'G\\\(\\\).G::data\\\(\\\)' from 'float' to 'const char\\\*'" "" { target *-*-* } .-2 }
+struct H { short size () const { return 0; }
+	   constexpr const char *data () const { return ""; } };
+static_assert (true, H {});	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+struct I { constexpr signed char size () const { return 0; }
+	   const char *data () const { return ""; } };
+static_assert (true, I {});	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+struct J { constexpr int size () const { return j ? throw 1 : 0; }	// { dg-error "expression '<throw-expression>' is not a constant expression" }
+	   constexpr const char *data () const { return ""; };
+	   constexpr J (int x) : j (x) {}
+	   int j; };
+static_assert (true, J (1));	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+static_assert (false, J (0));	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+				// { dg-error "static assertion failed" "" { target *-*-* } .-1 }
+static_assert (false, J (1));	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+				// { dg-error "'static_assert' message 'size\\\(\\\)' must be a constant expression" "" { target *-*-* } .-1 }
+struct K { constexpr operator int () { return 4; } };
+struct L { constexpr operator const char * () { return "test"; } };
+struct M { constexpr K size () const { return {}; }
+	   constexpr L data () const { return {}; } };
+static_assert (true, M {});	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+static_assert (false, M {});	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+				// { dg-error "static assertion failed: test" "" { target *-*-* } .-1 }
+#if  __cpp_constexpr_dynamic_alloc >= 201907L
+struct N { constexpr int size () const { return 3; }
+	   constexpr const char *data () const { return new char[3] { 'b', 'a', 'd' }; } }; // { dg-error "'\\\* N\\\(\\\).N::data\\\(\\\)' is not a constant expression because allocated storage has not been deallocated" "" { target c++20 } }
+static_assert (true, N {});	// { dg-warning "'static_assert' with non-string message only available with" "" { target { c++20 && c++23_down } } }
+static_assert (false, N {});	// { dg-warning "'static_assert' with non-string message only available with" "" { target { c++20 && c++23_down } } }
+				// { dg-error "'static_assert' message 'data\\\(\\\)\\\[0\\\]' must be a constant expression" "" { target c++20 } .-1 }
+#endif
+constexpr const char a[] = { 't', 'e', 's', 't' };
+struct O { constexpr int size () const { return 4; }
+	   constexpr const char *data () const { return a; } };
+static_assert (false, O {});	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+				// { dg-error "static assertion failed: test" "" { target *-*-* } .-1 }
+struct P { constexpr int size () const { return 4 - p; }
+	   constexpr const char *data () const { return &a[p]; }
+	   constexpr P (int x) : p (x) {}
+	   int p; };
+static_assert (false, P (0));	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+				// { dg-error "static assertion failed: test" "" { target *-*-* } .-1 }
+static_assert (false, P (2));	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+				// { dg-error "static assertion failed: st" "" { target *-*-* } .-1 }
+struct Q { constexpr int size () const { return 4 - q; }
+	   constexpr const char *data () const { return &"test"[q]; }
+	   constexpr Q (int x) : q (x) {}
+	   int q; };
+static_assert (false, Q (0));	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+				// { dg-error "static assertion failed: test" "" { target *-*-* } .-1 }
+static_assert (false, Q (1));	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+				// { dg-error "static assertion failed: est" "" { target *-*-* } .-1 }
+struct R { constexpr int size () const { return 4 - r; }
+	   constexpr const char *d () const { return "test"; }
+	   constexpr const char *data () const { return d () + r; }
+	   constexpr R (int x) : r (x) {}
+	   int r; };
+static_assert (false, R (0));	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+				// { dg-error "static assertion failed: test" "" { target *-*-* } .-1 }
+static_assert (false, R (2));	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+				// { dg-error "static assertion failed: st" "" { target *-*-* } .-1 }
+struct S { constexpr float size (float) const { return 42.0f; }
+	   constexpr int size (void * = nullptr) const { return 4; }
+	   constexpr double data (double) const { return 42.0; }
+	   constexpr const char *data (int = 0) const { return "test"; } };
+static_assert (true, S {});	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+static_assert (false, S {});	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+				// { dg-error "static assertion failed: test" "" { target *-*-* } .-1 }
+
+using size_t = decltype (sizeof (0));
+struct string_view {
+  size_t s;
+  const char *d;
+  constexpr string_view () : s (0), d (nullptr) {}
+  constexpr string_view (const char *p) : s (__builtin_strlen (p)), d (p) {}
+  constexpr string_view (size_t l, const char *p) : s (l), d (p) {}
+  constexpr size_t size () const noexcept { return s; }
+  constexpr const char *data () const noexcept { return d; }
+};
+static_assert (true, string_view{});				// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+static_assert (false, string_view ("test"));			// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+								// { dg-error "static assertion failed: test" "" { target *-*-* } .-1 }
+static_assert (false, string_view ("א"));			// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+								// { dg-error "static assertion failed: א" "" { target *-*-* } .-1 }
+static_assert (false, string_view (0, nullptr));		// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+								// { dg-error "static assertion failed" "" { target *-*-* } .-1 }
+static_assert (false, string_view (4, "testwithextrachars"));	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+								// { dg-error "static assertion failed: test" "" { target *-*-* } .-1 }
+static_assert (false, string_view (42, "test"));		// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+								// { dg-error "array subscript value '41' is outside the bounds of array type 'const char \\\[5\\\]'" "" { target *-*-* } .-1 }
+								// { dg-error "'static_assert' message 'data\\\(\\\)\\\[41\\\]' must be a constant expression" "" { target *-*-* } .-2 }
+
+template <typename T, size_t N>
+struct array {
+  constexpr size_t size () const { return N; }
+  constexpr const T *data () const { return a; }
+  const T a[N];
+};
+static_assert (true, array<char, 2> { 'O', 'K' });		// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+static_assert (true, array<wchar_t, 2> { L'O', L'K' });		// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+								// { dg-error "'static_assert' message 'data\\\(\\\)' must be implicitly convertible to 'const char\\\*'" "" { target *-*-* } .-1 }
+								// { dg-error "could not convert 'array<wchar_t, 2>{const wchar_t \\\[2\\\]{\[0-9]+, \[0-9]+}}.array<wchar_t, 2>::data\\\(\\\)' from 'const wchar_t\\\*' to 'const char\\\*'" "" { target *-*-* } .-2 }
+static_assert (false, array<char, 4> { 't', 'e', 's', 't' });	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+								// { dg-error "static assertion failed: test" "" { target *-*-* } .-1 }
+
+void
+foo ()
+{
+  constexpr auto a = array<char, 4> { 't', 'e', 's', 't' };
+  static_assert (false, a);					// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+}								// { dg-error "static assertion failed: test" "" { target *-*-* } .-1 }
+
+#if  __cpp_constexpr_dynamic_alloc >= 201907L
+struct T {
+  const char *d = init ();
+  constexpr int size () const { return 4; }
+  constexpr const char *data () const { return d; }
+  constexpr const char *init () const { return new char[4] { 't', 'e', 's', 't' }; }
+  constexpr ~T () { delete[] d; }
+};
+static_assert (false, T{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target { c++20 && c++23_down } } }
+					// { dg-error "static assertion failed: test" "" { target c++20 } .-1 }
+#endif
+struct U { constexpr operator const char * () const { return u; }
+	   char u[5] = "test"; };
+#if __cplusplus >= 201402L
+struct V { constexpr auto size () const { return K{}; }
+	   constexpr auto data () const { return U{}; } };
+static_assert (false, V{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target { c++14 && c++23_down } } }
+					// { dg-error "static assertion failed: test" "" { target c++14 } .-1 }
+#endif
+struct W { constexpr int size (int) const { return 4; }
+	   constexpr const char *data () const { return "test"; } };
+static_assert (true, W{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+					// { dg-error "no matching function for call to 'W::size\\\(\\\)'" "" { target *-*-* } .-1 }
+struct X { constexpr int size () const { return 4; }
+	   constexpr const char *data (int) const { return "test"; } };
+static_assert (true, X{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+					// { dg-error "no matching function for call to 'X::data\\\(\\\)'" "" { target *-*-* } .-1 }
+struct Y { constexpr int size () { return 4; }
+	   constexpr const char *data (int) { return "test"; } };
+static_assert (true, Y{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+					// { dg-error "no matching function for call to 'Y::data\\\(\\\)'" "" { target *-*-* } .-1 }
+#if __cpp_concepts >= 201907L
+struct Z { constexpr int size (auto...) const { return 4; }
+	   constexpr const char *data (auto...) const { return "test"; } };
+static_assert (false, Z{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target { c++20 && c++23_down } } }
+					// { dg-error "static assertion failed: test" "" { target c++20 } .-1 }
+#endif
+
+namespace NN
+{
+  template <typename T>
+  struct A {
+    constexpr int size () const = delete;
+    constexpr const char *data () const { return "test"; } };
+  static_assert (true, A<int>{});	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+					// { dg-error "use of deleted function 'constexpr int NN::A<T>::size\\\(\\\) const \\\[with T = int\\\]'" "" { target *-*-* } .-1 }
+#if __cpp_concepts >= 201907L
+  template <typename T>
+  struct B {
+    constexpr int size () const { return 4; }
+    constexpr const char *data () const requires false { return "test"; } };
+  static_assert (true, B<short>{});	// { dg-warning "'static_assert' with non-string message only available with" "" { target { c++20 && c++23_down } } }
+					// { dg-error "no matching function for call to 'NN::B<short int>::data\\\(\\\)'" "" { target c++20 } .-1 }
+#endif
+  class C {
+    constexpr int size () const = delete;
+    constexpr const char *data () const { return "test"; } };
+  static_assert (true, C{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+					// { dg-error "use of deleted function 'constexpr int NN::C::size\\\(\\\) const'" "" { target *-*-* } .-1 }
+					// { dg-error "'constexpr const char\\\* NN::C::data\\\(\\\) const' is private within this context" "" { target *-*-* } .-2 }
+#if __cplusplus >= 201402L
+  struct D {
+    constexpr int size () { return 4; }
+    constexpr int size () const { return 3; }
+    constexpr const char *data () { return "test"; }
+    constexpr const char *data () const { return "ehlo"; } };
+  static_assert (true, D{});	// { dg-warning "'static_assert' with non-string message only available with" "" { target { c++14 && c++23_down } } }
+  static_assert (false, D{});	// { dg-warning "'static_assert' with non-string message only available with" "" { target { c++14 && c++23_down } } }
+				// { dg-error "static assertion failed: test" "" { target c++14 } .-1 }
+#endif
+  struct E {
+    constexpr int size () const { return 4; }
+    constexpr const char *data () const { return "test"; } };
+  template <typename T>
+  struct F {
+    static_assert (false, T{});	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+  };				// { dg-error "static assertion failed: test" "" { target *-*-* } .-1 }
+  template <typename T>
+  struct G {
+    static_assert (false, T{});	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+  };				// { dg-error "'static_assert' message must be a string literal or object with 'size' and 'data' members" "" { target *-*-* } .-1 }
+				// { dg-error "request for member 'size' in '0', which is of non-class type 'long int'" "" { target *-*-* } .-2 }
+  F<E> fe;
+  G<long> gl;
+  constexpr E operator ""_myd (const char *, size_t) { return E{}; }
+  static_assert (false, "foo"_myd);	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+					// { dg-error "static assertion failed: test" "" { target *-*-* } .-1 }
+  constexpr E operator + (const char *, const E &) { return E{}; }
+  static_assert (false, "foo" + E{});	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+					// { dg-error "static assertion failed: test" "" { target *-*-* } .-1 }
+  struct H {
+    static constexpr int size () { return 7; }
+    static constexpr const char *data () { return "message"; } };
+  static_assert (true, H{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+  static_assert (false, H{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+					// { dg-error "static assertion failed: message" "" { target *-*-* } .-1 }
+  struct I {
+    static constexpr int size () { return 0; }
+    static constexpr const char *data () { return nullptr; } };
+  static_assert (true, I{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+  static_assert (false, I{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+					// { dg-error "static assertion failed" "" { target *-*-* } .-1 }
+#if __cplusplus >= 201402L
+  struct J {
+    static constexpr int size () { return 0; }
+    static constexpr const char *data (int x = 0) { if (x) return nullptr; else throw 1; } }; // { dg-error "expression '<throw-expression>' is not a constant expression" "" { target c++14 } } 
+  static_assert (true, J{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target { c++14 && c++23_down } } }
+  static_assert (false, J{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target { c++14 && c++23_down } } }
+					// { dg-error "'static_assert' message 'data\\\(\\\)' must be a core constant expression" "" { target c++14 } .-1 }
+#endif
+#if __cpp_if_consteval >= 202106L
+  struct K {
+    static constexpr int size () { if consteval { return 4; } else { throw 1; } }
+    static constexpr const char *data () { return "test"; }
+  };
+  static_assert (true, K{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_only } }
+  static_assert (false, K{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_only } }
+					// { dg-error "static assertion failed: test" "" { target c++23 } .-1 }
+  struct L {
+    static constexpr int size () { return 4; }
+    static constexpr const char *data () { if consteval { return "test"; } else { throw 1; } }
+  };
+  static_assert (true, L{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_only } }
+  static_assert (false, L{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_only } }
+					// { dg-error "static assertion failed: test" "" { target c++23 } .-1 }
+  struct M {
+    static constexpr int size () { if consteval { throw 1; } else { return 4; } } // { dg-error "expression '<throw-expression>' is not a constant expression" "" { target c++23 } }
+    static constexpr const char *data () { return "test"; }
+  };
+  static_assert (true, M{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_only } }
+  static_assert (false, M{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_only } }
+					// { dg-error "'static_assert' message 'size\\\(\\\)' must be a constant expression" "" { target c++23 } .-1 }
+  struct N {
+    static constexpr int size () { return 4; }
+    static constexpr const char *data () { if consteval { throw 1; } else { return "test"; } } // { dg-error "expression '<throw-expression>' is not a constant expression" "" { target c++23 } }
+  };
+  static_assert (true, N{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_only } }
+  static_assert (false, N{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_only } }
+					// { dg-error "'static_assert' message 'data\\\(\\\)\\\[0\\\]' must be a constant expression" "" { target c++23 } .-1 }
+#endif
+  struct O { constexpr int operator () () const { return 12; } };
+  struct P { constexpr const char *operator () () const { return "another test"; } };
+  struct Q { O size; P data; };
+  static_assert (true, Q ());	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+  static_assert (false, Q {});	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+				// { dg-error "static assertion failed: another test" "" { target *-*-* } .-1 }
+  constexpr int get_size () { return 16; }
+  constexpr const char *get_data () { return "yet another test"; }
+  struct R { int (*size) () = NN::get_size;
+	     const char *(*data) () = NN::get_data; };
+  static_assert (true, R ());	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+  static_assert (false, R {});	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+				// { dg-error "static assertion failed: yet another test" "" { target *-*-* } .-1 }
+}
--- gcc/testsuite/g++.dg/cpp26/feat-cxx26.C.jj	2023-11-22 10:14:56.057375857 +0100
+++ gcc/testsuite/g++.dg/cpp26/feat-cxx26.C	2023-11-22 10:17:41.340064988 +0100
@@ -304,8 +304,8 @@ 
 
 #ifndef __cpp_static_assert
 #  error "__cpp_static_assert"
-#elif __cpp_static_assert != 201411
-#  error "__cpp_static_assert != 201411"
+#elif __cpp_static_assert != 202306
+#  error "__cpp_static_assert != 202306"
 #endif
 
 #ifndef __cpp_namespace_attributes
--- gcc/testsuite/g++.dg/cpp0x/udlit-error1.C.jj	2023-11-22 10:14:56.037376137 +0100
+++ gcc/testsuite/g++.dg/cpp0x/udlit-error1.C	2023-11-22 10:38:53.298282525 +0100
@@ -11,7 +11,9 @@  void operator""_x(const char *, decltype
 #pragma message "hi"_x	  // { dg-warning "string literal with user-defined suffix is invalid in this context" }
 
 extern "C"_x { void g(); } // { dg-error "before user-defined string literal" }
-static_assert(true, "foo"_x); // { dg-error "string literal with user-defined suffix is invalid in this context|expected" }
+static_assert(true, "foo"_x);	// { dg-error "'static_assert' with non-string message only available with" "" { target c++23_down } }
+				// { dg-error "'static_assert' message must be a string literal or object with 'size' and 'data' members" "" { target *-*-* } .-1 }
+				// { dg-error "invalid use of 'void'" "" { target *-*-* } .-2 }
 
 [[deprecated("oof"_x)]]	// { dg-error "string literal with user-defined suffix is invalid in this context" "" { target c++26 } }
 void