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

    Add resolvecontext structure and optimze type resolving process
    
    The resolvecontext will from now on be used as store for all data required 
to resolve the
    return type of a statement at any given point.
    
    `phpinspect--word-end-regex` has been altered to match words that are 
directly followed by
    other, "non-word" characters.
    
    `phpinspect-describe-handler` has been added to list and describe handlers 
during runtime.
    
    A bug has been fixed that made the parser interpret the "static" keyword 
inside functions
    as a class attribute in stead of just a word.
    
    Aside from the company backend, `phpinspect-eldoc-function` has been 
simplified and
    adapted to make use of the resolvecontext structure.
    
    The resolving of statment/variable types has been altered to make use of 
imperative loops
    in a couple of places for the sake of simplicity and optimization.
---
 .gitignore                        |   1 +
 phpinspect.el                     | 984 ++++++++++++++++++++++----------------
 test/fixtures/IncompleteClass.eld |   1 +
 test/fixtures/IncompleteClass.php |  81 ++++
 test/phpinspect-test.el           |  25 +
 5 files changed, 681 insertions(+), 411 deletions(-)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000..016d3b1692
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+*.elc
\ No newline at end of file
diff --git a/phpinspect.el b/phpinspect.el
index b093bf5d9f..1e8fcf1f10 100644
--- a/phpinspect.el
+++ b/phpinspect.el
@@ -1,4 +1,4 @@
-;;; phpinspect.el --- PHP parsing and IntelliSense package  -*- 
lexical-binding: t; -*-
+;;; phpinspect.el --- PHP parsing and completion package  -*- lexical-binding: 
t; -*-
 
 ;; Copyright (C) 2021  Free Software Foundation, Inc
 
@@ -70,7 +70,24 @@ Should normally be set to \"phpinspect-index.bash\" in the 
source
 
 (eval-when-compile
   (define-inline phpinspect--word-end-regex ()
-    (inline-quote "[[:blank:]]")))
+    (inline-quote "\\([[:blank:]]\\|[^0-9a-zA-Z_]\\)")))
+
+(defun phpinspect-list-handlers ()
+  (let ((handlers))
+    (mapatoms (lambda (handler)
+                (push (symbol-name handler) handlers))
+              phpinspect-handler-obarray)
+    handlers))
+
+(defun phpinspect-describe-handler (handler-name)
+  "Display a buffer containing HANDLER-NAMEs docstring and attribute-plist."
+  (interactive (list (completing-read "Pick a handler:" 
(phpinspect-list-handlers))))
+  (with-current-buffer (get-buffer-create "phpinspect-handler-description")
+    (insert (concat
+             (pp (symbol-value (intern handler-name 
phpinspect-handler-obarray)))
+             "\n"
+             (documentation (intern handler-name phpinspect-handler-obarray))))
+    (pop-to-buffer (current-buffer))))
 
 (defsubst phpinspect--strip-last-char (string)
   (substring string 0 (- (length string) 1)))
@@ -112,6 +129,13 @@ Type can be any of the token types returned by
       (phpinspect-comma-p token)
       (phpinspect-html-p token)))
 
+(defsubst phpinspect-incomplete-block-p (token)
+  (phpinspect-type-p token :incomplete-block))
+
+(defsubst phpinspect-block-p (token)
+  (or (phpinspect-type-p token :block)
+      (phpinspect-incomplete-block-p token)))
+
 (defun phpinspect-end-of-use-p (token)
   (or (phpinspect-block-p token)
       (phpinspect-end-of-statement-p token)))
@@ -131,11 +155,8 @@ Type can be any of the token types returned by
       (phpinspect-type-p token :private)
       (phpinspect-type-p token :protected)))
 
-(defun phpinspect-block-p (token)
-  (or (phpinspect-type-p token :block) (phpinspect-incomplete-block-p token)))
-
-(defun phpinspect-incomplete-block-p (token)
-  (phpinspect-type-p token :incomplete-block))
+(defsubst phpinspect-namespace-p (object)
+  (phpinspect-type-p object :namespace))
 
 (defun phpinspect-incomplete-class-p (token)
   (and (phpinspect-class-p token)
@@ -149,6 +170,7 @@ Type can be any of the token types returned by
 (defun phpinspect-function-p (token)
   (phpinspect-type-p token :function))
 
+
 (defun phpinspect-class-p (token)
   (phpinspect-type-p token :class))
 
@@ -196,7 +218,6 @@ Type can be any of the token types returned by
   (or (phpinspect-type-p token :array)
       (phpinspect-incomplete-array-p token)))
 
-
 (defsubst phpinspect-incomplete-token-p (token)
   (or (phpinspect-incomplete-class-p token)
       (phpinspect-incomplete-block-p token)
@@ -226,8 +247,13 @@ Type can be any of the token types returned by
 (defun phpinspect-use-keyword-p (token)
   (and (phpinspect-word-p token) (string= (car (last token)) "use")))
 
-(defun phpinspect-namespace-p (object)
-  (phpinspect-type-p object :namespace))
+
+(defsubst phpinspect-root-p (object)
+  (phpinspect-type-p object :root))
+
+(defsubst phpinspect-namespace-or-root-p (object)
+  (or (phpinspect-namespace-p object)
+      (phpinspect-root-p object)))
 
 (defun phpinspect-use-p (object)
   (phpinspect-type-p object :use))
@@ -235,6 +261,21 @@ Type can be any of the token types returned by
 (defun phpinspect-comment-p (token)
   (phpinspect-type-p token :comment))
 
+(defsubst phpinspect-class-block (class)
+  (caddr class))
+
+(defsubst phpinspect-namespace-block (namespace)
+  (when (and (= (length namespace) 3)
+             (phpinspect-block-p (caddr namespace)))
+    (caddr namespace)))
+
+(defsubst phpinspect-function-block (php-func)
+  (caddr php-func))
+
+(defsubst phpinspect-not-class-p (token)
+  "Apply inverse of `phpinspect-class-p' to TOKEN."
+  (not (phpinspect-class-p token)))
+
 (defmacro phpinspect-defhandler (name arguments docstring attribute-plist 
&rest body)
   "Define a parser handler that becomes available for use with 
phpinspect-parse.
 
@@ -502,7 +543,7 @@ token is \";\", which marks the end of a statement in PHP."
 
   (let ((parser (phpinspect-get-parser-create
                  :use
-                 '(word tag block-without-classes terminator)
+                 '(word tag block-without-scopes terminator)
                  #'phpinspect-end-of-use-p)))
     (funcall parser (current-buffer) max-point)))
 
@@ -557,7 +598,30 @@ token is \";\", which marks the end of a statement in PHP."
   (regexp "\"\\|'")
   (list :string (phpinspect--munch-string start-token)))
 
-(phpinspect-defhandler block-without-classes (start-token max-point)
+(phpinspect-defhandler block-without-scopes (start-token max-point)
+  "Handler for code blocks that cannot contain scope, const or
+static keywords with the same meaning as in a class block."
+  (regexp "{")
+  (forward-char (length start-token))
+  (let* ((complete-block nil)
+         (parser (phpinspect-get-parser-create
+                  :block
+                  '(array tag equals list comma
+                          attribute-reference variable
+                          assignment-operator whitespace
+                          function-keyword word terminator here-doc
+                          string comment block-without-scopes)))
+         (continue-condition (lambda ()
+                               (not (and (char-equal (char-after) ?})
+                                         (setq complete-block t)))))
+         (parsed (funcall parser (current-buffer) max-point 
continue-condition)))
+    (if complete-block
+        (forward-char)
+      (setcar parsed :incomplete-block))
+    parsed))
+
+
+(phpinspect-defhandler class-block (start-token max-point)
   "Handler for code blocks that cannot contain classes"
   (regexp "{")
   (forward-char (length start-token))
@@ -636,9 +700,15 @@ nature like argument lists"
   (regexp "(")
   (forward-char (length start-token))
   (let* ((complete-list nil)
-         (php-list (phpinspect-parse-with-handler-list
+         (php-list (funcall
+                    (phpinspect-get-parser-create
+                     :list
+                     '(array tag equals list comma
+                             attribute-reference variable
+                             assignment-operator whitespace
+                             function-keyword word terminator here-doc
+                             string comment block-without-scopes))
                     (current-buffer)
-                    :list
                     max-point
                     (lambda () (not (and (char-equal (char-after) ?\)) (setq 
complete-list t)))))))
 
@@ -668,7 +738,7 @@ nature like argument lists"
         (list :function declaration)
       (list :function
             declaration
-            (funcall (phpinspect-handler 'block)
+            (funcall (phpinspect-handler 'block-without-scopes)
                      (char-to-string (char-after)) max-point)))))
 
 (phpinspect-defhandler scope-keyword (start-token max-point)
@@ -742,7 +812,7 @@ the properties of the class"
                         (current-buffer)
                         max-point
                         (lambda () (not (char-equal (char-after) ?{))))
-        (funcall (phpinspect-handler 'block-without-classes)
+        (funcall (phpinspect-handler 'class-block)
                  (char-to-string (char-after)) max-point)))
 
 (defun phpinspect-parse-with-handler-list
@@ -856,6 +926,59 @@ candidate. Candidates can be indexed functions and 
variables.")
                             "")))
    :kind 'function))
 
+(cl-defstruct (phpinspect--resolvecontext
+            (:constructor phpinspect--make-resolvecontext))
+  (subject nil
+           :type phpinspect--token
+           :documentation
+           "The statement we're trying to resolve the type of.")
+  (project-root nil
+                :type string
+                :documentation
+                "The root directory of the project we're resolving types for.")
+  (enclosing-tokens nil
+                    :type list
+                    :documentation
+                    "Tokens that enclose the subject."))
+
+(cl-defmethod phpinspect--resolvecontext-push-enclosing-token
+  ((resolvecontext phpinspect--resolvecontext) enclosing-token)
+  "Add ENCLOSING-TOKEN to RESOLVECONTEXTs enclosing token stack."
+  (push enclosing-token (phpinspect--resolvecontext-enclosing-tokens
+                         resolvecontext)))
+
+(defun phpinspect--get-resolvecontext (token &optional resolvecontext)
+  "Find the deepest nested incomplete token in TOKEN.
+If RESOLVECONTEXT is nil, it is created.  Returns RESOLVECONTEXT
+of type `phpinspect--resolvecontext' containing the last
+statement of the innermost incomplete token as subject
+accompanied by all of its enclosing tokens."
+  (unless resolvecontext
+    (setq resolvecontext (phpinspect--make-resolvecontext
+                          :project-root (phpinspect--get-project-root))))
+
+  (let ((last-token (car (last token)))
+        (last-encountered-token (car
+                                 (phpinspect--resolvecontext-enclosing-tokens
+                                  resolvecontext))))
+    (if (and (or (phpinspect-function-p last-encountered-token)
+                 (phpinspect-class-p last-encountered-token))
+             (phpinspect-block-p token))
+        ;; When a class or function has been inserted already, its block
+        ;; doesn't need to be added on top.
+        (phpinspect--resolvecontext-push-enclosing-token resolvecontext nil)
+      (phpinspect--resolvecontext-push-enclosing-token resolvecontext token))
+
+    (if (phpinspect-incomplete-token-p last-token)
+        (phpinspect--get-resolvecontext last-token resolvecontext)
+    ;; else
+    (setf (phpinspect--resolvecontext-subject resolvecontext)
+          (phpinspect--get-last-statement-in-token token))
+
+    ;; Delete all occurences of nil caused higher up in the function.
+    (cl-delete nil (phpinspect--resolvecontext-enclosing-tokens
+                    resolvecontext))
+    resolvecontext)))
 
 (defun phpinspect-toggle-logging ()
   (interactive)
@@ -867,97 +990,125 @@ candidate. Candidates can be indexed functions and 
variables.")
 (defsubst phpinspect--log (&rest args)
   (when phpinspect--debug
     (with-current-buffer (get-buffer-create "**phpinspect-logs**")
+      (unless window-point-insertion-type
+        (set (make-local-variable 'window-point-insertion-type) t))
       (goto-char (buffer-end 1))
-      (insert (concat (apply #'format args) "\n")))))
+      (insert (concat "[" (format-time-string "%H:%M:%S.%N") "]: "
+                           (apply #'format args) "\n")))))
 
 (defsubst phpinspect-cache-project-class (project-root indexed-class)
-  (phpinspect--project-add-class
-   (phpinspect--cache-get-project-create 
(phpinspect--get-or-create-global-cache)
-                                         project-root)
-   indexed-class))
+  (when project-root
+    (phpinspect--project-add-class
+     (phpinspect--cache-get-project-create 
(phpinspect--get-or-create-global-cache)
+                                           project-root)
+     indexed-class)))
 
 (defsubst phpinspect-get-cached-project-class (project-root class-fqn)
-  (phpinspect--project-get-class
-   (phpinspect--cache-get-project-create 
(phpinspect--get-or-create-global-cache)
-                                         project-root)
-   class-fqn))
+  (when project-root
+    (phpinspect--project-get-class
+     (phpinspect--cache-get-project-create 
(phpinspect--get-or-create-global-cache)
+                                           project-root)
+     class-fqn)))
+
+(defun phpinspect-get-project-class-inherit-classes (project-root class)
+  (phpinspect--log "Getting inherit classes for %s" class)
+  (let ((classnames `(,@(alist-get 'extends class)
+                      ,@(alist-get 'implements class)))
+        (classes))
+
+    (phpinspect--log "Found inherit classes: %s" classnames)
+    (while classnames
+      (let ((inherit-class (phpinspect-get-or-create-cached-project-class
+                            project-root
+                            (pop classnames))))
+        (push inherit-class classes)
+        (dolist (nested-class (phpinspect-get-project-class-inherit-classes
+                               project-root
+                               inherit-class))
+          (push nested-class classes))))
+
+    (seq-uniq classes #'eq)))
+
 
 (defun phpinspect-get-cached-project-class-methods
     (project-root class-fqn &optional static)
   (phpinspect--log "Getting cached project class methods for %s (%s)"
                    project-root class-fqn)
-  (let ((index (phpinspect-get-or-create-cached-project-class
-                project-root
-                class-fqn)))
-    (when index
-      (phpinspect--log "Retrieved class index, starting method collection %s 
(%s)"
-                       project-root class-fqn)
-      ;; Use nreverse to give precedence to interface and abstract class return
-      ;; types. Those are usually more well documented.
-      (nreverse
-       (append (alist-get (if static 'static-methods 'methods)
-                          index)
-               (apply #'append
-                      (mapcar (lambda (class-fqn)
-                                (phpinspect-get-cached-project-class-methods
-                                 project-root class-fqn static))
-                              (append
-                               (alist-get 'extends index)
-                               (alist-get 'implements index)))))))))
+  (when project-root
+    (let ((index (phpinspect-get-or-create-cached-project-class
+                  project-root
+                  class-fqn)))
+      (when index
+        (phpinspect--log "Retrieved class index, starting method collection %s 
(%s)"
+                         project-root class-fqn)
+
+        (let ((methods-key (if static 'static-methods 'methods))
+              (methods))
+
+          (dolist (method (alist-get methods-key index))
+            (push method methods))
+
+          (dolist (class (phpinspect-get-project-class-inherit-classes
+                          project-root
+                          index))
+            (dolist (method (alist-get methods-key class))
+              (push method methods)))
+
+          methods)))))
 
 (defsubst phpinspect-get-cached-project-class-method-type
   (project-root class-fqn method-name)
-  (phpinspect--log "Getting cached project class method type for %s (%s::%s)"
-                   project-root class-fqn method-name)
-  (let ((found-method
-         (seq-find (lambda (method)
-                     (and (string= (phpinspect--function-name method) 
method-name)
-                          (phpinspect--function-return-type method)))
-                   (phpinspect-get-cached-project-class-methods
-                    project-root
-                    class-fqn))))
-    (when found-method
-      (phpinspect--log "Found method: %s" found-method)
-      (phpinspect--function-return-type found-method))))
+  (when project-root
+    (phpinspect--log "Getting cached project class method type for %s (%s::%s)"
+                     project-root class-fqn method-name)
+    (let ((found-method
+           (seq-find (lambda (method)
+                       (and (string= (phpinspect--function-name method) 
method-name)
+                            (phpinspect--function-return-type method)))
+                     (phpinspect-get-cached-project-class-methods
+                      project-root
+                      class-fqn))))
+      (when found-method
+        (phpinspect--log "Found method: %s" found-method)
+        (phpinspect--function-return-type found-method)))))
 
 (defsubst phpinspect-get-cached-project-class-variable-type
   (project-root class-fqn variable-name)
-  (let ((found-variable
-         (seq-find (lambda (variable)
-                     (string= (phpinspect--variable-name variable) 
variable-name))
-                   (alist-get 'variables
-                              (phpinspect-get-or-create-cached-project-class
-                               project-root
-                               class-fqn)))))
-    (when found-variable
-      (phpinspect--variable-type found-variable))))
-
+  (when project-root
+    (let ((found-variable
+           (seq-find (lambda (variable)
+                       (string= (phpinspect--variable-name variable) 
variable-name))
+                     (alist-get 'variables
+                                (phpinspect-get-or-create-cached-project-class
+                                 project-root
+                                 class-fqn)))))
+      (when found-variable
+        (phpinspect--variable-type found-variable)))))
 
 (defsubst phpinspect-get-cached-project-class-static-method-type
   (project-root class-fqn method-name)
-  (let* ((found-method
-          (seq-find (lambda (method)
-                      (and (string= (phpinspect--function-name method) 
method-name)
-                           (phpinspect--function-return-type method)))
-                    (phpinspect-get-cached-project-class-methods
-                     project-root
-                     class-fqn
-                     'static))))
-    (when found-method
-      (phpinspect--function-return-type found-method))))
+  (when project-root
+    (let* ((found-method
+            (seq-find (lambda (method)
+                        (and (string= (phpinspect--function-name method) 
method-name)
+                             (phpinspect--function-return-type method)))
+                      (phpinspect-get-cached-project-class-methods
+                       project-root
+                       class-fqn
+                       'static))))
+      (when found-method
+        (phpinspect--function-return-type found-method)))))
 
 (defun phpinspect-parse-file (file)
   (with-temp-buffer
     (insert-file-contents-literally file)
     (phpinspect-parse-current-buffer)))
 
-
 (defun phpinspect-parse-current-buffer ()
   (phpinspect-parse-buffer-until-point
    (current-buffer)
    (point-max)))
 
-
 (defun phpinspect--split-list (predicate list)
   (seq-reduce (let ((current-sublist))
                 (lambda (result elt)
@@ -999,42 +1150,49 @@ TODO:
 "
   (phpinspect--log "Starting eldoc function execution")
   (let* ((token-tree (phpinspect-parse-buffer-until-point (current-buffer) 
(point)))
-         (namespace (phpinspect--find-innermost-incomplete-namespace 
token-tree))
-         (incomplete-token (phpinspect--find-innermost-incomplete-token 
token-tree))
-         (enclosing-token 
(phpinspect--find-token-enclosing-innermost-incomplete-token token-tree))
-         (type-resolver)
+         (resolvecontext (phpinspect--get-resolvecontext token-tree))
+         (incomplete-token (car (phpinspect--resolvecontext-enclosing-tokens
+                                 resolvecontext)))
+         (enclosing-token (cadr (phpinspect--resolvecontext-enclosing-tokens
+                                 resolvecontext)))
+         (statement (phpinspect--get-last-statement-in-token
+                     enclosing-token))
+         (type-resolver (phpinspect--make-type-resolver-for-resolvecontext
+                               resolvecontext
+                               token-tree))
          (static))
+    (phpinspect--log "Enclosing token: %s" enclosing-token)
+    (phpinspect--log "reference token: %s" (car (last statement 2)))
+
     (when (and (phpinspect-incomplete-list-p incomplete-token)
                enclosing-token
-               (or (phpinspect-object-attrib-p (car (last enclosing-token 2)))
-                   (setq static (phpinspect-static-attrib-p (car (last 
enclosing-token 2))))))
-      (if namespace
-          (setq type-resolver (phpinspect--make-type-resolver-for-namespace
-                               namespace
-                               token-tree))
-        ;; else
-        (setq type-resolver (phpinspect--make-type-resolver
-                             (phpinspect--uses-to-types
-                              (seq-filter #'phpinspect-use-p token-tree)))))
-      (let* ((previous-statement (phpinspect--get-last-statement-in-token 
(butlast enclosing-token 2)))
-             (type-of-previous-statement
-              (phpinspect-get-type-of-derived-statement-in-token
-               previous-statement
-               (or namespace token-tree)
-               type-resolver))
-             (method-name (cadr (cadar (last enclosing-token 2))))
-             (class-index (and type-of-previous-statement
-                               (phpinspect--get-or-create-index-for-class-file
-                                type-of-previous-statement)))
-             (method (and class-index
+               (or (phpinspect-object-attrib-p (car (last statement 2)))
+                   (setq static (phpinspect-static-attrib-p (car (last 
statement 2))))))
+
+      ;; Set resolvecontext subject to the last statement in the enclosing 
token, minus
+      ;; the method name. The last enclosing token is an incomplete list, so 
point is
+      ;; likely to be at a location inside a method call like 
"$a->b->doSomething(". The
+      ;; resulting subject would be "$a->b".
+      (setf (phpinspect--resolvecontext-subject resolvecontext)
+            (phpinspect--get-last-statement-in-token (butlast statement 2)))
+
+      (let* ((type-of-previous-statement
+              (phpinspect-resolve-type-from-context resolvecontext 
type-resolver))
+             (method-name (cadr (cadar (last statement 2))))
+             (class-methods (phpinspect-get-cached-project-class-methods
+                            (phpinspect--resolvecontext-project-root 
resolvecontext)
+                            type-of-previous-statement
+                            static))
+             (method (and class-methods
                           (seq-find
                            (lambda (func)
                              (when func
                                (string= method-name
                                         (phpinspect--function-name func))))
-                           (alist-get (if static 'static-methods 'methods)
-                                      class-index)))))
+                           class-methods))))
         (phpinspect--log "Eldoc method name: %s" method-name)
+        (phpinspect--log "Eldoc type of previous statement: %s"
+                         type-of-previous-statement)
         (phpinspect--log "Eldoc method: %s" method)
         (when method
           (let ((arg-count -1)
@@ -1082,7 +1240,6 @@ TODO:
                     (phpinspect--find-assignments-in-token code-block)
                     assignments)))))
     ;; return
-    (phpinspect--log "assignments: %s" assignments)
     assignments))
 
 (defun phpinspect-not-assignment-p (token)
@@ -1102,12 +1259,11 @@ TODO:
           (push assignment variable-assignments)))
     (nreverse variable-assignments)))
 
-
-
 (defun phpinspect-get-derived-statement-type-in-block
-    (statement php-block type-resolver &optional function-arg-list)
-  "Get type of derived STATEMENT in PHP-BLOCK using
-TYPE-RESOLVER and FUNCTION-ARG-LIST.
+    (resolvecontext statement php-block type-resolver &optional 
function-arg-list)
+  "Get type of RESOLVECONTEXT subject in PHP-BLOCK.
+
+Use TYPE-RESOLVER and FUNCTION-ARG-LIST in the process.
 
 An example of a derived statement would be the following php code:
 $variable->attribute->method();
@@ -1117,96 +1273,100 @@ self::method();
 ClassName::method();
 $variable = ClassName::method();
 $variable = $variable->method();"
-  ;; A derived statement can be an assignment itself.
-  (when (seq-find #'phpinspect-assignment-p statement)
-    (phpinspect--log "Derived statement is an assignment: %s" statement)
-    (setq statement (cdr (seq-drop-while #'phpinspect-not-assignment-p 
statement))))
-  (phpinspect--log "Get derived statement type in block: %s" statement)
-  (let* ((first-token (pop statement))
-         (current-token)
-         (previous-attribute-type))
-    ;; No first token means we were passed an empty list.
-    (when (and first-token
-               (setq previous-attribute-type
-                     ;; Statements that are only bare words can be something 
preceding
-                     ;; a static attribute that is not passed to this 
function. For
-                     ;; example "return self" could have prefixed another 
attribute
-                     ;; that the caller is trying to derive. Therefore we just 
try to
-                     ;; resolve the type of the last bare word in the 
statement.
-                     (or (when (and (phpinspect-word-p first-token)
-                                    (seq-every-p #'phpinspect-word-p 
statement))
-                           (setq statement (last statement))
-                           (funcall type-resolver (cadr (pop statement))))
-
-                         ;; Statements starting with a bare word can indicate 
a static
-                         ;; method call. These could be statements with 
"return" or
-                         ;; another bare-word at the start though, so we dop 
tokens
-                         ;; from the statement until it starts with a static 
attribute
-                         ;; refererence (::something in PHP code).
-                         (when (phpinspect-word-p first-token)
-                           (while (and first-token
-                                       (not (phpinspect-static-attrib-p
-                                             (car statement))))
-                             (setq first-token (pop statement)))
-                           (funcall type-resolver (cadr first-token)))
-
-                         ;; No bare word, assume we're dealing with a variable.
-                         (phpinspect-get-variable-type-in-block
-                          (cadr first-token)
-                          php-block
-                          type-resolver
-                          function-arg-list))))
-
-      (phpinspect--log "Statement: %s" statement)
-      (phpinspect--log "Starting attribute type: %s" previous-attribute-type)
-      (while (setq current-token (pop statement))
-        (phpinspect--log "Current derived statement token: %s" current-token)
-        (cond ((phpinspect-object-attrib-p current-token)
-               (let ((attribute-word (cadr current-token)))
-                 (when (phpinspect-word-p attribute-word)
-                   (if (phpinspect-list-p (car statement))
-                       (progn
-                         (pop statement)
-                         (setq previous-attribute-type
-                               (or
-                                
(phpinspect-get-cached-project-class-method-type
-                                 (phpinspect--get-project-root)
-                                 (funcall type-resolver 
previous-attribute-type)
-                                 (cadr attribute-word))
-                                previous-attribute-type)))
-                     (setq previous-attribute-type
-                           (or
-                            (phpinspect-get-cached-project-class-variable-type
-                             (phpinspect--get-project-root)
-                             (funcall type-resolver previous-attribute-type)
-                             (cadr attribute-word))
-                            previous-attribute-type))))))
-              ((phpinspect-static-attrib-p current-token)
-               (let ((attribute-word (cadr current-token)))
-                 (phpinspect--log "Found attribute word: %s" attribute-word)
-                 (phpinspect--log "checking if next token is a list. Token: %s"
-                                  (car statement))
-                 (when (phpinspect-word-p attribute-word)
-                   (if (phpinspect-list-p (car statement))
-                       (progn
-                         (pop statement)
-                         (setq previous-attribute-type
-                               (or
-                                
(phpinspect-get-cached-project-class-static-method-type
-                                 (phpinspect--get-project-root)
-                                 (funcall type-resolver 
previous-attribute-type)
-                                 (cadr attribute-word))
-                                previous-attribute-type)))))))))
-      (phpinspect--log "Found derived type: %s" previous-attribute-type)
-      ;; Make sure to always return a FQN
-      (funcall type-resolver previous-attribute-type))))
+    ;; A derived statement can be an assignment itself.
+    (when (seq-find #'phpinspect-assignment-p statement)
+      (phpinspect--log "Derived statement is an assignment: %s" statement)
+      (setq statement (cdr (seq-drop-while #'phpinspect-not-assignment-p 
statement))))
+    (phpinspect--log "Get derived statement type in block: %s" statement)
+    (let* ((first-token (pop statement))
+           (current-token)
+           (previous-attribute-type))
+      ;; No first token means we were passed an empty list.
+      (when (and first-token
+                 (setq previous-attribute-type
+                       ;; Statements that are only bare words can be something 
preceding
+                       ;; a static attribute that is not passed to this 
function. For
+                       ;; example "return self" could have prefixed another 
attribute
+                       ;; that the caller is trying to derive. Therefore we 
just try to
+                       ;; resolve the type of the last bare word in the 
statement.
+                       (or (when (and (phpinspect-word-p first-token)
+                                      (seq-every-p #'phpinspect-word-p 
statement))
+                             (setq statement (last statement))
+                             (funcall type-resolver (cadr (pop statement))))
+
+                           ;; Statements starting with a bare word can 
indicate a static
+                           ;; method call. These could be statements with 
"return" or
+                           ;; another bare-word at the start though, so we dop 
tokens
+                           ;; from the statement until it starts with a static 
attribute
+                           ;; refererence (::something in PHP code).
+                           (when (phpinspect-word-p first-token)
+                             (while (and first-token
+                                         (not (phpinspect-static-attrib-p
+                                               (car statement))))
+                               (setq first-token (pop statement)))
+                             (funcall type-resolver (cadr first-token)))
+
+                           ;; No bare word, assume we're dealing with a 
variable.
+                           (phpinspect-get-variable-type-in-block
+                            resolvecontext
+                            (cadr first-token)
+                            php-block
+                            type-resolver
+                            function-arg-list))))
+
+        (phpinspect--log "Statement: %s" statement)
+        (phpinspect--log "Starting attribute type: %s" previous-attribute-type)
+        (while (setq current-token (pop statement))
+          (phpinspect--log "Current derived statement token: %s" current-token)
+          (cond ((phpinspect-object-attrib-p current-token)
+                 (let ((attribute-word (cadr current-token)))
+                   (when (phpinspect-word-p attribute-word)
+                     (if (phpinspect-list-p (car statement))
+                         (progn
+                           (pop statement)
+                           (setq previous-attribute-type
+                                 (or
+                                  
(phpinspect-get-cached-project-class-method-type
+                                   (phpinspect--resolvecontext-project-root
+                                    resolvecontext)
+                                   (funcall type-resolver 
previous-attribute-type)
+                                   (cadr attribute-word))
+                                  previous-attribute-type)))
+                       (setq previous-attribute-type
+                             (or
+                              
(phpinspect-get-cached-project-class-variable-type
+                               (phpinspect--resolvecontext-project-root
+                                resolvecontext)
+                               (funcall type-resolver previous-attribute-type)
+                               (cadr attribute-word))
+                              previous-attribute-type))))))
+                ((phpinspect-static-attrib-p current-token)
+                 (let ((attribute-word (cadr current-token)))
+                   (phpinspect--log "Found attribute word: %s" attribute-word)
+                   (phpinspect--log "checking if next token is a list. Token: 
%s"
+                                    (car statement))
+                   (when (phpinspect-word-p attribute-word)
+                     (if (phpinspect-list-p (car statement))
+                         (progn
+                           (pop statement)
+                           (setq previous-attribute-type
+                                 (or
+                                  
(phpinspect-get-cached-project-class-static-method-type
+                                   (phpinspect--resolvecontext-project-root
+                                    resolvecontext)
+                                   (funcall type-resolver 
previous-attribute-type)
+                                   (cadr attribute-word))
+                                  previous-attribute-type)))))))))
+        (phpinspect--log "Found derived type: %s" previous-attribute-type)
+        ;; Make sure to always return a FQN
+        (funcall type-resolver previous-attribute-type))))
 
 ;;;;
 ;; TODO: since we're passing type-resolver to all of the get-variable-type 
functions now,
 ;; we may as well always return FQNs in stead of relative type names.
 ;;;;
 (defun phpinspect-get-variable-type-in-block
-    (variable-name php-block type-resolver &optional function-arg-list)
+    (resolvecontext variable-name php-block type-resolver &optional 
function-arg-list)
   "Find the type of VARIABLE-NAME in PHP-BLOCK using TYPE-RESOLVER.
 
 Returns either a FQN or a relative type name, depending on
@@ -1224,7 +1384,6 @@ resolve types of function argument variables."
            (last-assignment (when assignments (car (last assignments))))
            (right-of-assignment (when assignments (cdr (seq-drop-while 
#'phpinspect-not-assignment-p
                                                                        
last-assignment)))))
-      (phpinspect--log "Assignments: %s" assignments)
       (phpinspect--log "Last assignment: %s" right-of-assignment)
       ;; When the right of an assignment is more than $variable; or 
"string";(so
       ;; (:variable "variable") (:terminator ";") or (:string "string") 
(:terminator ";")
@@ -1236,7 +1395,8 @@ resolve types of function argument variables."
             ((and (> (length right-of-assignment) 2)
                   (seq-find #'phpinspect-attrib-p right-of-assignment))
              (phpinspect--log "Variable was assigned with a derived statement")
-             (phpinspect-get-derived-statement-type-in-block 
right-of-assignment
+             (phpinspect-get-derived-statement-type-in-block resolvecontext
+                                                             
right-of-assignment
                                                              php-block
                                                              type-resolver
                                                              
function-arg-list))
@@ -1247,7 +1407,8 @@ resolve types of function argument variables."
              (or (when function-arg-list
                    (phpinspect-get-variable-type-in-function-arg-list (cadar 
right-of-assignment)
                                                                       
function-arg-list))
-                 (phpinspect-get-variable-type-in-block (cadar 
right-of-assignment)
+                 (phpinspect-get-variable-type-in-block resolvecontext
+                                                        (cadar 
right-of-assignment)
                                                         php-block
                                                         type-resolver
                                                         function-arg-list)))
@@ -1256,67 +1417,47 @@ resolve types of function argument variables."
              (phpinspect-get-variable-type-in-function-arg-list variable-name 
function-arg-list))))))
 
 
-(defun phpinspect-get-derived-statement-type-in-function (statement php-func 
type-resolver)
-  "Attempt to find the type of the php statement in php function
-token `php-func`.  If no type can be found, this function
-evaluates to nil."
-  (let* ((arg-list (phpinspect-function-argument-list php-func)))
-    (phpinspect-get-derived-statement-type-in-block statement
-                                                    (car (last php-func))
-                                                    type-resolver
-                                                    arg-list)))
-
-(defun phpinspect-get-derived-statement-type-in-method (statement php-method 
type-resolver)
-  "Find the type of a statement in a php method."
-  (cond ((phpinspect-function-p php-method)
-         (phpinspect-get-derived-statement-type-in-function statement
-                                                            php-method
-                                                            type-resolver))
-        ((phpinspect-scope-p php-method)
-         (phpinspect-get-derived-statement-type-in-method statement
-                                                          (car (last 
php-method))
-                                                          type-resolver))
-        ((phpinspect-static-p php-method)
-         (phpinspect-get-derived-statement-type-in-method statement
-                                                          (car (last 
php-method))
-                                                          type-resolver))
-        (t (error (concat "phpinspect-get-derived-statement-type-in-method: "
-                          "php-method must be either a function or a scoped 
function")))))
-
-(defun phpinspect-get-derived-statement-type-in-class (statement php-class 
type-resolver)
-  (phpinspect--log "recursing into class")
-  (let ((last-token-in-class-block (car (last (car (last php-class))))))
-    (phpinspect--log "last token in class block: %s" last-token-in-class-block)
-    (cond ((phpinspect-incomplete-method-p last-token-in-class-block)
-           (phpinspect-get-derived-statement-type-in-method statement
-                                                            (car (last (car 
(last php-class))))
-                                                            type-resolver))
-          ;; We're dealing with a const statement, so not a block, but that 
doesn't matter
-          ;; much for the outcome. We're not trying to check syntax after all, 
just trying
-          ;; to guess the type of the statement as well as we can.
-          ((phpinspect-incomplete-const-p last-token-in-class-block)
-           (phpinspect--log "Found incomplete constant")
-           (phpinspect-get-derived-statement-type-in-block
-            statement
-            last-token-in-class-block
-            type-resolver)))))
-
-(defun phpinspect-get-type-of-derived-statement-in-token (statement token 
type-resolver)
-  (phpinspect--log "Looking for type of statement: %s in token: %s" statement 
token)
-  (cond ((phpinspect-namespace-p token)
-         (if (phpinspect-incomplete-block-p (car (last token)))
-             (phpinspect-get-type-of-derived-statement-in-token statement
-                                                                (car (last 
(car (last token))))
-                                                                type-resolver)
-           (phpinspect-get-type-of-derived-statement-in-token statement
-                                                              (car (last 
token))
-                                                              type-resolver)))
-        ((phpinspect-incomplete-block-p token)
-         (phpinspect-get-derived-statement-type-in-block statement token 
type-resolver))
-        ((phpinspect-class-p token)
-         (phpinspect-get-derived-statement-type-in-class statement token 
type-resolver))
-        ((phpinspect-incomplete-function-p token)
-         (phpinspect-get-derived-statement-type-in-function statement token 
type-resolver))))
+(defun phpinspect-resolve-type-from-context (resolvecontext type-resolver)
+  (phpinspect--log "Looking for type of statement: %s in nested token"
+                   (phpinspect--resolvecontext-subject resolvecontext))
+  ;; Find all enclosing tokens that aren't classes. Classes do not contain 
variable
+  ;; assignments which have effect in the current scope, which is what we're 
trying
+  ;; to find here to infer the statement type.
+  (let ((enclosing-tokens (seq-filter #'phpinspect-not-class-p
+                                       
(phpinspect--resolvecontext-enclosing-tokens
+                                        resolvecontext)))
+        (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))
+
+      (setq type
+            (cond ((phpinspect-namespace-p enclosing-token)
+                   (phpinspect-get-derived-statement-type-in-block
+                    resolvecontext
+                    (phpinspect--resolvecontext-subject
+                     resolvecontext)
+                    (or (phpinspect-namespace-block enclosing-token)
+                        enclosing-token)
+                    type-resolver))
+                  ((or (phpinspect-block-p enclosing-token)
+                       (phpinspect-root-p enclosing-token))
+                   (phpinspect-get-derived-statement-type-in-block
+                    resolvecontext
+                    (phpinspect--resolvecontext-subject
+                     resolvecontext)
+                    enclosing-token
+                    type-resolver))
+                  ((phpinspect-function-p enclosing-token)
+                   (phpinspect-get-derived-statement-type-in-block
+                    resolvecontext
+                    (phpinspect--resolvecontext-subject
+                     resolvecontext)
+                    (phpinspect-function-block enclosing-token)
+                    type-resolver
+                    (phpinspect-function-argument-list enclosing-token))))))
+    type))
 
 (defun phpinspect--function-from-scope (scope)
   (cond ((and (phpinspect-static-p (cadr scope))
@@ -1531,7 +1672,8 @@ said FQN's by class name"
         (phpinspect--log "Constructor was found")
         (dolist (variable variables)
           (when (not (phpinspect--variable-type variable))
-            (phpinspect--log "Looking for variable type")
+            (phpinspect--log "Looking for variable type in constructor 
arguments (%s)"
+                             variable)
             (let ((constructor-parameter-type
                    (car (alist-get (phpinspect--variable-name variable)
                                    (phpinspect--function-arguments constructor)
@@ -1608,7 +1750,7 @@ namespace if not provided"
   )
 
 (defun phpinspect--get-or-create-index-for-class-file (class-fqn)
-  (phpinspect--log "Getting or creating")
+  (phpinspect--log "Getting or creating index for %s" class-fqn)
   (phpinspect-get-or-create-cached-project-class
    (phpinspect--get-project-root)
    class-fqn))
@@ -1617,33 +1759,33 @@ namespace if not provided"
   (phpinspect--index-tokens (phpinspect-parse-file file-name)))
 
 (defun phpinspect-get-or-create-cached-project-class (project-root class-fqn)
-  (let ((existing-index (phpinspect-get-cached-project-class
-                         project-root
-                         class-fqn)))
-    (or
-     existing-index
-     (progn
-       (let* ((class-file (phpinspect-get-class-filepath class-fqn))
-              (visited-buffer (when class-file (find-buffer-visiting 
class-file)))
-              (new-index))
-
-         (phpinspect--log "FQN: %s" class-fqn)
-         (phpinspect--log "filepath: %s" class-file)
-         (when class-file
-           (if visited-buffer
-               (setq new-index (with-current-buffer visited-buffer
-                                 (phpinspect--index-current-buffer)))
-             (setq new-index (phpinspect-index-file class-file)))
-           (phpinspect--log "New index: %s" new-index)
-           (dolist (class (alist-get 'classes new-index))
-             (when class
-               (phpinspect-cache-project-class
-                (phpinspect--get-project-root)
-                (cdr class))))
-           (alist-get class-fqn (alist-get 'classes new-index)
-                      nil
-                      nil
-                      #'string=)))))))
+  (when project-root
+    (let ((existing-index (phpinspect-get-cached-project-class
+                           project-root
+                           class-fqn)))
+      (or
+       existing-index
+       (progn
+         (let* ((class-file (phpinspect-get-class-filepath class-fqn))
+                (visited-buffer (when class-file (find-buffer-visiting 
class-file)))
+                (new-index))
+
+           (phpinspect--log "FQN: %s" class-fqn)
+           (phpinspect--log "filepath: %s" class-file)
+           (when class-file
+             (if visited-buffer
+                 (setq new-index (with-current-buffer visited-buffer
+                                   (phpinspect--index-current-buffer)))
+               (setq new-index (phpinspect-index-file class-file)))
+             (dolist (class (alist-get 'classes new-index))
+               (when class
+                 (phpinspect-cache-project-class
+                  project-root
+                  (cdr class))))
+             (alist-get class-fqn (alist-get 'classes new-index)
+                        nil
+                        nil
+                        #'string=))))))))
 
 
 (defun phpinspect--index-current-buffer ()
@@ -1663,31 +1805,26 @@ namespace if not provided"
         (alist-get 'variables class-index)))))
 
 
-(defun phpinspect--get-methods-for-class (buffer-classes class &optional 
static)
+(defun phpinspect--get-methods-for-class
+    (resolvecontext buffer-classes class &optional static)
   "Extract all possible methods for a class from `buffer-classes` and the 
class index.
 `buffer-classes` will be preferred because their data should be
 more recent"
-  (let ((class-index (or (alist-get class buffer-classes nil nil #'string=)
-                         (phpinspect--get-or-create-index-for-class-file 
class))))
-    (phpinspect--log "Getting methods for class (%s)" class)
-    (phpinspect--log "index: %s" class-index)
-    (if class-index
-        ;; Use nreverse to give precedence to interfaces and abstract class 
method
-        ;; typehints and doc blocks.
-        ;; TODO: Merge this somehow with 
phpinspect-get-cached-project-class-methods
-        (nreverse
-         (append (alist-get (if static 'static-methods 'methods) class-index)
-                 (apply #'append
-                        (mapcar (lambda (inherit-class)
-                                  (phpinspect--log "Inherit class: %s" 
inherit-class)
-                                  (phpinspect--get-methods-for-class
-                                   buffer-classes
-                                   inherit-class
-                                   static))
-                                (append (alist-get 'extends class-index)
-                                        (alist-get 'implements 
class-index))))))
-      ;; else
-      (phpinspect--log "Unable to complete for %s :(" class) nil)))
+  (let ((methods (phpinspect-get-cached-project-class-methods
+                  (phpinspect--resolvecontext-project-root
+                   resolvecontext)
+                  class
+                  static))
+        (buffer-index (alist-get class buffer-classes nil nil #'string=)))
+      (phpinspect--log "Getting methods for class (%s)" class)
+      (when buffer-index
+          (dolist (method (alist-get (if static 'static-methods 'methods)
+                                     buffer-index))
+            (push method methods)))
+      (unless methods
+        (phpinspect--log "Failed to find metods for class %s :(" class))
+      methods))
+
 
 (defun phpinspect--init-mode ()
   "Initialize the phpinspect minor mode for the current buffer."
@@ -1734,40 +1871,31 @@ users will have to use \\[phpinspect-purge-cache]."
       (phpinspect--init-mode)
     (phpinspect--disable-mode)))
 
-(define-minor-mode phpinspect-mode "Activate phpinspect-mode"
-  :after-hook (phpinspect--mode-function))
+(define-minor-mode phpinspect-mode
+  "A minor mode for intelligent completion for and interaction
+with PHP files.
 
-(defun phpinspect--find-innermost-incomplete-namespace (token)
-  (let ((last-token (car (last token))))
-    (cond ((phpinspect-incomplete-namespace-p token) token)
-          ((phpinspect-incomplete-token-p last-token)
-           (phpinspect--find-innermost-incomplete-namespace last-token)))))
+For completion see the company-mode backend:
+`phpinspect-company-backend'.
 
-(defun phpinspect--find-innermost-incomplete-block (token &optional last-block)
-  (when (phpinspect-incomplete-block-p token)
-    (setq last-block token))
+For eldoc see `phpinspect-eldoc-function'.
 
-  (let ((last-token (car (last token))))
-    (if (phpinspect-incomplete-token-p last-token)
-        (phpinspect--find-innermost-incomplete-block last-token last-block)
-      last-block)))
+For finding/opening class files see
+ `phpinspect-find-own-class-file' (bound to 
\\[phpinspect-find-own-class-file]) and
+ `phpinspect-find-class-file' (bound to \\[phpinspect-find-class-file]).
 
+To automatically add missing use statements for used classes to a
+visited file, use `phpinspect-fix-uses-interactive'
+(bound to \\[phpinspect-fix-uses-interactive]].)"
+  :after-hook (phpinspect--mode-function))
 
 (defun phpinspect--find-class-token (token)
   "Recurse into token tree until a class is found."
-  (let ((last-token (car (last token))))
-    (cond ((phpinspect-class-p token) token)
-          (last-token
-           (phpinspect--find-class-token last-token)))))
-
-(defun phpinspect--find-token-enclosing-innermost-incomplete-token (token 
&optional enclosing-token)
-  "Like `phpinspect--find-innermost-incomplete-token` but returns
-  the enclosing incomplete token if there is one"
-  (let ((last-token (car (last token))))
-    (if (phpinspect-incomplete-token-p last-token)
-        (phpinspect--find-token-enclosing-innermost-incomplete-token 
last-token token)
-      enclosing-token)))
-
+  (when (and (listp token) (> 1 (length token)))
+    (let ((last-token (car (last token))))
+      (cond ((phpinspect-class-p token) token)
+            (last-token
+             (phpinspect--find-class-token last-token))))))
 
 (defun phpinspect--find-innermost-incomplete-class (token)
   (let ((last-token (car (last token))))
@@ -1775,20 +1903,6 @@ users will have to use \\[phpinspect-purge-cache]."
           ((phpinspect-incomplete-token-p last-token)
            (phpinspect--find-innermost-incomplete-class last-token)))))
 
-(defun phpinspect--find-innermost-incomplete-function (token)
-  (let ((last-token (car (last token))))
-    (cond ((phpinspect-incomplete-function-p token) token)
-          ((phpinspect-incomplete-token-p last-token)
-           (phpinspect--find-innermost-incomplete-function last-token)))))
-
-(defun phpinspect--find-innermost-incomplete-token (token)
-  (phpinspect--log "Checking token %s" token)
-  (let ((last-token (car (last token))))
-    (if (phpinspect-incomplete-token-p last-token)
-        (phpinspect--find-innermost-incomplete-token last-token)
-      token)))
-
-
 (defun phpinspect--find-last-variable-position-in-token (token)
   "Find the last variable that can be encountered in the top
 level of a token. Nested variables are ignored."
@@ -1800,9 +1914,9 @@ level of a token. Nested variables are ignored."
 
     (if (not (= i 0))(- (length token)  i))))
 
-(defun phpinspect--make-method-lister (buffer-classes &optional static)
+(defun phpinspect--make-method-lister (resolvecontext buffer-classes &optional 
static)
   (lambda (fqn)
-    (phpinspect--get-methods-for-class buffer-classes fqn static)))
+    (phpinspect--get-methods-for-class resolvecontext buffer-classes fqn 
static)))
 
 (defun phpinspect--buffer-index (buffer)
   (with-current-buffer buffer phpinspect--buffer-index))
@@ -1888,18 +2002,31 @@ level of a token. Nested variables are ignored."
              completion
              (phpinspect--completion-list-metadata comp-list))))
 
+(defun phpinspect--suggest-attributes-at-point
+    (token-tree resolvecontext &optional static)
+  "Suggest object or class attributes at point.
+
+TOKEN-TREE must be a syntax tree containing enough context to
+infer the types of the preceding statements
+
+RESOLVECONTEXT must be a structure of the type
+`phpinspect--resolvecontext'.  The PHP type of its subject is
+resolved to provide completion candidates.
 
-(defun phpinspect--suggest-attributes-at-point (token-tree incomplete-token 
&optional static)
+If STATIC is non-nil, candidates are provided for constants,
+static variables and static methods."
   (let* ((buffer-classes (phpinspect--merge-indexes
                           phpinspect--buffer-index
                           (phpinspect--index-tokens token-tree)))
-         (namespace (phpinspect--find-innermost-incomplete-namespace
-                     token-tree))
-         (type-resolver (phpinspect--make-type-resolver-for-namespace 
namespace token-tree))
-         (method-lister (phpinspect--make-method-lister buffer-classes 
static)))
-    (let ((statement-type (phpinspect-get-type-of-derived-statement-in-token
-                           (phpinspect--get-last-statement-in-token 
incomplete-token)
-                           namespace
+         (type-resolver (phpinspect--make-type-resolver-for-resolvecontext
+                         resolvecontext
+                         token-tree))
+         (method-lister (phpinspect--make-method-lister
+                         resolvecontext
+                         buffer-classes
+                         static)))
+    (let ((statement-type (phpinspect-resolve-type-from-context
+                           resolvecontext
                            type-resolver)))
       (when statement-type
         (let ((type (funcall type-resolver statement-type)))
@@ -1909,14 +2036,25 @@ level of a token. Nested variables are ignored."
                    static)
                   (funcall method-lister type)))))))
 
-(defun phpinspect--make-type-resolver-for-namespace (namespace-token &optional 
token-tree)
-  (phpinspect--make-type-resolver
-   (phpinspect--uses-to-types
-    (seq-filter #'phpinspect-use-p namespace-token))
-   token-tree
-   (cadadr namespace-token)))
+(defun phpinspect--make-type-resolver-for-resolvecontext
+    (resolvecontext &optional token-tree)
+  (let ((namespace-or-root
+         (seq-find #'phpinspect-namespace-or-root-p
+                   (phpinspect--resolvecontext-enclosing-tokens
+                    resolvecontext))))
+      (phpinspect--make-type-resolver
+       (phpinspect--uses-to-types
+        (seq-filter #'phpinspect-use-p namespace-or-root))
+       token-tree
+       (when (phpinspect-namespace-p namespace-or-root)
+         (cadadr namespace-or-root)))))
 
 (defun phpinspect--get-last-statement-in-token (token)
+  (setq token (cond ((phpinspect-function-p token)
+                     (phpinspect-function-block token))
+                    ((phpinspect-namespace-p token)
+                     (phpinspect-namespace-block token))
+                    (t token)))
   (nreverse
    (seq-take-while
     (let ((keep-taking t) (last-test nil))
@@ -1929,47 +2067,50 @@ level of a token. Nested variables are ignored."
              (listp elt))))
     (reverse token))))
 
-(defun phpinspect--suggest-variables-at-point (token-tree token)
-  (let ((assignments (phpinspect--find-assignments-in-token
-                      (if (phpinspect-incomplete-list-p token)
-                          (phpinspect--find-innermost-incomplete-block 
token-tree)
-                        token)))
-        (variables)
-        (func (phpinspect--find-innermost-incomplete-function token-tree)))
-    (dolist (assignment assignments)
-      (dolist (token assignment)
-        (when (phpinspect-variable-p token)
-          (push (phpinspect--make-variable
-                 :name (cadr token)
-                 :type "")
-                variables))))
-
-    (when func
-      (dolist (token (phpinspect-function-argument-list func))
-        (when (phpinspect-variable-p token)
-          (push (phpinspect--make-variable
-                 :name (cadr token)
-                 :type "")
-                variables))))
+(defun phpinspect--suggest-variables-at-point (resolvecontext)
+  (phpinspect--log "Suggesting variables at point")
+  (let ((variables))
+    (dolist (token (phpinspect--resolvecontext-enclosing-tokens 
resolvecontext))
+      (when (phpinspect-not-class-p token)
+        (dolist (potential-variable token)
+          (cond ((phpinspect-variable-p potential-variable)
+                 (phpinspect--log "Pushing variable %s" potential-variable)
+                 (push (phpinspect--make-variable
+                        :name (cadr potential-variable)
+                        :type "")
+                       variables))
+                ((phpinspect-function-p potential-variable)
+                 (dolist (argument (phpinspect-function-argument-list
+                                    potential-variable))
+                   (when (phpinspect-variable-p argument)
+                     (push (phpinspect--make-variable
+                            :name (cadr argument)
+                            :type "")
+                           variables))))))))
     variables))
 
 (defun phpinspect--suggest-at-point ()
+      (phpinspect--log "Entering suggest at point." )
   (let* ((token-tree (phpinspect-parse-buffer-until-point (current-buffer) 
(point)))
-         (incomplete-token (phpinspect--find-innermost-incomplete-token 
token-tree))
-         (last-tokens (last incomplete-token 2)))
+         (resolvecontext (phpinspect--get-resolvecontext token-tree))
+         (last-tokens (last (phpinspect--resolvecontext-subject 
resolvecontext) 2)))
+    (phpinspect--log "Subject: %s" (phpinspect--resolvecontext-subject
+                                    resolvecontext))
+    (phpinspect--log "Last tokens: %s" last-tokens)
     (cond ((and (phpinspect-object-attrib-p (car last-tokens))
                 (phpinspect-word-p (cadr last-tokens)))
            (phpinspect--log "word-attributes")
            (phpinspect--suggest-attributes-at-point token-tree
-                                                    incomplete-token))
+                                                    resolvecontext))
           ((phpinspect-object-attrib-p (cadr last-tokens))
            (phpinspect--log "object-attributes")
-           (phpinspect--suggest-attributes-at-point token-tree 
incomplete-token))
+           (phpinspect--suggest-attributes-at-point token-tree resolvecontext))
           ((phpinspect-static-attrib-p (cadr last-tokens))
            (phpinspect--log "static-attributes")
-           (phpinspect--suggest-attributes-at-point token-tree 
incomplete-token t))
-          ((phpinspect-variable-p (cadr last-tokens))
-           (phpinspect--suggest-variables-at-point token-tree 
incomplete-token)))))
+           (phpinspect--suggest-attributes-at-point token-tree resolvecontext 
t))
+          ((phpinspect-variable-p (car(phpinspect--resolvecontext-subject
+                                       resolvecontext)))
+           (phpinspect--suggest-variables-at-point resolvecontext)))))
 
 
 (defun phpinspect-company-backend (command &optional arg &rest _ignored)
@@ -2125,7 +2266,7 @@ If START-FILE is provided, searching starts at the 
directory
 level of START-FILE in stead of `default-directory`."
   (let ((project-file (phpinspect--locate-dominating-project-file
                        (or start-file default-directory))))
-    (phpinspect--log "Checking for project root at  %s" start-file)
+    (phpinspect--log "Checking for project root at  %s" project-file)
     (when project-file
       (let* ((directory (file-name-directory project-file))
              (directory-slugs (split-string (expand-file-name directory) "/")))
@@ -2145,17 +2286,19 @@ level of START-FILE in stead of `default-directory`."
 (defun phpinspect-fix-uses-interactive ()
   "Add missing use statements to the currently visited PHP file."
   (interactive)
-  (save-buffer)
-  (let* ((phpinspect-json (shell-command-to-string
-                                      (format "cd %s && %s fxu --json %s"
-                                                  (shell-quote-argument 
(phpinspect--get-project-root))
-                                   (shell-quote-argument 
phpinspect-index-executable)
-                                                  (shell-quote-argument 
buffer-file-name)))))
-       (let* ((json-object-type 'hash-table)
-                  (json-array-type 'list)
-                  (json-key-type 'string)
-                  (phpinspect-json-data (json-read-from-string 
phpinspect-json)))
-         (maphash #'phpinspect-handle-phpinspect-json phpinspect-json-data))))
+  (let ((project-root (phpinspect--get-project-root)))
+    (when project-root
+      (save-buffer)
+      (let* ((phpinspect-json (shell-command-to-string
+                                          (format "cd %s && %s fxu --json %s"
+                                                      (shell-quote-argument 
project-root)
+                                       (shell-quote-argument 
phpinspect-index-executable)
+                                                      (shell-quote-argument 
buffer-file-name)))))
+           (let* ((json-object-type 'hash-table)
+                      (json-array-type 'list)
+                      (json-key-type 'string)
+                      (phpinspect-json-data (json-read-from-string 
phpinspect-json)))
+             (maphash #'phpinspect-handle-phpinspect-json 
phpinspect-json-data))))))
 
 (defun phpinspect-handle-phpinspect-json (class-name candidates)
   "Handle key value pair of classname and FQN's"
@@ -2199,16 +2342,31 @@ level of START-FILE in stead of `default-directory`."
     (split-string (buffer-string) (char-to-string ?\n))))
 
 ;;;###autoload
-(defun phpinspect-find-class-file (class)
+(defun phpinspect-find-class-file (fqn)
+  "`find-file', but for FQNs of PHP classes.
+
+When called interactively, presents the the user with a list of
+available FQNs in a project.  This may require
+`phpinspect-index-current-project' to have run once for the
+project directory before it can be used."
   (interactive (list (completing-read "Class: " (phpinspect-get-all-fqns))))
-  (find-file (phpinspect-get-class-filepath class)))
+  (find-file (phpinspect-get-class-filepath fqn)))
+
+(defun phpinspect-find-own-class-file (fqn)
+  "`phpinspect-find-class-file', but for non-vendored classes.
 
-(defun phpinspect-find-own-class-file (class)
+When called interactively, presents the user with a list of
+available FQNs for classes in the current project, which aren't
+located in \"vendor\" folder."
   (interactive (list (completing-read "Class: " (phpinspect-get-all-fqns 
"uses_own"))))
-  (find-file (phpinspect-get-class-filepath class)))
+  (find-file (phpinspect-get-class-filepath fqn)))
 
 
 (defun phpinspect-get-class-filepath (class &optional index-new)
+  "Retrieve filepath to CLASS definition file.
+
+when INDEX-NEW is non-nil, new files are added to the index
+before the search is executed."
   (phpinspect--log "%s" (phpinspect--get-project-root))
   (when (eq index-new 'index-new)
     (with-temp-buffer
@@ -2243,6 +2401,10 @@ level of START-FILE in stead of `default-directory`."
    strings))
 
 (defun phpinspect-index-current-project ()
+  "Index all available FQNs in the current project.
+
+Index is stored in files in the .cache directory of
+the project root."
   (interactive)
   (let* ((default-directory (phpinspect--get-project-root)))
     (with-current-buffer (get-buffer-create "**phpinspect-index**")
diff --git a/test/fixtures/IncompleteClass.eld 
b/test/fixtures/IncompleteClass.eld
new file mode 100644
index 0000000000..67684e6872
--- /dev/null
+++ b/test/fixtures/IncompleteClass.eld
@@ -0,0 +1 @@
+(:root (:word "declare") (:list (:word "strict_types") (:assignment "=")) 
(:terminator ";") (:namespace (:word "App\\Controller") (:terminator ";") (:use 
(:word "Symfony\\Component\\HttpFoundation\\Response") (:terminator ";")) (:use 
(:word "App\\Entity\\Address") (:terminator ";")) (:use (:word 
"Symfony\\Component\\HttpFoundation\\RedirectResponse") (:terminator ";")) 
(:use (:word "App\\Repository\\AddressRepository") (:terminator ";")) (:use 
(:word "App\\Repository\\UserRepository") (: [...]
\ No newline at end of file
diff --git a/test/fixtures/IncompleteClass.php 
b/test/fixtures/IncompleteClass.php
new file mode 100644
index 0000000000..c89f1cc4a2
--- /dev/null
+++ b/test/fixtures/IncompleteClass.php
@@ -0,0 +1,81 @@
+<?php
+declare(strict_types=1);
+
+namespace App\Controller;
+
+use Symfony\Component\HttpFoundation\Response;
+use App\Entity\Address;
+use Symfony\Component\HttpFoundation\RedirectResponse;
+use App\Repository\AddressRepository;
+use App\Repository\UserRepository;
+use Doctrine\ORM\EntityManagerInterface;
+use Twig\Environment;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Routing\Annotation\Route;
+
+class AddressController
+{
+    const A_CONSTANT_FOR_THE_SAKE_OF_HAVING_ONE = 'a value';
+    public const ARRAY_CONSTANT = [
+        'key' => 'value',
+        'key' => 0
+    ];
+
+    private $repo;
+    private $user_repo;
+    private $twig;
+    private $em;
+
+    public function __construct(
+        AddressRepository $repo,
+        UserRepository $user_repo,
+        Environment $twig,
+        EntityManagerInterface $em
+    ) {
+        $this->repo = $repo;
+        $this->user_repo = $user_repo;
+        $this->twig = $twig;
+        $this->em = $em;
+    }
+
+    /**
+     * @Route("/address/add", methods={"GET"})
+     */
+    public function addAddressPage(Request $req): Response
+    {
+        $user = $this->user_repo->findOne($req->get('user'));
+
+        return new Response(
+            $this->twig->render('address/create.html.twig', [
+                'user' => $user,
+            ])
+        );
+    }
+
+    /**
+     * @Route("/address/add", methods={"POST"})
+     */
+    public function addAddressAction(Request $req): Response
+    {
+        $user = $this->user_repo->findOne($req->request->get('user'));
+        $address_string = $req->request->get('address');
+
+        $address = new Address($user, $address_string);
+
+        $this->em->persist($address);
+        $this->em->flush();
+
+
+        return new RedirectResponse('/user/' . $user->getLoginName() . 
'/manage');
+    }
+
+    /**
+     * @Route("/address/delete", methods={"POST"})
+     */
+    public function deleteAddressAction(Request $req): Response
+    {
+        $address = $this->repo->find($req->request->get('address'));
+
+        // This is what a while looks like to phpinspect when it parses up
+        // until "point" to complete.
+        $this->em->remove($this->em->
diff --git a/test/phpinspect-test.el b/test/phpinspect-test.el
index 91d9af3f48..8cdb154c9b 100644
--- a/test/phpinspect-test.el
+++ b/test/phpinspect-test.el
@@ -127,5 +127,30 @@
            (phpinspect-test-read-fixture-data
             "class-index-1-2-undestructive-merge"))))
 
+(ert-deftest phpinspect-find-innermost-incomplete-nested-token ()
+  (let ((resolvecontext (phpinspect--get-resolvecontext
+                         (phpinspect-test-read-fixture-data 
"IncompleteClass"))))
+
+    (should (equal (phpinspect--resolvecontext-subject resolvecontext)
+                   '((:variable "this")
+                     (:object-attrib (:word "em"))
+                     (:object-attrib nil))))
+
+    (should (phpinspect-root-p
+             (car (last (phpinspect--resolvecontext-enclosing-tokens
+                         resolvecontext)))))
+
+    (should (phpinspect-incomplete-list-p
+             (car (phpinspect--resolvecontext-enclosing-tokens
+                   resolvecontext))))
+
+    (should (phpinspect-incomplete-function-p
+             (cadr (phpinspect--resolvecontext-enclosing-tokens
+                    resolvecontext))))
+
+    (should (phpinspect-incomplete-class-p
+             (cadddr (phpinspect--resolvecontext-enclosing-tokens
+                     resolvecontext))))))
+
 (provide 'phpinspect-test)
 ;;; phpinspect-test.el ends here

Reply via email to