Subject: bug#66911: CC Mode 5.35.2 (C++//l); Noise macro being taken as anchor to class-open

From: Alan Mackenzie <[email-masked]>
Date: Fri, 17 Nov 2023 12:09:28 +0000

Hello, Arsen.

On Thu, Nov 16, 2023 at 21:29:29 +0100, Arsen Arsenović wrote:
> Hi Alan,

> Thanks for working on this.

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

[ .... ]

> > That was a mistake - the second anchor point is needed on class-open
> > lines, not topmost-intro-cont ones.

> > I've coded up this patch, which I include below.  Actually, only the last
> > two smallish hunks of the patch are new, the rest is what you've seen
> > before, so it will be necessary to start from a clean copy of
> > cc-engine.el again.

> > The syntactical context of the HERE line (the opening brace of that
> > class) is now something like:

> >     ((class-open 76 107))

> > , where 76 is the position of the template keyword and 107 that of the
> > class keyword.  When there's no template, both of these anchor points are
> > the same.  Actually, that's not quite accurate: the 107 is the point
> > after the indentation of the line that class is on; it just seemed better
> > that way.

> This behavior seems reasonable to me at a glance and at a test.  I like
> the idea :-)

[ .... ]

> > So, again, please let me know how you get on with this patch.  Is the
> > extra anchor point sufficient to write an alignment function to get the
> > indentation you want?

> The following did work nicely (after I made c-langelem-2nd-col, by
> copying and altering c-langelem-col as is obvious).  I'm not sure if it
> is idiomatic or the cleanest solution, though (plus, my Elisp-fu is
> poor).

> --8<---------------cut here---------------start------------->8---
> (defun glibcxx-style/line-up-struct (sym-form)
>   "Lines up a class-open with its prior struct line"
>   (if (not (eq 'class-open (c-langelem-sym sym-form)))
>       nil
>     (let ((col (c-langelem-2nd-col c-syntactic-element t)))
>       (if col
> 	  (vector col)
> 	nil))))
> --8<---------------cut here---------------end--------------->8---

> ... however, I noticed that class-close still anchors on the template
> that precedes it, and seems to not have a 2nd-pos to play with, so the
> above line-up fn, when set on class-open, produces:

> --8<---------------cut here---------------start------------->8---
> template<typename foo>
>   struct f
>   {
> };
> --8<---------------cut here---------------end--------------->8---

Yes.  I should have noticed this myself.  :-(

> ... so, we're getting quite close!

So, please try out the new patch (below) which supplies the second anchor
point to class-close too.  The patch applies cleanly to the Emacs master
branch.  Again, please start off with a clean cc-engine.el before
applying the new patch, not the version with the previous patch applied.

[ .... ]

diff -r 2760aada61fa cc-engine.el
--- a/cc-engine.el	Sun Oct 15 10:44:22 2023 +0000
+++ b/cc-engine.el	Fri Nov 17 11:49:55 2023 +0000
@@ -12633,31 +12633,27 @@
   (let ((open-brace (point)) kwd-start first-specifier-pos)
     (c-syntactic-skip-backward c-block-prefix-charset limit t)
 
-    (when (and c-recognize-<>-arglists
-	       (eq (char-before) ?>))
-      ;; Could be at the end of a template arglist.
-      (let ((c-parse-and-markup-<>-arglists t))
-	(while (and
-		(c-backward-<>-arglist nil limit)
-		(progn
-		  (c-syntactic-skip-backward c-block-prefix-charset limit t)
-		  (eq (char-before) ?>))))))
-
-    ;; Skip back over noise clauses.
-    (while (and
-	    c-opt-cpp-prefix
-	    (eq (char-before) ?\))
-	    (let ((after-paren (point)))
-	      (if (and (c-go-list-backward)
-		       (progn (c-backward-syntactic-ws)
-			      (c-simple-skip-symbol-backward))
-		       (or (looking-at c-paren-nontype-key)
-			   (looking-at c-noise-macro-with-parens-name-re)))
-		  (progn
-		    (c-syntactic-skip-backward c-block-prefix-charset limit t)
-		    t)
-		(goto-char after-paren)
-		nil))))
+    (while
+	(or
+	 ;; Could be after a template arglist....
+	 (and c-recognize-<>-arglists
+	      (eq (char-before) ?>)
+	      (let ((c-parse-and-markup-<>-arglists t))
+		(c-backward-<>-arglist nil limit)))
+	 ;; .... or after a noise clause with parens.
+	 (and c-opt-cpp-prefix
+	      (let ((after-paren (point)))
+		(if (eq (char-before) ?\))
+		    (and
+		     (c-go-list-backward)
+		     (eq (char-after) ?\()
+		     (progn (c-backward-syntactic-ws)
+			    (c-simple-skip-symbol-backward))
+		     (or (looking-at c-paren-nontype-key) ; e.g. __attribute__
+			 (looking-at c-noise-macro-with-parens-name-re)))
+		  (goto-char after-paren)
+		  nil))))
+      (c-syntactic-skip-backward c-block-prefix-charset limit t))
 
     ;; Note: Can't get bogus hits inside template arglists below since they
     ;; have gotten paren syntax above.
@@ -12667,10 +12663,18 @@
 	   ;; The `c-decl-block-key' search continues from there since
 	   ;; we know it can't match earlier.
 	   (if goto-start
-	       (when (c-syntactic-re-search-forward c-symbol-start
-						    open-brace t t)
-		 (goto-char (setq first-specifier-pos (match-beginning 0)))
-		 t)
+	       (progn
+		 (while
+		     (and
+		      (c-syntactic-re-search-forward c-symbol-start
+						     open-brace t t)
+		      (goto-char (match-beginning 0))
+		      (if (or (looking-at c-noise-macro-name-re)
+			      (looking-at c-noise-macro-with-parens-name-re))
+			  (c-forward-noise-clause)
+			(setq first-specifier-pos (match-beginning 0))
+			nil)))
+		 first-specifier-pos)
 	     t)
 
 	   (cond
@@ -12739,34 +12743,39 @@
 	    (goto-char first-specifier-pos)
 
 	    (while (< (point) kwd-start)
-	      (if (looking-at c-symbol-key)
-		  ;; Accept any plain symbol token on the ground that
-		  ;; it's a specifier masked through a macro (just
-		  ;; like `c-forward-decl-or-cast-1' skip forward over
-		  ;; such tokens).
-		  ;;
-		  ;; Could be more restrictive wrt invalid keywords,
-		  ;; but that'd only occur in invalid code so there's
-		  ;; no use spending effort on it.
-		  (let ((end (match-end 0))
-			(kwd-sym (c-keyword-sym (match-string 0))))
-		    (unless
-			(and kwd-sym
-			     ;; Moving over a protection kwd and the following
-			     ;; ":" (in C++ Mode) to the next token could take
-			     ;; us all the way up to `kwd-start', leaving us
-			     ;; no chance to update `first-specifier-pos'.
-			     (not (c-keyword-member kwd-sym 'c-protection-kwds))
-			     (c-forward-keyword-clause 0))
-		      (goto-char end)
-		      (c-forward-syntactic-ws)))
-
+	      (cond
+	       ((or (looking-at c-noise-macro-name-re)
+		    (looking-at c-noise-macro-with-parens-name-re))
+		(c-forward-noise-clause))
+	       ((looking-at c-symbol-key)
+		;; Accept any plain symbol token on the ground that
+		;; it's a specifier masked through a macro (just
+		;; like `c-forward-decl-or-cast-1' skips forward over
+		;; such tokens).
+		;;
+		;; Could be more restrictive wrt invalid keywords,
+		;; but that'd only occur in invalid code so there's
+		;; no use spending effort on it.
+		(let ((end (match-end 0))
+		      (kwd-sym (c-keyword-sym (match-string 0))))
+		  (unless
+		      (and kwd-sym
+			   ;; Moving over a protection kwd and the following
+			   ;; ":" (in C++ Mode) to the next token could take
+			   ;; us all the way up to `kwd-start', leaving us
+			   ;; no chance to update `first-specifier-pos'.
+			   (not (c-keyword-member kwd-sym 'c-protection-kwds))
+			   (c-forward-keyword-clause 0))
+		    (goto-char end)
+		    (c-forward-syntactic-ws))))
+
+	       ((c-syntactic-re-search-forward c-symbol-start
+					       kwd-start 'move t)
 		;; Can't parse a declaration preamble and is still
 		;; before `kwd-start'.  That means `first-specifier-pos'
 		;; was in some earlier construct.  Search again.
-		(if (c-syntactic-re-search-forward c-symbol-start
-						   kwd-start 'move t)
-		    (goto-char (setq first-specifier-pos (match-beginning 0)))
+		(goto-char (setq first-specifier-pos (match-beginning 0))))
+	       (t
 		  ;; Got no preamble before the block declaration keyword.
 		  (setq first-specifier-pos kwd-start))))
 
@@ -14181,7 +14190,8 @@
 (defun c-add-class-syntax (symbol
 			   containing-decl-open
 			   containing-decl-start
-			   containing-decl-kwd)
+			   containing-decl-kwd
+			   &rest args)
   ;; The inclass and class-close syntactic symbols are added in
   ;; several places and some work is needed to fix everything.
   ;; Therefore it's collected here.
@@ -14196,7 +14206,7 @@
     ;; Ought to use `c-add-stmt-syntax' instead of backing up to boi
     ;; here, but we have to do like this for compatibility.
     (back-to-indentation)
-    (c-add-syntax symbol (point))
+    (apply #'c-add-syntax symbol (point) args)
     (if (and (c-keyword-member containing-decl-kwd
 			       'c-inexpr-class-kwds)
 	     (/= containing-decl-start (c-point 'boi containing-decl-start)))
@@ -14230,9 +14240,10 @@
        ;; CASE B.1: class-open
        ((save-excursion
 	  (and (eq (char-after) ?{)
-	       (c-looking-at-decl-block t)
+	       (setq placeholder (c-looking-at-decl-block t))
 	       (setq beg-of-same-or-containing-stmt (point))))
-	(c-add-syntax 'class-open beg-of-same-or-containing-stmt))
+	(c-add-syntax 'class-open beg-of-same-or-containing-stmt
+		      (c-point 'boi placeholder)))
 
        ;; CASE B.2: brace-list-open
        ((or (consp special-brace-list)
@@ -14727,7 +14738,10 @@
 			    'lambda-intro-cont)))
 	(goto-char (cdr placeholder))
 	(back-to-indentation)
-	(c-add-stmt-syntax tmpsymbol nil t
+	(c-add-stmt-syntax tmpsymbol
+			   (and (eq tmpsymbol 'class-open)
+				(list (point)))
+			   t
 			   (c-most-enclosing-brace state-cache (point))
 			   paren-state)
 	(unless (eq (point) (cdr placeholder))
@@ -14770,9 +14784,10 @@
 	      (goto-char indent-point)
 	      (skip-chars-forward " \t")
 	      (and (eq (char-after) ?{)
-		   (c-looking-at-decl-block t)
+		   (setq tmp-pos (c-looking-at-decl-block t))
 		   (setq placeholder (point))))
-	    (c-add-syntax 'class-open placeholder))
+	    (c-add-syntax 'class-open placeholder
+			  (c-point 'boi tmp-pos)))
 
 	   ;; CASE 5A.3: brace list open
 	   ((save-excursion
@@ -15170,10 +15185,14 @@
 	 ((and containing-sexp
 	       (eq char-after-ip ?})
 	       (eq containing-decl-open containing-sexp))
+	  (save-excursion
+	    (goto-char containing-decl-open)
+	    (setq tmp-pos (c-looking-at-decl-block t)))
 	  (c-add-class-syntax 'class-close
 			      containing-decl-open
 			      containing-decl-start
-			      containing-decl-kwd))
+			      containing-decl-kwd
+			      (c-point 'boi tmp-pos)))
 
 	 ;; CASE 5H: we could be looking at subsequent knr-argdecls
 	 ((and c-recognize-knr-p

> -- 
> Arsen Arsenović

-- 
Alan Mackenzie (Nuremberg, Germany).