Subject: bug#62386: CC Mode 5.35.2 (C++//l); C++ concept indentation

From: Alan Mackenzie <[email-masked]>
Date: Fri, 24 Mar 2023 17:23:00 +0000

Hello, Michael,

Thanks for taking the trouble to report this bug.

On Wed, Mar 22, 2023 at 11:58:32 -0400, Michael Welsh Duggan wrote:
> Package: cc-mode

> Given a concept definition like:

>     template <typename T>
>     concept Foo =
>       Bar<T>
>       && requires (T t) {
>         { t + t } -> std::same_as<T>;
>       }
>       && something_else;

> This is how it actually indents in cc-mode:

>     template <typename T>
>     concept Foo =
>     Bar<T>
>       && requires (T t) {
>       { t + t } -> std::same_as<T>;
>     }
>       && something_else;

> I would expect "Bar<T>" to be statement-cont, but it is
> topmost-intro-cont instead.  The indentation of "{ t + t }" isn't offset
> properly with respect to the "requires" clause, and the closing brace is
> way out of line.

Concepts can only occur at the top level, so it seemed logical to use
topmost-intro-cont and friends.  In the current indentation, just about
everything is anchored on the start of the statement (i.e. the
"template" keyword) so that's why the closing brace is in column 0.

> I suspect that this is merely because concepts haven't really been
> handled specially outside of recognizing the keyword.

Hah!  The patch file for the implementation of concepts and requireses
was over 600 lines long.  ;-)

Your bug report is in fact the first feedback I've had about this
feature since it was published ~6 months ago.  Most of the sample source
I used to program it came from https://en.cppreference.com, which was
formatted in a particular fashion.  So I missed the points which you've
just raised.

> Changing the "concept" keyword to "int" improves the indentation of
> "Bar<T>", demonstrating what I think the indentation should be, and
> replacing the "requires" with "[]", making it into lambda syntax, also
> improves the indentation.  I think that requires expression should be
> indented in a similar manner as lambda expressions are indented.

I've made an attempt to improve the indentation of code like your test
case.  Would you please try it out and let me know how well it solves
the problem.  Thanks!

diff -r 300ebf19cd62 cc-align.el
--- a/cc-align.el	Fri Feb 17 08:58:11 2023 +0000
+++ b/cc-align.el	Fri Mar 24 17:04:07 2023 +0000
@@ -93,7 +93,7 @@
 			       (c-point 'eol) t)
       (beginning-of-line)
       (c-backward-syntactic-ws (c-langelem-pos langelem))
-      (if (and (memq (char-before) '(?} ?,))
+      (if (and (memq (char-before) '(?} ?, ?=))
 	       (not (and c-overloadable-operators-regexp
 			 (c-after-special-operator-id))))
 	  c-basic-offset))))
diff -r 300ebf19cd62 cc-engine.el
--- a/cc-engine.el	Fri Feb 17 08:58:11 2023 +0000
+++ b/cc-engine.el	Fri Mar 24 17:04:07 2023 +0000
@@ -13223,6 +13223,28 @@
 	   (t nil)))
       (goto-char here))))
 
+(defun c-looking-at-concept (limit)
+  ;; Are we currently at the start of a concept construct?  I.e. at the
+  ;; "template" keyword followed by the construct?  If so, we return the
+  ;; position of the first constraint expression following the "=" sign,
+  ;; otherwise we return nil.  LIMIT is a forward search limit
+  (save-excursion
+    (and (looking-at c-pre-concept-<>-key)
+	 (goto-char (match-end 1))
+	 (progn (c-forward-syntactic-ws limit)
+		(eq (char-after) ?<))
+	 (let ((c-parse-and-markup-<>-arglists t)
+	       c-restricted-<>-arglists)
+	   (c-forward-<>-arglist nil))
+	 (progn (c-forward-syntactic-ws limit)
+		(looking-at c-equals-nontype-decl-key)) ; "concept"
+	 (goto-char (match-end 0))
+	 (c-syntactic-re-search-forward
+	  "=" limit t t)
+	 (goto-char (match-end 0))
+	 (progn (c-forward-syntactic-ws limit)
+		(point)))))
+
 (defun c-looking-at-inexpr-block (lim containing-sexp &optional check-at-end)
   ;; Return non-nil if we're looking at the beginning of a block
   ;; inside an expression.  The value returned is actually a cons of
@@ -15310,6 +15332,15 @@
 	      (c-add-syntax 'defun-close (point))
 	    (c-add-syntax 'inline-close (point))))
 
+	 ;; CASE 16G: Do we have the closing brace of a "requires" clause
+	 ;; of a C++20 "concept"?
+	 ((save-excursion
+	    (and (eq (c-beginning-of-statement-1 lim nil nil t) 'same)
+		 (setq placeholder (c-looking-at-concept containing-sexp))))
+	  (goto-char placeholder)
+	  (back-to-indentation)
+	  (c-add-stmt-syntax 'defun-close nil t lim paren-state))
+
 	 ;; CASE 16F: Can be a defun-close of a function declared
 	 ;; in a statement block, e.g. in Pike or when using gcc
 	 ;; extensions, but watch out for macros followed by
@@ -15460,6 +15491,17 @@
 	  (if (eq char-after-ip ?{)
 	      (c-add-syntax 'block-open)))
 
+	 ;; CASE 17J: first "statement" inside a C++20 requires
+	 ;; "function".
+	 ((save-excursion
+	    (and
+	     (goto-char containing-sexp)
+	     (eq (c-beginning-of-statement-1 lim nil nil t) 'same)
+	     (setq placeholder (c-looking-at-concept lim))))
+	  (goto-char placeholder)
+	  (back-to-indentation)
+	  (c-add-syntax 'defun-block-intro (point)))
+
 	 ;; CASE 17F: first statement in an inline, or first
 	 ;; statement in a top-level defun. we can tell this is it
 	 ;; if there are no enclosing braces that haven't been

> Thank you!

-- 
Alan Mackenzie (Nuremberg, Germany).