Subject: bug#21409: 24.5; Wrong syntactic information for two line statement in an arglist

From: Alan Mackenzie <[email-masked]>
Date: Sun, 13 Mar 2022 13:43:11 +0000

Hello again, Gulshan.

On Sat, Mar 12, 2022 at 11:32:50 +0000, Alan Mackenzie wrote:

> Sorry I missed your bug report back in 2015.

> On Fri, Mar 11, 2022 at 17:52:38 -0800, Gulshan Singh wrote:
> > On Thu, Dec 3, 2020 at 3:07 AM Lars Ingebrigtsen <[email-masked]> wrote:

> > > Gulshan Singh <[email-masked]> writes:

> > > > In c-mode (and all derivatives), the following code has the wrong
> > > > syntactic information (at least, in my opinion):

> > > > foo(bar
> > > >     .baz()
> > > >     .qux());

[ .... ]

> I think the best solution to the problem would be to write a new Line-Up
> function for this particular scenario, and to make it available to users
> to insert into the c-offsets-alist entry for arglist-cont-nonempty.  The
> page "Customizing Indentation" in the CC Mode manual is pertinent here.

> But first, we need to firm up the specification.  What, precisely, will
> trigger this new Line-Up function?

I've come up with an answer to that question.  On _any_ argument
continued onto the next line, we indent it c-basic-offset from the
_first_ argument.  This is easy to implement, since it's a minor
variation on c-lineup-arglist.  See the following patch for an example
of what that does.

[ .... ]

> > It's definitely reasonable, but it's not what I'd prefer, which would be
> > this:

> > foo(bar
> >       .baz()
> >       .qux());

> > `.baz()` and `.qux()` are indented two spaces (my value for
> > `c-basic-offset`) from the start of `bar`, as opposed to aligned with
> > `bar`. This matches what happens if the call to `foo` isn't there:

> > bar
> >   .baz()
> >   .qux();

I've hacked up the following patch, which introduces the new Line-Up
function c-lineup-arglist-+.  To use it (temporarily) do C-c C-o RET on
the .baz() line, and change the setting for arglist-cont-nonempty from

    (c-lineup-gcc-asm-reg c-lineup-arglist)

to

    (c-lineup-gcc-asm-reg c-lineup-arglist-+ c-lineup-arglist)

..  Note that c-lineup-arglist-+ is a function which returns nil to mean
"not appropriate here", so it must be in a list, not in the last
position.  This is all better explained in the CC Mode manual on page
"c-offsets-alist".

If this patch does what you want, you can then incorporate the new
Line-Up function into your CC Mode style, or however else you set up
your indentation.  If you want any help with this, feel free to ask on
this list, or on [email-masked].

Here's the patch.  It should apply to either the latest version of
stand-alone CC Mode, or the version in the Emacs master branch.  Please
apply the patch, byte compile the changed file (or all of CC Mode), and
make the amendment to your indentation setup noted above.  (If you want
any help with any of this, feel free to send me private email).  Then
please let us know if this patch does the Right Thing.  Thanks!

diff -r 1a0681da2be1 cc-align.el
--- a/cc-align.el	Thu Feb 10 16:46:58 2022 +0000
+++ b/cc-align.el	Sun Mar 13 13:35:56 2022 +0000
@@ -207,6 +207,58 @@
 	  (vector (current-column)))))))
 
 ;; Contributed by Kevin Ryde <[email-masked]>.
+(defun c-lineup-argcont-1 (elem)
+  ;; Move to the start of the current arg and return non-nil, otherwise
+  ;; return nil.
+  (beginning-of-line)
+
+  (when (eq (car elem) 'arglist-cont-nonempty)
+    ;; Our argument list might not be the innermost one.  If it
+    ;; isn't, go back to the last position in it.  We do this by
+    ;; stepping back over open parens until we get to the open paren
+    ;; of our argument list.
+    (let ((open-paren (c-langelem-2nd-pos c-syntactic-element))
+	  (paren-state (c-parse-state)))
+      (while (not (eq (car paren-state) open-paren))
+	(unless (consp (car paren-state)) ;; ignore matched braces
+	  (goto-char (car paren-state)))
+	(setq paren-state (cdr paren-state)))))
+
+  (let ((start (point)) c)
+
+    (when (bolp)
+      ;; Previous line ending in a comma means we're the start of an
+      ;; argument.  This should quickly catch most cases not for us.
+      ;; This case is only applicable if we're the innermost arglist.
+      (c-backward-syntactic-ws)
+      (setq c (char-before)))
+
+    (unless (eq c ?,)
+      ;; In a gcc asm, ":" on the previous line means the start of an
+      ;; argument.  And lines starting with ":" are not for us, don't
+      ;; want them to indent to the preceding operand.
+      (let ((gcc-asm (save-excursion
+		       (goto-char start)
+		       (c-in-gcc-asm-p))))
+	(unless (and gcc-asm
+		     (or (eq c ?:)
+			 (save-excursion
+			   (goto-char start)
+			   (looking-at "[ \t]*:"))))
+
+	  (c-lineup-argcont-scan (if gcc-asm ?:))
+	  t)))))
+
+(defun c-lineup-argcont-scan (&optional other-match)
+  ;; Find the start of an argument, for `c-lineup-argcont'.
+  (when (zerop (c-backward-token-2 1 t))
+    (let ((c (char-after)))
+      (if (or (eq c ?,) (eq c other-match))
+	  (progn
+	    (forward-char)
+	    (c-forward-syntactic-ws))
+	(c-lineup-argcont-scan other-match)))))
+
 (defun c-lineup-argcont (elem)
   "Line up a continued argument.
 
@@ -221,56 +273,28 @@
 for the operands.
 
 Works with: arglist-cont, arglist-cont-nonempty."
-
   (save-excursion
-    (beginning-of-line)
+    (when (c-lineup-argcont-1 elem)
+      (vector (current-column)))))
 
-    (when (eq (car elem) 'arglist-cont-nonempty)
-      ;; Our argument list might not be the innermost one.  If it
-      ;; isn't, go back to the last position in it.  We do this by
-      ;; stepping back over open parens until we get to the open paren
-      ;; of our argument list.
-      (let ((open-paren (c-langelem-2nd-pos c-syntactic-element))
-	    (paren-state (c-parse-state)))
-	(while (not (eq (car paren-state) open-paren))
-	  (unless (consp (car paren-state)) ;; ignore matched braces
-	    (goto-char (car paren-state)))
-	  (setq paren-state (cdr paren-state)))))
+(defun c-lineup-argcont-+ (langelem)
+  "Indent an argument continuation `c-basic-offset' in from the first argument.
 
-    (let ((start (point)) c)
-
-      (when (bolp)
-	;; Previous line ending in a comma means we're the start of an
-	;; argument.  This should quickly catch most cases not for us.
-	;; This case is only applicable if we're the innermost arglist.
-	(c-backward-syntactic-ws)
-	(setq c (char-before)))
+foo (xyz, uvw, aaa + bbb + ccc
+         + ddd + eee + fff);    <- c-lineup-argcont-+
+     <-->                          c-basic-offset
 
-      (unless (eq c ?,)
-	;; In a gcc asm, ":" on the previous line means the start of an
-	;; argument.  And lines starting with ":" are not for us, don't
-	;; want them to indent to the preceding operand.
-	(let ((gcc-asm (save-excursion
-			 (goto-char start)
-			 (c-in-gcc-asm-p))))
-	  (unless (and gcc-asm
-		       (or (eq c ?:)
-			   (save-excursion
-			     (goto-char start)
-			     (looking-at "[ \t]*:"))))
+Only continuation lines like this are touhced, nil being returned
+on lines which are the start of an argument.
 
-	    (c-lineup-argcont-scan (if gcc-asm ?:))
-	    (vector (current-column))))))))
-
-(defun c-lineup-argcont-scan (&optional other-match)
-  ;; Find the start of an argument, for `c-lineup-argcont'.
-  (when (zerop (c-backward-token-2 1 t))
-    (let ((c (char-after)))
-      (if (or (eq c ?,) (eq c other-match))
-	  (progn
-	    (forward-char)
-	    (c-forward-syntactic-ws))
-	(c-lineup-argcont-scan other-match)))))
+Works with: arglist-cont, arglist-cont-nonempty."
+  (save-excursion
+    (when (c-lineup-argcont-1 langelem)	; Check we've got a continued argument...
+      ;; ... but ignore the position found.
+      (goto-char (c-langelem-2nd-pos c-syntactic-element))
+      (forward-char)
+      (c-forward-syntactic-ws)
+      (vector (+ (current-column) c-basic-offset)))))
 
 (defun c-lineup-arglist-intro-after-paren (_langelem)
   "Line up a line to just after the open paren of the surrounding paren

-- 
Alan Mackenzie (Nuremberg, Germany).