public inbox for gcc-patches@gcc.gnu.org
 help / color / mirror / Atom feed
* [PATCH] c++: Implement C++26 P2741R3 - user-generated static_assert messages [PR110348]
@ 2023-08-24 14:30 Jakub Jelinek
  2023-09-18 17:21 ` [PATCH] c++, v2: " Jakub Jelinek
  0 siblings, 1 reply; 20+ messages in thread
From: Jakub Jelinek @ 2023-08-24 14:30 UTC (permalink / raw)
  To: Jason Merrill; +Cc: gcc-patches, Jonathan Wakely

Hi!

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.

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

2023-08-24  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.

--- gcc/c-family/c-cppbuiltin.cc.jj	2023-08-23 16:09:15.146715860 +0200
+++ gcc/c-family/c-cppbuiltin.cc	2023-08-24 11:31:20.756788257 +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-08-23 16:09:15.157715719 +0200
+++ gcc/cp/parser.cc	2023-08-23 17:10:47.573754415 +0200
@@ -16427,6 +16427,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.  */
 
@@ -16457,10 +16458,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)
     {
@@ -16479,8 +16480,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-08-08 15:55:06.272167733 +0200
+++ gcc/cp/semantics.cc	2023-08-24 10:40:22.803103810 +0200
@@ -11337,6 +11337,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
@@ -11346,11 +11347,67 @@ 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;
+	}
+      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;
+	}
+      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. */
@@ -11388,9 +11445,76 @@ 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);
+	      message_data
+		= fold_non_dependent_expr (message_data, complain,
+					   /*manifestly_const_eval=*/true);
+	      if (!reduced_constant_expression_p (message_data))
+		{
+		  error_at (location,
+			    "%<static_assert%> message %<data()%> member "
+			    "function must be a constant expression");
+		  return;
+		}
+	      if (len)
+		{
+		  msg = c_getstr (message_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
+	    {
+	      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);
@@ -11404,8 +11528,9 @@ finish_static_assert (tree condition, tr
 	  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-08-23 16:09:15.160715681 +0200
+++ gcc/cp/pt.cc	2023-08-23 16:35:00.670093754 +0200
@@ -19429,15 +19429,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-08-24 11:27:52.909566524 +0200
+++ gcc/testsuite/g++.dg/cpp26/static_assert1.C	2023-08-24 14:44:59.775429645 +0200
@@ -0,0 +1,238 @@
+// 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 } }
+struct I { constexpr signed char size () const { return 0; }
+	   const char *data () const { return ""; } };
+static_assert (true, I {});	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
+struct J { constexpr int size () const { return j ? throw 1 : 0; }
+	   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_assert' message 'data\\\(\\\)' member function must be a constant expression" "" { 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_assert' message 'data\\\(\\\)' member function must be a constant expression" "" { 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_assert' message 'data\\\(\\\)' member function must be a constant expression" "" { 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_assert' message 'data\\\(\\\)' member function must be a constant expression" "" { 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 }
+}
--- gcc/testsuite/g++.dg/cpp26/feat-cxx26.C.jj	2023-08-23 16:09:15.174715504 +0200
+++ gcc/testsuite/g++.dg/cpp26/feat-cxx26.C	2023-08-24 11:29:53.247957973 +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-08-24 15:46:18.149708095 +0200
+++ gcc/testsuite/g++.dg/cpp0x/udlit-error1.C	2023-08-24 15:46:28.719574134 +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

	Jakub


^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH] c++, v2: Implement C++26 P2741R3 - user-generated static_assert messages [PR110348]
  2023-08-24 14:30 [PATCH] c++: Implement C++26 P2741R3 - user-generated static_assert messages [PR110348] Jakub Jelinek
@ 2023-09-18 17:21 ` Jakub Jelinek
  2023-10-27  1:21   ` Jason Merrill
  0 siblings, 1 reply; 20+ messages in thread
From: Jakub Jelinek @ 2023-09-18 17:21 UTC (permalink / raw)
  To: Jason Merrill; +Cc: gcc-patches

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.

--- 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

	Jakub


^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH] c++, v2: Implement C++26 P2741R3 - user-generated static_assert messages [PR110348]
  2023-09-18 17:21 ` [PATCH] c++, v2: " Jakub Jelinek
@ 2023-10-27  1:21   ` Jason Merrill
  2023-11-17 14:18     ` Jason Merrill
  2023-11-21 17:17     ` [PATCH] c++, v3: " Jakub Jelinek
  0 siblings, 2 replies; 20+ messages in thread
From: Jason Merrill @ 2023-10-27  1:21 UTC (permalink / raw)
  To: Jakub Jelinek; +Cc: gcc-patches

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
> 


^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH] c++, v2: Implement C++26 P2741R3 - user-generated static_assert messages [PR110348]
  2023-10-27  1:21   ` Jason Merrill
@ 2023-11-17 14:18     ` Jason Merrill
  2023-11-17 14:22       ` Jakub Jelinek
  2023-11-21 17:17     ` [PATCH] c++, v3: " Jakub Jelinek
  1 sibling, 1 reply; 20+ messages in thread
From: Jason Merrill @ 2023-11-17 14:18 UTC (permalink / raw)
  To: Jakub Jelinek; +Cc: gcc-patches


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
>>
> 


^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH] c++, v2: Implement C++26 P2741R3 - user-generated static_assert messages [PR110348]
  2023-11-17 14:18     ` Jason Merrill
@ 2023-11-17 14:22       ` Jakub Jelinek
  0 siblings, 0 replies; 20+ messages in thread
From: Jakub Jelinek @ 2023-11-17 14:22 UTC (permalink / raw)
  To: Jason Merrill; +Cc: gcc-patches

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


^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH] c++, v3: Implement C++26 P2741R3 - user-generated static_assert messages [PR110348]
  2023-10-27  1:21   ` Jason Merrill
  2023-11-17 14:18     ` Jason Merrill
@ 2023-11-21 17:17     ` Jakub Jelinek
  2023-11-21 17:23       ` Jakub Jelinek
  2023-11-21 21:44       ` Jason Merrill
  1 sibling, 2 replies; 20+ messages in thread
From: Jakub Jelinek @ 2023-11-21 17:17 UTC (permalink / raw)
  To: Jason Merrill; +Cc: gcc-patches

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

Thanks, these 2 now work.

Most of review comments incorporated.

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

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

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

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

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

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

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

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

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

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


	Jakub


^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH] c++, v3: Implement C++26 P2741R3 - user-generated static_assert messages [PR110348]
  2023-11-21 17:17     ` [PATCH] c++, v3: " Jakub Jelinek
@ 2023-11-21 17:23       ` Jakub Jelinek
  2023-11-21 21:44       ` Jason Merrill
  1 sibling, 0 replies; 20+ messages in thread
From: Jakub Jelinek @ 2023-11-21 17:23 UTC (permalink / raw)
  To: Jason Merrill, gcc-patches

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

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

	Jakub


^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH] c++, v3: Implement C++26 P2741R3 - user-generated static_assert messages [PR110348]
  2023-11-21 17:17     ` [PATCH] c++, v3: " Jakub Jelinek
  2023-11-21 17:23       ` Jakub Jelinek
@ 2023-11-21 21:44       ` Jason Merrill
  2023-11-21 22:19         ` Jakub Jelinek
  1 sibling, 1 reply; 20+ messages in thread
From: Jason Merrill @ 2023-11-21 21:44 UTC (permalink / raw)
  To: Jakub Jelinek; +Cc: gcc-patches

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

Agreed.

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

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

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

>>>    [[deprecated("oof"_x)]]	// { dg-error "string literal with user-defined suffix is invalid in this context" "" { target c++26 } }
>>>    void
> 
> Anyway, here is the updated patch with all the changes, but nothing done
> about user-defined literals.
> 
> Note, as the placeholder patch hasn't been reviewed, I've moved the
> -Wc++26-extensions hunks from that patch to this patch.
> 
> 2023-11-21  Jakub Jelinek  <jakub@redhat.com>
> 
> 	PR c++/110348
> gcc/
> 	* doc/invoke.texi (-Wno-c++26-extensions): Document.
> gcc/c-family/
> 	* c.opt (Wc++26-extensions): New option.
> 	* c-cppbuiltin.cc (c_cpp_builtins): For C++26 predefine
> 	__cpp_static_assert to 202306L rather than 201411L.
> gcc/cp/
> 	* parser.cc: Implement C++26 P2741R3 - user-generated static_assert
> 	messages.
> 	(cp_parser_static_assert): Parse message argument as
> 	conditional-expression if it is not a pure string literal or
> 	several of them concatenated followed by closing paren.
> 	* semantics.cc (finish_static_assert): Handle message which is not
> 	STRING_CST.
> 	* pt.cc (tsubst_expr) <case STATIC_ASSERT>: Also tsubst_expr
> 	message and make sure that if it wasn't originally STRING_CST, it
> 	isn't after tsubst_expr either.
> gcc/testsuite/
> 	* g++.dg/cpp26/static_assert1.C: New test.
> 	* g++.dg/cpp26/feat-cxx26.C (__cpp_static_assert): Expect
> 	202306L rather than 201411L.
> 	* g++.dg/cpp0x/udlit-error1.C: Expect different diagnostics for
> 	static_assert with user-defined literal.
> 
> --- gcc/doc/invoke.texi.jj	2023-11-21 09:31:36.008392269 +0100
> +++ gcc/doc/invoke.texi	2023-11-21 15:39:52.448638303 +0100
> @@ -9106,6 +9106,13 @@ Do not warn about C++23 constructs in co
>   an older C++ standard.  Even without this option, some C++23 constructs
>   will only be diagnosed if @option{-Wpedantic} is used.
>   
> +@opindex Wc++26-extensions
> +@opindex Wno-c++26-extensions
> +@item -Wno-c++26-extensions @r{(C++ and Objective-C++ only)}
> +Do not warn about C++26 constructs in code being compiled using
> +an older C++ standard.  Even without this option, some C++26 constructs
> +will only be diagnosed if @option{-Wpedantic} is used.
> +
>   @opindex Wcast-qual
>   @opindex Wno-cast-qual
>   @item -Wcast-qual
> --- gcc/c-family/c.opt.jj	2023-11-11 08:52:20.129849104 +0100
> +++ gcc/c-family/c.opt	2023-11-21 15:39:52.859632548 +0100
> @@ -498,6 +498,10 @@ Wc++23-extensions
>   C++ ObjC++ Var(warn_cxx23_extensions) Warning Init(1)
>   Warn about C++23 constructs in code compiled with an older standard.
>   
> +Wc++26-extensions
> +C++ ObjC++ Var(warn_cxx26_extensions) Warning Init(1)
> +Warn about C++26 constructs in code compiled with an older standard.
> +
>   Wcast-function-type
>   C ObjC C++ ObjC++ Var(warn_cast_function_type) Warning EnabledBy(Wextra)
>   Warn about casts between incompatible function types.
> --- gcc/c-family/c-cppbuiltin.cc.jj	2023-11-20 09:50:07.731214433 +0100
> +++ gcc/c-family/c-cppbuiltin.cc	2023-11-21 15:39:30.042951889 +0100
> @@ -1023,7 +1023,8 @@ c_cpp_builtins (cpp_reader *pfile)
>   	{
>   	  /* Set feature test macros for C++17.  */
>   	  cpp_define (pfile, "__cpp_unicode_characters=201411L");
> -	  cpp_define (pfile, "__cpp_static_assert=201411L");
> +	  if (cxx_dialect <= cxx23)
> +	    cpp_define (pfile, "__cpp_static_assert=201411L");
>   	  cpp_define (pfile, "__cpp_namespace_attributes=201411L");
>   	  cpp_define (pfile, "__cpp_enumerator_attributes=201411L");
>   	  cpp_define (pfile, "__cpp_nested_namespace_definitions=201411L");
> @@ -1086,6 +1087,7 @@ c_cpp_builtins (cpp_reader *pfile)
>   	{
>   	  /* Set feature test macros for C++26.  */
>   	  cpp_define (pfile, "__cpp_constexpr=202306L");
> +	  cpp_define (pfile, "__cpp_static_assert=202306L");
>   	}
>         if (flag_concepts)
>           {
> --- gcc/cp/parser.cc.jj	2023-11-20 09:50:08.067209741 +0100
> +++ gcc/cp/parser.cc	2023-11-21 15:31:39.366534804 +0100
> @@ -16616,6 +16616,7 @@ cp_parser_linkage_specification (cp_pars
>      static_assert-declaration:
>        static_assert ( constant-expression , string-literal ) ;
>        static_assert ( constant-expression ) ; (C++17)
> +     static_assert ( constant-expression, conditional-expression ) ; (C++26)
>   
>      If MEMBER_P, this static_assert is a class member.  */
>   
> @@ -16646,10 +16647,10 @@ cp_parser_static_assert (cp_parser *pars
>   
>     /* Parse the constant-expression.  Allow a non-constant expression
>        here in order to give better diagnostics in finish_static_assert.  */
> -  condition =
> -    cp_parser_constant_expression (parser,
> -                                   /*allow_non_constant_p=*/true,
> -				   /*non_constant_p=*/nullptr);
> +  condition
> +    = cp_parser_constant_expression (parser,
> +				     /*allow_non_constant_p=*/true,
> +				     /*non_constant_p=*/nullptr);
>   
>     if (cp_lexer_peek_token (parser->lexer)->type == CPP_CLOSE_PAREN)
>       {
> @@ -16668,8 +16669,32 @@ cp_parser_static_assert (cp_parser *pars
>         /* Parse the separating `,'.  */
>         cp_parser_require (parser, CPP_COMMA, RT_COMMA);
>   
> -      /* Parse the string-literal message.  */
> -      if (cxx_dialect >= cxx26)
> +      /* Parse the message expression.  */
> +      bool string_lit = true;
> +      for (unsigned int i = 1; ; ++i)
> +	{
> +	  cp_token *tok = cp_lexer_peek_nth_token (parser->lexer, i);
> +	  if (cp_parser_is_pure_string_literal (tok))
> +	    continue;
> +	  else if (tok->type == CPP_CLOSE_PAREN)
> +	    break;
> +	  string_lit = false;
> +	  break;
> +	}
> +      if (!string_lit)
> +	{
> +	  location_t loc = cp_lexer_peek_token (parser->lexer)->location;
> +	  if (cxx_dialect < cxx26)
> +	    pedwarn (loc, OPT_Wc__26_extensions,
> +		     "%<static_assert%> with non-string message only "
> +		     "available with %<-std=c++2c%> or %<-std=gnu++2c%>");
> +
> +	  message = cp_parser_conditional_expression (parser);
> +	  if (TREE_CODE (message) == STRING_CST)
> +	    message = build1_loc (loc, PAREN_EXPR, TREE_TYPE (message),
> +				  message);
> +	}
> +      else if (cxx_dialect >= cxx26)
>   	message = cp_parser_unevaluated_string_literal (parser);
>         else
>   	message = cp_parser_string_literal (parser, /*translate=*/false,
> --- gcc/cp/semantics.cc.jj	2023-11-11 08:52:20.555843211 +0100
> +++ gcc/cp/semantics.cc	2023-11-21 17:34:24.074374632 +0100
> @@ -11434,6 +11434,7 @@ finish_static_assert (tree condition, tr
>   		      bool member_p, bool show_expr_p)
>   {
>     tsubst_flags_t complain = tf_warning_or_error;
> +  tree message_sz = NULL_TREE, message_data = NULL_TREE;
>   
>     if (message == NULL_TREE
>         || message == error_mark_node
> @@ -11443,11 +11444,69 @@ finish_static_assert (tree condition, tr
>   
>     if (check_for_bare_parameter_packs (condition))
>       condition = error_mark_node;
> +  if (check_for_bare_parameter_packs (message))
> +    return;

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

> +  if (TREE_CODE (message) != STRING_CST
> +      && !type_dependent_expression_p (message))
> +    {
> +      message_sz
> +	= finish_class_member_access_expr (message,
> +					   get_identifier ("size"),
> +					   false, tf_none);
> +      message_data
> +	= finish_class_member_access_expr (message,
> +					   get_identifier ("data"),
> +					   false, tf_none);
> +      if (message_sz == error_mark_node || message_data == error_mark_node)
> +	{
> +	  error_at (location, "%<static_assert%> message must be a string "
> +			      "literal or object with %<size%> and "
> +			      "%<data%> members");

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

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

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

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

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

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

Let's also drop this check.

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

And print this type.

> +	  return;
> +	}
> +    }
>   
>     /* Save the condition in case it was a concept check.  */
>     tree orig_condition = condition;
>   
> -  if (instantiation_dependent_expression_p (condition))
> +  if (instantiation_dependent_expression_p (condition)
> +      || instantiation_dependent_expression_p (message))
>       {
>         /* We're in a template; build a STATIC_ASSERT and put it in
>            the right place. */
> @@ -11485,9 +11544,96 @@ finish_static_assert (tree condition, tr
>   	  if (processing_template_decl)
>   	    goto defer;
>   
> -	  int sz = TREE_INT_CST_LOW (TYPE_SIZE_UNIT
> -				     (TREE_TYPE (TREE_TYPE (message))));
> -	  int len = TREE_STRING_LENGTH (message) / sz - 1;
> +	  int len;
> +	  const char *msg = NULL;
> +	  char *buf = NULL;
> +	  if (message_sz && message_data)
> +	    {
> +	      tree msz
> +		= fold_non_dependent_expr (message_sz, complain,
> +					   /*manifestly_const_eval=*/true);

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

> +	      if (!tree_fits_uhwi_p (msz))
> +		{
> +		  cxx_constant_value (message_sz);
> +		  error_at (location,
> +			    "%<static_assert%> message %<size()%> "
> +			    "must be a constant expression");
> +		  return;
> +		}
> +	      else if ((unsigned HOST_WIDE_INT) (int) tree_to_uhwi (msz)
> +		       != tree_to_uhwi (msz))
> +		{
> +		  error_at (location,
> +			    "%<static_assert%> message %<size()%> "
> +			    "%qE too large", message_sz);
> +		  return;
> +		}
> +	      len = tree_to_uhwi (msz);
> +	      tree data
> +		= fold_non_dependent_expr (message_data, tf_none,
> +					   /*manifestly_const_eval=*/true);

And this can be maybe_constant_value.

> +	      if (!reduced_constant_expression_p (data))
> +		data = NULL_TREE;
> +	      if (len)
> +		{
> +		  if (data)
> +		    msg = c_getstr (data);
> +		  if (msg == NULL)
> +		    buf = XNEWVEC (char, len);
> +		  for (int i = 0; i < len; ++i)
> +		    {
> +		      tree t = message_data;
> +		      if (i)
> +			t = build2 (POINTER_PLUS_EXPR,
> +				    TREE_TYPE (message_data), message_data,
> +				    size_int (i));
> +		      t = build1 (INDIRECT_REF, TREE_TYPE (TREE_TYPE (t)), t);
> +		      tree t2
> +			= fold_non_dependent_expr (t, complain,
> +						   /*manifestly_const_eval=*/
> +						   true);

This can also be cxx_constant_value.

> +		      if (!tree_fits_shwi_p (t2))
> +			{
> +			  cxx_constant_value (t);
> +			  error_at (location,
> +				    "%<static_assert%> message %<data()[%d]%> "
> +				    "must be a constant expression", i);
> +			  return;
> +			}
> +		      if (msg == NULL)
> +			buf[i] = tree_to_shwi (t2);
> +		      /* If c_getstr worked, just verify the first and
> +			 last characters using constant evaluation.  */
> +		      else if (len > 2 && i == 0)
> +			i = len - 2;
> +		    }
> +		  if (msg == NULL)
> +		    msg = buf;
> +		}
> +	      else if (!data)
> +		{

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

> +		  data = build2 (COMPOUND_EXPR, integer_type_node,
> +				 message_data, integer_zero_node);
> +		  tree t
> +		    = fold_non_dependent_expr (data, complain,
> +					       /*manifestly_const_eval=*/true);
> +		  if (!integer_zerop (t))
> +		    {
> +		      cxx_constant_value (data);
> +		      error_at (location,
> +				"%<static_assert%> message %<data()%> "
> +				"must be a core constant expression");
> +		      return;
> +		    }
> +		}
> +	    }
> +	  else
> +	    {
> +	      tree eltype = TREE_TYPE (TREE_TYPE (message));
> +	      int sz = TREE_INT_CST_LOW (TYPE_SIZE_UNIT (eltype));
> +	      msg = TREE_STRING_POINTER (message);
> +	      len = TREE_STRING_LENGTH (message) / sz - 1;
> +	    }
>   
>   	  /* See if we can find which clause was failing (for logical AND).  */
>   	  tree bad = find_failing_clause (NULL, orig_condition);
> @@ -11497,12 +11643,13 @@ finish_static_assert (tree condition, tr
>   
>   	  auto_diagnostic_group d;
>   
> -          /* Report the error. */
> +	  /* Report the error. */
>   	  if (len == 0)
>   	    error_at (cloc, "static assertion failed");
>   	  else
> -	    error_at (cloc, "static assertion failed: %s",
> -		      TREE_STRING_POINTER (message));
> +	    error_at (cloc, "static assertion failed: %.*s", len, msg);
> +
> +	  XDELETEVEC (buf);
>   
>   	  diagnose_failing_condition (bad, cloc, show_expr_p);
>   	}
> --- gcc/cp/pt.cc.jj	2023-11-20 09:50:08.081209546 +0100
> +++ gcc/cp/pt.cc	2023-11-21 15:31:39.425533979 +0100
> @@ -18701,15 +18701,20 @@ tsubst_stmt (tree t, tree args, tsubst_f
>   
>       case STATIC_ASSERT:
>         {
> -	tree condition;
> +	tree condition, message;
>   
>   	++c_inhibit_evaluation_warnings;
>   	condition = tsubst_expr (STATIC_ASSERT_CONDITION (t), args,
>   				 complain, in_decl);
> +	message = tsubst_expr (STATIC_ASSERT_MESSAGE (t), args,
> +			       complain, in_decl);
> +	if (TREE_CODE (STATIC_ASSERT_MESSAGE (t)) != STRING_CST
> +	    && TREE_CODE (message) == STRING_CST)
> +	  message = build1_loc (STATIC_ASSERT_SOURCE_LOCATION (t),
> +				PAREN_EXPR, TREE_TYPE (message), message);
>   	--c_inhibit_evaluation_warnings;
>   
> -        finish_static_assert (condition,
> -                              STATIC_ASSERT_MESSAGE (t),
> +	finish_static_assert (condition, message,
>                                 STATIC_ASSERT_SOURCE_LOCATION (t),
>   			      /*member_p=*/false, /*show_expr_p=*/true);
>         }
> --- gcc/testsuite/g++.dg/cpp26/static_assert1.C.jj	2023-11-21 15:31:39.426533965 +0100
> +++ gcc/testsuite/g++.dg/cpp26/static_assert1.C	2023-11-21 17:45:27.862086804 +0100
> @@ -0,0 +1,300 @@
> +// C++26 P2741R3 - user-generated static_assert messages
> +// { dg-do compile { target c++11 } }
> +// { dg-options "" }
> +
> +static_assert (true, "");
> +static_assert (true, (""));	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +				// { dg-error "'static_assert' message must be a string literal or object with 'size' and 'data' members" "" { target *-*-* } .-1 }
> +static_assert (true, "" + 0);	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +				// { dg-error "'static_assert' message must be a string literal or object with 'size' and 'data' members" "" { target *-*-* } .-1 }
> +static_assert (true, 0);	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +				// { dg-error "'static_assert' message must be a string literal or object with 'size' and 'data' members" "" { target *-*-* } .-1 }
> +struct A {};
> +static_assert (true, A {});	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +				// { dg-error "'static_assert' message must be a string literal or object with 'size' and 'data' members" "" { target *-*-* } .-1 }
> +struct B { int size; };
> +static_assert (true, B {});	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +				// { dg-error "'static_assert' message must be a string literal or object with 'size' and 'data' members" "" { target *-*-* } .-1 }
> +struct C { constexpr int size () const { return 0; } };
> +static_assert (true, C {});	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +				// { dg-error "'static_assert' message must be a string literal or object with 'size' and 'data' members" "" { target *-*-* } .-1 }
> +struct D { constexpr int size () const { return 0; } int data; };
> +static_assert (true, D {});	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +				// { dg-error "'D\\\(\\\).D::data' cannot be used as a function" "" { target *-*-* } .-1 }
> +struct E { int size = 0;
> +	   constexpr const char *data () const { return ""; } };
> +static_assert (true, E {});	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +				// { dg-error "'E\\\(\\\).E::size' cannot be used as a function" "" { target c++11_only } .-1 }
> +				// { dg-error "'E\\\{0\\\}.E::size' cannot be used as a function" "" { target c++14 } .-2 }
> +struct F { constexpr const char *size () const { return ""; }
> +	   constexpr const char *data () const { return ""; } };
> +static_assert (true, F {});	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +				// { dg-error "'static_assert' message 'size\\\(\\\)' must be implicitly convertible to 'std::size_t'" "" { target *-*-* } .-1 }
> +struct G { constexpr long size () const { return 0; }
> +	   constexpr float data () const { return 0.0f; } };
> +static_assert (true, G {});	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +				// { dg-error "'static_assert' message 'data\\\(\\\)' must be implicitly convertible to 'const char\\\*'" "" { target *-*-* } .-1 }
> +struct H { short size () const { return 0; }
> +	   constexpr const char *data () const { return ""; } };
> +static_assert (true, H {});	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +				// { dg-warning "'short int H::size\\\(\\\) const' used in 'static_assert' message is not 'constexpr'" "" { target *-*-* } .-1 }
> +struct I { constexpr signed char size () const { return 0; }
> +	   const char *data () const { return ""; } };
> +static_assert (true, I {});	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +				// { dg-warning "'const char\\\* I::data\\\(\\\) const' used in 'static_assert' message is not 'constexpr'" "" { target *-*-* } .-1 }
> +struct J { constexpr int size () const { return j ? throw 1 : 0; }	// { dg-error "expression '<throw-expression>' is not a constant expression" }
> +	   constexpr const char *data () const { return ""; };
> +	   constexpr J (int x) : j (x) {}
> +	   int j; };
> +static_assert (true, J (1));	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +static_assert (false, J (0));	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +				// { dg-error "static assertion failed" "" { target *-*-* } .-1 }
> +static_assert (false, J (1));	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +				// { dg-error "'static_assert' message 'size\\\(\\\)' must be a constant expression" "" { target *-*-* } .-1 }
> +struct K { constexpr operator int () { return 4; } };
> +struct L { constexpr operator const char * () { return "test"; } };
> +struct M { constexpr K size () const { return {}; }
> +	   constexpr L data () const { return {}; } };
> +static_assert (true, M {});	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +static_assert (false, M {});	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +				// { dg-error "static assertion failed: test" "" { target *-*-* } .-1 }
> +#if  __cpp_constexpr_dynamic_alloc >= 201907L
> +struct N { constexpr int size () const { return 3; }
> +	   constexpr const char *data () const { return new char[3] { 'b', 'a', 'd' }; } }; // { dg-error "'\\\* N\\\(\\\).N::data\\\(\\\)' is not a constant expression because allocated storage has not been deallocated" "" { target c++20 } }
> +static_assert (true, N {});	// { dg-warning "'static_assert' with non-string message only available with" "" { target { c++20 && c++23_down } } }
> +static_assert (false, N {});	// { dg-warning "'static_assert' with non-string message only available with" "" { target { c++20 && c++23_down } } }
> +				// { dg-error "'static_assert' message 'data\\\(\\\)\\\[0\\\]' must be a constant expression" "" { target c++20 } .-1 }
> +#endif
> +constexpr const char a[] = { 't', 'e', 's', 't' };
> +struct O { constexpr int size () const { return 4; }
> +	   constexpr const char *data () const { return a; } };
> +static_assert (false, O {});	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +				// { dg-error "static assertion failed: test" "" { target *-*-* } .-1 }
> +struct P { constexpr int size () const { return 4 - p; }
> +	   constexpr const char *data () const { return &a[p]; }
> +	   constexpr P (int x) : p (x) {}
> +	   int p; };
> +static_assert (false, P (0));	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +				// { dg-error "static assertion failed: test" "" { target *-*-* } .-1 }
> +static_assert (false, P (2));	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +				// { dg-error "static assertion failed: st" "" { target *-*-* } .-1 }
> +struct Q { constexpr int size () const { return 4 - q; }
> +	   constexpr const char *data () const { return &"test"[q]; }
> +	   constexpr Q (int x) : q (x) {}
> +	   int q; };
> +static_assert (false, Q (0));	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +				// { dg-error "static assertion failed: test" "" { target *-*-* } .-1 }
> +static_assert (false, Q (1));	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +				// { dg-error "static assertion failed: est" "" { target *-*-* } .-1 }
> +struct R { constexpr int size () const { return 4 - r; }
> +	   constexpr const char *d () const { return "test"; }
> +	   constexpr const char *data () const { return d () + r; }
> +	   constexpr R (int x) : r (x) {}
> +	   int r; };
> +static_assert (false, R (0));	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +				// { dg-error "static assertion failed: test" "" { target *-*-* } .-1 }
> +static_assert (false, R (2));	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +				// { dg-error "static assertion failed: st" "" { target *-*-* } .-1 }
> +struct S { constexpr float size (float) const { return 42.0f; }
> +	   constexpr int size (void * = nullptr) const { return 4; }
> +	   constexpr double data (double) const { return 42.0; }
> +	   constexpr const char *data (int = 0) const { return "test"; } };
> +static_assert (true, S {});	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +static_assert (false, S {});	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +				// { dg-error "static assertion failed: test" "" { target *-*-* } .-1 }
> +
> +using size_t = decltype (sizeof (0));
> +struct string_view {
> +  size_t s;
> +  const char *d;
> +  constexpr string_view () : s (0), d (nullptr) {}
> +  constexpr string_view (const char *p) : s (__builtin_strlen (p)), d (p) {}
> +  constexpr string_view (size_t l, const char *p) : s (l), d (p) {}
> +  constexpr size_t size () const noexcept { return s; }
> +  constexpr const char *data () const noexcept { return d; }
> +};
> +static_assert (true, string_view{});				// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +static_assert (false, string_view ("test"));			// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +								// { dg-error "static assertion failed: test" "" { target *-*-* } .-1 }
> +static_assert (false, string_view ("א"));			// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +								// { dg-error "static assertion failed: א" "" { target *-*-* } .-1 }
> +static_assert (false, string_view (0, nullptr));		// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +								// { dg-error "static assertion failed" "" { target *-*-* } .-1 }
> +static_assert (false, string_view (4, "testwithextrachars"));	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +								// { dg-error "static assertion failed: test" "" { target *-*-* } .-1 }
> +static_assert (false, string_view (42, "test"));		// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +								// { dg-error "array subscript value '41' is outside the bounds of array type 'const char \\\[5\\\]'" "" { target *-*-* } .-1 }
> +								// { dg-error "'static_assert' message 'data\\\(\\\)\\\[41\\\]' must be a constant expression" "" { target *-*-* } .-2 }
> +
> +template <typename T, size_t N>
> +struct array {
> +  constexpr size_t size () const { return N; }
> +  constexpr const T *data () const { return a; }
> +  const T a[N];
> +};
> +static_assert (true, array<char, 2> { 'O', 'K' });		// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +static_assert (true, array<wchar_t, 2> { L'O', L'K' });		// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +								// { dg-error "'static_assert' message 'data\\\(\\\)' must be implicitly convertible to 'const char\\\*'" "" { target *-*-* } .-1 }
> +static_assert (false, array<char, 4> { 't', 'e', 's', 't' });	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +								// { dg-error "static assertion failed: test" "" { target *-*-* } .-1 }
> +
> +void
> +foo ()
> +{
> +  constexpr auto a = array<char, 4> { 't', 'e', 's', 't' };
> +  static_assert (false, a);					// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +}								// { dg-error "static assertion failed: test" "" { target *-*-* } .-1 }
> +
> +#if  __cpp_constexpr_dynamic_alloc >= 201907L
> +struct T {
> +  const char *d = init ();
> +  constexpr int size () const { return 4; }
> +  constexpr const char *data () const { return d; }
> +  constexpr const char *init () const { return new char[4] { 't', 'e', 's', 't' }; }
> +  constexpr ~T () { delete[] d; }
> +};
> +static_assert (false, T{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target { c++20 && c++23_down } } }
> +					// { dg-error "static assertion failed: test" "" { target c++20 } .-1 }
> +#endif
> +struct U { constexpr operator const char * () const { return u; }
> +	   char u[5] = "test"; };
> +#if __cplusplus >= 201402L
> +struct V { constexpr auto size () const { return K{}; }
> +	   constexpr auto data () const { return U{}; } };
> +static_assert (false, V{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target { c++14 && c++23_down } } }
> +					// { dg-error "static assertion failed: test" "" { target c++14 } .-1 }
> +#endif
> +struct W { constexpr int size (int) const { return 4; }
> +	   constexpr const char *data () const { return "test"; } };
> +static_assert (true, W{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +					// { dg-error "no matching function for call to 'W::size\\\(\\\)'" "" { target *-*-* } .-1 }
> +struct X { constexpr int size () const { return 4; }
> +	   constexpr const char *data (int) const { return "test"; } };
> +static_assert (true, X{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +					// { dg-error "no matching function for call to 'X::data\\\(\\\)'" "" { target *-*-* } .-1 }
> +struct Y { constexpr int size () { return 4; }
> +	   constexpr const char *data (int) { return "test"; } };
> +static_assert (true, Y{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +					// { dg-error "no matching function for call to 'Y::data\\\(\\\)'" "" { target *-*-* } .-1 }
> +#if __cpp_concepts >= 201907L
> +struct Z { constexpr int size (auto...) const { return 4; }
> +	   constexpr const char *data (auto...) const { return "test"; } };
> +static_assert (false, Z{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target { c++20 && c++23_down } } }
> +					// { dg-error "static assertion failed: test" "" { target c++20 } .-1 }
> +#endif
> +
> +namespace NN
> +{
> +  template <typename T>
> +  struct A {
> +    constexpr int size () const = delete;
> +    constexpr const char *data () const { return "test"; } };
> +  static_assert (true, A<int>{});	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +					// { dg-error "use of deleted function 'constexpr int NN::A<T>::size\\\(\\\) const \\\[with T = int\\\]'" "" { target *-*-* } .-1 }
> +#if __cpp_concepts >= 201907L
> +  template <typename T>
> +  struct B {
> +    constexpr int size () const { return 4; }
> +    constexpr const char *data () const requires false { return "test"; } };
> +  static_assert (true, B<short>{});	// { dg-warning "'static_assert' with non-string message only available with" "" { target { c++20 && c++23_down } } }
> +					// { dg-error "no matching function for call to 'NN::B<short int>::data\\\(\\\)'" "" { target c++20 } .-1 }
> +#endif
> +  class C {
> +    constexpr int size () const = delete;
> +    constexpr const char *data () const { return "test"; } };
> +  static_assert (true, C{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +					// { dg-error "use of deleted function 'constexpr int NN::C::size\\\(\\\) const'" "" { target *-*-* } .-1 }
> +					// { dg-error "'constexpr const char\\\* NN::C::data\\\(\\\) const' is private within this context" "" { target *-*-* } .-2 }
> +#if __cplusplus >= 201402L
> +  struct D {
> +    constexpr int size () { return 4; }
> +    constexpr int size () const { return 3; }
> +    constexpr const char *data () { return "test"; }
> +    constexpr const char *data () const { return "ehlo"; } };
> +  static_assert (true, D{});	// { dg-warning "'static_assert' with non-string message only available with" "" { target { c++14 && c++23_down } } }
> +  static_assert (false, D{});	// { dg-warning "'static_assert' with non-string message only available with" "" { target { c++14 && c++23_down } } }
> +				// { dg-error "static assertion failed: test" "" { target c++14 } .-1 }
> +#endif
> +  struct E {
> +    constexpr int size () const { return 4; }
> +    constexpr const char *data () const { return "test"; } };
> +  template <typename T>
> +  struct F {
> +    static_assert (false, T{});	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +  };				// { dg-error "static assertion failed: test" "" { target *-*-* } .-1 }
> +  template <typename T>
> +  struct G {
> +    static_assert (false, T{});	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +  };				// { dg-error "'static_assert' message must be a string literal or object with 'size' and 'data' members" "" { target *-*-* } .-1 }
> +  F<E> fe;
> +  G<long> gl;
> +  constexpr E operator ""_myd (const char *, size_t) { return E{}; }
> +  static_assert (false, "foo"_myd);	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +					// { dg-error "static assertion failed: test" "" { target *-*-* } .-1 }
> +  constexpr E operator + (const char *, const E &) { return E{}; }
> +  static_assert (false, "foo" + E{});	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +					// { dg-error "static assertion failed: test" "" { target *-*-* } .-1 }
> +  struct H {
> +    static constexpr int size () { return 7; }
> +    static constexpr const char *data () { return "message"; } };
> +  static_assert (true, H{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +  static_assert (false, H{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +					// { dg-error "static assertion failed: message" "" { target *-*-* } .-1 }
> +  struct I {
> +    static constexpr int size () { return 0; }
> +    static constexpr const char *data () { return nullptr; } };
> +  static_assert (true, I{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +  static_assert (false, I{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +					// { dg-error "static assertion failed" "" { target *-*-* } .-1 }
> +#if __cplusplus >= 201402L
> +  struct J {
> +    static constexpr int size () { return 0; }
> +    static constexpr const char *data (int x = 0) { if (x) return nullptr; else throw 1; } }; // { dg-error "expression '<throw-expression>' is not a constant expression" "" { target c++14 } }
> +  static_assert (true, J{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target { c++14 && c++23_down } } }
> +  static_assert (false, J{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target { c++14 && c++23_down } } }
> +					// { dg-error "'static_assert' message 'data\\\(\\\)' must be a core constant expression" "" { target c++14 } .-1 }
> +#endif
> +#if __cpp_if_consteval >= 202106L
> +  struct K {
> +    static constexpr int size () { if consteval { return 4; } else { throw 1; } }
> +    static constexpr const char *data () { return "test"; }
> +  };
> +  static_assert (true, K{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_only } }
> +  static_assert (false, K{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_only } }
> +					// { dg-error "static assertion failed: test" "" { target c++23 } .-1 }
> +  struct L {
> +    static constexpr int size () { return 4; }
> +    static constexpr const char *data () { if consteval { return "test"; } else { throw 1; } }
> +  };
> +  static_assert (true, L{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_only } }
> +  static_assert (false, L{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_only } }
> +					// { dg-error "static assertion failed: test" "" { target c++23 } .-1 }
> +  struct M {
> +    static constexpr int size () { if consteval { throw 1; } else { return 4; } } // { dg-error "expression '<throw-expression>' is not a constant expression" "" { target c++23 } }
> +    static constexpr const char *data () { return "test"; }
> +  };
> +  static_assert (true, M{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_only } }
> +  static_assert (false, M{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_only } }
> +					// { dg-error "'static_assert' message 'size\\\(\\\)' must be a constant expression" "" { target c++23 } .-1 }
> +  struct N {
> +    static constexpr int size () { return 4; }
> +    static constexpr const char *data () { if consteval { throw 1; } else { return "test"; } } // { dg-error "expression '<throw-expression>' is not a constant expression" "" { target c++23 } }
> +  };
> +  static_assert (true, N{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_only } }
> +  static_assert (false, N{});		// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_only } }
> +					// { dg-error "'static_assert' message 'data\\\(\\\)\\\[0\\\]' must be a constant expression" "" { target c++23 } .-1 }
> +#endif
> +  struct O { constexpr int operator () () const { return 12; } };
> +  struct P { constexpr const char *operator () () const { return "another test"; } };
> +  struct Q { O size; P data; };
> +  static_assert (true, Q ());	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +  static_assert (false, Q {});	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +				// { dg-error "static assertion failed: another test" "" { target *-*-* } .-1 }
> +  constexpr int get_size () { return 16; }
> +  constexpr const char *get_data () { return "yet another test"; }
> +  struct R { int (*size) () = NN::get_size;
> +	     const char *(*data) () = NN::get_data; };
> +  static_assert (true, R ());	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +  static_assert (false, R {});	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +				// { dg-error "static assertion failed: yet another test" "" { target *-*-* } .-1 }
> +}
> --- gcc/testsuite/g++.dg/cpp26/feat-cxx26.C.jj	2023-09-19 09:24:20.921882354 +0200
> +++ gcc/testsuite/g++.dg/cpp26/feat-cxx26.C	2023-11-21 15:31:39.447533671 +0100
> @@ -304,8 +304,8 @@
>   
>   #ifndef __cpp_static_assert
>   #  error "__cpp_static_assert"
> -#elif __cpp_static_assert != 201411
> -#  error "__cpp_static_assert != 201411"
> +#elif __cpp_static_assert != 202306
> +#  error "__cpp_static_assert != 202306"
>   #endif
>   
>   #ifndef __cpp_namespace_attributes
> --- gcc/testsuite/g++.dg/cpp0x/udlit-error1.C.jj	2023-11-02 07:49:18.265848989 +0100
> +++ gcc/testsuite/g++.dg/cpp0x/udlit-error1.C	2023-11-21 15:31:39.470533350 +0100
> @@ -11,7 +11,8 @@ void operator""_x(const char *, decltype
>   #pragma message "hi"_x	  // { dg-warning "string literal with user-defined suffix is invalid in this context" }
>   
>   extern "C"_x { void g(); } // { dg-error "before user-defined string literal" }
> -static_assert(true, "foo"_x); // { dg-error "string literal with user-defined suffix is invalid in this context|expected" }
> +static_assert(true, "foo"_x);	// { dg-error "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +				// { dg-error "'static_assert' message must be a string literal or object with 'size\\\(\\\)' and 'data\\\(\\\)' members" "" { target *-*-* } .-1 }
>   
>   [[deprecated("oof"_x)]]	// { dg-error "string literal with user-defined suffix is invalid in this context" "" { target c++26 } }
>   void
> 
> 
> 	Jakub
> 


^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH] c++, v3: Implement C++26 P2741R3 - user-generated static_assert messages [PR110348]
  2023-11-21 21:44       ` Jason Merrill
@ 2023-11-21 22:19         ` Jakub Jelinek
  2023-11-21 22:51           ` Jakub Jelinek
  0 siblings, 1 reply; 20+ messages in thread
From: Jakub Jelinek @ 2023-11-21 22:19 UTC (permalink / raw)
  To: Jason Merrill; +Cc: gcc-patches

On Tue, Nov 21, 2023 at 04:44:01PM -0500, Jason Merrill wrote:
> > Or do you want to just use
> > 	  error_at (location, "%<static_assert%> message must be a "
> > 			      "unevaluated string literal or object with "
> > 			      "%<size%> and %<data%> members");
> > wording (even when it is in C++26 term) regardless of the -std= level?
> 
> No, I think "unevaluated string literal" will be confusing to users.  I
> guess it's fine as it is, let's just print the type (as commented inline
> below).

Ok.

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

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

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

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

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

" while it has type %qT" ?

> > @@ -11485,9 +11544,96 @@ finish_static_assert (tree condition, tr
> >   	  if (processing_template_decl)
> >   	    goto defer;
> > -	  int sz = TREE_INT_CST_LOW (TYPE_SIZE_UNIT
> > -				     (TREE_TYPE (TREE_TYPE (message))));
> > -	  int len = TREE_STRING_LENGTH (message) / sz - 1;
> > +	  int len;
> > +	  const char *msg = NULL;
> > +	  char *buf = NULL;
> > +	  if (message_sz && message_data)
> > +	    {
> > +	      tree msz
> > +		= fold_non_dependent_expr (message_sz, complain,
> > +					   /*manifestly_const_eval=*/true);
> 
> We can call cxx_constant_value here instead of fold_non_dependent_expr,
> since we don't get here in a template.

Ok.

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

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

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

Ok.

	Jakub


^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH] c++, v3: Implement C++26 P2741R3 - user-generated static_assert messages [PR110348]
  2023-11-21 22:19         ` Jakub Jelinek
@ 2023-11-21 22:51           ` Jakub Jelinek
  2023-11-22  3:51             ` Jason Merrill
  0 siblings, 1 reply; 20+ messages in thread
From: Jakub Jelinek @ 2023-11-21 22:51 UTC (permalink / raw)
  To: Jason Merrill, gcc-patches

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

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

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

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

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


	Jakub


^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH] c++, v3: Implement C++26 P2741R3 - user-generated static_assert messages [PR110348]
  2023-11-21 22:51           ` Jakub Jelinek
@ 2023-11-22  3:51             ` Jason Merrill
  2023-11-22 10:00               ` [PATCH] c++, v4: " Jakub Jelinek
  0 siblings, 1 reply; 20+ messages in thread
From: Jason Merrill @ 2023-11-22  3:51 UTC (permalink / raw)
  To: Jakub Jelinek, gcc-patches

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

Actually, let's go back to the previous message, but change the tf_nones 
above to 'complain' so that we see those errors and then this 
explanation.  Likewise with the conversion checks later in the function.

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

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

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

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

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

Jason


^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH] c++, v4: Implement C++26 P2741R3 - user-generated static_assert messages [PR110348]
  2023-11-22  3:51             ` Jason Merrill
@ 2023-11-22 10:00               ` Jakub Jelinek
  2023-11-22 21:53                 ` Jason Merrill
  0 siblings, 1 reply; 20+ messages in thread
From: Jakub Jelinek @ 2023-11-22 10:00 UTC (permalink / raw)
  To: Jason Merrill; +Cc: gcc-patches

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

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

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

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

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

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


	Jakub


^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH] c++, v4: Implement C++26 P2741R3 - user-generated static_assert messages [PR110348]
  2023-11-22 10:00               ` [PATCH] c++, v4: " Jakub Jelinek
@ 2023-11-22 21:53                 ` Jason Merrill
  2023-11-23  8:32                   ` Jakub Jelinek
  0 siblings, 1 reply; 20+ messages in thread
From: Jason Merrill @ 2023-11-22 21:53 UTC (permalink / raw)
  To: Jakub Jelinek; +Cc: gcc-patches

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

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

OK with that change.

Jason


^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH] c++, v4: Implement C++26 P2741R3 - user-generated static_assert messages [PR110348]
  2023-11-22 21:53                 ` Jason Merrill
@ 2023-11-23  8:32                   ` Jakub Jelinek
  2023-11-28 16:31                     ` Jason Merrill
  2023-11-28 17:08                     ` Fix 'g++.dg/cpp26/static_assert1.C' for '-fno-exceptions' configurations (was: [PATCH] c++, v4: Implement C++26 P2741R3 - user-generated static_assert messages [PR110348]) Thomas Schwinge
  0 siblings, 2 replies; 20+ messages in thread
From: Jakub Jelinek @ 2023-11-23  8:32 UTC (permalink / raw)
  To: Jason Merrill; +Cc: gcc-patches

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

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

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

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

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

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


	Jakub


^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH] c++, v4: Implement C++26 P2741R3 - user-generated static_assert messages [PR110348]
  2023-11-23  8:32                   ` Jakub Jelinek
@ 2023-11-28 16:31                     ` Jason Merrill
  2023-11-28 17:52                       ` Jakub Jelinek
  2023-11-28 17:08                     ` Fix 'g++.dg/cpp26/static_assert1.C' for '-fno-exceptions' configurations (was: [PATCH] c++, v4: Implement C++26 P2741R3 - user-generated static_assert messages [PR110348]) Thomas Schwinge
  1 sibling, 1 reply; 20+ messages in thread
From: Jason Merrill @ 2023-11-28 16:31 UTC (permalink / raw)
  To: Jakub Jelinek; +Cc: gcc-patches, Jonathan Wakely

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

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

> +		  for (int i = 0; i < len; ++i)
> +		    {
> +		      tree t = message_data;
> +		      if (i)
> +			t = build2 (POINTER_PLUS_EXPR,
> +				    TREE_TYPE (message_data), message_data,
> +				    size_int (i));
> +		      t = build1 (INDIRECT_REF, TREE_TYPE (TREE_TYPE (t)), t);
> +		      tree t2 = cxx_constant_value (t, NULL_TREE, complain);
> +		      if (!tree_fits_shwi_p (t2))
> +			{
> +			  error_at (location,
> +				    "%<static_assert%> message %<data()[%d]%> "
> +				    "must be a constant expression", i);
> +			  return;
> +			}
> +		      if (msg == NULL)
> +			buf[i] = tree_to_shwi (t2);
> +		      /* If c_getstr worked, just verify the first and
> +			 last characters using constant evaluation.  */
> +		      else if (len > 2 && i == 0)
> +			i = len - 2;
> +		    }
> +		  if (msg == NULL)
> +		    msg = buf;
> +		}
> +	      else if (!data)
> +		{
> +		  /* We don't have any function to test whether some
> +		     expression is a core constant expression.  So, instead
> +		     test whether (message.data (), 0) is a constant
> +		     expression.  */
> +		  data = build2 (COMPOUND_EXPR, integer_type_node,
> +				 message_data, integer_zero_node);
> +		  tree t = cxx_constant_value (data, NULL_TREE, complain);
> +		  if (!integer_zerop (t))
> +		    {
> +		      error_at (location,
> +				"%<static_assert%> message %<data()%> "
> +				"must be a core constant expression");
> +		      return;
> +		    }
> +		}
> +	    }
> +	  else
> +	    {
> +	      tree eltype = TREE_TYPE (TREE_TYPE (message));
> +	      int sz = TREE_INT_CST_LOW (TYPE_SIZE_UNIT (eltype));
> +	      msg = TREE_STRING_POINTER (message);
> +	      len = TREE_STRING_LENGTH (message) / sz - 1;
> +	    }
>   
>   	  /* See if we can find which clause was failing (for logical AND).  */
>   	  tree bad = find_failing_clause (NULL, orig_condition);
> @@ -11497,12 +11626,13 @@ finish_static_assert (tree condition, tr
>   
>   	  auto_diagnostic_group d;
>   
> -          /* Report the error. */
> +	  /* Report the error. */
>   	  if (len == 0)
>   	    error_at (cloc, "static assertion failed");
>   	  else
> -	    error_at (cloc, "static assertion failed: %s",
> -		      TREE_STRING_POINTER (message));
> +	    error_at (cloc, "static assertion failed: %.*s", len, msg);
> +
> +	  XDELETEVEC (buf);
>   
>   	  diagnose_failing_condition (bad, cloc, show_expr_p);
>   	}


^ permalink raw reply	[flat|nested] 20+ messages in thread

* Fix 'g++.dg/cpp26/static_assert1.C' for '-fno-exceptions' configurations (was: [PATCH] c++, v4: Implement C++26 P2741R3 - user-generated static_assert messages [PR110348])
  2023-11-23  8:32                   ` Jakub Jelinek
  2023-11-28 16:31                     ` Jason Merrill
@ 2023-11-28 17:08                     ` Thomas Schwinge
  2023-11-28 17:11                       ` Fix 'g++.dg/cpp26/static_assert1.C' for '-fno-exceptions' configurations Jason Merrill
  1 sibling, 1 reply; 20+ messages in thread
From: Thomas Schwinge @ 2023-11-28 17:08 UTC (permalink / raw)
  To: Jakub Jelinek, Jason Merrill, gcc-patches

[-- Attachment #1: Type: text/plain, Size: 28643 bytes --]

Hi!

On 2023-11-23T09:32:24+0100, Jakub Jelinek <jakub@redhat.com> wrote:
> Here is what I've committed

> --- gcc/testsuite/g++.dg/cpp26/static_assert1.C.jj    2023-11-22 10:17:41.340064988 +0100
> +++ gcc/testsuite/g++.dg/cpp26/static_assert1.C       2023-11-22 10:47:45.045848504 +0100
> @@ -0,0 +1,309 @@
> +// C++26 P2741R3 - user-generated static_assert messages

OK to push the attached
"Fix 'g++.dg/cpp26/static_assert1.C' for '-fno-exceptions' configurations"?


Grüße
 Thomas


> +// { dg-do compile { target c++11 } }
> +// { dg-options "" }
> +
> +static_assert (true, "");
> +static_assert (true, (""));  // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +                             // { dg-error "'static_assert' message must be a string literal or object with 'size' and 'data' members" "" { target *-*-* } .-1 }
> +                             // { dg-error "request for member 'size' in '\\\(\\\"\\\"\\\)', which is of non-class type 'const char \\\[1\\\]'" "" { target *-*-* } .-2 }
> +static_assert (true, "" + 0);        // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +                             // { dg-error "'static_assert' message must be a string literal or object with 'size' and 'data' members" "" { target *-*-* } .-1 }
> +                             // { dg-error "request for member 'size' in '\\\(const char\\\*\\\)\\\"\\\"', which is of non-class type 'const char\\\*'" "" { target *-*-* } .-2 }
> +static_assert (true, 0);     // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +                             // { dg-error "'static_assert' message must be a string literal or object with 'size' and 'data' members" "" { target *-*-* } .-1 }
> +                             // { dg-error "request for member 'size' in '0', which is of non-class type 'int'" "" { target *-*-* } .-2 }
> +struct A {};
> +static_assert (true, A {});  // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +                             // { dg-error "'static_assert' message must be a string literal or object with 'size' and 'data' members" "" { target *-*-* } .-1 }
> +                             // { dg-error "'struct A' has no member named 'size'" "" { target *-*-* } .-2 }
> +struct B { int size; };
> +static_assert (true, B {});  // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +                             // { dg-error "'static_assert' message must be a string literal or object with 'size' and 'data' members" "" { target *-*-* } .-1 }
> +                             // { dg-error "'struct B' has no member named 'data'" "" { target *-*-* } .-2 }
> +struct C { constexpr int size () const { return 0; } };
> +static_assert (true, C {});  // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +                             // { dg-error "'static_assert' message must be a string literal or object with 'size' and 'data' members" "" { target *-*-* } .-1 }
> +                             // { dg-error "'struct C' has no member named 'data'" "" { target *-*-* } .-2 }
> +struct D { constexpr int size () const { return 0; } int data; };
> +static_assert (true, D {});  // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +                             // { dg-error "'D\\\(\\\).D::data' cannot be used as a function" "" { target *-*-* } .-1 }
> +struct E { int size = 0;
> +        constexpr const char *data () const { return ""; } };
> +static_assert (true, E {});  // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +                             // { dg-error "'E\\\(\\\).E::size' cannot be used as a function" "" { target c++11_only } .-1 }
> +                             // { dg-error "'E\\\{0\\\}.E::size' cannot be used as a function" "" { target c++14 } .-2 }
> +struct F { constexpr const char *size () const { return ""; }
> +        constexpr const char *data () const { return ""; } };
> +static_assert (true, F {});  // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +                             // { dg-error "'static_assert' message 'size\\\(\\\)' must be implicitly convertible to 'std::size_t'" "" { target *-*-* } .-1 }
> +                             // { dg-error "could not convert 'F\\\(\\\).F::size\\\(\\\)' from 'const char\\\*' to '\[^']*'" "" { target *-*-* } .-2 }
> +                             // { dg-error "conversion from 'const char\\\*' to '\[^']*' in a converted constant expression" "" { target *-*-* } .-3 }
> +struct G { constexpr long size () const { return 0; }
> +        constexpr float data () const { return 0.0f; } };
> +static_assert (true, G {});  // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +                             // { dg-error "'static_assert' message 'data\\\(\\\)' must be implicitly convertible to 'const char\\\*'" "" { target *-*-* } .-1 }
> +                             // { dg-error "could not convert 'G\\\(\\\).G::data\\\(\\\)' from 'float' to 'const char\\\*'" "" { target *-*-* } .-2 }
> +struct H { short size () const { return 0; }
> +        constexpr const char *data () const { return ""; } };
> +static_assert (true, H {});  // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +struct I { constexpr signed char size () const { return 0; }
> +        const char *data () const { return ""; } };
> +static_assert (true, I {});  // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +struct J { constexpr int size () const { return j ? throw 1 : 0; }   // { dg-error "expression '<throw-expression>' is not a constant expression" }
> +        constexpr const char *data () const { return ""; };
> +        constexpr J (int x) : j (x) {}
> +        int j; };
> +static_assert (true, J (1)); // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +static_assert (false, J (0));        // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +                             // { dg-error "static assertion failed" "" { target *-*-* } .-1 }
> +static_assert (false, J (1));        // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +                             // { dg-error "'static_assert' message 'size\\\(\\\)' must be a constant expression" "" { target *-*-* } .-1 }
> +struct K { constexpr operator int () { return 4; } };
> +struct L { constexpr operator const char * () { return "test"; } };
> +struct M { constexpr K size () const { return {}; }
> +        constexpr L data () const { return {}; } };
> +static_assert (true, M {});  // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +static_assert (false, M {}); // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +                             // { dg-error "static assertion failed: test" "" { target *-*-* } .-1 }
> +#if  __cpp_constexpr_dynamic_alloc >= 201907L
> +struct N { constexpr int size () const { return 3; }
> +        constexpr const char *data () const { return new char[3] { 'b', 'a', 'd' }; } }; // { dg-error "'\\\* N\\\(\\\).N::data\\\(\\\)' is not a constant expression because allocated storage has not been deallocated" "" { target c++20 } }
> +static_assert (true, N {});  // { dg-warning "'static_assert' with non-string message only available with" "" { target { c++20 && c++23_down } } }
> +static_assert (false, N {}); // { dg-warning "'static_assert' with non-string message only available with" "" { target { c++20 && c++23_down } } }
> +                             // { dg-error "'static_assert' message 'data\\\(\\\)\\\[0\\\]' must be a constant expression" "" { target c++20 } .-1 }
> +#endif
> +constexpr const char a[] = { 't', 'e', 's', 't' };
> +struct O { constexpr int size () const { return 4; }
> +        constexpr const char *data () const { return a; } };
> +static_assert (false, O {}); // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +                             // { dg-error "static assertion failed: test" "" { target *-*-* } .-1 }
> +struct P { constexpr int size () const { return 4 - p; }
> +        constexpr const char *data () const { return &a[p]; }
> +        constexpr P (int x) : p (x) {}
> +        int p; };
> +static_assert (false, P (0));        // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +                             // { dg-error "static assertion failed: test" "" { target *-*-* } .-1 }
> +static_assert (false, P (2));        // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +                             // { dg-error "static assertion failed: st" "" { target *-*-* } .-1 }
> +struct Q { constexpr int size () const { return 4 - q; }
> +        constexpr const char *data () const { return &"test"[q]; }
> +        constexpr Q (int x) : q (x) {}
> +        int q; };
> +static_assert (false, Q (0));        // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +                             // { dg-error "static assertion failed: test" "" { target *-*-* } .-1 }
> +static_assert (false, Q (1));        // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +                             // { dg-error "static assertion failed: est" "" { target *-*-* } .-1 }
> +struct R { constexpr int size () const { return 4 - r; }
> +        constexpr const char *d () const { return "test"; }
> +        constexpr const char *data () const { return d () + r; }
> +        constexpr R (int x) : r (x) {}
> +        int r; };
> +static_assert (false, R (0));        // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +                             // { dg-error "static assertion failed: test" "" { target *-*-* } .-1 }
> +static_assert (false, R (2));        // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +                             // { dg-error "static assertion failed: st" "" { target *-*-* } .-1 }
> +struct S { constexpr float size (float) const { return 42.0f; }
> +        constexpr int size (void * = nullptr) const { return 4; }
> +        constexpr double data (double) const { return 42.0; }
> +        constexpr const char *data (int = 0) const { return "test"; } };
> +static_assert (true, S {});  // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +static_assert (false, S {}); // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +                             // { dg-error "static assertion failed: test" "" { target *-*-* } .-1 }
> +
> +using size_t = decltype (sizeof (0));
> +struct string_view {
> +  size_t s;
> +  const char *d;
> +  constexpr string_view () : s (0), d (nullptr) {}
> +  constexpr string_view (const char *p) : s (__builtin_strlen (p)), d (p) {}
> +  constexpr string_view (size_t l, const char *p) : s (l), d (p) {}
> +  constexpr size_t size () const noexcept { return s; }
> +  constexpr const char *data () const noexcept { return d; }
> +};
> +static_assert (true, string_view{});                         // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +static_assert (false, string_view ("test"));                 // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +                                                             // { dg-error "static assertion failed: test" "" { target *-*-* } .-1 }
> +static_assert (false, string_view ("א"));                    // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +                                                             // { dg-error "static assertion failed: א" "" { target *-*-* } .-1 }
> +static_assert (false, string_view (0, nullptr));             // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +                                                             // { dg-error "static assertion failed" "" { target *-*-* } .-1 }
> +static_assert (false, string_view (4, "testwithextrachars"));        // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +                                                             // { dg-error "static assertion failed: test" "" { target *-*-* } .-1 }
> +static_assert (false, string_view (42, "test"));             // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +                                                             // { dg-error "array subscript value '41' is outside the bounds of array type 'const char \\\[5\\\]'" "" { target *-*-* } .-1 }
> +                                                             // { dg-error "'static_assert' message 'data\\\(\\\)\\\[41\\\]' must be a constant expression" "" { target *-*-* } .-2 }
> +
> +template <typename T, size_t N>
> +struct array {
> +  constexpr size_t size () const { return N; }
> +  constexpr const T *data () const { return a; }
> +  const T a[N];
> +};
> +static_assert (true, array<char, 2> { 'O', 'K' });           // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +static_assert (true, array<wchar_t, 2> { L'O', L'K' });              // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +                                                             // { dg-error "'static_assert' message 'data\\\(\\\)' must be implicitly convertible to 'const char\\\*'" "" { target *-*-* } .-1 }
> +                                                             // { dg-error "could not convert 'array<wchar_t, 2>{const wchar_t \\\[2\\\]{\[0-9]+, \[0-9]+}}.array<wchar_t, 2>::data\\\(\\\)' from 'const wchar_t\\\*' to 'const char\\\*'" "" { target *-*-* } .-2 }
> +static_assert (false, array<char, 4> { 't', 'e', 's', 't' });        // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +                                                             // { dg-error "static assertion failed: test" "" { target *-*-* } .-1 }
> +
> +void
> +foo ()
> +{
> +  constexpr auto a = array<char, 4> { 't', 'e', 's', 't' };
> +  static_assert (false, a);                                  // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +}                                                            // { dg-error "static assertion failed: test" "" { target *-*-* } .-1 }
> +
> +#if  __cpp_constexpr_dynamic_alloc >= 201907L
> +struct T {
> +  const char *d = init ();
> +  constexpr int size () const { return 4; }
> +  constexpr const char *data () const { return d; }
> +  constexpr const char *init () const { return new char[4] { 't', 'e', 's', 't' }; }
> +  constexpr ~T () { delete[] d; }
> +};
> +static_assert (false, T{});          // { dg-warning "'static_assert' with non-string message only available with" "" { target { c++20 && c++23_down } } }
> +                                     // { dg-error "static assertion failed: test" "" { target c++20 } .-1 }
> +#endif
> +struct U { constexpr operator const char * () const { return u; }
> +        char u[5] = "test"; };
> +#if __cplusplus >= 201402L
> +struct V { constexpr auto size () const { return K{}; }
> +        constexpr auto data () const { return U{}; } };
> +static_assert (false, V{});          // { dg-warning "'static_assert' with non-string message only available with" "" { target { c++14 && c++23_down } } }
> +                                     // { dg-error "static assertion failed: test" "" { target c++14 } .-1 }
> +#endif
> +struct W { constexpr int size (int) const { return 4; }
> +        constexpr const char *data () const { return "test"; } };
> +static_assert (true, W{});           // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +                                     // { dg-error "no matching function for call to 'W::size\\\(\\\)'" "" { target *-*-* } .-1 }
> +struct X { constexpr int size () const { return 4; }
> +        constexpr const char *data (int) const { return "test"; } };
> +static_assert (true, X{});           // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +                                     // { dg-error "no matching function for call to 'X::data\\\(\\\)'" "" { target *-*-* } .-1 }
> +struct Y { constexpr int size () { return 4; }
> +        constexpr const char *data (int) { return "test"; } };
> +static_assert (true, Y{});           // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +                                     // { dg-error "no matching function for call to 'Y::data\\\(\\\)'" "" { target *-*-* } .-1 }
> +#if __cpp_concepts >= 201907L
> +struct Z { constexpr int size (auto...) const { return 4; }
> +        constexpr const char *data (auto...) const { return "test"; } };
> +static_assert (false, Z{});          // { dg-warning "'static_assert' with non-string message only available with" "" { target { c++20 && c++23_down } } }
> +                                     // { dg-error "static assertion failed: test" "" { target c++20 } .-1 }
> +#endif
> +
> +namespace NN
> +{
> +  template <typename T>
> +  struct A {
> +    constexpr int size () const = delete;
> +    constexpr const char *data () const { return "test"; } };
> +  static_assert (true, A<int>{});    // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +                                     // { dg-error "use of deleted function 'constexpr int NN::A<T>::size\\\(\\\) const \\\[with T = int\\\]'" "" { target *-*-* } .-1 }
> +#if __cpp_concepts >= 201907L
> +  template <typename T>
> +  struct B {
> +    constexpr int size () const { return 4; }
> +    constexpr const char *data () const requires false { return "test"; } };
> +  static_assert (true, B<short>{});  // { dg-warning "'static_assert' with non-string message only available with" "" { target { c++20 && c++23_down } } }
> +                                     // { dg-error "no matching function for call to 'NN::B<short int>::data\\\(\\\)'" "" { target c++20 } .-1 }
> +#endif
> +  class C {
> +    constexpr int size () const = delete;
> +    constexpr const char *data () const { return "test"; } };
> +  static_assert (true, C{});         // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +                                     // { dg-error "use of deleted function 'constexpr int NN::C::size\\\(\\\) const'" "" { target *-*-* } .-1 }
> +                                     // { dg-error "'constexpr const char\\\* NN::C::data\\\(\\\) const' is private within this context" "" { target *-*-* } .-2 }
> +#if __cplusplus >= 201402L
> +  struct D {
> +    constexpr int size () { return 4; }
> +    constexpr int size () const { return 3; }
> +    constexpr const char *data () { return "test"; }
> +    constexpr const char *data () const { return "ehlo"; } };
> +  static_assert (true, D{}); // { dg-warning "'static_assert' with non-string message only available with" "" { target { c++14 && c++23_down } } }
> +  static_assert (false, D{});        // { dg-warning "'static_assert' with non-string message only available with" "" { target { c++14 && c++23_down } } }
> +                             // { dg-error "static assertion failed: test" "" { target c++14 } .-1 }
> +#endif
> +  struct E {
> +    constexpr int size () const { return 4; }
> +    constexpr const char *data () const { return "test"; } };
> +  template <typename T>
> +  struct F {
> +    static_assert (false, T{});      // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +  };                         // { dg-error "static assertion failed: test" "" { target *-*-* } .-1 }
> +  template <typename T>
> +  struct G {
> +    static_assert (false, T{});      // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +  };                         // { dg-error "'static_assert' message must be a string literal or object with 'size' and 'data' members" "" { target *-*-* } .-1 }
> +                             // { dg-error "request for member 'size' in '0', which is of non-class type 'long int'" "" { target *-*-* } .-2 }
> +  F<E> fe;
> +  G<long> gl;
> +  constexpr E operator ""_myd (const char *, size_t) { return E{}; }
> +  static_assert (false, "foo"_myd);  // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +                                     // { dg-error "static assertion failed: test" "" { target *-*-* } .-1 }
> +  constexpr E operator + (const char *, const E &) { return E{}; }
> +  static_assert (false, "foo" + E{});        // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +                                     // { dg-error "static assertion failed: test" "" { target *-*-* } .-1 }
> +  struct H {
> +    static constexpr int size () { return 7; }
> +    static constexpr const char *data () { return "message"; } };
> +  static_assert (true, H{});         // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +  static_assert (false, H{});                // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +                                     // { dg-error "static assertion failed: message" "" { target *-*-* } .-1 }
> +  struct I {
> +    static constexpr int size () { return 0; }
> +    static constexpr const char *data () { return nullptr; } };
> +  static_assert (true, I{});         // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +  static_assert (false, I{});                // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +                                     // { dg-error "static assertion failed" "" { target *-*-* } .-1 }
> +#if __cplusplus >= 201402L
> +  struct J {
> +    static constexpr int size () { return 0; }
> +    static constexpr const char *data (int x = 0) { if (x) return nullptr; else throw 1; } }; // { dg-error "expression '<throw-expression>' is not a constant expression" "" { target c++14 } }
> +  static_assert (true, J{});         // { dg-warning "'static_assert' with non-string message only available with" "" { target { c++14 && c++23_down } } }
> +  static_assert (false, J{});                // { dg-warning "'static_assert' with non-string message only available with" "" { target { c++14 && c++23_down } } }
> +                                     // { dg-error "'static_assert' message 'data\\\(\\\)' must be a core constant expression" "" { target c++14 } .-1 }
> +#endif
> +#if __cpp_if_consteval >= 202106L
> +  struct K {
> +    static constexpr int size () { if consteval { return 4; } else { throw 1; } }
> +    static constexpr const char *data () { return "test"; }
> +  };
> +  static_assert (true, K{});         // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_only } }
> +  static_assert (false, K{});                // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_only } }
> +                                     // { dg-error "static assertion failed: test" "" { target c++23 } .-1 }
> +  struct L {
> +    static constexpr int size () { return 4; }
> +    static constexpr const char *data () { if consteval { return "test"; } else { throw 1; } }
> +  };
> +  static_assert (true, L{});         // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_only } }
> +  static_assert (false, L{});                // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_only } }
> +                                     // { dg-error "static assertion failed: test" "" { target c++23 } .-1 }
> +  struct M {
> +    static constexpr int size () { if consteval { throw 1; } else { return 4; } } // { dg-error "expression '<throw-expression>' is not a constant expression" "" { target c++23 } }
> +    static constexpr const char *data () { return "test"; }
> +  };
> +  static_assert (true, M{});         // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_only } }
> +  static_assert (false, M{});                // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_only } }
> +                                     // { dg-error "'static_assert' message 'size\\\(\\\)' must be a constant expression" "" { target c++23 } .-1 }
> +  struct N {
> +    static constexpr int size () { return 4; }
> +    static constexpr const char *data () { if consteval { throw 1; } else { return "test"; } } // { dg-error "expression '<throw-expression>' is not a constant expression" "" { target c++23 } }
> +  };
> +  static_assert (true, N{});         // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_only } }
> +  static_assert (false, N{});                // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_only } }
> +                                     // { dg-error "'static_assert' message 'data\\\(\\\)\\\[0\\\]' must be a constant expression" "" { target c++23 } .-1 }
> +#endif
> +  struct O { constexpr int operator () () const { return 12; } };
> +  struct P { constexpr const char *operator () () const { return "another test"; } };
> +  struct Q { O size; P data; };
> +  static_assert (true, Q ());        // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +  static_assert (false, Q {});       // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +                             // { dg-error "static assertion failed: another test" "" { target *-*-* } .-1 }
> +  constexpr int get_size () { return 16; }
> +  constexpr const char *get_data () { return "yet another test"; }
> +  struct R { int (*size) () = NN::get_size;
> +          const char *(*data) () = NN::get_data; };
> +  static_assert (true, R ());        // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +  static_assert (false, R {});       // { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
> +                             // { dg-error "static assertion failed: yet another test" "" { target *-*-* } .-1 }
> +}


-----------------
Siemens Electronic Design Automation GmbH; Anschrift: Arnulfstraße 201, 80634 München; Gesellschaft mit beschränkter Haftung; Geschäftsführer: Thomas Heurung, Frank Thürauf; Sitz der Gesellschaft: München; Registergericht München, HRB 106955

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Fix-g-.dg-cpp26-static_assert1.C-for-fno-exceptions-.patch --]
[-- Type: text/x-diff, Size: 4428 bytes --]

From b93caa5f0680f4c2c3c898373bc561bc54673765 Mon Sep 17 00:00:00 2001
From: Thomas Schwinge <thomas@codesourcery.com>
Date: Tue, 28 Nov 2023 17:52:34 +0100
Subject: [PATCH] Fix 'g++.dg/cpp26/static_assert1.C' for '-fno-exceptions'
 configurations

This test case, added in recent commit 6ce952188ab39e303e4f63e474b5cba83b5b12fd
"c++: Implement C++26 P2741R3 - user-generated static_assert messages [PR110348]",
expectedly runs into 'UNSUPPORTED: [...]: exception handling disabled', but
along the way also FAILs a few tests:

    UNSUPPORTED: g++.dg/cpp26/static_assert1.C  -std=gnu++98
    PASS: g++.dg/cpp26/static_assert1.C  -std=gnu++11  (test for warnings, line 6)
    [...]
    PASS: g++.dg/cpp26/static_assert1.C  -std=gnu++11  (test for warnings, line 51)
    FAIL: g++.dg/cpp26/static_assert1.C  -std=gnu++11  (test for errors, line 52)
    PASS: g++.dg/cpp26/static_assert1.C  -std=gnu++11  (test for warnings, line 56)
    PASS: g++.dg/cpp26/static_assert1.C  -std=gnu++11  (test for warnings, line 57)
    FAIL: g++.dg/cpp26/static_assert1.C  -std=gnu++11  at line 58 (test for errors, line 57)
    PASS: g++.dg/cpp26/static_assert1.C  -std=gnu++11  (test for warnings, line 59)
    [...]
    PASS: g++.dg/cpp26/static_assert1.C  -std=gnu++11  at line 308 (test for errors, line 307)
    UNSUPPORTED: g++.dg/cpp26/static_assert1.C  -std=gnu++11: exception handling disabled
    PASS: g++.dg/cpp26/static_assert1.C  -std=gnu++14  (test for warnings, line 6)
    [...]
    PASS: g++.dg/cpp26/static_assert1.C  -std=gnu++14  (test for warnings, line 51)
    FAIL: g++.dg/cpp26/static_assert1.C  -std=gnu++14  (test for errors, line 52)
    PASS: g++.dg/cpp26/static_assert1.C  -std=gnu++14  (test for warnings, line 56)
    PASS: g++.dg/cpp26/static_assert1.C  -std=gnu++14  (test for warnings, line 57)
    FAIL: g++.dg/cpp26/static_assert1.C  -std=gnu++14  at line 58 (test for errors, line 57)
    PASS: g++.dg/cpp26/static_assert1.C  -std=gnu++14  (test for warnings, line 59)
    [...]
    PASS: g++.dg/cpp26/static_assert1.C  -std=gnu++14  at line 257 (test for errors, line 256)
    FAIL: g++.dg/cpp26/static_assert1.C  -std=gnu++14  (test for errors, line 261)
    PASS: g++.dg/cpp26/static_assert1.C  -std=gnu++14  (test for warnings, line 262)
    [...]
    PASS: g++.dg/cpp26/static_assert1.C  -std=gnu++14  at line 308 (test for errors, line 307)
    UNSUPPORTED: g++.dg/cpp26/static_assert1.C  -std=gnu++14: exception handling disabled
    PASS: g++.dg/cpp26/static_assert1.C  -std=gnu++20  (test for warnings, line 6)
    [...]
    PASS: g++.dg/cpp26/static_assert1.C  -std=gnu++20  (test for warnings, line 51)
    FAIL: g++.dg/cpp26/static_assert1.C  -std=gnu++20  (test for errors, line 52)
    PASS: g++.dg/cpp26/static_assert1.C  -std=gnu++20  (test for warnings, line 56)
    PASS: g++.dg/cpp26/static_assert1.C  -std=gnu++20  (test for warnings, line 57)
    FAIL: g++.dg/cpp26/static_assert1.C  -std=gnu++20  at line 58 (test for errors, line 57)
    PASS: g++.dg/cpp26/static_assert1.C  -std=gnu++20  (test for warnings, line 59)
    [...]
    PASS: g++.dg/cpp26/static_assert1.C  -std=gnu++20  at line 257 (test for errors, line 256)
    FAIL: g++.dg/cpp26/static_assert1.C  -std=gnu++20  (test for errors, line 261)
    PASS: g++.dg/cpp26/static_assert1.C  -std=gnu++20  (test for warnings, line 262)
    [...]
    PASS: g++.dg/cpp26/static_assert1.C  -std=gnu++20  at line 308 (test for errors, line 307)
    UNSUPPORTED: g++.dg/cpp26/static_assert1.C  -std=gnu++20: exception handling disabled

Use an explicit '-fexceptions' to turn this front end test case all-PASS.

	gcc/testsuite/
	* g++.dg/cpp26/static_assert1.C: Fix for '-fno-exceptions'
	configurations.
---
 gcc/testsuite/g++.dg/cpp26/static_assert1.C | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/gcc/testsuite/g++.dg/cpp26/static_assert1.C b/gcc/testsuite/g++.dg/cpp26/static_assert1.C
index 9dec52b90d6..59724ae32ce 100644
--- a/gcc/testsuite/g++.dg/cpp26/static_assert1.C
+++ b/gcc/testsuite/g++.dg/cpp26/static_assert1.C
@@ -1,6 +1,8 @@
 // C++26 P2741R3 - user-generated static_assert messages
 // { dg-do compile { target c++11 } }
 // { dg-options "" }
+// Override any default-'-fno-exceptions':
+// { dg-additional-options -fexceptions }
 
 static_assert (true, "");
 static_assert (true, (""));	// { dg-warning "'static_assert' with non-string message only available with" "" { target c++23_down } }
-- 
2.34.1


^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: Fix 'g++.dg/cpp26/static_assert1.C' for '-fno-exceptions' configurations
  2023-11-28 17:08                     ` Fix 'g++.dg/cpp26/static_assert1.C' for '-fno-exceptions' configurations (was: [PATCH] c++, v4: Implement C++26 P2741R3 - user-generated static_assert messages [PR110348]) Thomas Schwinge
@ 2023-11-28 17:11                       ` Jason Merrill
  2023-11-29 13:19                         ` Thomas Schwinge
  0 siblings, 1 reply; 20+ messages in thread
From: Jason Merrill @ 2023-11-28 17:11 UTC (permalink / raw)
  To: Thomas Schwinge, Jakub Jelinek, gcc-patches

On 11/28/23 12:08, Thomas Schwinge wrote:
> Hi!
> 
> On 2023-11-23T09:32:24+0100, Jakub Jelinek <jakub@redhat.com> wrote:
>> Here is what I've committed
> 
>> --- gcc/testsuite/g++.dg/cpp26/static_assert1.C.jj    2023-11-22 10:17:41.340064988 +0100
>> +++ gcc/testsuite/g++.dg/cpp26/static_assert1.C       2023-11-22 10:47:45.045848504 +0100
>> @@ -0,0 +1,309 @@
>> +// C++26 P2741R3 - user-generated static_assert messages
> 
> OK to push the attached
> "Fix 'g++.dg/cpp26/static_assert1.C' for '-fno-exceptions' configurations"?

>  // { dg-options "" }
> +// Override any default-'-fno-exceptions':
> +// { dg-additional-options -fexceptions }

Might as well put the -fexceptions into the dg-options instead of having 
two separate lines?  OK either way.

Jason


^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH] c++, v4: Implement C++26 P2741R3 - user-generated static_assert messages [PR110348]
  2023-11-28 16:31                     ` Jason Merrill
@ 2023-11-28 17:52                       ` Jakub Jelinek
  2023-11-28 21:33                         ` Jason Merrill
  0 siblings, 1 reply; 20+ messages in thread
From: Jakub Jelinek @ 2023-11-28 17:52 UTC (permalink / raw)
  To: Jason Merrill; +Cc: gcc-patches, Jonathan Wakely

On Tue, Nov 28, 2023 at 11:31:48AM -0500, Jason Merrill wrote:
> > +	      if (len)
> > +		{
> > +		  if (data)
> > +		    msg = c_getstr (data);
> > +		  if (msg == NULL)
> > +		    buf = XNEWVEC (char, len);
> 
> Jonathan pointed out elsewhere that this gets leaked if error return
> prevents us from getting to the XDELETEVEC.

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

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

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

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


	Jakub


^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH] c++, v4: Implement C++26 P2741R3 - user-generated static_assert messages [PR110348]
  2023-11-28 17:52                       ` Jakub Jelinek
@ 2023-11-28 21:33                         ` Jason Merrill
  0 siblings, 0 replies; 20+ messages in thread
From: Jason Merrill @ 2023-11-28 21:33 UTC (permalink / raw)
  To: Jakub Jelinek; +Cc: gcc-patches, Jonathan Wakely

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

RAII is generally preferable, but this is sufficient.

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


^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: Fix 'g++.dg/cpp26/static_assert1.C' for '-fno-exceptions' configurations
  2023-11-28 17:11                       ` Fix 'g++.dg/cpp26/static_assert1.C' for '-fno-exceptions' configurations Jason Merrill
@ 2023-11-29 13:19                         ` Thomas Schwinge
  0 siblings, 0 replies; 20+ messages in thread
From: Thomas Schwinge @ 2023-11-29 13:19 UTC (permalink / raw)
  To: Jason Merrill; +Cc: Jakub Jelinek, gcc-patches

Hi!

On 2023-11-28T12:11:22-0500, Jason Merrill <jason@redhat.com> wrote:
> On 11/28/23 12:08, Thomas Schwinge wrote:
>>  // { dg-options "" }
>> +// Override any default-'-fno-exceptions':
>> +// { dg-additional-options -fexceptions }
>
> Might as well put the -fexceptions into the dg-options instead of having
> two separate lines?

The net effect is the same, but in my opinion, the intentions are clearer
in the "separate" form: 'dg-options ""' cancels the standard options, and
then 'dg-additional-options -fexceptions' adds an additional option, with
rationale.


Grüße
 Thomas


> OK either way.


-----------------
Siemens Electronic Design Automation GmbH; Anschrift: Arnulfstraße 201, 80634 München; Gesellschaft mit beschränkter Haftung; Geschäftsführer: Thomas Heurung, Frank Thürauf; Sitz der Gesellschaft: München; Registergericht München, HRB 106955

^ permalink raw reply	[flat|nested] 20+ messages in thread

end of thread, other threads:[~2023-11-29 13:19 UTC | newest]

Thread overview: 20+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-08-24 14:30 [PATCH] c++: Implement C++26 P2741R3 - user-generated static_assert messages [PR110348] Jakub Jelinek
2023-09-18 17:21 ` [PATCH] c++, v2: " Jakub Jelinek
2023-10-27  1:21   ` Jason Merrill
2023-11-17 14:18     ` Jason Merrill
2023-11-17 14:22       ` Jakub Jelinek
2023-11-21 17:17     ` [PATCH] c++, v3: " Jakub Jelinek
2023-11-21 17:23       ` Jakub Jelinek
2023-11-21 21:44       ` Jason Merrill
2023-11-21 22:19         ` Jakub Jelinek
2023-11-21 22:51           ` Jakub Jelinek
2023-11-22  3:51             ` Jason Merrill
2023-11-22 10:00               ` [PATCH] c++, v4: " Jakub Jelinek
2023-11-22 21:53                 ` Jason Merrill
2023-11-23  8:32                   ` Jakub Jelinek
2023-11-28 16:31                     ` Jason Merrill
2023-11-28 17:52                       ` Jakub Jelinek
2023-11-28 21:33                         ` Jason Merrill
2023-11-28 17:08                     ` Fix 'g++.dg/cpp26/static_assert1.C' for '-fno-exceptions' configurations (was: [PATCH] c++, v4: Implement C++26 P2741R3 - user-generated static_assert messages [PR110348]) Thomas Schwinge
2023-11-28 17:11                       ` Fix 'g++.dg/cpp26/static_assert1.C' for '-fno-exceptions' configurations Jason Merrill
2023-11-29 13:19                         ` Thomas Schwinge

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).