Subject: Re: bug#35454: 26.2.50; CC-Mode fontification fails inside macro

From: Alan Mackenzie <[email-masked]>
Date: Wed, 1 May 2019 21:02:30 +0000

Hello again, Mauro.

On Sat, Apr 27, 2019 at 20:36:46 +0000, Alan Mackenzie wrote:
> On Sat, Apr 27, 2019 at 12:57:05 -0300, Mauro Aranda wrote:
> > Hello.

> > Steps to reproduce:
> > 1) emacs -Q
> > 2) Open up a new .c file:
> > C-x C-f test.c
> > 3) Type this text:
> > #define FOO        \
> >   /* Some comms.  */     \
> >   struct foobar my_foo; \
> >   struct foobar my_bar;

> > The first struct foobar after the comment is not fontified as the second
> > one.  That is, the second foobar has face font-lock-type-face, and
> > my_bar has face font-lock-variable-name-face, but the first foobar and
> > my_foo don't get those face values.

> > I actually bumped into this issue while visiting the emacs source file
> > src/editfns.c.  In that file, search for "#define EXTRA_CONTEXT_FIELDS"
> > and the problem should be evident.

> Thanks!  I can reproduce this easily, and will look into it in the next
> day or two.

Please try out the patch below.  On my system, it corrects the
fontification in both your test file and editfns.c.

> Of interest is the fact that if FOO is given an empty argument list
> (i.e. one writes
>     #define FOO()  \
>     ...
> ), the bug doesn't happen.

> > I can reproduce it with the latest Emacs 26, as well as with the latest
> > master:
> > Repository revision: 8dc00b2f1e6523c634df3e24379afbe712a32b27
> > Repository branch: master

diff -r 9d58d1e3ab27 cc-engine.el
--- a/cc-engine.el	Sat Apr 27 17:07:23 2019 +0000
+++ b/cc-engine.el	Wed May 01 20:46:40 2019 +0000
@@ -5668,7 +5668,10 @@
 	       (setq cfd-re-match cfd-limit)
 	       nil)
 	      ((c-got-face-at
-		(if (setq cfd-re-match (match-end 1))
+		(if (setq cfd-re-match
+			  (or (match-end 1)
+			      (and c-dposr-cpp-macro-depth
+				   (match-end (1+ c-dposr-cpp-macro-depth)))))
 		    ;; Matched the end of a token preceding a decl spot.
 		    (progn
 		      (goto-char cfd-re-match)
@@ -5679,15 +5682,19 @@
 		c-literal-faces)
 	       ;; Pseudo match inside a comment or string literal.  Skip out
 	       ;; of comments and string literals.
-	       (while (progn
-			(unless
-			    (and (match-end 1)
-				 (c-got-face-at (1- (point)) c-literal-faces)
-				 (not (c-got-face-at (point) c-literal-faces)))
-			  (goto-char (c-next-single-property-change
-				      (point) 'face nil cfd-limit)))
-			(and (< (point) cfd-limit)
-			     (c-got-face-at (point) c-literal-faces))))
+	       (while
+		   (progn
+		     (unless
+			 (and
+			  (or (match-end 1)
+			      (and c-dposr-cpp-macro-depth
+				   (match-end (1+ c-dposr-cpp-macro-depth))))
+			  (c-got-face-at (1- (point)) c-literal-faces)
+			  (not (c-got-face-at (point) c-literal-faces)))
+		       (goto-char (c-next-single-property-change
+				   (point) 'face nil cfd-limit)))
+		     (and (< (point) cfd-limit)
+			  (c-got-face-at (point) c-literal-faces))))
 	       t)		      ; Continue the loop over pseudo matches.
 	      ((and c-opt-identifier-concat-key
 		    (match-string 1)
diff -r 9d58d1e3ab27 cc-langs.el
--- a/cc-langs.el	Sat Apr 27 17:07:23 2019 +0000
+++ b/cc-langs.el	Wed May 01 20:46:40 2019 +0000
@@ -964,6 +964,14 @@
 (c-lang-defvar c-opt-cpp-macro-define-id
   (c-lang-const c-opt-cpp-macro-define-id))
 
+(c-lang-defconst c-anchored-hash-define-no-parens
+  ;; Regexp matching everything up to the end of a cpp define which has no
+  ;; argument parentheses.  Or nil in languages which don't have them.
+  t (if (c-lang-const c-opt-cpp-macro-define)
+	(concat (c-lang-const c-anchored-cpp-prefix)
+		(c-lang-const c-opt-cpp-macro-define)
+		"[ \t]+\\(\\sw\\|_\\)+\\([^(a-zA-Z0-9_]\\|$\\)")))
+
 (c-lang-defconst c-cpp-expr-directives
   "List of cpp directives (without the prefix) that are followed by an
 expression."
@@ -1578,7 +1586,7 @@
   t (concat (c-lang-const c-comment-start-regexp)
 	    "\\|"
 	    (if (memq 'gen-string-delim c-emacs-features)
-		"\"|"
+		"\"\\|\\s|"
 	      "\"")))
 (c-lang-defvar c-literal-start-regexp (c-lang-const c-literal-start-regexp))
 
@@ -3152,24 +3160,40 @@
   ;; token that might precede such a construct, e.g. ';', '}' or '{'.
   ;; It's built from `c-decl-prefix-re'.
   ;;
-  ;; If the first submatch did not match, the match of the whole
-  ;; regexp is taken to be at the first token in the declaration.
-  ;; `c-decl-start-re' is not checked in this case.
+  ;; If the first submatch did not match, we have either a #define construct
+  ;; without parentheses or the match of the whole regexp is taken to be at
+  ;; the first token in the declaration.  `c-decl-start-re' is not checked in
+  ;; these cases.
   ;;
   ;; Design note: The reason the same regexp is used to match both
   ;; tokens that precede declarations and start them is to avoid an
   ;; extra regexp search from the previous declaration spot in
   ;; `c-find-decl-spots'.  Users of `c-find-decl-spots' also count on
-  ;; that it finds all declaration/cast/label starts in approximately
+  ;; it finding all declaration/cast/label starts in approximately
   ;; linear order, so we can't do the searches in two separate passes.
-  t (if (c-lang-const c-decl-start-kwds)
-	(concat (c-lang-const c-decl-prefix-re)
-		"\\|"
-		(c-make-keywords-re t (c-lang-const c-decl-start-kwds)))
-      (c-lang-const c-decl-prefix-re)))
+  t (cond
+     ((and (c-lang-const c-decl-start-kwds)
+	   (c-lang-const c-anchored-hash-define-no-parens))
+      (concat (c-lang-const c-decl-prefix-re)
+	      "\\|" (c-lang-const c-anchored-hash-define-no-parens)
+	      "\\|" (c-make-keywords-re t (c-lang-const c-decl-start-kwds))))
+     ((c-lang-const c-decl-start-kwds)
+      (concat (c-lang-const c-decl-prefix-re)
+	      "\\|" (c-make-keywords-re t (c-lang-const c-decl-start-kwds))))
+     ((c-lang-const c-anchored-hash-define-no-parens)
+      (concat (c-lang-const c-decl-prefix-re)
+	      "\\|" (c-lang-const c-anchored-hash-define-no-parens)))
+     (t (c-lang-const c-decl-prefix-re))))
 (c-lang-defvar c-decl-prefix-or-start-re
   (c-lang-const c-decl-prefix-or-start-re))
 
+(c-lang-defconst c-dposr-cpp-macro-depth
+  ;; The match number of `c-anchored-hash-define-no-parens''s first match
+  ;; within `c-decl-prefix-or-start-re', or nil if there is no such component.
+  t (if (c-lang-const c-anchored-hash-define-no-parens)
+	(1+ (regexp-opt-depth (c-lang-const c-decl-prefix-re)))))
+(c-lang-defvar c-dposr-cpp-macro-depth (c-lang-const c-dposr-cpp-macro-depth))
+
 (c-lang-defconst c-cast-parens
   ;; List containing the paren characters that can open a cast, or nil in
   ;; languages without casts.

> > Best regards,
> > Mauro.

-- 
Alan Mackenzie (Nuremberg, Germany).