branch: externals/phpinspect
commit af623fe4692b197ddff419de49636a21ad840257
Author: Hugo Thunnissen <de...@hugot.nl>
Commit: Hugo Thunnissen <de...@hugot.nl>

    Fix endless recursion bug in resolving of self-referential variable type
---
 phpinspect-resolve.el       | 54 ++++++++++++++++++++++-------------
 test/phpinspect-test.el     | 23 +++++++++++++--
 test/test-buffer-resolve.el | 68 +++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 124 insertions(+), 21 deletions(-)

diff --git a/phpinspect-resolve.el b/phpinspect-resolve.el
index 5c9e0c5ae8..efe69adc02 100644
--- a/phpinspect-resolve.el
+++ b/phpinspect-resolve.el
@@ -64,6 +64,19 @@
   (preceding-assignments nil
                          :type list))
 
+(defun phpi--assignment-statement-p (statement)
+  (when (length> statement 2)
+    (let (assignment-found)
+      (catch 'phpi--return
+        (dolist (token statement)
+          (when (and assignment-found
+                     (phpinspect-not-comment-p token)
+                     (not (phpinspect-terminator-p token)))
+            (throw 'phpi--return t))
+
+          (when (phpinspect-maybe-assignment-p token)
+            (setq assignment-found t)))))))
+
 (defun phpinspect--find-assignment-ctxs-in-token (token &optional 
assignments-before var-annotations)
   (when (keywordp (car token))
     (setq token (cdr token)))
@@ -74,8 +87,9 @@
         assignments blocks-or-lists)
     (dolist (statement statements)
       (phpinspect--log "Finding assignment in statement '%s'" statement)
-      (when (seq-find #'phpinspect-maybe-assignment-p statement)
-        (phpinspect--log "Found assignment statement")
+
+      ;; Only add assignments which have more than 2 tokens, meaning
+      (when (phpi--assignment-statement-p statement)
         (push (phpinspect--make-assignment-context
                :annotations var-annotations
                :tokens statement
@@ -95,16 +109,16 @@
       (when (setq blocks-or-lists (seq-filter #'phpinspect-block-or-list-p 
statement))
         (dolist (block-or-list blocks-or-lists)
           (phpinspect--log "Found block or list %s" block-or-list)
-          (let ((local-assignments
-                 (phpinspect--find-assignment-ctxs-in-token
-                  block-or-list assignments-before var-annotations)))
-            (dolist (local-assignment (nreverse local-assignments))
-              (push local-assignment assignments))
+          (when-let ((local-assignments
+                      (phpinspect--find-assignment-ctxs-in-token
+                       block-or-list assignments-before var-annotations)))
+            (setq assignments (nconc local-assignments assignments))
             (setq assignments-before assignments)))))
 
     ;; return
     (phpinspect--log "Found assignments in token: %s" assignments)
     (phpinspect--log "Found statements in token: %s" statements)
+
     assignments))
 
 (defun phpinspect--find-assignment-by-predicate (assignment-ctxs predicate)
@@ -351,13 +365,14 @@ resolve types of function argument variables."
                      pattern-code (length assignments))
 
     (if (not last-assignment)
-        (when (and (= (length pattern-code) 2) (phpinspect-variable-p (cadr 
pattern-code)))
-          (let ((variable-name (cadadr pattern-code)))
-            (progn
-              (phpinspect--log "No assignments found for variable %s, checking 
function arguments: %s"
-                               variable-name function-arg-list)
-              (setq result (phpinspect-get-variable-type-in-function-arg-list
-                            variable-name type-resolver function-arg-list)))))
+        (progn
+          (when (and (= (length pattern-code) 2) (phpinspect-variable-p (cadr 
pattern-code)))
+            (let ((variable-name (cadadr pattern-code)))
+              (progn
+                (phpinspect--log "No assignments found for variable %s, 
checking function arguments: %s"
+                                 variable-name function-arg-list)
+                (setq result (phpinspect-get-variable-type-in-function-arg-list
+                              variable-name type-resolver 
function-arg-list))))))
 
       (setq result
             (phpinspect--interpret-expression-type-in-context
@@ -536,7 +551,9 @@ value/type."
                 (cadar expression)
                 type-resolver function-arg-list))
              (phpinspect--get-variable-type-in-block
-              resolvecontext (cadar expression) php-block assignments 
type-resolver function-arg-list)))))
+              resolvecontext (cadar expression) php-block
+              assignments type-resolver function-arg-list)))))
+
 
 (defun phpinspect-resolve-type-from-context (resolvecontext &optional 
type-resolver assume-derived)
   "Resolve the type that RESOLVECONTEXT's subject evaluates to.
@@ -564,10 +581,8 @@ type. (`phpinspect--type-p')"
   (let ((enclosing-tokens (seq-filter #'phpinspect-not-class-p
                                        
(phpinspect--resolvecontext-enclosing-tokens
                                         resolvecontext)))
-        (enclosing-token)
-        (type))
+        enclosing-token type)
     (while (and enclosing-tokens (not type))
-      ;;(phpinspect--log "Trying to find type in %s" enclosing-token)
       (setq enclosing-token (pop enclosing-tokens))
 
       (let ((subject (phpinspect--resolvecontext-subject resolvecontext)))
@@ -579,7 +594,8 @@ type. (`phpinspect--type-p')"
                     ((phpinspect-namespace-p enclosing-token)
                      (phpinspect-interpret-expression-type-in-context
                       resolvecontext
-                      (or (phpinspect-namespace-block enclosing-token) 
enclosing-token)
+                      (or (phpinspect-namespace-block enclosing-token)
+                          enclosing-token)
                       type-resolver subject))
 
                     ((or (phpinspect-block-p enclosing-token)
diff --git a/test/phpinspect-test.el b/test/phpinspect-test.el
index 8f88a3779a..aa838302bd 100644
--- a/test/phpinspect-test.el
+++ b/test/phpinspect-test.el
@@ -123,9 +123,20 @@
       (should (phpinspect--type= (phpinspect--make-type :name "\\Thing")
                                  result)))))
 
+(ert-deftest phpinspect--find-assignment-ctxs-in-token-no-self-reference ()
+
+  (let* ((tokens (cadr
+                  (phpinspect-parse-string "{ $a = $b; $foo = 'abc'; $a = 'c'; 
if ($a = $b) { /* comment */ $a = $b; $something->doSomething(); $bla = $foo; } 
if (true) { $x = 34; $y = 99; $foo = $nr7; }}")))
+         (assignments (phpinspect--find-assignment-ctxs-in-token tokens)))
+    (dolist (assignment (cdr assignments))
+      (should-not (seq-find (lambda (actx)
+                              (eq (phpinspect--actx-tokens assignment)
+                                  (phpinspect--actx-tokens actx)))
+                            (phpinspect--actx-preceding-assignments 
assignment))))))
+
 (ert-deftest phpinspect--find-assignment-ctxs-in-token ()
   (let* ((tokens (cadr
-                  (phpinspect-parse-string "{ $foo = ['nr 1']; $bar = $nr2; if 
(true === ($foo = $nr3)) { $foo = $nr4; $notfoo = $nr5; if ([] === ($foo = [ 
$nr6 ])){ $foo = [ $nr7 ];}}}")))
+                  (phpinspect-parse-string "{ $foo = ['nr 1']; $bar = $nr2; if 
(true === ($foo = $nr3)) { $foo = $nr4; $notfoo = $nr5; if ([] === ($foo = [ 
$nr6 ])){ $foobar = /* comment */; $foo = [ $nr7 ]; $bar = }}}")))
          (expected '(((:variable "foo")
                       (:assignment "=")
                       (:array
@@ -152,7 +163,14 @@
                        (:string "nr 1")))))
          (assignments (phpinspect--find-assignment-ctxs-in-token tokens)))
 
-    (should (equal expected (mapcar #'phpinspect--actx-tokens assignments)))))
+    (should (equal expected (mapcar #'phpinspect--actx-tokens assignments)))
+
+    (dolist (assignment (cdr assignments))
+      (should-not (seq-find (lambda (actx)
+                              (eq (phpinspect--actx-tokens assignment)
+                                  (phpinspect--actx-tokens actx)))
+                            (phpinspect--actx-preceding-assignments 
assignment))))))
+
 
 
 (ert-deftest phpinspect-resolve-type-from-context ()
@@ -340,6 +358,7 @@ class FlufferUpper
 (load-file (concat phpinspect-test-directory "/test-completion.el"))
 (load-file (concat phpinspect-test-directory "/test-change.el"))
 (load-file (concat phpinspect-test-directory "/test-token-predicates.el"))
+(load-file (concat phpinspect-test-directory "/test-buffer-resolve.el"))
 
 
 (provide 'phpinspect-test)
diff --git a/test/test-buffer-resolve.el b/test/test-buffer-resolve.el
new file mode 100644
index 0000000000..a8c604c8ce
--- /dev/null
+++ b/test/test-buffer-resolve.el
@@ -0,0 +1,68 @@
+;;; test-buffer-resolve.el --- Test resolving types in live edited buffers  
-*- lexical-binding: t; -*-
+
+;; Copyright (C) 2025  Free Software Foundation
+
+;; Author: Hugo Thunnissen <de...@hugot.nl>
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;;
+
+;;; Code:
+
+(require 'ert)
+(require 'phpinspect-buffer)
+(require 'phpinspect-test-env
+         (expand-file-name "phpinspect-test-env.el"
+                           (file-name-directory (macroexp-file-name))))
+
+(ert-deftest phpinspect-buffer-test-resolve-self-referential-variable ()
+  (with-temp-buffer
+    (let ((buffer (phpinspect-claim-buffer
+                   (current-buffer) 
(phpinspect--make-dummy-composer-project-with-code))))
+      (insert "<?php
+
+namespace App\\Testerino;
+
+class Test
+{
+    public function test(bool $flipflop = false): void
+    {
+        $value = new \\App\\Bar();
+        if (true) {
+             $b = $flipflop;
+             // for some reason this bug only triggers when there is at least
+             // one line of code before the self-referential assignment
+             $value = $value->foo();
+        }
+
+        $this->value = $value->
+    }
+}")
+
+      (phpinspect-buffer-parse buffer)
+
+      (backward-char 7)
+      (insert "$value->")
+
+      (let* ((rctx (phpinspect-buffer-get-resolvecontext buffer (point)))
+             (type (phpinspect-resolve-type-from-context rctx)))
+
+        (should type)
+        (should (phpinspect--type= (phpinspect--make-type :name "\\App\\Foo" 
:fully-qualified t) type ))))))
+
+(provide 'test-buffer-resolve)
+;;; test-buffer-resolve.el ends here

Reply via email to