From 472c44f9d63e57f167897b5abf5a29e9c2341380 Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Fri, 3 Jul 2026 02:29:33 +0900 Subject: [PATCH 1/6] Use nonl instead of any in RX form --- lisp/php-mode.el | 2 +- lisp/php.el | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lisp/php-mode.el b/lisp/php-mode.el index afd51294..d6537ab9 100644 --- a/lisp/php-mode.el +++ b/lisp/php-mode.el @@ -102,7 +102,7 @@ (let ((tag (replace-regexp-in-string (rx bos "v") "" (shell-command-to-string "git describe --tags"))) - (pattern (rx (group (+ any)) eol))) + (pattern (rx (group (+ nonl)) eol))) (if (string-match pattern tag) (match-string 0 tag) (error "Faild to obtain git tag")))) diff --git a/lisp/php.el b/lisp/php.el index ef46272c..3124ced0 100644 --- a/lisp/php.el +++ b/lisp/php.el @@ -327,7 +327,7 @@ which will be the name of the method." (* (syntax whitespace)) "(" ,@(when include-args - '((* any) line-end)))))) + '((* nonl) line-end)))))) (defun php-create-regexp-for-classlike (type) "Accepts a `TYPE' of a `classlike' object as a string, such as @@ -378,7 +378,7 @@ can be used to match against definitions for that classlike." (+ (or (syntax word) (syntax symbol))) (* (syntax whitespace)) (? "=" (* (syntax whitespace)) - (repeat 0 40 any)))) + (repeat 0 40 nonl)))) 1) ("Functions" ,(rx line-start @@ -389,7 +389,7 @@ can be used to match against definitions for that classlike." (+ (or (syntax word) (syntax symbol))) (* (syntax whitespace)) "(" - (repeat 0 100 any))) + (repeat 0 100 nonl))) 1) ("Import" ,(rx line-start @@ -397,7 +397,7 @@ can be used to match against definitions for that classlike." (group "use" (+ (syntax whitespace)) - (repeat 0 100 any))) + (repeat 0 100 nonl))) 1) ("Classes" ,(php-create-regexp-for-classlike "\\(?:class\\|interface\\|trait\\|enum\\)") 0) From 1c9e08dde1eb83286547ec5d832689533bf91cad Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Fri, 3 Jul 2026 02:29:53 +0900 Subject: [PATCH 2/6] Set c-after-brace-list-decl-kwds --- lisp/php-mode.el | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lisp/php-mode.el b/lisp/php-mode.el index d6537ab9..38534b13 100644 --- a/lisp/php-mode.el +++ b/lisp/php-mode.el @@ -470,6 +470,12 @@ any) is a brace list. PHP does not have an C-like \"enum\" keyword." php nil) +;; cc-mode renamed `c-after-brace-list-decl-kwds' to `c-after-enum-list-kwds'; +;; keep the old name defined so cc-langs' transitional lookup stays quiet where +;; the rename landed (see `c-after-enum-list-key' in cc-langs.el). +(c-lang-defconst c-after-brace-list-decl-kwds + php nil) + (c-lang-defconst c-typeless-decl-kwds php (append (c-lang-const c-class-decl-kwds php) '("function" "const"))) From dc13865a3b911f87676ff823ad7968a3d786cbc0 Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Fri, 3 Jul 2026 02:30:26 +0900 Subject: [PATCH 3/6] Add guard to obsolete font-lock-* variables --- lisp/php-mode.el | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/lisp/php-mode.el b/lisp/php-mode.el index 38534b13..bae38fc0 100644 --- a/lisp/php-mode.el +++ b/lisp/php-mode.el @@ -1185,13 +1185,19 @@ After setting the stylevars run hook `php-mode-STYLENAME-hook'." (setq-local comment-end "") (setq-local page-delimiter php-mode-page-delimiter) - (setq-local font-lock-string-face 'php-string) - (setq-local font-lock-keyword-face 'php-keyword) - (setq-local font-lock-builtin-face 'php-builtin) - (setq-local c-preprocessor-face-name 'php-php-tag) - (setq-local font-lock-function-name-face 'php-function-name) - (setq-local font-lock-variable-name-face 'php-variable-name) - (setq-local font-lock-constant-face 'php-constant) + (with-suppressed-warnings ((obsolete font-lock-string-face + font-lock-keyword-face + font-lock-builtin-face + font-lock-function-name-face + font-lock-variable-name-face + font-lock-constant-face)) + (setq-local font-lock-string-face 'php-string) + (setq-local font-lock-keyword-face 'php-keyword) + (setq-local font-lock-builtin-face 'php-builtin) + (setq-local c-preprocessor-face-name 'php-php-tag) + (setq-local font-lock-function-name-face 'php-function-name) + (setq-local font-lock-variable-name-face 'php-variable-name) + (setq-local font-lock-constant-face 'php-constant)) (setq-local syntax-propertize-function #'php-syntax-propertize-function) (add-hook 'syntax-propertize-extend-region-functions From 8293aea4328329bfea03ad3b581810efde2f64e0 Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Fri, 10 Jul 2026 04:25:07 +0900 Subject: [PATCH 4/6] Fix closure body indentation on Emacs 31+ CC Mode >= 31 (the change for bug#19867) no longer treats a brace block whose contents are inconclusive -- e.g. a closure body whose first line is only a comment -- as a statement block. `c-looking-at-statement-block' now falls back to context analysis, and for an anonymous PHP `function () {...}' the token before the parameter list is the `function' keyword rather than a function-name identifier, so the body is misclassified as a brace list. This mis-indents both the body and its closing brace (the `lang/function/closure.php' indentation test fails on Emacs 31/32 but passes on 30.2). Advise `c-looking-at-statement-block' to report a closure-opening brace as a statement block. A closure body always is one, so this is also correct on Emacs versions unaffected by the change. --- lisp/php-mode.el | 54 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/lisp/php-mode.el b/lisp/php-mode.el index bae38fc0..c844f93d 100644 --- a/lisp/php-mode.el +++ b/lisp/php-mode.el @@ -681,6 +681,57 @@ a backward search limit." (cons (point) t)))) (t nil))) +(defun php-mode--anonymous-function-brace-p (pos) + "Return non-nil when the opening brace at POS starts a closure body. + +POS must be the position of a `{'. A closure is an anonymous +`function (...) [use (...)] [: type] {' expression; named function +declarations and other constructs (e.g. `match', `if') return nil." + (save-excursion + (goto-char pos) + (c-backward-syntactic-ws) + ;; Step back over an optional return type declaration `): Type {' so + ;; that point lands just after the parameter (or `use') list's `)'. + (unless (eq (char-before) ?\)) + (let ((colon (save-excursion + (skip-chars-backward "^):;{}(" (point-min)) + (and (eq (char-before) ?:) (1- (point)))))) + (when colon + (goto-char colon) + (c-backward-syntactic-ws)))) + ;; Walk backwards over the `)...(' list(s): the parameter list, plus an + ;; optional `use (...)' clause, until we reach `function' or give up. + (let ((result nil) (continue t)) + (while (and continue (eq (char-before) ?\))) + (condition-case nil + (backward-list) + (error (setq continue nil))) + (when continue + (c-backward-syntactic-ws) + (if (zerop (c-backward-token-2)) + (cond + ((looking-at-p "\\_") (setq result t continue nil)) + ((looking-at-p "\\_") (c-backward-syntactic-ws)) + (t (setq continue nil))) + (setq continue nil)))) + result))) + +(defun php-c-looking-at-statement-block (orig-fun &rest args) + "Treat a closure body as a statement block around `c-looking-at-statement-block'. + +Emacs' CC Mode >= 31 (the change for bug#19867) fails to recognize an +anonymous function body whose first line is a comment as a statement +block, and reports it as a brace list, mis-indenting both the body and +its closing brace. When point is at the opening brace of such a closure, +return non-nil; otherwise fall back to ORIG-FUN with ARGS. + +A closure body is always a statement block, so this is also correct on +Emacs versions unaffected by the bug." + (or (and (derived-mode-p 'php-mode) + (eq (char-after) ?\{) + (php-mode--anonymous-function-brace-p (point))) + (apply orig-fun args))) + (c-add-style "php" `((c-basic-offset . 4) @@ -1256,6 +1307,9 @@ After setting the stylevars run hook `php-mode-STYLENAME-hook'." (advice-add 'c-looking-at-or-maybe-in-bracelist :around 'php-c-looking-at-or-maybe-in-bracelist) + (when (fboundp 'c-looking-at-statement-block) + (advice-add 'c-looking-at-statement-block + :around 'php-c-looking-at-statement-block)) (advice-add 'fixup-whitespace :after #'php-mode--fixup-whitespace-after) (advice-add 'acm-backend-tabnine-candidate-expand From 68faa1cfe9dd7742efac462eda85f63b94478d43 Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Fri, 10 Jul 2026 04:33:59 +0900 Subject: [PATCH 5/6] Fix error-control operator "@" fontification on Emacs 31+ CC Mode >= 31 fontifies a bare `@' as a keyword because `@new' is registered in `c-type-list-kwds', so the error-control operator in e.g. `@fopen(...)' or `@$a' was highlighted as `php-keyword' rather than `php-errorcontrol-op' (the `lang/errorcontrol.php' faces test fails on Emacs 31/32). The previous matcher `\<\(@\)' never matched: `@' has punctuation syntax, so a word boundary can never precede it. Replace it with a matcher function that highlights a single `@' as the error-control operator, overriding CC Mode's keyword face, while skipping `@' inside strings and comments and the `@new' keyword. --- lisp/php-mode.el | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/lisp/php-mode.el b/lisp/php-mode.el index c844f93d..07dd4ee4 100644 --- a/lisp/php-mode.el +++ b/lisp/php-mode.el @@ -1516,7 +1516,7 @@ for \\[find-tag] (which see)." ;; already fontified by another pattern. Note that using OVERRIDE ;; is usually overkill. `( - ("\\<\\(@\\)" 1 'php-errorcontrol-op) + (php-mode--error-control-op-font-lock-find 0 'php-errorcontrol-op t) ;; import function statement (,(rx symbol-start (group "use" (+ (syntax whitespace)) "function") (+ (syntax whitespace))) @@ -1618,6 +1618,24 @@ The output will appear in the buffer *PHP*." (defconst php-string-interpolated-variable-regexp "{\\$[^}\n\\\\]*\\(?:\\\\.[^}\n\\\\]*\\)*}\\|\\${\\sw+}\\|\\$\\sw+") +(defun php-mode--error-control-op-font-lock-find (limit) + "Font-lock matcher for the error-control operator `@' up to LIMIT. + +Match a single `@' that is used as the error-control operator, skipping +occurrences inside strings or comments and the `@new' keyword (which CC +Mode fontifies as a keyword). CC Mode >= 31 fontifies a bare `@' as a +keyword because `@new' is registered in `c-type-list-kwds', so this +matcher is set to override that face." + (let (found) + (while (and (not found) + (re-search-forward "@" limit t)) + (unless (or (php-in-string-or-comment-p) + (save-excursion + (goto-char (match-beginning 0)) + (looking-at-p "@new\\_>"))) + (setq found t))) + found)) + (defun php-mode--string-interpolated-variable-font-lock-find (limit) "Apply text-property to LIMIT for string interpolation by font-lock." (let (quoted-stuff) From c439aac957a1775b1176b2cf20db97830983e84b Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Fri, 10 Jul 2026 04:37:29 +0900 Subject: [PATCH 6/6] Guard match data in error-control operator matcher `php-in-string-or-comment-p' calls `syntax-ppss', which may trigger `syntax-propertize' and clobber the match data set by the preceding `re-search-forward'. That could misplace the `@new' look-ahead check and the face applied by font-lock. Wrap the call in `save-match-data' so the matched `@' is preserved. --- lisp/php-mode.el | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lisp/php-mode.el b/lisp/php-mode.el index 07dd4ee4..490ff7b8 100644 --- a/lisp/php-mode.el +++ b/lisp/php-mode.el @@ -1629,7 +1629,10 @@ matcher is set to override that face." (let (found) (while (and (not found) (re-search-forward "@" limit t)) - (unless (or (php-in-string-or-comment-p) + ;; `php-in-string-or-comment-p' calls `syntax-ppss', which may run + ;; `syntax-propertize' and clobber the match data, so guard it to + ;; keep the `@' match for both the `@new' check below and font-lock. + (unless (or (save-match-data (php-in-string-or-comment-p)) (save-excursion (goto-char (match-beginning 0)) (looking-at-p "@new\\_>")))