Subject: Re: bug#61144: CC Mode 5.35.2 (C/*l); Incorrect fontification (but different from last time)

From: Alan Mackenzie <[email-masked]>
Date: Mon, 30 Jan 2023 21:53:11 +0000

Hello, Po.

On Sun, Jan 29, 2023 at 19:00:47 +0800, Po Lu via CC-Mode-help wrote:
> Package: cc-mode

> Emacs  : GNU Emacs 30.0.50 (build 41, x86_64-pc-linux-gnu)
>  of 2023-01-29
> Package: CC Mode 5.35.2 (C/*l)
> Buffer Style: gnu
> c-emacs-features: (pps-extended-state col-0-paren posix-char-classes gen-string-delim gen-comment-delim syntax-properties category-properties 1-bit)

> Insert the following text in a C Mode buffer:

> static sfnt_f26dot6
> sfnt_mul_f26dot6 (sfnt_f26dot6 a, sfnt_f26dot6 b)
> {
> #ifdef INT64_MAX
>   return (sfnt_f26dot6) ((int64_t) a * b + (1 << 5) >> 6);
> #else
>   int negative;
>   unsigned short al, bl, ah, bh;
>   unsigned int lowlong, midlong, hilong;

>   negative = 0;

>   /* Compensate for complement and determine if the result will be
>      negative.  */

>   if (a < 0)
>     {
>       a = -a;
>       negative = 1;
>     }

>   if (b < 0)
>     {
>       b = -b;
>       negative ^= true;
>     }

>   /* Load low and high words from A and B.  */
>   al = a & 0xffff;
>   bl = b & 0xffff;
>   ah = a >> 16;
>   bh = b >> 16;

>   /* Multiply the various bits separately.  */
>   midlong = (unsigned int) al * bh;
> #endif
> }

> In ``al * bh'', al is fontified as a type, and bh an identifier.

Yes.  foo * bar is a difficult case, since it can either be a
multiplication or a declaration of bar as a pointer to foo.  There is no
reliable way to distinguish these two cases without a full parser.  No
doubt c-ts-mode will do better here.

In another similar case, I have disambiguated these with "whitespace
asymmetry" - If there's WS both before and after the "*" or neither,
it's treated as a multiplication.  Otherwise it's a declaration.

I've extended this heuristic to treat the case in your test file.  Would
you try out the attached patch, please (it applies cleanly to the master
branch).  I would be in favour of putting the patch into the release
branch, but given the patch is ~90 lines long, Eli might object.

Anyhow, please try it out, and let me know what you think about it.
Thanks!

[ .... ]

-- 
Alan Mackenzie (Nuremberg, Germany).

diff -r 15a49bc02b66 cc-engine.el
--- a/cc-engine.el	Mon Jan 30 19:39:13 2023 +0000
+++ b/cc-engine.el	Mon Jan 30 21:39:55 2023 +0000
@@ -10167,6 +10167,24 @@
 	;; This identifier is bound only in the inner let.
 	'(setq start id-start))))
 
+(defmacro c-fdoc-assymetric-space-about-asterisk ()
+  ;; We've got a "*" at `id-start' between two identifiers, the first at
+  ;; `type-start'.  Return non-nil when there is either whitespace between the
+  ;; first id and the "*" or between the "*" and the second id, but not both.
+  `(let ((space-before-id
+	 (save-excursion
+	   (goto-char id-start)		; Position of "*".
+	   (and (> (skip-chars-forward "* \t\n\r") 0)
+		(memq (char-before) '(?\  ?\t ?\n ?\r)))))
+	(space-after-type
+	 (save-excursion
+	   (goto-char type-start)
+	   (and (c-forward-type nil t)
+		(or (eolp)
+		    (memq (char-after) '(?\  ?\t)))))))
+     (not (eq (not space-before-id)
+	      (not space-after-type)))))
+
 (defun c-forward-decl-or-cast-1 (preceding-token-end context last-cast-end
 						     &optional inside-macro)
   ;; Move forward over a declaration or a cast if at the start of one.
@@ -11185,19 +11203,25 @@
 	       ;; CASE 16
 	       (when (and got-prefix-before-parens
 			  at-type
-			  (or at-decl-end (looking-at "=[^=]"))
 			  (memq context '(nil top))
 			  (or (not got-suffix)
 			      at-decl-start))
 		 ;; Got something like "foo * bar;".  Since we're not inside
 		 ;; an arglist it would be a meaningless expression because
 		 ;; the result isn't used.  We therefore choose to recognize
-		 ;; it as a declaration.  We only allow a suffix (which makes
-		 ;; the construct look like a function call) when
-		 ;; `at-decl-start' provides additional evidence that we do
-		 ;; have a declaration.
+		 ;; it as a declaration when there's "symmetrical WS" around
+		 ;; the "*" or the flag `c-assymetry-fontification-flag' is
+		 ;; not set.  We only allow a suffix (which makes the
+		 ;; construct look like a function call) when `at-decl-start'
+		 ;; provides additional evidence that we do have a
+		 ;; declaration.
 		 (setq maybe-expression t)
-		 (throw 'at-decl-or-cast t))
+		 (when (or (not c-asymmetry-fontification-flag)
+			   (looking-at "=[^=]")
+			   (c-fdoc-assymetric-space-about-asterisk))
+		   (when (eq at-type 'maybe)
+		     (setq unsafe-maybe t))
+		   (throw 'at-decl-or-cast t)))
 
 	       ;; CASE 17
 	       (when (and (or got-suffix-after-parens
@@ -11216,24 +11240,12 @@
 			  got-prefix-before-parens
 			  at-type
 			  (or (not got-suffix)
-			      at-decl-start))
-		 (let ((space-before-id
-			(save-excursion
-			  (goto-char id-start) ; Position of "*".
-			  (and (> (skip-chars-forward "* \t\n\r") 0)
-			       (memq (char-before) '(?\  ?\t ?\n ?\r)))))
-		       (space-after-type
-			(save-excursion
-			  (goto-char type-start)
-			  (and (c-forward-type nil t)
-			       (or (eolp)
-				   (memq (char-after) '(?\  ?\t)))))))
-		   (when (not (eq (not space-before-id)
-				  (not space-after-type)))
-		     (when (eq at-type 'maybe)
-		       (setq unsafe-maybe t))
-		     (setq maybe-expression t)
-		     (throw 'at-decl-or-cast t)))))
+			      at-decl-start)
+			  (c-fdoc-assymetric-space-about-asterisk))
+		 (when (eq at-type 'maybe)
+		   (setq unsafe-maybe t))
+		 (setq maybe-expression t)
+		 (throw 'at-decl-or-cast t)))
 
 	   ;; CASE 18
 	   (when (and at-decl-end