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

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

Checks

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

Commit Message

Jakub Jelinek Nov. 21, 2023, 5:17 p.m. UTC
  On Thu, Oct 26, 2023 at 09:21:47PM -0400, Jason Merrill wrote:
> On 9/18/23 13:21, Jakub Jelinek wrote:
> > Here is an updated version of the patch.
> > Compared to the last version, based on the discussion in the PR, the patch
> > 1) warns (but only that) if size()/data() methods aren't declared
> >     constexpr/consteval (or implicitly constexpr)
> 
> The language requirements also seem to be satisfied by

Thanks, these 2 now work.

Most of review comments incorporated.

> > +	      if (!tree_fits_uhwi_p (message_sz)
> > +		  || ((unsigned HOST_WIDE_INT) (int) tree_to_uhwi (message_sz)
> > +		      != tree_to_uhwi (message_sz)))
> > +		{
> > +		  error_at (location,
> > +			    "%<static_assert%> message %<size()%> member "
> > +			    "function must be a constant expression");
> 
> This can use cxx_constant_value to show what makes it not a
> constant-expression.  And also don't assume size is a member function.

In this case, I've split it, if !tree_fits_uhwi_p (message_sz)
(as the value is known to be size_t typed) it really means it isn't
constant expression, while the case when it is too large for host
int is just a restriction we imply on it because we don't really support
too large strings.
Furthermore, I've used cxx_constant_value in addition to the messages
(just removing "member function " part from the wording, and using [%d]
for data or adding "core ").  The reason is that the issues during
constant expression evaluation are typically diagnosed at a different
location and I think it is useful that people know both why it isn't
a constant expression and during evaluation of what it happened.
> > --- gcc/testsuite/g++.dg/cpp0x/udlit-error1.C.jj	2023-09-18 13:08:31.530448184 +0200
> > +++ gcc/testsuite/g++.dg/cpp0x/udlit-error1.C	2023-09-18 13:09:47.167440904 +0200
> > @@ -11,7 +11,8 @@ 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 } }
> 
> This diagnostic message seems unclear for a UDL?
> 
> > +				// { dg-error "'static_assert' message must be a string literal or object with 'size\\\(\\\)' and 'data\\\(\\\)' members" "" { target *-*-* } .-1 }

The real diagnostic is this line, not the first one (which is just a pedwarn
about trying to use something C++26 in older code).
And I'm not really sure what to do about it.

The
static_assert (false, "foo"_myd);
in the new testcase shows where it is valid (in C++26 and as extension in
older standards).  For C++26 we could use unevaluated string literal rather
than string literal in the wording, but C++23 and earlier don't have that,
so we would need to say something like non user-defined string literal without
encoding prefix or object with 'size' and 'data' members.

> >   [[deprecated("oof"_x)]]	// { dg-error "string literal with user-defined suffix is invalid in this context" "" { target c++26 } }
> >   void

Anyway, here is the updated patch with all the changes, but nothing done
about user-defined literals.

Note, as the placeholder patch hasn't been reviewed, I've moved the
-Wc++26-extensions hunks from that patch to this patch.

2023-11-21  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.
	* 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

Jakub Jelinek Nov. 21, 2023, 5:23 p.m. UTC | #1
On Tue, Nov 21, 2023 at 06:17:02PM +0100, Jakub Jelinek wrote:
> The
> static_assert (false, "foo"_myd);
> in the new testcase shows where it is valid (in C++26 and as extension in
> older standards).  For C++26 we could use unevaluated string literal rather
> than string literal in the wording, but C++23 and earlier don't have that,
> so we would need to say something like non user-defined string literal without
> encoding prefix or object with 'size' and 'data' members.

Or do you want to just use
	  error_at (location, "%<static_assert%> message must be a "
			      "unevaluated string literal or object with "
			      "%<size%> and %<data%> members");
wording (even when it is in C++26 term) regardless of the -std= level?

	Jakub
  
Jason Merrill Nov. 21, 2023, 9:44 p.m. UTC | #2
On 11/21/23 12:17, Jakub Jelinek wrote:
> On Thu, Oct 26, 2023 at 09:21:47PM -0400, Jason Merrill wrote:
>> On 9/18/23 13:21, Jakub Jelinek wrote:
>>> Here is an updated version of the patch.
>>> Compared to the last version, based on the discussion in the PR, the patch
>>> 1) warns (but only that) if size()/data() methods aren't declared
>>>      constexpr/consteval (or implicitly constexpr)
>>
>> The language requirements also seem to be satisfied by
> 
> Thanks, these 2 now work.
> 
> Most of review comments incorporated.
> 
>>> +	      if (!tree_fits_uhwi_p (message_sz)
>>> +		  || ((unsigned HOST_WIDE_INT) (int) tree_to_uhwi (message_sz)
>>> +		      != tree_to_uhwi (message_sz)))
>>> +		{
>>> +		  error_at (location,
>>> +			    "%<static_assert%> message %<size()%> member "
>>> +			    "function must be a constant expression");
>>
>> This can use cxx_constant_value to show what makes it not a
>> constant-expression.  And also don't assume size is a member function.
> 
> In this case, I've split it, if !tree_fits_uhwi_p (message_sz)
> (as the value is known to be size_t typed) it really means it isn't
> constant expression, while the case when it is too large for host
> int is just a restriction we imply on it because we don't really support
> too large strings.
> Furthermore, I've used cxx_constant_value in addition to the messages
> (just removing "member function " part from the wording, and using [%d]
> for data or adding "core ").  The reason is that the issues during
> constant expression evaluation are typically diagnosed at a different
> location and I think it is useful that people know both why it isn't
> a constant expression and during evaluation of what it happened.

Agreed.

>>> --- gcc/testsuite/g++.dg/cpp0x/udlit-error1.C.jj	2023-09-18 13:08:31.530448184 +0200
>>> +++ gcc/testsuite/g++.dg/cpp0x/udlit-error1.C	2023-09-18 13:09:47.167440904 +0200
>>> @@ -11,7 +11,8 @@ 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 } }
>>
>> This diagnostic message seems unclear for a UDL?
>>
>>> +				// { dg-error "'static_assert' message must be a string literal or object with 'size\\\(\\\)' and 'data\\\(\\\)' members" "" { target *-*-* } .-1 }
> 
> The real diagnostic is this line, not the first one (which is just a pedwarn
> about trying to use something C++26 in older code).
> And I'm not really sure what to do about it.
> 
> The
> static_assert (false, "foo"_myd);
> in the new testcase shows where it is valid (in C++26 and as extension in
> older standards).  For C++26 we could use unevaluated string literal rather
> than string literal in the wording, but C++23 and earlier don't have that,
> so we would need to say something like non user-defined string literal without
> encoding prefix or object with 'size' and 'data' members.

> Or do you want to just use
> 	  error_at (location, "%<static_assert%> message must be a "
> 			      "unevaluated string literal or object with "
> 			      "%<size%> and %<data%> members");
> wording (even when it is in C++26 term) regardless of the -std= level?

No, I think "unevaluated string literal" will be confusing to users.  I 
guess it's fine as it is, let's just print the type (as commented inline 
below).

>>>    [[deprecated("oof"_x)]]	// { dg-error "string literal with user-defined suffix is invalid in this context" "" { target c++26 } }
>>>    void
> 
> Anyway, here is the updated patch with all the changes, but nothing done
> about user-defined literals.
> 
> Note, as the placeholder patch hasn't been reviewed, I've moved the
> -Wc++26-extensions hunks from that patch to this patch.
> 
> 2023-11-21  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.
> 	* 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-21 09:31:36.008392269 +0100
> +++ gcc/doc/invoke.texi	2023-11-21 15:39:52.448638303 +0100
> @@ -9106,6 +9106,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-11 08:52:20.129849104 +0100
> +++ gcc/c-family/c.opt	2023-11-21 15:39:52.859632548 +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-20 09:50:07.731214433 +0100
> +++ gcc/c-family/c-cppbuiltin.cc	2023-11-21 15:39:30.042951889 +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-20 09:50:08.067209741 +0100
> +++ gcc/cp/parser.cc	2023-11-21 15:31:39.366534804 +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-11 08:52:20.555843211 +0100
> +++ gcc/cp/semantics.cc	2023-11-21 17:34:24.074374632 +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
> @@ -11443,11 +11444,69 @@ finish_static_assert (tree condition, tr
>   
>     if (check_for_bare_parameter_packs (condition))
>       condition = error_mark_node;
> +  if (check_for_bare_parameter_packs (message))
> +    return;

This seems asymmetric, let's return for bare packs in the condition as well.

> +  if (TREE_CODE (message) != STRING_CST
> +      && !type_dependent_expression_p (message))
> +    {
> +      message_sz
> +	= finish_class_member_access_expr (message,
> +					   get_identifier ("size"),
> +					   false, tf_none);
> +      message_data
> +	= finish_class_member_access_expr (message,
> +					   get_identifier ("data"),
> +					   false, tf_none);
> +      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");

Let's print the type of the message as well.

> +	  return;
> +	}
> +      releasing_vec size_args, data_args;
> +      message_sz = finish_call_expr (message_sz, &size_args, false, false,
> +				     tf_warning_or_error);
> +      message_data = finish_call_expr (message_data, &data_args, false, false,
> +				       tf_warning_or_error);
> +      if (message_sz == error_mark_node || message_data == error_mark_node)
> +	return;
> +      if (tree s
> +	  = cp_get_callee_fndecl_nofold (extract_call_expr (message_sz)))
> +	if (!DECL_DECLARED_CONSTEXPR_P (s))
> +	  warning_at (location, 0, "%qD used in %<static_assert%> message "
> +				   "is not %<constexpr%>", s);

I don't think we need this check, it should be covered by the later 
constant-expression checks.

> +      message_sz = build_converted_constant_expr (size_type_node, message_sz,
> +						  tf_none);
> +      if (message_sz == error_mark_node)
> +	{
> +	  error_at (location, "%<static_assert%> message %<size()%> "
> +			      "must be implicitly convertible to "
> +			      "%<std::size_t%>");

Let's also print the type of size().

> +	  return;
> +	}
> +      if (tree d
> +	  = cp_get_callee_fndecl_nofold (extract_call_expr (message_data)))
> +	if (!DECL_DECLARED_CONSTEXPR_P (d))
> +	  warning_at (location, 0, "%qD used in %<static_assert%> message "
> +				   "is not %<constexpr%>", d);

Let's also drop this check.

> +      message_data = build_converted_constant_expr (const_string_type_node,
> +						    message_data, tf_none);
> +      if (message_data == error_mark_node)
> +	{
> +	  error_at (location, "%<static_assert%> message %<data()%> "
> +			      "must be implicitly convertible to "
> +			      "%<const char*%>");

And print this type.

> +	  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 +11544,96 @@ 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
> +		= fold_non_dependent_expr (message_sz, complain,
> +					   /*manifestly_const_eval=*/true);

We can call cxx_constant_value here instead of fold_non_dependent_expr, 
since we don't get here in a template.

> +	      if (!tree_fits_uhwi_p (msz))
> +		{
> +		  cxx_constant_value (message_sz);
> +		  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", message_sz);
> +		  return;
> +		}
> +	      len = tree_to_uhwi (msz);
> +	      tree data
> +		= fold_non_dependent_expr (message_data, tf_none,
> +					   /*manifestly_const_eval=*/true);

And this can be maybe_constant_value.

> +	      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
> +			= fold_non_dependent_expr (t, complain,
> +						   /*manifestly_const_eval=*/
> +						   true);

This can also be cxx_constant_value.

> +		      if (!tree_fits_shwi_p (t2))
> +			{
> +			  cxx_constant_value (t);
> +			  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)
> +		{

Let's add a comment here about how you're using (data(), 0) to test 
core-constant.

> +		  data = build2 (COMPOUND_EXPR, integer_type_node,
> +				 message_data, integer_zero_node);
> +		  tree t
> +		    = fold_non_dependent_expr (data, complain,
> +					       /*manifestly_const_eval=*/true);
> +		  if (!integer_zerop (t))
> +		    {
> +		      cxx_constant_value (data);
> +		      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 +11643,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-20 09:50:08.081209546 +0100
> +++ gcc/cp/pt.cc	2023-11-21 15:31:39.425533979 +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-21 15:31:39.426533965 +0100
> +++ gcc/testsuite/g++.dg/cpp26/static_assert1.C	2023-11-21 17:45:27.862086804 +0100
> @@ -0,0 +1,300 @@
> +// 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 }
> +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 }
> +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 }
> +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 }
> +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 }
> +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 }
> +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 }
> +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 }
> +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 } }
> +				// { dg-warning "'short int H::size\\\(\\\) const' used in 'static_assert' message is not 'constexpr'" "" { target *-*-* } .-1 }
> +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 } }
> +				// { dg-warning "'const char\\\* I::data\\\(\\\) const' used in 'static_assert' message is not 'constexpr'" "" { target *-*-* } .-1 }
> +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 }
> +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 }
> +  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-09-19 09:24:20.921882354 +0200
> +++ gcc/testsuite/g++.dg/cpp26/feat-cxx26.C	2023-11-21 15:31:39.447533671 +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-02 07:49:18.265848989 +0100
> +++ gcc/testsuite/g++.dg/cpp0x/udlit-error1.C	2023-11-21 15:31:39.470533350 +0100
> @@ -11,7 +11,8 @@ 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 }
>   
>   [[deprecated("oof"_x)]]	// { dg-error "string literal with user-defined suffix is invalid in this context" "" { target c++26 } }
>   void
> 
> 
> 	Jakub
>
  
Jakub Jelinek Nov. 21, 2023, 10:19 p.m. UTC | #3
On Tue, Nov 21, 2023 at 04:44:01PM -0500, Jason Merrill wrote:
> > Or do you want to just use
> > 	  error_at (location, "%<static_assert%> message must be a "
> > 			      "unevaluated string literal or object with "
> > 			      "%<size%> and %<data%> members");
> > wording (even when it is in C++26 term) regardless of the -std= level?
> 
> No, I think "unevaluated string literal" will be confusing to users.  I
> guess it's fine as it is, let's just print the type (as commented inline
> below).

Ok.

> > +	  error_at (location, "%<static_assert%> message must be a string "
> > +			      "literal or object with %<size%> and "
> > +			      "%<data%> members");
> 
> Let's print the type of the message as well.

so add " while it has type %qT", TREE_TYPE (message) or something else?

> > +      releasing_vec size_args, data_args;
> > +      message_sz = finish_call_expr (message_sz, &size_args, false, false,
> > +				     tf_warning_or_error);
> > +      message_data = finish_call_expr (message_data, &data_args, false, false,
> > +				       tf_warning_or_error);
> > +      if (message_sz == error_mark_node || message_data == error_mark_node)
> > +	return;
> > +      if (tree s
> > +	  = cp_get_callee_fndecl_nofold (extract_call_expr (message_sz)))
> > +	if (!DECL_DECLARED_CONSTEXPR_P (s))
> > +	  warning_at (location, 0, "%qD used in %<static_assert%> message "
> > +				   "is not %<constexpr%>", s);
> 
> I don't think we need this check, it should be covered by the later
> constant-expression checks.

If the static_assert condition is true, we won't diagnose anything then.
clang++ there incorrectly errors, but I thought a warning could be useful
to users.  Perhaps it could warn only if the condition is true?

> > +	  error_at (location, "%<static_assert%> message %<size()%> "
> > +			      "must be implicitly convertible to "
> > +			      "%<std::size_t%>");
> 
> Let's also print the type of size().

" while it has type %qT" ?

> > @@ -11485,9 +11544,96 @@ 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
> > +		= fold_non_dependent_expr (message_sz, complain,
> > +					   /*manifestly_const_eval=*/true);
> 
> We can call cxx_constant_value here instead of fold_non_dependent_expr,
> since we don't get here in a template.

Ok.

> > +		      t = build1 (INDIRECT_REF, TREE_TYPE (TREE_TYPE (t)), t);
> > +		      tree t2
> > +			= fold_non_dependent_expr (t, complain,
> > +						   /*manifestly_const_eval=*/
> > +						   true);
> 
> This can also be cxx_constant_value.
> 
> > +		      if (!tree_fits_shwi_p (t2))
> > +			{
> > +			  cxx_constant_value (t);

But in that case I don't have to call it again, right?

> Let's add a comment here about how you're using (data(), 0) to test
> core-constant.

Ok.

	Jakub
  
Jakub Jelinek Nov. 21, 2023, 10:51 p.m. UTC | #4
On Tue, Nov 21, 2023 at 11:19:56PM +0100, Jakub Jelinek wrote:
> > > +	  error_at (location, "%<static_assert%> message must be a string "
> > > +			      "literal or object with %<size%> and "
> > > +			      "%<data%> members");
> > 
> > Let's print the type of the message as well.
> 
> so add " while it has type %qT", TREE_TYPE (message) or something else?

Now in patch form (except for the removal of warning_at for now):

2023-11-21  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-21 21:00:41.980429829 +0100
+++ gcc/doc/invoke.texi	2023-11-21 23:25:16.849237207 +0100
@@ -9106,6 +9106,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-21 21:00:41.865431433 +0100
+++ gcc/c-family/c.opt	2023-11-21 23:25:16.850237193 +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-21 21:00:41.865431433 +0100
+++ gcc/c-family/c-cppbuiltin.cc	2023-11-21 23:25:16.850237193 +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-21 21:00:41.933430484 +0100
+++ gcc/cp/parser.cc	2023-11-21 23:25:16.856237110 +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-21 21:00:41.960430108 +0100
+++ gcc/cp/semantics.cc	2023-11-21 23:39:04.861665987 +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,73 @@ 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, tf_none);
+      message_data
+	= finish_class_member_access_expr (message,
+					   get_identifier ("data"),
+					   false, tf_none);
+      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 while it has type %qT",
+			      TREE_TYPE (message));
+	  return;
+	}
+      releasing_vec size_args, data_args;
+      message_sz = finish_call_expr (message_sz, &size_args, false, false,
+				     tf_warning_or_error);
+      message_data = finish_call_expr (message_data, &data_args, false, false,
+				       tf_warning_or_error);
+      if (message_sz == error_mark_node || message_data == error_mark_node)
+	return;
+      if (tree s
+	  = cp_get_callee_fndecl_nofold (extract_call_expr (message_sz)))
+	if (!DECL_DECLARED_CONSTEXPR_P (s))
+	  warning_at (location, 0, "%qD used in %<static_assert%> message "
+				   "is not %<constexpr%>", s);
+      tree t = build_converted_constant_expr (size_type_node, message_sz,
+					      tf_none);
+      if (t == error_mark_node)
+	{
+	  error_at (location, "%<static_assert%> message %<size()%> "
+			      "with type %qT must be implicitly convertible "
+			      "to %<std::size_t%>", TREE_TYPE (message_sz));
+	  return;
+	}
+      message_sz = t;
+      if (tree d
+	  = cp_get_callee_fndecl_nofold (extract_call_expr (message_data)))
+	if (!DECL_DECLARED_CONSTEXPR_P (d))
+	  warning_at (location, 0, "%qD used in %<static_assert%> message "
+				   "is not %<constexpr%>", d);
+      t = build_converted_constant_expr (const_string_type_node,
+					 message_data, tf_none);
+      if (t == error_mark_node)
+	{
+	  error_at (location, "%<static_assert%> message %<data()%> "
+			      "with type %qT must be implicitly convertible "
+			      "to %<const char*%>", TREE_TYPE (message_data));
+	  return;
+	}
+      message_data = t;
+    }
 
   /* 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 +11546,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 +11638,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-21 21:00:41.938430415 +0100
+++ gcc/cp/pt.cc	2023-11-21 23:25:16.861237040 +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-21 23:25:16.861237040 +0100
+++ gcc/testsuite/g++.dg/cpp26/static_assert1.C	2023-11-21 23:48:10.630030663 +0100
@@ -0,0 +1,300 @@
+// 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 while it has type 'const char \\\[1\\\]'" "" { target *-*-* } .-1 }
+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 while it has type 'const char\\\*'" "" { target *-*-* } .-1 }
+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 while it has type 'int'" "" { target *-*-* } .-1 }
+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 while it has type 'A'" "" { target *-*-* } .-1 }
+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 while it has type 'B'" "" { target *-*-* } .-1 }
+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 while it has type 'C'" "" { target *-*-* } .-1 }
+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\\\(\\\)' with type 'const char\\\*' must be implicitly convertible to 'std::size_t'" "" { target *-*-* } .-1 }
+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\\\(\\\)' with type 'float' must be implicitly convertible to 'const char\\\*'" "" { target *-*-* } .-1 }
+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 } }
+				// { dg-warning "'short int H::size\\\(\\\) const' used in 'static_assert' message is not 'constexpr'" "" { target *-*-* } .-1 }
+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 } }
+				// { dg-warning "'const char\\\* I::data\\\(\\\) const' used in 'static_assert' message is not 'constexpr'" "" { target *-*-* } .-1 }
+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\\\(\\\)' with type 'const wchar_t\\\*' must be implicitly convertible to 'const char\\\*'" "" { target *-*-* } .-1 }
+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 while it has type 'long int'" "" { target *-*-* } .-1 }
+  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-21 21:00:42.024429214 +0100
+++ gcc/testsuite/g++.dg/cpp26/feat-cxx26.C	2023-11-21 23:25:16.861237040 +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-21 21:00:42.004429494 +0100
+++ gcc/testsuite/g++.dg/cpp0x/udlit-error1.C	2023-11-21 23:25:16.861237040 +0100
@@ -11,7 +11,8 @@ 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 }
 
 [[deprecated("oof"_x)]]	// { dg-error "string literal with user-defined suffix is invalid in this context" "" { target c++26 } }
 void


	Jakub
  
Jason Merrill Nov. 22, 2023, 3:51 a.m. UTC | #5
On 11/21/23 17:51, Jakub Jelinek wrote:
> On Tue, Nov 21, 2023 at 11:19:56PM +0100, Jakub Jelinek wrote:
>>>> +	  error_at (location, "%<static_assert%> message must be a string "
>>>> +			      "literal or object with %<size%> and "
>>>> +			      "%<data%> members");
>>>
>>> Let's print the type of the message as well.
>>
>> so add " while it has type %qT", TREE_TYPE (message) or something else?
> 
> Now in patch form (except for the removal of warning_at for now):
> 
> +  if (TREE_CODE (message) != STRING_CST
> +      && !type_dependent_expression_p (message))
> +    {
> +      message_sz
> +	= finish_class_member_access_expr (message,
> +					   get_identifier ("size"),
> +					   false, tf_none);
> +      message_data
> +	= finish_class_member_access_expr (message,
> +					   get_identifier ("data"),
> +					   false, tf_none);
> +      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 while it has type %qT",

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.

> +			      TREE_TYPE (message));
> +	  return;
> +	}
> +      releasing_vec size_args, data_args;
> +      message_sz = finish_call_expr (message_sz, &size_args, false, false,
> +				     tf_warning_or_error);
> +      message_data = finish_call_expr (message_data, &data_args, false, false,
> +				       tf_warning_or_error);

Can use 'complain' instead of tf_warning_or_error here, too.

> +      if (message_sz == error_mark_node || message_data == error_mark_node)
> +	return;
> +      if (tree s
> +	  = cp_get_callee_fndecl_nofold (extract_call_expr (message_sz)))
> +	if (!DECL_DECLARED_CONSTEXPR_P (s))
> +	  warning_at (location, 0, "%qD used in %<static_assert%> message "
> +				   "is not %<constexpr%>", s);

>> I don't think we need this check, it should be covered by the later
>> constant-expression checks.
> 
> If the static_assert condition is true, we won't diagnose anything then.
> clang++ there incorrectly errors, but I thought a warning could be useful
> to users.  Perhaps it could warn only if the condition is true?

I don't think the extra warning is that useful, especially with no flag 
to suppress it; we specifically decided not to require the message to be 
constant if the condition is true, and involving a non-constexpr 
function is just one example of how it might not be constant.

Jason
  

Patch

--- gcc/doc/invoke.texi.jj	2023-11-21 09:31:36.008392269 +0100
+++ gcc/doc/invoke.texi	2023-11-21 15:39:52.448638303 +0100
@@ -9106,6 +9106,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-11 08:52:20.129849104 +0100
+++ gcc/c-family/c.opt	2023-11-21 15:39:52.859632548 +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-20 09:50:07.731214433 +0100
+++ gcc/c-family/c-cppbuiltin.cc	2023-11-21 15:39:30.042951889 +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-20 09:50:08.067209741 +0100
+++ gcc/cp/parser.cc	2023-11-21 15:31:39.366534804 +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-11 08:52:20.555843211 +0100
+++ gcc/cp/semantics.cc	2023-11-21 17:34:24.074374632 +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
@@ -11443,11 +11444,69 @@  finish_static_assert (tree condition, tr
 
   if (check_for_bare_parameter_packs (condition))
     condition = error_mark_node;
+  if (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, tf_none);
+      message_data
+	= finish_class_member_access_expr (message,
+					   get_identifier ("data"),
+					   false, tf_none);
+      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,
+				     tf_warning_or_error);
+      message_data = finish_call_expr (message_data, &data_args, false, false,
+				       tf_warning_or_error);
+      if (message_sz == error_mark_node || message_data == error_mark_node)
+	return;
+      if (tree s
+	  = cp_get_callee_fndecl_nofold (extract_call_expr (message_sz)))
+	if (!DECL_DECLARED_CONSTEXPR_P (s))
+	  warning_at (location, 0, "%qD used in %<static_assert%> message "
+				   "is not %<constexpr%>", s);
+      message_sz = build_converted_constant_expr (size_type_node, message_sz,
+						  tf_none);
+      if (message_sz == error_mark_node)
+	{
+	  error_at (location, "%<static_assert%> message %<size()%> "
+			      "must be implicitly convertible to "
+			      "%<std::size_t%>");
+	  return;
+	}
+      if (tree d
+	  = cp_get_callee_fndecl_nofold (extract_call_expr (message_data)))
+	if (!DECL_DECLARED_CONSTEXPR_P (d))
+	  warning_at (location, 0, "%qD used in %<static_assert%> message "
+				   "is not %<constexpr%>", d);
+      message_data = build_converted_constant_expr (const_string_type_node,
+						    message_data, tf_none);
+      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 +11544,96 @@  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
+		= fold_non_dependent_expr (message_sz, complain,
+					   /*manifestly_const_eval=*/true);
+	      if (!tree_fits_uhwi_p (msz))
+		{
+		  cxx_constant_value (message_sz);
+		  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", message_sz);
+		  return;
+		}
+	      len = tree_to_uhwi (msz);
+	      tree data
+		= fold_non_dependent_expr (message_data, tf_none,
+					   /*manifestly_const_eval=*/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
+			= fold_non_dependent_expr (t, complain,
+						   /*manifestly_const_eval=*/
+						   true);
+		      if (!tree_fits_shwi_p (t2))
+			{
+			  cxx_constant_value (t);
+			  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)
+		{
+		  data = build2 (COMPOUND_EXPR, integer_type_node,
+				 message_data, integer_zero_node);
+		  tree t
+		    = fold_non_dependent_expr (data, complain,
+					       /*manifestly_const_eval=*/true);
+		  if (!integer_zerop (t))
+		    {
+		      cxx_constant_value (data);
+		      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 +11643,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-20 09:50:08.081209546 +0100
+++ gcc/cp/pt.cc	2023-11-21 15:31:39.425533979 +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-21 15:31:39.426533965 +0100
+++ gcc/testsuite/g++.dg/cpp26/static_assert1.C	2023-11-21 17:45:27.862086804 +0100
@@ -0,0 +1,300 @@ 
+// 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 }
+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 }
+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 }
+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 }
+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 }
+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 }
+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 }
+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 }
+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 } }
+				// { dg-warning "'short int H::size\\\(\\\) const' used in 'static_assert' message is not 'constexpr'" "" { target *-*-* } .-1 }
+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 } }
+				// { dg-warning "'const char\\\* I::data\\\(\\\) const' used in 'static_assert' message is not 'constexpr'" "" { target *-*-* } .-1 }
+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 }
+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 }
+  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-09-19 09:24:20.921882354 +0200
+++ gcc/testsuite/g++.dg/cpp26/feat-cxx26.C	2023-11-21 15:31:39.447533671 +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-02 07:49:18.265848989 +0100
+++ gcc/testsuite/g++.dg/cpp0x/udlit-error1.C	2023-11-21 15:31:39.470533350 +0100
@@ -11,7 +11,8 @@  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 }
 
 [[deprecated("oof"_x)]]	// { dg-error "string literal with user-defined suffix is invalid in this context" "" { target c++26 } }
 void