From f10444ecb9a6063c944235a57c5f1bd520b953be Mon Sep 17 00:00:00 2001
From: Herb Sutter <herb.sutter@gmail.com>
Date: Wed, 8 Nov 2023 07:18:00 -1000
Subject: [PATCH] Require `;` after assert statement

It's just consistent.

And update the grammar.
---
 .../mixed-bounds-safety-with-assert-2.cpp2    |  4 +--
 .../mixed-bounds-safety-with-assert.cpp2      |  4 +--
 .../mixed-bugfix-for-literal-as-nttp.cpp2     |  6 ++---
 ...re2-bugfix-for-assign-expression-list.cpp2 |  6 ++---
 ...2-bugfix-for-non-local-initialization.cpp2 |  4 +--
 .../pure2-concept-definition.cpp2             |  4 +--
 regression-tests/pure2-print.cpp2             |  2 +-
 source/parse.h                                | 25 ++++++++-----------
 source/reflect.h                              |  2 +-
 source/reflect.h2                             | 18 ++++++-------
 10 files changed, 36 insertions(+), 39 deletions(-)

diff --git a/regression-tests/mixed-bounds-safety-with-assert-2.cpp2 b/regression-tests/mixed-bounds-safety-with-assert-2.cpp2
index 054cfb7e49..4b6c11d0cf 100644
--- a/regression-tests/mixed-bounds-safety-with-assert-2.cpp2
+++ b/regression-tests/mixed-bounds-safety-with-assert-2.cpp2
@@ -9,8 +9,8 @@ main: () -> int = {
 
 add_42_to_subrange: (inout rng:_, start:int, end:int)
 = {
-    assert<Bounds>( 0 <= start )
-    assert<Bounds>( end <= rng.ssize() )
+    assert<Bounds>( 0 <= start );
+    assert<Bounds>( end <= rng.ssize() );
 
     count := 0;
     for  rng
diff --git a/regression-tests/mixed-bounds-safety-with-assert.cpp2 b/regression-tests/mixed-bounds-safety-with-assert.cpp2
index d4245c6bab..dc9fba07cc 100644
--- a/regression-tests/mixed-bounds-safety-with-assert.cpp2
+++ b/regression-tests/mixed-bounds-safety-with-assert.cpp2
@@ -7,8 +7,8 @@ main: () -> int = {
 }
 
 print_subrange: (rng:_, start:int, end:int) = {
-    assert<Bounds>( 0 <= start )
-    assert<Bounds>( end <= rng.ssize() )
+    assert<Bounds>( 0 <= start );
+    assert<Bounds>( end <= rng.ssize() );
 
     count := 0;
     for  rng
diff --git a/regression-tests/mixed-bugfix-for-literal-as-nttp.cpp2 b/regression-tests/mixed-bugfix-for-literal-as-nttp.cpp2
index 2600a3a668..932218b968 100644
--- a/regression-tests/mixed-bugfix-for-literal-as-nttp.cpp2
+++ b/regression-tests/mixed-bugfix-for-literal-as-nttp.cpp2
@@ -1,7 +1,7 @@
 #include <chrono>
 using namespace std::chrono_literals;
 main: () = {
-  assert( 10 as i32 == 10 )
-  assert( 10LL as i32 == 10 )
-  assert( 10s as std::chrono::seconds == 10s )
+  assert( 10 as i32 == 10 );
+  assert( 10LL as i32 == 10 );
+  assert( 10s as std::chrono::seconds == 10s );
 }
diff --git a/regression-tests/pure2-bugfix-for-assign-expression-list.cpp2 b/regression-tests/pure2-bugfix-for-assign-expression-list.cpp2
index b7eb84a0d6..5341a7e608 100644
--- a/regression-tests/pure2-bugfix-for-assign-expression-list.cpp2
+++ b/regression-tests/pure2-bugfix-for-assign-expression-list.cpp2
@@ -2,9 +2,9 @@ main: () = {
   vec: type == std::vector<int>;
   v: vec              = (0);
   v                   = ();
-  assert( v == :vec = () )
+  assert( v == :vec = () );
   v                   = (1);
-  assert( v == :vec = (1) )
+  assert( v == :vec = (1) );
   v                   = (2, 3);
-  assert( v == :vec = (2, 3) )
+  assert( v == :vec = (2, 3) );
 }
diff --git a/regression-tests/pure2-bugfix-for-non-local-initialization.cpp2 b/regression-tests/pure2-bugfix-for-non-local-initialization.cpp2
index 3079f017c5..faa06039ea 100644
--- a/regression-tests/pure2-bugfix-for-non-local-initialization.cpp2
+++ b/regression-tests/pure2-bugfix-for-non-local-initialization.cpp2
@@ -3,6 +3,6 @@ t: @struct type = {
   this: std::integral_constant<u, :u = (17, 29)>;
 }
 main: () = {
-  assert<Testing>( t::value[0] == 17 )
-  assert<Testing>( t::value[1] == 29 )
+  assert<Testing>( t::value[0] == 17 );
+  assert<Testing>( t::value[1] == 29 );
 }
diff --git a/regression-tests/pure2-concept-definition.cpp2 b/regression-tests/pure2-concept-definition.cpp2
index 6a5c52c380..7b9cffb369 100644
--- a/regression-tests/pure2-concept-definition.cpp2
+++ b/regression-tests/pure2-concept-definition.cpp2
@@ -1,5 +1,5 @@
 arithmetic: <T> concept = std::integral<T> || std::floating_point<T>;
 main: ()                = {
-  assert<Testing>( arithmetic<i32> )
-  assert<Testing>( arithmetic<float> )
+  assert<Testing>( arithmetic<i32> );
+  assert<Testing>( arithmetic<float> );
 }
diff --git a/regression-tests/pure2-print.cpp2 b/regression-tests/pure2-print.cpp2
index 9791701e23..038fd328f5 100644
--- a/regression-tests/pure2-print.cpp2
+++ b/regression-tests/pure2-print.cpp2
@@ -46,7 +46,7 @@ outer: @print type = {
             else if !m.empty() { b(); }
             else { c(); }
 
-            assert( true )
+            assert( true );
 
             return :() -> std::string = (s + m[0])$; ();
         }
diff --git a/source/parse.h b/source/parse.h
index 9a8537ce2b..1215f15593 100644
--- a/source/parse.h
+++ b/source/parse.h
@@ -7001,9 +7001,12 @@ class parser
     //G     jump-statement
     //G     iteration-statement
     //G     compound-statement
-    //G     contract
+    //G     contract-statement
     //G     declaration
     //G     expression-statement
+    //G
+    //G contract-statement
+    //G     contract ';'
     //
     //GTODO     try-block
     //G
@@ -7089,6 +7092,11 @@ class parser
                 error("only 'assert' contracts are allowed at statement scope");
                 return {};
             }
+            if (curr().type() != lexeme::Semicolon) {
+                error("missing ';' after contract-statement");
+                return {};
+            }
+            next();
             n->statement = std::move(s);
             assert (n->is_contract());
             return n;
@@ -7467,8 +7475,8 @@ class parser
 
 
     //G contract:
-    //G     contract-kind contract-group? ':' logical-or-expression ']' ']'
-    //G     contract-kind contract-group? ':' logical-or-expression ',' string-literal ']' ']'
+    //G     contract-kind contract-group? ':' '(' logical-or-expression ')'
+    //G     contract-kind contract-group? ':' '(' logical-or-expression ',' string-literal ')'
     //G
     //G contract-group:
     //G     '<' id-expression '>'
@@ -7540,17 +7548,6 @@ class parser
         }
         next();
 
-        //  Allow optional ';' after an assert, which is really an empty
-        //  statement (I'm not putting it in the grammar) and so this is
-        //  to skip the "empty statement" check and error
-        if (
-            *n->kind == "assert"
-            && curr().type() == lexeme::Semicolon
-            )
-        {
-            next();
-        }
-
         return n;
     }
 
diff --git a/source/reflect.h b/source/reflect.h
index 59cc45b5c0..2d14523226 100644
--- a/source/reflect.h
+++ b/source/reflect.h
@@ -156,7 +156,7 @@ type_id: @polymorphic_base @copyable type =
     = {
         compiler_services = s;
         n = n_;
-        assert( n, "a meta::type_id must point to a valid type_id_node, not null" )
+        assert( n, "a meta::type_id must point to a valid type_id_node, not null" );
     }
 
     is_wildcard         : (this) -> bool        = n.is_wildcard();
diff --git a/source/reflect.h2 b/source/reflect.h2
index 370f77fb17..f3f926e4fb 100644
--- a/source/reflect.h2
+++ b/source/reflect.h2
@@ -107,7 +107,7 @@ compiler_services: @polymorphic_base @copyable type =
         tokens := generated_lexers.back()&;
         tokens*.lex( lines*, true );
 
-        assert( std::ssize(tokens* .get_map()) == 1 )
+        assert( std::ssize(tokens* .get_map()) == 1 );
 
         //  Now parse this single declaration from
         //  the lexed tokens
@@ -168,7 +168,7 @@ type_id: @polymorphic_base @copyable type =
     = {
         compiler_services = s;
         n = n_;
-        assert( n, "a meta::type_id must point to a valid type_id_node, not null" )
+        assert( n, "a meta::type_id must point to a valid type_id_node, not null" );
     }
 
     is_wildcard         : (this) -> bool        = n*.is_wildcard();
@@ -204,7 +204,7 @@ declaration_base: @polymorphic_base @copyable type =
     = {
         compiler_services = s;
         n = n_;
-        assert( n, "a meta::declaration must point to a valid declaration_node, not null" )
+        assert( n, "a meta::declaration must point to a valid declaration_node, not null" );
     }
 
     position: (override this) -> source_position = n*.position();
@@ -285,7 +285,7 @@ declaration: @polymorphic_base @copyable type =
         pre<Type>( parent_is_type() )               // this precondition should be sufficient ...
     = {
         test := n*.type_member_mark_for_removal();
-        assert( test )                              // ... to ensure this assert is true
+        assert( test );                             // ... to ensure this assert is true
     }
 }
 
@@ -304,7 +304,7 @@ function_declaration: @copyable type =
     ) =
     {
         declaration = (n_, s);
-        assert( n*.is_function() )
+        assert( n*.is_function() );
     }
 
     index_of_parameter_named     : (this, s: std::string_view) -> int  = n*.index_of_parameter_named(s);
@@ -361,7 +361,7 @@ object_declaration: @copyable type =
     ) =
     {
         declaration = (n_, s);
-        assert( n*.is_object() )
+        assert( n*.is_object() );
     }
 
     is_const         : (this) -> bool = n*.is_const();
@@ -397,7 +397,7 @@ type_declaration: @copyable type =
     ) =
     {
         declaration = (n_, s);
-        assert( n*.is_type() )
+        assert( n*.is_type() );
     }
 
     reserve_names: (this, name: std::string_view, forward etc...) = 
@@ -510,7 +510,7 @@ alias_declaration: @copyable type =
     ) =
     {
         declaration = (n_, s);
-        assert( n*.is_alias() )
+        assert( n*.is_alias() );
     }
 }
 
@@ -1239,7 +1239,7 @@ apply_metafunctions: (
     )
     -> bool
 = {
-    assert( n.is_type() )
+    assert( n.is_type() );
 
     //  Check for _names reserved for the metafunction implementation
     for  rtype.get_members()