Subject: Re: bug#72695: CC Mode 5.35.2 (C++//l); c++-mode misindents 'if constexpr' inside lambdas

From: Alan Mackenzie <[email-masked]>
Date: Mon, 9 Sep 2024 15:15:34 +0000

Hello, Arsen.

On Sun, Sep 01, 2024 at 03:03:30 +0200, Arsen Arsenović wrote:
> Hi Alan,

> Alan Mackenzie <[email-masked]> writes:

> > It's going well, thanks!  At the current rate of progress, I'll have my
> > mail setup set up by this time next week, and that is the point at
> > which, at least for me, my new machine becomes my current machine.

> Great to hear!

Thanks!  I'm afraid I've spent all week on that dashed email setup, and
it's now finally working.  So I've made only some progress on the bug,
sorry.

But here is what I have:

[ .... ]

> Heh, given there's only a dozen or so syntax elements in Elisp, I agree
> ;-).  C++ exposes lots of flexibility, so the syntax grew to match.

Anyhow, I'm feeling less negative about C++'s syntax, now.

> Lambda expressions declare both a data type and a function, and their
> syntax exposes the syntax elements of both, hence the proverbial
> complexity being the sum of the two.

[ .... ]

> > Anyhow, I've got a first version, incompletely tested, of a new
> > handling for a lambda expression.  The patch is not nearly as big as
> > it looks, two thirds of it is just re-indenting a large function I
> > put an extra `if' form aroun.

> I've tried it on a few examples I came up with, it seems to work well in
> 'real-life' code.

> I've also constructed the following "stress test" which includes (almost
> - see below) all the productions that can exist in a lambda function, in
> the same context as the original bad example:

Thanks, this was very useful.  As an important question, can and alignas
specifier exist inside a lambda expression?  If so, what does it mean?
My new patch below doesn't (yet) handle it.

>   void
>   def ()
>   {
>     auto foo = [&,
>   	      a,
>   	      b = 123,
>   	      c,
>   	      ...d,
>   	      ...e = 123
>   	      ]
>       <typename T,
>        typename S>
>       //alignas (128)
>       [[gnu::noreturn]]
>       requires some_concept<T>
>       (int a,
>        int b,
>        int c)
>       consteval
>       constexpr
>       mutable
>       static
>       //noexcept(true)
>       [[gnu::packed]]
>       //alignas (128)
>       //->
>       //int
>       //requires (std::same_as <decltype (a), int>)
>         {
>       if constexpr (true)
>         ;
>     }
>   }

Here's a modified version which is now fully handled (as far as it goes):

void
def ()
{
      auto foo = [&,
              a,
              b = 123,
              c,
              ...d,
              ...e = 123
      ]
        <typename T,
         typename S>
//alignas (128)
        [[gnu::noreturn]]
        requires some_concept<T>
        (int a,
         int b,
         int c)
        consteval
        constexpr
        mutable
        static
        noexcept(true)
        [[gnu::packed]]
//alignas (128)
        -> int
        requires (std::same_as <decltype (a), int>)
      {
        if constexpr (true)
          ;
      }
}

[ .... ]

> It seems that it gets confused by the trailing requires clauses, the
> alignas, the noexcept and the trailing return type (all commented -
> uncomment any or all to reproduce).

These were coding errors in CC Mode.  I hope the patch below will have
fixed these, apart from the alignas (see above).

> However, this does not appear to negatively impact the syntax
> recognition instead, for the most part (I removed an element from the
> stress test above in order to demonstrate that the issue with
> indentation appears to be separate).

> In the following example:

>   void
>   def ()
>   {
>     auto foo = [&,
>   	      a,
>   	      b = 123,
>   	      c,
>   	      ...d,
>   	      ...e = 123
>   	      ]
>       <typename T,
>        typename S>
>       alignas (128)
>       [[gnu::noreturn]]
>       //requires some_concept<T>
>       (int a,
>        int b,
>        int c)
>       consteval
>       constexpr
>       mutable
>       static
>       noexcept(true)
>       [[gnu::packed]]
>       alignas (128)
>       ->
>       int
>       requires (std::same_as <decltype (a), int>)
>     {
>       if constexpr (true)
>         ;
>     }
>   }

> ... uncommenting the requires line confuses the indentation engine.

I think this is fixed, now.

> Hopefully these examples help.

Very much so.

> Please also note that in a few places (such as alignas, the noexcept
> specifier, RHS of assignments in the capture list) near-arbitrary
> expressions can appear, and that requires clause have special boolean
> expression syntax (see
> https://eel.is/c++draft/temp.pre#nt:requires-clause).

I'm afraid I haven't looked at this, yet.

> Both of these pieces of syntax are reused in other contexts (other
> declarations), so if there's a bug in them in the lambda case, it likely
> carries over to the general declaration case.

> Thank you again for maintaining CC-mode!  It is immeasurably useful.
> And apologies for the trouble :-)

No trouble at all!

Here's an up to date version of the patch, which should work better than
the last version.  Please do the usual with it, and let me know how it
goes.  Thanks!

diff -r 48ad661a2d7d cc-engine.el
--- a/cc-engine.el	Mon Jul 22 12:09:34 2024 +0000
+++ b/cc-engine.el	Mon Sep 09 14:49:55 2024 +0000
@@ -9929,12 +9929,20 @@
 	 ((eq (char-after) ?\()
 	  (and (c-go-list-forward (point) limit)
 	       (eq (char-before) ?\))
-	       (let ((after-paren (point)))
-		 (c-forward-syntactic-ws limit)
-		 (prog1
-		     (not (eq (char-after) ?{))
-		   (when stop-at-end
-		     (goto-char after-paren))))))
+;;;; OLD STOUGH, 2024-09-01
+	       ;; (let ((after-paren (point)))
+		 ;; (c-forward-syntactic-ws limit)
+		 ;; (prog1
+		 ;;     (not (eq (char-after) ?{))
+		 ;;   (when stop-at-end
+		 ;;     (goto-char after-paren))))
+;;;; NEW STOUGH, 2024-09-01
+	       (progn
+		 (unless stop-at-end
+		   (c-forward-syntactic-ws limit))
+		 t)
+;;;; END OF NEW STOUGH
+					       ))
 	 ((c-forward-over-compound-identifier)
 	  (let ((after-id (point)))
 	    (c-forward-syntactic-ws limit)
@@ -13178,229 +13186,223 @@
   ;;
   ;; Here, "brace list" does not include the body of an enum.
   (save-excursion
-    (let ((start (point))
-	  (braceassignp 'dontknow)
-	  inexpr-brace-list bufpos macro-start res pos after-type-id-pos
-	  pos2 in-paren parens-before-brace
-	  paren-state paren-pos)
-
-      (setq res
-	    (or (progn (c-backward-syntactic-ws)
-		       (c-back-over-compound-identifier))
-		(c-backward-token-2 1 t lim)))
-      ;; Checks to do only on the first sexp before the brace.
-      ;; Have we a C++ initialization, without an "="?
-      (if (and (c-major-mode-is 'c++-mode)
-	       (cond
-		((and (or (not (memq res '(t 0)))
-			  (eq (char-after) ?,))
-		      (setq paren-state (c-parse-state))
-		      (setq paren-pos (c-pull-open-brace paren-state))
-		      (eq (char-after paren-pos) ?\())
-		 (goto-char paren-pos)
-		 (setq braceassignp 'c++-noassign
-		       in-paren 'in-paren))
-		((looking-at c-pre-brace-non-bracelist-key)
-		 (setq braceassignp nil))
-		((and
-		  (looking-at c-fun-name-substitute-key)
-		  (not (eq (char-after (match-end 0)) ?_)))
-		 (setq braceassignp nil))
-		((looking-at c-return-key))
-		((and (looking-at c-symbol-start)
-		      (not (looking-at c-keywords-regexp)))
-		 (if (save-excursion
-		       (and (zerop (c-backward-token-2 1 t lim))
-			    (looking-at c-pre-id-bracelist-key)))
-		     (setq braceassignp 'c++-noassign)
-		   (setq after-type-id-pos (point))))
-		((eq (char-after) ?\()
-		 (setq parens-before-brace t)
-		 ;; Have we a requires with a parenthesis list?
-		 (when (save-excursion
-			 (and (zerop (c-backward-token-2 1 nil lim))
-			      (looking-at c-fun-name-substitute-key)
-			      (not (eq (char-after (match-end 0)) ?_))))
+    (unless (and (c-major-mode-is 'c++-mode)
+		 (c-backward-over-lambda-expression lim))
+      (let ((start (point))
+	    (braceassignp 'dontknow)
+	    inexpr-brace-list bufpos macro-start res pos after-type-id-pos
+	    pos2 in-paren ;; parens-before-brace STOUGH, 2024-08-31
+	    paren-state paren-pos)
+
+	(setq res
+	      (or (progn (c-backward-syntactic-ws)
+			 (c-back-over-compound-identifier))
+		  (c-backward-token-2 1 t lim)))
+	;; Checks to do only on the first sexp before the brace.
+	;; Have we a C++ initialization, without an "="?
+	(if (and (c-major-mode-is 'c++-mode)
+		 (cond
+		  ((and (or (not (memq res '(t 0)))
+			    (eq (char-after) ?,))
+			(setq paren-state (c-parse-state))
+			(setq paren-pos (c-pull-open-brace paren-state))
+			(eq (char-after paren-pos) ?\())
+		   (goto-char paren-pos)
+		   (setq braceassignp 'c++-noassign
+			 in-paren 'in-paren))
+		  ((looking-at c-pre-brace-non-bracelist-key)
 		   (setq braceassignp nil))
-		 nil)
-		(t nil))
-	       (save-excursion
-		 (cond
-		  ((or (not (memq res '(t 0)))
-		       (eq (char-after) ?,))
-		   (and (setq paren-state (c-parse-state))
-			(setq paren-pos (c-pull-open-brace paren-state))
-			(eq (char-after paren-pos) ?\()
-			(setq in-paren 'in-paren)
-			(goto-char paren-pos)))
-		  ((looking-at c-pre-brace-non-bracelist-key))
+		  ((and
+		    (looking-at c-fun-name-substitute-key)
+		    (not (eq (char-after (match-end 0)) ?_)))
+		   (setq braceassignp nil))
 		  ((looking-at c-return-key))
 		  ((and (looking-at c-symbol-start)
-			(not (looking-at c-keywords-regexp))
-			(save-excursion
-			  (and (zerop (c-backward-token-2 1 t lim))
-			       (looking-at c-pre-id-bracelist-key)))))
-		  (t (setq after-type-id-pos (point))
-		     nil))))
-	  (setq braceassignp 'c++-noassign))
-
-      (when (and c-opt-inexpr-brace-list-key
-		 (eq (char-after) ?\[))
-	;; In Java, an initialization brace list may follow
-	;; directly after "new Foo[]", so check for a "new"
-	;; earlier.
-	(while (eq braceassignp 'dontknow)
-	  (setq braceassignp
-		(cond ((/= (c-backward-token-2 1 t lim) 0) nil)
-		      ((looking-at c-opt-inexpr-brace-list-key)
-		       (setq inexpr-brace-list t)
-		       t)
-		      ((looking-at "\\sw\\|\\s_\\|[.[]")
-		       ;; Carry on looking if this is an
-		       ;; identifier (may contain "." in Java)
-		       ;; or another "[]" sexp.
-		       'dontknow)
-		      (t nil)))))
-
-      (setq pos (point))
-      (cond
-       ((not braceassignp)
-	nil)
-       ((and after-type-id-pos
-	     (goto-char after-type-id-pos)
-	     (setq res (c-back-over-member-initializers))
-	     (goto-char res)
-	     (eq (car (c-beginning-of-decl-1 lim)) 'same))
-	(cons (point) nil))		; Return value.
-
-       ((and after-type-id-pos
-	     (progn
-	       (c-backward-syntactic-ws)
-	       (eq (char-before) ?\()))
-	;; Single identifier between '(' and '{'.  We have a bracelist.
-	(cons after-type-id-pos 'in-paren))
-
-       ;; Are we at the parens of a C++ lambda expression?
-       ((and parens-before-brace
-	     (save-excursion
-	       (and
-		(zerop (c-backward-token-2 1 t lim))
-		(c-looking-at-c++-lambda-capture-list))))
-	nil)			     ; a lambda expression isn't a brace list.
-
-       (t
-	(goto-char pos)
-	(when (eq braceassignp 'dontknow)
-	  (let* ((cache-entry (and containing-sexp
-				   (c-laomib-get-cache containing-sexp pos)))
-		 (lim2 (or (cadr cache-entry) lim))
-		 sub-bassign-p)
-	    (if cache-entry
-		(cond
-		 ((<= (point) (cadr cache-entry))
-		  ;; We're inside the region we've already scanned over, so
-		  ;; just go to that scan's end position.
-		  (goto-char (nth 2 cache-entry))
-		  (setq braceassignp (nth 3 cache-entry)))
-		 ((> (point) (cadr cache-entry))
-		  ;; We're beyond the previous scan region, so just scan as
-		  ;; far as the end of that region.
-		  (setq sub-bassign-p (c-laomib-loop lim2))
-		  (if (<= (point) (cadr cache-entry))
-		      (progn
-			(c-laomib-put-cache containing-sexp
-					    start (nth 2 cache-entry)
-					    (nth 3 cache-entry) ;; sub-bassign-p
-					    )
-			(setq braceassignp (nth 3 cache-entry))
-			(goto-char (nth 2 cache-entry)))
-		    (c-laomib-put-cache containing-sexp
-					start (point) sub-bassign-p)
-		    (setq braceassignp sub-bassign-p)))
-		 (t))
-
-	      (setq braceassignp (c-laomib-loop lim))
-	      (when lim
-		(c-laomib-put-cache lim start (point) braceassignp)))))
-
+			(not (looking-at c-keywords-regexp)))
+		   (if (save-excursion
+			 (and (zerop (c-backward-token-2 1 t lim))
+			      (looking-at c-pre-id-bracelist-key)))
+		       (setq braceassignp 'c++-noassign)
+		     (setq after-type-id-pos (point))))
+		  ((eq (char-after) ?\()
+		   ;; (setq parens-before-brace t) STOUGH, 2024-08-31
+		   ;; Have we a requires with a parenthesis list?
+		   (when (save-excursion
+			   (and (zerop (c-backward-token-2 1 nil lim))
+				(looking-at c-fun-name-substitute-key)
+				(not (eq (char-after (match-end 0)) ?_))))
+		     (setq braceassignp nil))
+		   nil)
+		  (t nil))
+		 (save-excursion
+		   (cond
+		    ((or (not (memq res '(t 0)))
+			 (eq (char-after) ?,))
+		     (and (setq paren-state (c-parse-state))
+			  (setq paren-pos (c-pull-open-brace paren-state))
+			  (eq (char-after paren-pos) ?\()
+			  (setq in-paren 'in-paren)
+			  (goto-char paren-pos)))
+		    ((looking-at c-pre-brace-non-bracelist-key))
+		    ((looking-at c-return-key))
+		    ((and (looking-at c-symbol-start)
+			  (not (looking-at c-keywords-regexp))
+			  (save-excursion
+			    (and (zerop (c-backward-token-2 1 t lim))
+				 (looking-at c-pre-id-bracelist-key)))))
+		    (t (setq after-type-id-pos (point))
+		       nil))))
+	    (setq braceassignp 'c++-noassign))
+
+	(when (and c-opt-inexpr-brace-list-key
+		   (eq (char-after) ?\[))
+	  ;; In Java, an initialization brace list may follow
+	  ;; directly after "new Foo[]", so check for a "new"
+	  ;; earlier.
+	  (while (eq braceassignp 'dontknow)
+	    (setq braceassignp
+		  (cond ((/= (c-backward-token-2 1 t lim) 0) nil)
+			((looking-at c-opt-inexpr-brace-list-key)
+			 (setq inexpr-brace-list t)
+			 t)
+			((looking-at "\\sw\\|\\s_\\|[.[]")
+			 ;; Carry on looking if this is an
+			 ;; identifier (may contain "." in Java)
+			 ;; or another "[]" sexp.
+			 'dontknow)
+			(t nil)))))
+
+	(setq pos (point))
 	(cond
-	 (braceassignp
-	  ;; We've hit the beginning of the aggregate list.
-	  (setq pos2 (point))
-	  (cons
-	   (if (eq (c-beginning-of-statement-1 containing-sexp) 'same)
-	       (point)
-	     pos2)
-	   (or in-paren inexpr-brace-list)))
+	 ((not braceassignp)
+	  nil)
+	 ((and after-type-id-pos
+	       (goto-char after-type-id-pos)
+	       (setq res (c-back-over-member-initializers))
+	       (goto-char res)
+	       (eq (car (c-beginning-of-decl-1 lim)) 'same))
+	  (cons (point) nil))		; Return value.
+
 	 ((and after-type-id-pos
-	       (save-excursion
-		 (when (eq (char-after) ?\;)
-		   (c-forward-over-token-and-ws t))
-		 (setq bufpos (point))
-		 (when (looking-at c-opt-<>-sexp-key)
-		   (c-forward-over-token-and-ws)
-		   (when (and (eq (char-after) ?<)
-			      (c-get-char-property (point) 'syntax-table))
-		     (c-go-list-forward nil after-type-id-pos)
-		     (c-forward-syntactic-ws)))
-		 (if (and (not (eq (point) after-type-id-pos))
-			  (or (not (looking-at c-class-key))
-			      (save-excursion
-				(goto-char (match-end 1))
-				(c-forward-syntactic-ws)
-				(not (eq (point) after-type-id-pos)))))
-		     (progn
-		       (setq res
-			     (c-forward-decl-or-cast-1 (c-point 'bosws)
-						       nil nil))
-		       (and (consp res)
-			    (cond
-			     ((eq (car res) after-type-id-pos))
-			     ((> (car res) after-type-id-pos) nil)
-			     (t
-			      (catch 'find-decl
+	       (progn
+		 (c-backward-syntactic-ws)
+		 (eq (char-before) ?\()))
+	  ;; Single identifier between '(' and '{'.  We have a bracelist.
+	  (cons after-type-id-pos 'in-paren))
+
+	 (t
+	  (goto-char pos)
+	  (when (eq braceassignp 'dontknow)
+	    (let* ((cache-entry (and containing-sexp
+				     (c-laomib-get-cache containing-sexp pos)))
+		   (lim2 (or (cadr cache-entry) lim))
+		   sub-bassign-p)
+	      (if cache-entry
+		  (cond
+		   ((<= (point) (cadr cache-entry))
+		    ;; We're inside the region we've already scanned over, so
+		    ;; just go to that scan's end position.
+		    (goto-char (nth 2 cache-entry))
+		    (setq braceassignp (nth 3 cache-entry)))
+		   ((> (point) (cadr cache-entry))
+		    ;; We're beyond the previous scan region, so just scan as
+		    ;; far as the end of that region.
+		    (setq sub-bassign-p (c-laomib-loop lim2))
+		    (if (<= (point) (cadr cache-entry))
+			(progn
+			  (c-laomib-put-cache containing-sexp
+					      start (nth 2 cache-entry)
+					      (nth 3 cache-entry) ;; sub-bassign-p
+					      )
+			  (setq braceassignp (nth 3 cache-entry))
+			  (goto-char (nth 2 cache-entry)))
+		      (c-laomib-put-cache containing-sexp
+					  start (point) sub-bassign-p)
+		      (setq braceassignp sub-bassign-p)))
+		   (t))
+
+		(setq braceassignp (c-laomib-loop lim))
+		(when lim
+		  (c-laomib-put-cache lim start (point) braceassignp)))))
+
+	  (cond
+	   (braceassignp
+	    ;; We've hit the beginning of the aggregate list.
+	    (setq pos2 (point))
+	    (cons
+	     (if (eq (c-beginning-of-statement-1 containing-sexp) 'same)
+		 (point)
+	       pos2)
+	     (or in-paren inexpr-brace-list)))
+	   ((and after-type-id-pos
+		 (save-excursion
+		   (when (eq (char-after) ?\;)
+		     (c-forward-over-token-and-ws t))
+		   (setq bufpos (point))
+		   (when (looking-at c-opt-<>-sexp-key)
+		     (c-forward-over-token-and-ws)
+		     (when (and (eq (char-after) ?<)
+				(c-get-char-property (point) 'syntax-table))
+		       (c-go-list-forward nil after-type-id-pos)
+		       (c-forward-syntactic-ws)))
+		   (if (and (not (eq (point) after-type-id-pos))
+			    (or (not (looking-at c-class-key))
 				(save-excursion
-				  (goto-char (car res))
-				  (c-do-declarators
-				   (point-max) t nil nil
-				   (lambda (id-start _id-end _tok _not-top _func _init)
-				     (cond
-				      ((> id-start after-type-id-pos)
-				       (throw 'find-decl nil))
-				      ((eq id-start after-type-id-pos)
-				       (throw 'find-decl t)))))
-				  nil))))))
-		   (save-excursion
-		     (goto-char start)
-		     (not (c-looking-at-statement-block))))))
-	  (cons bufpos (or in-paren inexpr-brace-list)))
-	 ((or (eq (char-after) ?\;)
-	      ;; Brace lists can't contain a semicolon, so we're done.
-	      (save-excursion
-		(c-backward-syntactic-ws)
-		(eq (char-before) ?}))
-	      ;; They also can't contain a bare }, which is probably the end
-	      ;; of a function.
-	      )
-	  nil)
-	 ((and (setq macro-start (point))
-	       (c-forward-to-cpp-define-body)
-	       (eq (point) start))
-	  ;; We've a macro whose expansion starts with the '{'.
-	  ;; Heuristically, if we have a ';' in it we've not got a
-	  ;; brace list, otherwise we have.
-	  (let ((macro-end (progn (c-end-of-macro) (point))))
-	    (goto-char start)
-	    (forward-char)
-	    (if (and (c-syntactic-re-search-forward "[;,]" macro-end t t)
-		     (eq (char-before) ?\;))
-		nil
-	      (cons macro-start nil)))) ; (2016-08-30): Lazy! We have no
+				  (goto-char (match-end 1))
+				  (c-forward-syntactic-ws)
+				  (not (eq (point) after-type-id-pos)))))
+		       (progn
+			 (setq res
+			       (c-forward-decl-or-cast-1 (c-point 'bosws)
+							 nil nil))
+			 (and (consp res)
+			      (cond
+			       ((eq (car res) after-type-id-pos))
+			       ((> (car res) after-type-id-pos) nil)
+			       (t
+				(catch 'find-decl
+				  (save-excursion
+				    (goto-char (car res))
+				    (c-do-declarators
+				     (point-max) t nil nil
+				     (lambda (id-start _id-end _tok _not-top _func _init)
+				       (cond
+					((> id-start after-type-id-pos)
+					 (throw 'find-decl nil))
+					((eq id-start after-type-id-pos)
+					 (throw 'find-decl t)))))
+				    nil))))))
+		     (save-excursion
+		       (goto-char start)
+		       (not (c-looking-at-statement-block))))))
+	    (cons bufpos (or in-paren inexpr-brace-list)))
+	   ((or (eq (char-after) ?\;)
+		;; Brace lists can't contain a semicolon, so we're done.
+		(save-excursion
+		  (c-backward-syntactic-ws)
+		  (eq (char-before) ?}))
+		;; They also can't contain a bare }, which is probably the end
+		;; of a function.
+		)
+	    nil)
+	   ((and (setq macro-start (point))
+		 (c-forward-to-cpp-define-body)
+		 (eq (point) start))
+	    ;; We've a macro whose expansion starts with the '{'.
+	    ;; Heuristically, if we have a ';' in it we've not got a
+	    ;; brace list, otherwise we have.
+	    (let ((macro-end (progn (c-end-of-macro) (point))))
+	      (goto-char start)
+	      (forward-char)
+	      (if (and (c-syntactic-re-search-forward "[;,]" macro-end t t)
+		       (eq (char-before) ?\;))
+		  nil
+		(cons macro-start nil)))) ; (2016-08-30): Lazy! We have no
 					; languages where
 					; `c-opt-inexpr-brace-list-key' is
 					; non-nil and we have macros.
-	 (t t))))			;; The caller can go up one level.
-      )))
+	   (t t))))			;; The caller can go up one level.
+	))))
 
 (defun c-inside-bracelist-p (containing-sexp paren-state accept-in-paren)
   ;; return the buffer position of the beginning of the brace list statement
@@ -13863,6 +13865,72 @@
 	 (looking-at c-pre-lambda-tokens-re)))
    (not (c-in-literal))))
 
+(defun c-looking-at-c++-lambda-expression (&optional lim)
+  ;; If point is at the [ opening a C++ lambda expressions's capture list,
+  ;; and the lambda expression is complete, return the position of the { which
+  ;; opens the body form, otherwise return nil.  LIM is the limit for forward
+  ;; searching for the {.
+  (let ((here (point))
+	got-params)
+    (when (and (c-looking-at-c++-lambda-capture-list)
+	       (c-go-list-forward nil lim))
+      (c-forward-syntactic-ws lim)
+      (when (c-forward-<>-arglist t)
+	(c-forward-syntactic-ws lim)
+	(when (looking-at "requires\\([^a-zA-Z0-9_]\\|$\\)")
+	  (c-forward-c++-requires-clause lim nil)))
+      (when (and (eq (char-after) ?\()
+		 (c-go-list-forward nil lim))
+	(setq got-params t)
+	(c-forward-syntactic-ws lim))
+      (while (and c-lambda-spec-key (looking-at c-lambda-spec-key))
+	(goto-char (match-end 1))
+	(c-forward-syntactic-ws lim))
+      (let (after-except-pos)
+	(cond
+	 ((save-excursion
+	    (and (looking-at "throw\\([^a-zA-Z0-9_]\\|$\\)")
+		 (progn (goto-char (match-beginning 1))
+			(c-forward-syntactic-ws lim)
+			(eq (char-after) ?\())
+		 (c-go-list-forward nil lim)
+		 (progn (c-forward-syntactic-ws lim)
+			(setq after-except-pos (point)))))
+	  (goto-char after-except-pos)
+	  (c-forward-syntactic-ws lim))
+	 ((looking-at "noexcept\\([^a-zA-Z0-9_]\\|$\\)")
+	  (goto-char (match-beginning 1))
+	  (c-forward-syntactic-ws lim)
+	  (setq after-except-pos (point))
+	  (when (and (eq (char-after) ?\()
+		     (c-go-list-forward nil lim))
+	    (c-forward-syntactic-ws lim)))))
+      (and (looking-at c-haskell-op-re)
+	   (goto-char (match-end 0))
+	   (progn (c-forward-syntactic-ws lim)
+		  (c-forward-type t)))	; t is BRACE-BLOCK-TOO.
+      (and got-params
+	   (looking-at "requires\\([^a-zA-Z0-9_]\\|$\\)")
+	   (c-forward-c++-requires-clause lim nil))
+      (prog1 (and (eq (char-after) ?{)
+		  (point))
+	(goto-char here)))))
+
+(defun c-backward-over-lambda-expression (&optional lim)
+  ;; Point is at a {.  Move back over the lambda expression this is a part of,
+  ;; stopping at the [ of the capture list, if this is the case, returning
+  ;; the position of that opening bracket.  If we're not at such a list, leave
+  ;; point unchanged and return nil.
+  (let ((here (point)))
+    (c-syntactic-skip-backward "^;}]" lim t)
+    (if (and (eq (char-before) ?\])
+	     (c-go-list-backward nil lim)
+	     (eq (c-looking-at-c++-lambda-expression (1+ here))
+		 here))
+	(point)
+      (goto-char here)
+      nil)))
+
 (defun c-c++-vsemi-p (&optional pos)
   ;; C++ Only - Is there a "virtual semicolon" at POS or point?
   ;; (See cc-defs.el for full details of "virtual semicolons".)
@@ -13870,11 +13938,17 @@
   ;; This is true when point is at the last non syntactic WS position on the
   ;; line, and either there is a "macro with semicolon" just before it (see
   ;; `c-at-macro-vsemi-p') or there is a "requires" clause which ends there.
-  (let (res)
-    (cond
-     ((setq res (c-in-requires-or-at-end-of-clause pos))
-      (and res (eq (cdr res) t)))
-     ((c-at-macro-vsemi-p)))))
+;;;; TEMP COMMENTED OUT STOUGH, 2024-09-01
+  ;; (let (res)
+  ;;   (cond
+     ;; ((setq res (c-in-requires-or-at-end-of-clause pos))
+     ;;  (and res (eq (cdr res) t)))
+     ;; (
+;;;; END OF TCO STOUGH
+      (c-at-macro-vsemi-p pos		; POS is NEW STOUGH, 2024-09-01
+			     )
+			      ;; ))) TEMP COMMENTED OUT STOUGH, 2024-09-01
+)
 
 (defun c-at-macro-vsemi-p (&optional pos)
   ;; Is there a "virtual semicolon" at POS or point?
diff -r 48ad661a2d7d cc-langs.el
--- a/cc-langs.el	Mon Jul 22 12:09:34 2024 +0000
+++ b/cc-langs.el	Mon Sep 09 14:49:55 2024 +0000
@@ -2639,6 +2639,19 @@
   t (c-make-keywords-re t (c-lang-const c-equals-type-clause-kwds)))
 (c-lang-defvar c-equals-type-clause-key (c-lang-const c-equals-type-clause-key))
 
+(c-lang-defconst c-lambda-spec-kwds
+  "Keywords which are specifiers of certain elements of a C++ lambda function.
+This is only used in C++ Mode."
+  t nil
+  c++ '("mutable" "constexpr" "consteval" "static"))
+
+(c-lang-defconst c-lambda-spec-key
+  ;; A regular expression which matches a member of `c-lambda-spec-kwds',
+  ;; or nil.
+  t (if (c-lang-const c-lambda-spec-kwds)
+	(c-make-keywords-re t (c-lang-const c-lambda-spec-kwds))))
+(c-lang-defvar c-lambda-spec-key (c-lang-const c-lambda-spec-key))
+
 (c-lang-defconst c-equals-nontype-decl-kwds
   "Keywords which are followed by an identifier then an \"=\"
 sign, which declares the identifier to be something other than a

> Have a lovely day.
> -- 
> Arsen Arsenović

-- 
Alan Mackenzie (Nuremberg, Germany).