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

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

Checks

Context Check Description
linaro-tcwg-bot/tcwg_gcc_check--master-aarch64 fail Patch failed to apply
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
linaro-tcwg-bot/tcwg_gcc_check--master-arm fail Patch failed to apply

Commit Message

Jakub Jelinek Sept. 18, 2023, 5:21 p.m. UTC
  On Thu, Aug 24, 2023 at 04:30:51PM +0200, Jakub Jelinek via Gcc-patches wrote:
> The following patch on top of PR110349 patch (weak dependency,
> only for -Wc++26-extensions, I could split that part into an independent
> patch) and PR110342 patch (again weak dependency, this time mainly
> because it touches the same code in cp_parser_static_assert and
> nearby spot in udlit-error1.C testcase) implements the user generated
> static_assert messages next to string literals.
> 
> As I wrote already in the PR, in addition to looking through the paper
> I looked at the clang++ testcase for this feature implemented there from
> paper's author and on godbolt played with various parts of the testcase
> coverage below, and there are 4 differences between what the patch
> implements and what clang++ implements.
> 
> The first is that clang++ diagnoses if M.size () or M.data () methods
> are present, but aren't constexpr; while the paper introduction talks about
> that, the standard wording changes don't seem to require that, all they say
> is that those methods need to exist (assuming accessible and the like)
> and be implicitly convertible to std::size_t or const char *, but rest is
> only if the static assertion fails.  If there is intent to change that
> wording, the question is how far to go, e.g. while M.size () could be
> constexpr, they could e.g. return some class object which wouldn't have
> constexpr conversion operator to size_t/const char * and tons of other
> reasons why the constant evaluation could fail.  Without actually evaluating
> it I don't see how we could guarantee anything for non-failed static_assert.
> 
> The second and most important is that clang++ has a couple of tests (and the
> testcase below as well) where M.data () is not a core constant expression
> but M.data ()[0] ... M.data ()[M.size () - 1] is integer constant
> expression.  From my reading of http://eel.is/c++draft/dcl.pre#11.2.2
> that means those should be rejected (examples of these are e.g.
> static_assert (false, T{});
> in the testcase, where T{}.data () returns pointer returned from new
> expression, but T{}'s destructor then deletes it, making it point to
> no longer live object.  Or
> static_assert (false, a);
> where a.data () returns &a.a but because a is constexpr automatic variable,
> that isn't valid core constant expression, while a.data ()[0] is.
> There are a couple of others.  Now, it seems allowing that is quite useful
> in real-world, but the question is with what standard changes to achieve
> that.  One possibility would be s/a core constant/an/; from implementation
> POV that would mean that if M.size () is 0, then M.data () doesn't have
> to be constexpr at all.  Otherwise, implementation could try to evaluate
> silently M.data () as constant expression, if it would be one, it could
> just use c_getstr in the GCC case as the patch does + optionally the 2
> M.data ()[0] and M.data ()[M.size () - 1] tests to verify boundary cases
> more carefully.  And if it wouldn't be one, it would need to evaluate
> M.data ()[i] for i in [0, M.size () - 1] to get all the characters one by
> one.  Another possibility would be to require that say ((void) (M.data ()), 0)
> is a constant expression, that doesn't help much with the optimized way
> to get at the message characters, but would require that data () is
> constexpr even for the 0 case etc.
> 
> The third difference is that 
> static_assert (false, "foo"_myd);
> in the testcase is normal failed static assertion and
> static_assert (true, "foo"_myd);
> would be accepted, while clang++ rejects it.  IMHO
> "foo"_myd doesn't match the syntactic requirements of unevaluated-string
> as mentioned in http://eel.is/c++draft/dcl.pre#10 , and because
> a constexpr udlit operator can return something which is valid, it shouldn't
> be rejected just in case.
> 
> Last is clang++ ICEs on non-static data members size/data.
> 
> The patch implements what I see in the paper, because it is unclear what
> further changes will be voted in (and the changes can be done at that
> point).
> The patch uses tf_none in 6 spots so that just the static_assert specific
> errors are emitted and not others, but it would certainly be possible to
> use complain instead of tf_none there, get more errors in some cases, but
> perhaps help users figure out what exactly is wrong in detail.

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)
2) as I don't see a function which would determine if some expression
   is core constant expression (for the data() case), the patch just as an
   optimization tries to fold_nondependent_expr msg.data() expression
   quietly, if it is a constant expression, passes it to c_getstr if len > 0
   and if successful, only tries to constant expression evaluate
   msg.data()[0] and msg.data()[len - 1], otherwise it will constant
   expression evaluate the characters one by one;
   for the len == 0 case, it will fold_nondependent_expr + check result is
   integer_zero_node for (msg.data(), 0) which I think should fail if
   msg.data() is not a core constant expression, but succeed if it is
   even if it is not constant expression
3) already the earlier version of the patch was passing
   manifestly_const_eval=true argument, you said in the PR you've raised
   it in CWG, I've newly added testsuite coverage for that

Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk?

2023-09-18  Jakub Jelinek  <jakub@redhat.com>

	PR c++/110348
gcc/c-family/
	* 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

Jason Merrill Oct. 27, 2023, 1:21 a.m. UTC | #1
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

constexpr const char msg[] = "foo";
struct A { constexpr int operator () () { return sizeof(msg); } };
struct B { constexpr const char * operator()() { return msg; } };
struct C {
   A size;
   B data;
};
constexpr int i = C().size();
constexpr const char *p = C().data();
static_assert (false, C());

constexpr int size() { return sizeof(msg); }
constexpr const char *data() { return msg; }
struct D {
   int (*size)() = ::size;
   const char *(*data)() = ::data;
};
constexpr int di = D().size();
constexpr const char *dp = D().data();
static_assert (false, D());

so we shouldn't assume that size/data are methods.

> 2) as I don't see a function which would determine if some expression
>     is core constant expression (for the data() case), the patch just as an
>     optimization tries to fold_nondependent_expr msg.data() expression
>     quietly, if it is a constant expression, passes it to c_getstr if len > 0
>     and if successful, only tries to constant expression evaluate
>     msg.data()[0] and msg.data()[len - 1], otherwise it will constant
>     expression evaluate the characters one by one;
>     for the len == 0 case, it will fold_nondependent_expr + check result is
>     integer_zero_node for (msg.data(), 0) which I think should fail if
>     msg.data() is not a core constant expression, but succeed if it is
>     even if it is not constant expression

Sounds good.

> 3) already the earlier version of the patch was passing
>     manifestly_const_eval=true argument, you said in the PR you've raised
>     it in CWG, I've newly added testsuite coverage for that

CWG agreed with this direction.

> --- gcc/cp/semantics.cc.jj	2023-09-05 17:26:51.849921954 +0200
> +++ gcc/cp/semantics.cc	2023-09-18 14:31:55.269431759 +0200
> @@ -11388,11 +11389,77 @@ 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);
> +      if (TREE_CODE (message_sz) != COMPONENT_REF)
> +	message_sz = error_mark_node;
> +      if (message_sz != error_mark_node)
> +	message_sz = build_new_method_call (message,
> +					    TREE_OPERAND (message_sz, 1),
> +					    NULL, NULL_TREE, LOOKUP_NORMAL,
> +					    NULL, tf_none);

This should probably use finish_call_expr instead of 
build_new_method_call because of my example above, and also so you don't 
need to pull out the TREE_OPERAND or check the result of name lookup.

> +      message_data
> +	= finish_class_member_access_expr (message,
> +					   get_identifier ("data"),
> +					   false, tf_none);
> +      if (TREE_CODE (message_data) != COMPONENT_REF)
> +	message_data = error_mark_node;
> +      if (message_data != error_mark_node)
> +	message_data = build_new_method_call (message,
> +					      TREE_OPERAND (message_data, 1),
> +					      NULL, NULL_TREE, LOOKUP_NORMAL,
> +					      NULL, tf_none);

Likewise.

> +      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");

This diagnostic should be just if the calls to 
finish_class_member_access_expr fail; better to get the normal 
diagnostics from finish_call_expr if the calls fail for whatever reason.

> +	  return;
> +	}
> +      if (tree s
> +	  = cp_get_callee_fndecl_nofold (extract_call_expr (message_sz)))
> +	if (!DECL_DECLARED_CONSTEXPR_P (s))
> +	  warning_at (location, 0, "%<static_assert%> message %qs "
> +				   "member not %<constexpr%>", "size()");
> +      message_sz = perform_implicit_conversion (size_type_node, message_sz,
> +						tf_none);

This should probably use build_converted_constant_expr?

> +      if (message_sz == error_mark_node)
> +	{
> +	  error_at (location, "%<static_assert%> message %<size()%> member "
> +			      "function 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, "%<static_assert%> message %qs "
> +				   "member not %<constexpr%>", "data()");
> +      message_data = perform_implicit_conversion (const_string_type_node,
> +						  message_data, tf_none);
> +      if (message_data == error_mark_node)
> +	{
> +	  error_at (location, "%<static_assert%> message %<data()%> member "
> +			      "function 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. */
> @@ -11430,9 +11497,88 @@ 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)
> +	    {
> +	      message_sz
> +		= fold_non_dependent_expr (message_sz, complain,
> +					   /*manifestly_const_eval=*/true);
> +	      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.

> +		  return;
> +		}
> +	      len = tree_to_uhwi (message_sz);
> +	      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);
> +		      t = fold_non_dependent_expr (t, complain,
> +						   /*manifestly_const_eval=*/
> +						   true);
> +		      if (!tree_fits_shwi_p (t))
> +			{
> +			  error_at (location,
> +				    "%<static_assert%> message %<data()%> "
> +				    "member function must be a constant "
> +				    "expression");

Likewise.

> +			  return;
> +			}
> +		      if (msg == NULL)
> +			buf[i] = tree_to_shwi (t);
> +		      /* 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);
> +		  data = fold_non_dependent_expr (data, complain,
> +						  /*manifestly_const_eval=*/
> +						  true);
> +		  if (!integer_zerop (data))
> +		    {
> +		      error_at (location,
> +				"%<static_assert%> message %<data()%> "
> +				"member function must be a constant "
> +				"expression");

Likewise.

> +		      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);
> @@ -11442,12 +11588,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-09-18 12:42:16.309387948 +0200
> +++ gcc/cp/pt.cc	2023-09-18 13:09:47.166440918 +0200
> @@ -19430,15 +19430,20 @@ tsubst_expr (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-09-18 13:09:47.167440904 +0200
> +++ gcc/testsuite/g++.dg/cpp26/static_assert1.C	2023-09-18 15:04:25.402596093 +0200
> @@ -0,0 +1,284 @@
> +// 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 "'static_assert' message must be a string literal or object with 'size\\\(\\\)' and 'data\\\(\\\)' members" "" { 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 "'static_assert' message must be a string literal or object with 'size\\\(\\\)' and 'data\\\(\\\)' members" "" { target *-*-* } .-1 }

The diagnostic states properties that E satisfies, and not the problem 
(that E{}.size isn't invocable).

> +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\\\(\\\)' member function 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\\\(\\\)' member function 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 "'static_assert' message 'size\\\(\\\)' member 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 "'static_assert' message 'data\\\(\\\)' member not 'constexpr'" "" { target *-*-* } .-1 }
> +struct J { constexpr int size () const { return j ? throw 1 : 0; }
> +	   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\\\(\\\)' member function 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' }; } };
> +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\\\(\\\)' member function must be a constant expression" "" { target c++20 } .-1 }

The cxx_constant_value diagnostics should be helpful here.

> +#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 "'static_assert' message 'data\\\(\\\)' member function must be a constant expression" "" { target *-*-* } .-1 }

The cxx_constant_value diagnostics should be helpful here.

> +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\\\(\\\)' member function 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 "'static_assert' message must be a string literal or object with 'size\\\(\\\)' and 'data\\\(\\\)' members" "" { 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 "'static_assert' message must be a string literal or object with 'size\\\(\\\)' and 'data\\\(\\\)' members" "" { 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 "'static_assert' message must be a string literal or object with 'size\\\(\\\)' and 'data\\\(\\\)' members" "" { target *-*-* } .-1 }

More cases of the diagnostic not stating the problem; as above, this 
message should only be for when lookup fails.

> +#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 "'static_assert' message must be a string literal or object with 'size\\\(\\\)' and 'data\\\(\\\)' members" "" { 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 "'static_assert' message must be a string literal or object with 'size\\\(\\\)' and 'data\\\(\\\)' members" "" { 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 "'static_assert' message must be a string literal or object with 'size\\\(\\\)' and 'data\\\(\\\)' members" "" { target *-*-* } .-1 }
> +#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; } };
> +  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\\\(\\\)' member function must be a 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; } }
> +    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\\\(\\\)' member function 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"; } }
> +  };
> +  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\\\(\\\)' member function must be a constant expression" "" { target c++23 } .-1 }
> +#endif
> +}
> --- gcc/testsuite/g++.dg/cpp26/feat-cxx26.C.jj	2023-09-18 12:42:16.327387707 +0200
> +++ gcc/testsuite/g++.dg/cpp26/feat-cxx26.C	2023-09-18 13:09:47.167440904 +0200
> @@ -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-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 }
>   
>   [[deprecated("oof"_x)]]	// { dg-error "string literal with user-defined suffix is invalid in this context" "" { target c++26 } }
>   void
> 
> 	Jakub
>
  
Jason Merrill Nov. 17, 2023, 2:18 p.m. UTC | #2
You recently pinged this patch, but I haven't seen an update since this 
review?

On 10/26/23 21:21, 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
> 
> constexpr const char msg[] = "foo";
> struct A { constexpr int operator () () { return sizeof(msg); } };
> struct B { constexpr const char * operator()() { return msg; } };
> struct C {
>    A size;
>    B data;
> };
> constexpr int i = C().size();
> constexpr const char *p = C().data();
> static_assert (false, C());
> 
> constexpr int size() { return sizeof(msg); }
> constexpr const char *data() { return msg; }
> struct D {
>    int (*size)() = ::size;
>    const char *(*data)() = ::data;
> };
> constexpr int di = D().size();
> constexpr const char *dp = D().data();
> static_assert (false, D());
> 
> so we shouldn't assume that size/data are methods.
> 
>> 2) as I don't see a function which would determine if some expression
>>     is core constant expression (for the data() case), the patch just 
>> as an
>>     optimization tries to fold_nondependent_expr msg.data() expression
>>     quietly, if it is a constant expression, passes it to c_getstr if 
>> len > 0
>>     and if successful, only tries to constant expression evaluate
>>     msg.data()[0] and msg.data()[len - 1], otherwise it will constant
>>     expression evaluate the characters one by one;
>>     for the len == 0 case, it will fold_nondependent_expr + check 
>> result is
>>     integer_zero_node for (msg.data(), 0) which I think should fail if
>>     msg.data() is not a core constant expression, but succeed if it is
>>     even if it is not constant expression
> 
> Sounds good.
> 
>> 3) already the earlier version of the patch was passing
>>     manifestly_const_eval=true argument, you said in the PR you've raised
>>     it in CWG, I've newly added testsuite coverage for that
> 
> CWG agreed with this direction.
> 
>> --- gcc/cp/semantics.cc.jj    2023-09-05 17:26:51.849921954 +0200
>> +++ gcc/cp/semantics.cc    2023-09-18 14:31:55.269431759 +0200
>> @@ -11388,11 +11389,77 @@ 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);
>> +      if (TREE_CODE (message_sz) != COMPONENT_REF)
>> +    message_sz = error_mark_node;
>> +      if (message_sz != error_mark_node)
>> +    message_sz = build_new_method_call (message,
>> +                        TREE_OPERAND (message_sz, 1),
>> +                        NULL, NULL_TREE, LOOKUP_NORMAL,
>> +                        NULL, tf_none);
> 
> This should probably use finish_call_expr instead of 
> build_new_method_call because of my example above, and also so you don't 
> need to pull out the TREE_OPERAND or check the result of name lookup.
> 
>> +      message_data
>> +    = finish_class_member_access_expr (message,
>> +                       get_identifier ("data"),
>> +                       false, tf_none);
>> +      if (TREE_CODE (message_data) != COMPONENT_REF)
>> +    message_data = error_mark_node;
>> +      if (message_data != error_mark_node)
>> +    message_data = build_new_method_call (message,
>> +                          TREE_OPERAND (message_data, 1),
>> +                          NULL, NULL_TREE, LOOKUP_NORMAL,
>> +                          NULL, tf_none);
> 
> Likewise.
> 
>> +      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");
> 
> This diagnostic should be just if the calls to 
> finish_class_member_access_expr fail; better to get the normal 
> diagnostics from finish_call_expr if the calls fail for whatever reason.
> 
>> +      return;
>> +    }
>> +      if (tree s
>> +      = cp_get_callee_fndecl_nofold (extract_call_expr (message_sz)))
>> +    if (!DECL_DECLARED_CONSTEXPR_P (s))
>> +      warning_at (location, 0, "%<static_assert%> message %qs "
>> +                   "member not %<constexpr%>", "size()");
>> +      message_sz = perform_implicit_conversion (size_type_node, 
>> message_sz,
>> +                        tf_none);
> 
> This should probably use build_converted_constant_expr?
> 
>> +      if (message_sz == error_mark_node)
>> +    {
>> +      error_at (location, "%<static_assert%> message %<size()%> member "
>> +                  "function 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, "%<static_assert%> message %qs "
>> +                   "member not %<constexpr%>", "data()");
>> +      message_data = perform_implicit_conversion 
>> (const_string_type_node,
>> +                          message_data, tf_none);
>> +      if (message_data == error_mark_node)
>> +    {
>> +      error_at (location, "%<static_assert%> message %<data()%> member "
>> +                  "function 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. */
>> @@ -11430,9 +11497,88 @@ 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)
>> +        {
>> +          message_sz
>> +        = fold_non_dependent_expr (message_sz, complain,
>> +                       /*manifestly_const_eval=*/true);
>> +          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.
> 
>> +          return;
>> +        }
>> +          len = tree_to_uhwi (message_sz);
>> +          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);
>> +              t = fold_non_dependent_expr (t, complain,
>> +                           /*manifestly_const_eval=*/
>> +                           true);
>> +              if (!tree_fits_shwi_p (t))
>> +            {
>> +              error_at (location,
>> +                    "%<static_assert%> message %<data()%> "
>> +                    "member function must be a constant "
>> +                    "expression");
> 
> Likewise.
> 
>> +              return;
>> +            }
>> +              if (msg == NULL)
>> +            buf[i] = tree_to_shwi (t);
>> +              /* 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);
>> +          data = fold_non_dependent_expr (data, complain,
>> +                          /*manifestly_const_eval=*/
>> +                          true);
>> +          if (!integer_zerop (data))
>> +            {
>> +              error_at (location,
>> +                "%<static_assert%> message %<data()%> "
>> +                "member function must be a constant "
>> +                "expression");
> 
> Likewise.
> 
>> +              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);
>> @@ -11442,12 +11588,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-09-18 12:42:16.309387948 +0200
>> +++ gcc/cp/pt.cc    2023-09-18 13:09:47.166440918 +0200
>> @@ -19430,15 +19430,20 @@ tsubst_expr (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-09-18 
>> 13:09:47.167440904 +0200
>> +++ gcc/testsuite/g++.dg/cpp26/static_assert1.C    2023-09-18 
>> 15:04:25.402596093 +0200
>> @@ -0,0 +1,284 @@
>> +// 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 "'static_assert' message must be a 
>> string literal or object with 'size\\\(\\\)' and 'data\\\(\\\)' 
>> members" "" { 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 "'static_assert' message must be a 
>> string literal or object with 'size\\\(\\\)' and 'data\\\(\\\)' 
>> members" "" { target *-*-* } .-1 }
> 
> The diagnostic states properties that E satisfies, and not the problem 
> (that E{}.size isn't invocable).
> 
>> +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\\\(\\\)' 
>> member function 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\\\(\\\)' 
>> member function 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 "'static_assert' message 
>> 'size\\\(\\\)' member 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 "'static_assert' message 
>> 'data\\\(\\\)' member not 'constexpr'" "" { target *-*-* } .-1 }
>> +struct J { constexpr int size () const { return j ? throw 1 : 0; }
>> +       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\\\(\\\)' 
>> member function 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' }; } };
>> +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\\\(\\\)' 
>> member function must be a constant expression" "" { target c++20 } .-1 }
> 
> The cxx_constant_value diagnostics should be helpful here.
> 
>> +#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 "'static_assert' 
>> message 'data\\\(\\\)' member function must be a constant expression" 
>> "" { target *-*-* } .-1 }
> 
> The cxx_constant_value diagnostics should be helpful here.
> 
>> +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\\\(\\\)' member function 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 "'static_assert' message must be a 
>> string literal or object with 'size\\\(\\\)' and 'data\\\(\\\)' 
>> members" "" { 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 "'static_assert' message must be a 
>> string literal or object with 'size\\\(\\\)' and 'data\\\(\\\)' 
>> members" "" { 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 "'static_assert' message must be a 
>> string literal or object with 'size\\\(\\\)' and 'data\\\(\\\)' 
>> members" "" { target *-*-* } .-1 }
> 
> More cases of the diagnostic not stating the problem; as above, this 
> message should only be for when lookup fails.
> 
>> +#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 "'static_assert' message must be a 
>> string literal or object with 'size\\\(\\\)' and 'data\\\(\\\)' 
>> members" "" { 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 "'static_assert' message must be a 
>> string literal or object with 'size\\\(\\\)' and 'data\\\(\\\)' 
>> members" "" { 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 "'static_assert' message must be a 
>> string literal or object with 'size\\\(\\\)' and 'data\\\(\\\)' 
>> members" "" { target *-*-* } .-1 }
>> +#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; } };
>> +  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\\\(\\\)' member function must be a 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; } }
>> +    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\\\(\\\)' member function 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"; } }
>> +  };
>> +  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\\\(\\\)' member function must be a constant expression" "" { 
>> target c++23 } .-1 }
>> +#endif
>> +}
>> --- gcc/testsuite/g++.dg/cpp26/feat-cxx26.C.jj    2023-09-18 
>> 12:42:16.327387707 +0200
>> +++ gcc/testsuite/g++.dg/cpp26/feat-cxx26.C    2023-09-18 
>> 13:09:47.167440904 +0200
>> @@ -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-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 }
>>   [[deprecated("oof"_x)]]    // { dg-error "string literal with 
>> user-defined suffix is invalid in this context" "" { target c++26 } }
>>   void
>>
>>     Jakub
>>
>
  
Jakub Jelinek Nov. 17, 2023, 2:22 p.m. UTC | #3
On Fri, Nov 17, 2023 at 09:18:39AM -0500, Jason Merrill wrote:
> You recently pinged this patch, but I haven't seen an update since this
> review?

Oops, sorry, I've missed this and DR 2406 review posts in my inbox
during vacation, will get to that momentarily.

Thanks.

	Jakub
  

Patch

--- gcc/c-family/c-cppbuiltin.cc.jj	2023-09-18 12:42:16.294388148 +0200
+++ gcc/c-family/c-cppbuiltin.cc	2023-09-18 13:09:47.155441065 +0200
@@ -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");
@@ -1087,6 +1088,7 @@  c_cpp_builtins (cpp_reader *pfile)
 	  /* Set feature test macros for C++26.  */
 	  cpp_define (pfile, "__cpp_constexpr=202306L");
 	  cpp_define (pfile, "__cpp_placeholder_variables=202306L");
+	  cpp_define (pfile, "__cpp_static_assert=202306L");
 	}
       if (flag_concepts)
         {
--- gcc/cp/parser.cc.jj	2023-09-18 13:08:31.116453667 +0200
+++ gcc/cp/parser.cc	2023-09-18 13:09:47.161440985 +0200
@@ -16601,6 +16601,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.  */
 
@@ -16631,10 +16632,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)
     {
@@ -16653,8 +16654,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-09-05 17:26:51.849921954 +0200
+++ gcc/cp/semantics.cc	2023-09-18 14:31:55.269431759 +0200
@@ -11379,6 +11379,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
@@ -11388,11 +11389,77 @@  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);
+      if (TREE_CODE (message_sz) != COMPONENT_REF)
+	message_sz = error_mark_node;
+      if (message_sz != error_mark_node)
+	message_sz = build_new_method_call (message,
+					    TREE_OPERAND (message_sz, 1),
+					    NULL, NULL_TREE, LOOKUP_NORMAL,
+					    NULL, tf_none);
+      message_data
+	= finish_class_member_access_expr (message,
+					   get_identifier ("data"),
+					   false, tf_none);
+      if (TREE_CODE (message_data) != COMPONENT_REF)
+	message_data = error_mark_node;
+      if (message_data != error_mark_node)
+	message_data = build_new_method_call (message,
+					      TREE_OPERAND (message_data, 1),
+					      NULL, NULL_TREE, LOOKUP_NORMAL,
+					      NULL, 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;
+	}
+      if (tree s
+	  = cp_get_callee_fndecl_nofold (extract_call_expr (message_sz)))
+	if (!DECL_DECLARED_CONSTEXPR_P (s))
+	  warning_at (location, 0, "%<static_assert%> message %qs "
+				   "member not %<constexpr%>", "size()");
+      message_sz = perform_implicit_conversion (size_type_node, message_sz,
+						tf_none);
+      if (message_sz == error_mark_node)
+	{
+	  error_at (location, "%<static_assert%> message %<size()%> member "
+			      "function 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, "%<static_assert%> message %qs "
+				   "member not %<constexpr%>", "data()");
+      message_data = perform_implicit_conversion (const_string_type_node,
+						  message_data, tf_none);
+      if (message_data == error_mark_node)
+	{
+	  error_at (location, "%<static_assert%> message %<data()%> member "
+			      "function 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. */
@@ -11430,9 +11497,88 @@  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)
+	    {
+	      message_sz
+		= fold_non_dependent_expr (message_sz, complain,
+					   /*manifestly_const_eval=*/true);
+	      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");
+		  return;
+		}
+	      len = tree_to_uhwi (message_sz);
+	      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);
+		      t = fold_non_dependent_expr (t, complain,
+						   /*manifestly_const_eval=*/
+						   true);
+		      if (!tree_fits_shwi_p (t))
+			{
+			  error_at (location,
+				    "%<static_assert%> message %<data()%> "
+				    "member function must be a constant "
+				    "expression");
+			  return;
+			}
+		      if (msg == NULL)
+			buf[i] = tree_to_shwi (t);
+		      /* 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);
+		  data = fold_non_dependent_expr (data, complain,
+						  /*manifestly_const_eval=*/
+						  true);
+		  if (!integer_zerop (data))
+		    {
+		      error_at (location,
+				"%<static_assert%> message %<data()%> "
+				"member function must be a 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);
@@ -11442,12 +11588,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-09-18 12:42:16.309387948 +0200
+++ gcc/cp/pt.cc	2023-09-18 13:09:47.166440918 +0200
@@ -19430,15 +19430,20 @@  tsubst_expr (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-09-18 13:09:47.167440904 +0200
+++ gcc/testsuite/g++.dg/cpp26/static_assert1.C	2023-09-18 15:04:25.402596093 +0200
@@ -0,0 +1,284 @@ 
+// 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 "'static_assert' message must be a string literal or object with 'size\\\(\\\)' and 'data\\\(\\\)' members" "" { 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 "'static_assert' message must be a string literal or object with 'size\\\(\\\)' and 'data\\\(\\\)' members" "" { target *-*-* } .-1 }
+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\\\(\\\)' member function 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\\\(\\\)' member function 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 "'static_assert' message 'size\\\(\\\)' member 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 "'static_assert' message 'data\\\(\\\)' member not 'constexpr'" "" { target *-*-* } .-1 }
+struct J { constexpr int size () const { return j ? throw 1 : 0; }
+	   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\\\(\\\)' member function 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' }; } };
+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\\\(\\\)' member function 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 "'static_assert' message 'data\\\(\\\)' member function must be a constant expression" "" { target *-*-* } .-1 }
+
+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\\\(\\\)' member function 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 "'static_assert' message must be a string literal or object with 'size\\\(\\\)' and 'data\\\(\\\)' members" "" { 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 "'static_assert' message must be a string literal or object with 'size\\\(\\\)' and 'data\\\(\\\)' members" "" { 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 "'static_assert' message must be a string literal or object with 'size\\\(\\\)' and 'data\\\(\\\)' members" "" { 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 "'static_assert' message must be a string literal or object with 'size\\\(\\\)' and 'data\\\(\\\)' members" "" { 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 "'static_assert' message must be a string literal or object with 'size\\\(\\\)' and 'data\\\(\\\)' members" "" { 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 "'static_assert' message must be a string literal or object with 'size\\\(\\\)' and 'data\\\(\\\)' members" "" { target *-*-* } .-1 }
+#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; } };
+  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\\\(\\\)' member function must be a 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; } }
+    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\\\(\\\)' member function 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"; } }
+  };
+  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\\\(\\\)' member function must be a constant expression" "" { target c++23 } .-1 }
+#endif
+}
--- gcc/testsuite/g++.dg/cpp26/feat-cxx26.C.jj	2023-09-18 12:42:16.327387707 +0200
+++ gcc/testsuite/g++.dg/cpp26/feat-cxx26.C	2023-09-18 13:09:47.167440904 +0200
@@ -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-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 } }
+				// { 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