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

    Keep index synchronized with buffer state
---
 benchmarks/splay-tree.el                           |  21 +-
 phpinspect-bmap.el                                 |  54 +++-
 phpinspect-buffer.el                               | 355 +++++++++++++++++++--
 phpinspect-cache.el                                |  28 +-
 phpinspect-class.el                                | 130 ++++++--
 phpinspect-completion.el                           |   2 +
 phpinspect-eldoc.el                                |   4 +-
 phpinspect-imports.el                              |   7 +-
 phpinspect-index.el                                | 104 +++---
 phpinspect-meta.el                                 |  56 +++-
 phpinspect-parse-context.el                        |   1 +
 phpinspect-parser.el                               | 247 ++------------
 phpinspect-project.el                              |  21 +-
 phpinspect-serialize.el                            |   4 +-
 phpinspect-splayt.el                               | 120 ++++---
 phpinspect-toc.el                                  |  82 +++++
 phpinspect-token-predicates.el                     | 250 +++++++++++++++
 phpinspect-type.el                                 |   9 +
 phpinspect-util.el                                 |  11 +
 phpinspect-worker.el                               |   2 +-
 phpinspect.el                                      |  18 --
 test/fixtures/IncompleteClass.eld                  |   2 +-
 test/fixtures/IncompleteClassBlockedNamespace.eld  |   2 +-
 .../fixtures/IncompleteClassMultipleNamespaces.eld |   2 +-
 test/fixtures/IndexClass1-indexed.eld              |   2 +-
 test/fixtures/IndexClass1.eld                      |   2 +-
 test/fixtures/IndexClass2-indexed.eld              |   2 +-
 test/fixtures/IndexClass2.eld                      |   2 +-
 test/fixtures/NamespacedClass.eld                  |   2 +-
 test/phpinspect-test-env.el                        |  41 +++
 test/phpinspect-test.el                            |  38 +--
 test/test-buffer.el                                | 186 ++++++++++-
 test/test-class.el                                 |  69 ++++
 test/test-edtrack.el                               |   2 +
 test/test-index.el                                 |   9 +
 test/test-meta.el                                  |  67 ++++
 test/test-splayt.el                                |   9 +
 test/test-toc.el                                   |  50 +++
 38 files changed, 1548 insertions(+), 465 deletions(-)

diff --git a/benchmarks/splay-tree.el b/benchmarks/splay-tree.el
index 261ddfe08a..26ba88bd4f 100644
--- a/benchmarks/splay-tree.el
+++ b/benchmarks/splay-tree.el
@@ -14,7 +14,19 @@
   (garbage-collect)
   (benchmark
    1 '(dotimes (i 10000)
-        (phpinspect-splayt-find tree i))))
+        (phpinspect-splayt-find tree i)))
+
+  (message "Splay tree 10000 items traversal:")
+  (garbage-collect)
+  (benchmark
+   1 '(phpinspect-splayt-traverse (i tree)
+        nil))
+
+  (message "Splay tree 10000 items LR traversal:")
+  (garbage-collect)
+  (benchmark
+   1 '(phpinspect-splayt-traverse-lr (i tree)
+        nil)))
 
 
 (let (map)
@@ -30,4 +42,9 @@
   (garbage-collect)
   (benchmark
    1 '(dotimes (i 10000)
-        (gethash i map))))
+        (gethash i map)))
+
+  (message "Hashtable 10000 iterations:")
+  (garbage-collect)
+  (benchmark
+   1 '(maphash (lambda (k v) nil) map)))
diff --git a/phpinspect-bmap.el b/phpinspect-bmap.el
index 0fded42705..a2ad9ec39c 100644
--- a/phpinspect-bmap.el
+++ b/phpinspect-bmap.el
@@ -28,6 +28,7 @@
 (require 'phpinspect-changeset)
 (require 'phpinspect-util)
 (require 'compat)
+(require 'phpinspect-token-predicates)
 
 (eval-when-compile
   (defvar phpinspect-parse-context nil
@@ -39,18 +40,36 @@
 
 (cl-defstruct (phpinspect-bmap (:constructor phpinspect-make-bmap))
   (starts (make-hash-table :test #'eql
-                           :size (floor (/ (point-max) 4))
+                           :size (floor (/ (point-max) 2))
                            :rehash-size 1.5))
   (ends (make-hash-table :test #'eql
-                           :size (floor (/ (point-max) 4))
+                           :size (floor (/ (point-max) 2))
                            :rehash-size 1.5))
   (meta (make-hash-table :test #'eq
-                           :size (floor (/ (point-max) 4))
+                           :size (floor (/ (point-max) 2))
                            :rehash-size 1.5))
   (token-stack nil
                :type list)
   (overlays (phpinspect-make-splayt)
             :type phpinspect-splayt)
+  (declarations (phpinspect-make-splayt)
+                :type phpinspect-splayt
+                :documentation "The declaration tokens encountered.")
+  (imports (phpinspect-make-splayt)
+           :type phpinspect-splayt
+           :documentation "The import statements encountered.")
+  (functions (phpinspect-make-splayt)
+             :type phpinspect-splayt
+             :documentation "The function definitions encountered.")
+  (classes (phpinspect-make-splayt)
+           :type phpinspect-splayt
+           :documentation "The classes encountered.")
+  (class-variables (phpinspect-make-splayt)
+                   :type phpinspect-splayt
+                   :documentation "The class attribute variables encountered")
+  (namespaces (phpinspect-make-splayt)
+              :type phpinspect-splayt
+              :documentation "The namespaces encountered")
   (-root-meta nil
               :type phpinspect-meta)
   (last-token-start nil
@@ -158,7 +177,28 @@
 
     (puthash start token-meta starts)
 
-    (if existing-end
+    (cond
+     ((phpinspect-use-p (phpinspect-meta-token token-meta))
+      (phpinspect-splayt-insert
+       (phpinspect-bmap-imports bmap) (phpinspect-meta-start token-meta) 
token-meta))
+     ((phpinspect-class-p (phpinspect-meta-token token-meta))
+      (phpinspect-splayt-insert
+       (phpinspect-bmap-classes bmap) (phpinspect-meta-start token-meta) 
token-meta))
+     ((phpinspect-declaration-p (phpinspect-meta-token token-meta))
+      (phpinspect-splayt-insert
+       (phpinspect-bmap-declarations bmap) (phpinspect-meta-start token-meta) 
token-meta))
+     ((phpinspect-function-p (phpinspect-meta-token token-meta))
+      (phpinspect-splayt-insert
+       (phpinspect-bmap-functions bmap) (phpinspect-meta-start token-meta) 
token-meta))
+     ((phpinspect-namespace-p (phpinspect-meta-token token-meta))
+      (phpinspect-splayt-insert
+       (phpinspect-bmap-namespaces bmap) (phpinspect-meta-start token-meta) 
token-meta))
+     ((or (phpinspect-const-p (phpinspect-meta-token token-meta))
+          (phpinspect-class-variable-p (phpinspect-meta-token token-meta)))
+      (phpinspect-splayt-insert
+       (phpinspect-bmap-class-variables bmap) (phpinspect-meta-start 
token-meta) token-meta)))
+
+     (if existing-end
         (push token existing-end)
       (puthash end (list token-meta) ends))
 
@@ -222,10 +262,6 @@
 (cl-defmethod phpinspect-bmap-token-meta ((overlay (head overlay)) token)
   (phpinspect-bmap-token-meta (phpinspect-overlay-bmap overlay) token))
 
-(defsubst phpinspect-probably-token-p (token)
-  (and (listp token)
-       (keywordp (car token))))
-
 (cl-defmethod phpinspect-bmap-token-meta ((bmap phpinspect-bmap) token)
   (unless (phpinspect-probably-token-p token)
     (error "Unexpected argument, expected `phpinspect-token-p'. Got invalid 
token %s" token))
@@ -248,7 +284,7 @@
   (let* ((overlays (phpinspect-bmap-overlays bmap))
          (start (+ (phpinspect-meta-start token-meta) pos-delta))
          (end (+ (phpinspect-meta-end token-meta) pos-delta))
-         (overlay)
+         overlay
          (last-overlay (phpinspect-splayt-node-value 
(phpinspect-splayt-root-node overlays))))
 
     (phpinspect-meta-with-changeset token-meta
diff --git a/phpinspect-buffer.el b/phpinspect-buffer.el
index 7a46fd966b..905aed2e6b 100644
--- a/phpinspect-buffer.el
+++ b/phpinspect-buffer.el
@@ -27,6 +27,10 @@
 (require 'phpinspect-bmap)
 (require 'phpinspect-edtrack)
 (require 'phpinspect-index)
+(require 'phpinspect-toc)
+(require 'phpinspect-resolvecontext)
+(require 'phpinspect-resolve)
+(require 'phpinspect-util)
 
 (defvar-local phpinspect-current-buffer nil
   "An instance of `phpinspect-buffer' local to the active
@@ -44,12 +48,26 @@ emacs buffer."
         "Parsed token tree that resulted from last parse")
   (map nil
        :type phpinspect-bmap)
+  (-last-indexed-bmap nil)
+  (imports nil
+           :type phpinspect-toc)
+  (namespaces nil
+              :type phpinspect-toc)
+  (classes nil
+           :type phpinspect-toc)
+  (class-variables nil
+                   :type phpinspect-toc)
+  (declarations nil
+                :type phpinspect-toc)
+  (functions nil
+             :type phpinspect-toc)
+  (token-index (make-hash-table :test 'eq :size 100 :rehash-size 1.5))
   (project nil
            :type phpinspect-project)
   (edit-tracker (phpinspect-make-edtrack)
                 :type phpinspect-edtrack))
 
-(cl-defmethod phpinspect-buffer-parse ((buffer phpinspect-buffer) &optional 
no-interrupt no-index)
+(cl-defmethod phpinspect-buffer-parse ((buffer phpinspect-buffer) &optional 
no-interrupt)
   "Parse the PHP code in the the emacs buffer that this object is
 linked with."
   (let ((tree))
@@ -72,27 +90,331 @@ linked with."
               (phpinspect-edtrack-clear (phpinspect-buffer-edit-tracker 
buffer))
 
               ;; set return value
-              (setq tree parsed)
-
-              (unless (or no-index
-                          (not (phpinspect-buffer-project buffer)))
-                (phpinspect--log "Adding buffer index to project")
-                (phpinspect-project-add-index
-                 (phpinspect-buffer-project buffer)
-                 (phpinspect--index-tokens tree nil 
(phpinspect-buffer-location-resolver buffer))
-                 'index-imports))))))
+              (setq tree parsed)))))
     ;; Else: Just return last parse result
     (setq tree (phpinspect-buffer-tree buffer)))
-
   tree))
 
 
+
+(cl-defmethod phpinspect-buffer-get-index-for-token ((buffer 
phpinspect-buffer) token)
+  (gethash token (phpinspect-buffer-token-index buffer)))
+
+(cl-defmethod phpinspect-buffer-set-index-reference-for-token ((buffer 
phpinspect-buffer) token index)
+  (unless (phpinspect-probably-token-p token)
+    (error "%s does not seem to be a token" token))
+  (puthash token index (phpinspect-buffer-token-index buffer)))
+
+(cl-defmethod phpinspect-buffer-update-index-reference-for-token ((buffer 
phpinspect-buffer) old new)
+  (unless (and (phpinspect-probably-token-p old) (phpinspect-probably-token-p 
new))
+    (when (and old new)
+      (error "old and new parameters should be tokens")))
+
+  (when-let ((index (gethash old (phpinspect-buffer-token-index buffer))))
+    (remhash old (phpinspect-buffer-token-index buffer))
+    (puthash new index (phpinspect-buffer-token-index buffer))))
+
+(cl-defmethod phpinspect-buffer-delete-index-for-token ((buffer 
phpinspect-buffer) token)
+  (unless (phpinspect-probably-token-p token)
+    (error "%s does not seem to be a token" token))
+
+  (cond ((phpinspect-class-p token)
+         (when-let ((class (gethash token (phpinspect-buffer-token-index 
buffer))))
+           (remhash token (phpinspect-buffer-token-index buffer))
+           (phpinspect-project-delete-class (phpinspect-buffer-project buffer) 
class)))
+        ((or (phpinspect-const-p token) (phpinspect-variable-p token))
+         (when-let ((var (gethash token (phpinspect-buffer-token-index 
buffer))))
+           (remhash token (phpinspect-buffer-token-index buffer))
+           (when-let ((class (phpinspect-project-get-class
+                              (phpinspect-buffer-project buffer)
+                              (car var))))
+             (phpinspect--class-delete-variable class (cdr var)))))
+        ((phpinspect-function-p token)
+         (when-let ((func (gethash token (phpinspect-buffer-token-index 
buffer))))
+           (remhash token (phpinspect-buffer-token-index buffer))
+           (cond ((phpinspect-project-p (car func))
+                  (phpinspect-project-delete-function 
(phpinspect-buffer-project buffer) (phpinspect--function-name-symbol (cdr 
func))))
+                 ((phpinspect--type-p (car func))
+                  (when-let ((class (phpinspect-project-get-class
+                                     (phpinspect-buffer-project buffer)
+                                     (car func))))
+                    (phpinspect--class-delete-method class (cdr func))))
+                 (t (error "Invalid index location")))))
+        (t (error "Cannot delete index for token %s" token))))
+
+(cl-defmethod phpinspect-buffer-namespace-at-point ((buffer phpinspect-buffer) 
(point integer))
+  (let ((namespace (phpinspect-splayt-find-largest-before
+                    (phpinspect-toc-tree (phpinspect-buffer-namespaces buffer))
+                    point)))
+    (and namespace (phpinspect-meta-overlaps-point namespace point) 
namespace)))
+
+(cl-defmethod phpinspect-buffer-index-imports ((buffer phpinspect-buffer) 
(imports (head phpinspect-splayt)))
+  (let (to-be-indexed)
+    (if (phpinspect-buffer-imports buffer)
+        (pcase-let* ((`(,new) (phpinspect-toc-update
+                               (phpinspect-buffer-imports buffer) imports 
(phpinspect-buffer-root-meta buffer))))
+          (setq to-be-indexed new))
+      (setq to-be-indexed (phpinspect-splayt-to-list imports))
+      (setf (phpinspect-buffer-imports buffer) (phpinspect-make-toc imports)))
+
+    (phpinspect-project-enqueue-imports
+     (phpinspect-buffer-project buffer)
+     (phpinspect--uses-to-types (mapcar #'phpinspect-meta-token 
to-be-indexed)))))
+
+(cl-defmethod phpinspect-buffer-index-namespaces ((buffer phpinspect-buffer) 
(namespaces (head phpinspect-splayt)))
+  (if (phpinspect-buffer-namespaces buffer)
+      (phpinspect-toc-update (phpinspect-buffer-namespaces buffer) namespaces 
(phpinspect-buffer-root-meta buffer))
+    (setf (phpinspect-buffer-namespaces buffer) (phpinspect-make-toc 
namespaces))))
+
+(cl-defmethod phpinspect-buffer-index-declarations ((buffer phpinspect-buffer) 
(declarations (head phpinspect-splayt)))
+  (if (phpinspect-buffer-declarations buffer)
+      (phpinspect-toc-update (phpinspect-buffer-declarations buffer) 
declarations (phpinspect-buffer-root-meta buffer))
+    (setf (phpinspect-buffer-declarations buffer) (phpinspect-make-toc 
declarations))))
+
+(defun phpinspect-get-token-index-context (namespaces all-imports meta)
+  (let ((namespace (phpinspect-toc-token-at-point namespaces 
(phpinspect-meta-start meta)))
+        namespace-name imports)
+    (if namespace
+        (progn
+          (setq namespace-name (phpinspect-namespace-name 
(phpinspect-meta-token namespace))
+                imports (mapcar #'phpinspect-meta-token
+                                (phpinspect-toc-tokens-in-region
+                                 all-imports (phpinspect-meta-start namespace) 
(phpinspect-meta-start meta)))))
+      (setq namespace-name nil
+            imports (mapcar #'phpinspect-meta-token
+                            (phpinspect-toc-tokens-in-region
+                             all-imports 0 (phpinspect-meta-start meta)))))
+
+    (list imports namespace-name)))
+
+(cl-defmethod phpinspect-buffer-index-classes ((buffer phpinspect-buffer) 
(classes (head phpinspect-splayt)))
+  (let ((declarations (phpinspect-buffer-declarations buffer))
+        (namespaces (phpinspect-buffer-namespaces buffer))
+        (buffer-imports (phpinspect-buffer-imports buffer))
+        (project (phpinspect-buffer-project buffer)))
+    (if (phpinspect-buffer-classes buffer)
+        (pcase-let* ((`(,new-classes ,deleted-classes) (phpinspect-toc-update
+                                                        
(phpinspect-buffer-classes buffer)
+                                                        classes 
(phpinspect-buffer-root-meta buffer)))
+                     (new-declarations) (declaration) (replaced) (indexed) 
(class))
+          (dolist (class new-classes)
+            (when (setq declaration (phpinspect-toc-token-at-or-after-point 
declarations (phpinspect-meta-start class)))
+              (push (cons (phpinspect-meta-token declaration) class) 
new-declarations)))
+
+          (dolist (deleted deleted-classes)
+            (if (and (setq class (phpinspect-buffer-get-index-for-token
+                                  buffer (phpinspect-meta-token deleted)))
+                     (setq replaced (assoc (phpinspect--class-declaration 
class) new-declarations #'equal)))
+                (pcase-let ((`(,imports ,namespace-name) 
(phpinspect-get-token-index-context namespaces buffer-imports (cdr replaced))))
+                  (phpinspect-buffer-update-index-reference-for-token
+                   buffer (phpinspect-meta-token deleted) 
(phpinspect-meta-token (cdr replaced)))
+                  (phpinspect--class-update-declaration class (car replaced) 
imports namespace-name)
+                  (push (cdr replaced) indexed))
+              (phpinspect-buffer-delete-index-for-token buffer 
(phpinspect-meta-token deleted))))
+
+          (dolist (class new-declarations)
+            (unless (memq (cdr class) indexed)
+              (pcase-let* ((`(,imports ,namespace-name) 
(phpinspect-get-token-index-context namespaces buffer-imports (cdr class)))
+                           (`(,class-name) (phpinspect--index-class-declaration
+                                            (car class)
+                                            (phpinspect--make-type-resolver
+                                             (phpinspect--uses-to-types 
imports)
+                                             (phpinspect-class-block 
(phpinspect-meta-token (cdr class)))
+                                             namespace-name)))
+                           (class-obj (phpinspect-project-get-class-create 
project class-name 'no-enqueue)))
+                (phpinspect-buffer-set-index-reference-for-token buffer 
(phpinspect-meta-token (cdr class)) class-obj)
+                (phpinspect--class-update-declaration class-obj (car class) 
imports namespace-name)))))
+      ;; Else: Index all classes
+      (setf (phpinspect-buffer-classes buffer) (phpinspect-make-toc classes))
+      (phpinspect-splayt-traverse (class classes)
+        (pcase-let* ((declaration (phpinspect-toc-token-at-or-after-point 
declarations (phpinspect-meta-start class)))
+                     (`(,imports ,namespace-name) 
(phpinspect-get-token-index-context namespaces buffer-imports class))
+                     (`(,class-name) (phpinspect--index-class-declaration
+                                      (phpinspect-meta-token declaration)
+                                      (phpinspect--make-type-resolver
+                                       (phpinspect--uses-to-types imports)
+                                       (phpinspect-class-block 
(phpinspect-meta-token class))
+                                       namespace-name)))
+                     (class-obj (phpinspect-project-get-class-create project 
class-name 'no-enqueue)))
+          (phpinspect-buffer-set-index-reference-for-token buffer 
(phpinspect-meta-token class) class-obj)
+          (phpinspect--class-update-declaration class-obj 
(phpinspect-meta-token declaration) imports namespace-name))))))
+
+(cl-defmethod phpinspect-buffer-index-functions ((buffer phpinspect-buffer) 
(functions (head phpinspect-splayt)))
+  (let ((classes (phpinspect-buffer-classes buffer))
+        (namespaces (phpinspect-buffer-namespaces buffer))
+        (imports (phpinspect-buffer-imports buffer))
+        to-be-indexed class-environments class indexed)
+    (if (phpinspect-buffer-functions buffer)
+        (pcase-let ((`(,new-funcs ,deleted-funcs)
+                     (phpinspect-toc-update (phpinspect-buffer-functions 
buffer) functions (phpinspect-buffer-root-meta buffer))))
+          (setq to-be-indexed new-funcs)
+
+          (dolist (deleted deleted-funcs)
+            (phpinspect-buffer-delete-index-for-token buffer 
(phpinspect-meta-token deleted))))
+      (setq to-be-indexed (phpinspect-splayt-to-list functions))
+      (setf (phpinspect-buffer-functions buffer) (phpinspect-make-toc 
functions)))
+
+    (dolist (func to-be-indexed)
+      (if (setq class (phpinspect-toc-token-at-point classes 
(phpinspect-meta-start func)))
+          (let (scope static indexed index-env comment-before)
+            (if (phpinspect-static-p (phpinspect-meta-token 
(phpinspect-meta-parent func)))
+                (progn
+                  (setq static (phpinspect-meta-parent func))
+                  (when (phpinspect-scope-p (phpinspect-meta-token 
(phpinspect-meta-parent static)))
+                    (setq scope `(,(car (phpinspect-meta-token 
(phpinspect-meta-parent static)))
+                                  ,(phpinspect-meta-token func))
+                          comment-before (phpinspect-meta-find-left-sibling 
(phpinspect-meta-parent static)))))
+              (when (phpinspect-scope-p (phpinspect-meta-token 
(phpinspect-meta-parent func)))
+                (setq scope (phpinspect-meta-token (phpinspect-meta-parent 
func))
+                      comment-before (phpinspect-meta-find-left-sibling 
(phpinspect-meta-parent func)))))
+
+            (unless scope (setq scope `(:public ,(phpinspect-meta-token 
func))))
+
+            (unless (setq index-env (alist-get class class-environments nil 
nil #'eq))
+              (setq index-env (phpinspect-get-token-index-context namespaces 
imports class))
+              (setcar index-env (phpinspect--uses-to-types (car index-env)))
+              (push (phpinspect--make-type-resolver (car index-env) 
(phpinspect-meta-token class) (cadr index-env))
+                    index-env)
+              (push (cons class index-env) class-environments))
+
+            (unless comment-before
+              (setq comment-before (phpinspect-meta-find-left-sibling func)))
+
+            (setq comment-before (phpinspect-meta-token comment-before))
+
+            (pcase-let ((`(,type-resolver) index-env)
+                        (class-obj (phpinspect-buffer-get-index-for-token 
buffer (phpinspect-meta-token class))))
+              (unless class-obj (error "Unable to find class obj for class %s" 
(phpinspect-meta-token class)))
+
+              (setq indexed (phpinspect--index-function-from-scope
+                             type-resolver
+                             scope
+                             (and (phpinspect-comment-p comment-before) 
comment-before)))
+              (if static
+                  (phpinspect--class-set-static-method class-obj indexed)
+                (phpinspect--class-set-method class-obj indexed))
+
+              (phpinspect-buffer-set-index-reference-for-token
+               buffer (phpinspect-meta-token func)
+               (cons (phpinspect--class-name class-obj) indexed))))
+        ;; Else: index function
+        (pcase-let ((`(,imports ,namespace-name) 
(phpinspect-get-token-index-context namespaces imports func))
+                    (comment-before (phpinspect-meta-find-left-sibling func)))
+          (setq indexed (phpinspect--index-function-from-scope
+                         (phpinspect--make-type-resolver
+                          (phpinspect--uses-to-types imports) nil 
namespace-name)
+                         `(:public ,(phpinspect-meta-token func))
+                         (and (phpinspect-comment-p comment-before) 
comment-before)
+                         nil namespace-name))
+          (phpinspect-project-set-function (phpinspect-buffer-project buffer) 
indexed)
+          (phpinspect-buffer-set-index-reference-for-token
+           buffer (phpinspect-meta-token func)
+           (cons (phpinspect-buffer-project buffer) indexed)))))))
+
+
+(cl-defmethod phpinspect-buffer-index-class-variables ((buffer 
phpinspect-buffer) (class-variables (head phpinspect-splayt)))
+  (let ((classes (phpinspect-buffer-classes buffer))
+        (namespaces (phpinspect-buffer-namespaces buffer))
+        (imports (phpinspect-buffer-imports buffer))
+        to-be-indexed class-environments class class-obj)
+    (if (phpinspect-buffer-class-variables buffer)
+        (pcase-let ((`(,new-vars ,deleted-vars)
+                     (phpinspect-toc-update
+                      (phpinspect-buffer-class-variables buffer) 
class-variables (phpinspect-buffer-root-meta buffer))))
+          (setq to-be-indexed new-vars)
+
+          (dolist (deleted deleted-vars)
+            (phpinspect-buffer-delete-index-for-token buffer 
(phpinspect-meta-token deleted))))
+
+      (setq to-be-indexed (phpinspect-splayt-to-list class-variables))
+      (setf (phpinspect-buffer-class-variables buffer) (phpinspect-make-toc 
class-variables)))
+
+    (dolist (var to-be-indexed)
+      (when (and class (> (phpinspect-meta-start var) (phpinspect-meta-end 
class)))
+        (setq class nil))
+
+      (unless class
+        (setq class (phpinspect-toc-token-at-point classes 
(phpinspect-meta-start var))))
+
+      (setq class-obj (phpinspect-buffer-get-index-for-token buffer 
(phpinspect-meta-token class)))
+
+      (let (scope static indexed index-env comment-before)
+        (if (phpinspect-static-p (phpinspect-meta-token 
(phpinspect-meta-parent var)))
+            (progn
+              (setq static (phpinspect-meta-parent var))
+              (when (phpinspect-scope-p (phpinspect-meta-token 
(phpinspect-meta-parent static)))
+                (setq scope `(,(car (phpinspect-meta-token 
(phpinspect-meta-parent static)))
+                              ,(phpinspect-meta-token var))
+                      comment-before (phpinspect-meta-find-left-sibling 
(phpinspect-meta-parent static)))))
+          (when (phpinspect-scope-p (phpinspect-meta-token 
(phpinspect-meta-parent var)))
+            (setq scope (phpinspect-meta-token (phpinspect-meta-parent var))
+                  comment-before (phpinspect-meta-find-left-sibling 
(phpinspect-meta-parent var)))))
+
+        (unless scope (setq scope `(:public ,(phpinspect-meta-token var))))
+
+        (unless (setq index-env (alist-get class class-environments nil nil 
#'eq))
+          (setq index-env (phpinspect-get-token-index-context namespaces 
imports class))
+          (push (cons class index-env) class-environments))
+
+        (unless comment-before
+          (setq comment-before (phpinspect-meta-find-left-sibling var)))
+
+        (setq comment-before (phpinspect-meta-token comment-before))
+
+        (pcase-let* ((`(,imports ,namespace-name) index-env)
+                     (type-resolver
+                      (phpinspect--make-type-resolver
+                       (phpinspect--uses-to-types imports)
+                       (phpinspect-meta-token class)
+                       namespace-name)))
+
+          (setq indexed
+                (if (phpinspect-const-p (phpinspect-meta-token var))
+                    (phpinspect--index-const-from-scope scope)
+                  (phpinspect--index-variable-from-scope
+                   type-resolver
+                   scope
+                   (and (phpinspect-comment-p comment-before) comment-before)
+                   static)))
+
+          (when (and (phpinspect-variable-p (phpinspect-meta-token var)) (not 
(phpinspect--variable-type indexed)))
+            (when-let* ((constructor (phpinspect--class-get-method class-obj 
(phpinspect-intern-name "__construct")))
+                        (rctx (phpinspect--make-resolvecontext 
:enclosing-tokens (list (phpinspect-meta-token class))
+                                                               
:enclosing-metadata (list class))))
+              (setf (phpinspect--variable-type indexed)
+                    (phpinspect-get-pattern-type-in-block
+                     rctx (phpinspect--make-pattern :m `(:variable "this") :m 
`(:object-attrib (:word ,(cadr var))))
+                     (phpinspect-function-block (phpinspect--function-token 
constructor))
+                     type-resolver
+                     (phpinspect-function-argument-list 
(phpinspect--function-token constructor))))))
+
+
+          (phpinspect--class-set-variable class-obj indexed)
+
+          (phpinspect-buffer-set-index-reference-for-token
+           buffer (phpinspect-meta-token var)
+           (cons (phpinspect--class-name class-obj) indexed)))))))
+
 (cl-defmethod phpinspect-buffer-reparse ((buffer phpinspect-buffer))
   (setf (phpinspect-buffer-tree buffer) nil)
   (setf (phpinspect-buffer-map buffer) (phpinspect-make-bmap))
+  (setf (phpinspect-buffer-declarations buffer) nil)
+  (setf (phpinspect-buffer-imports buffer) nil)
   (phpinspect-edtrack-clear (phpinspect-buffer-edit-tracker buffer))
   (phpinspect-buffer-parse buffer 'no-interrupt))
 
+(cl-defmethod phpinspect-buffer-update-project-index ((buffer 
phpinspect-buffer))
+  (when (phpinspect-buffer-project buffer)
+    (let ((map (phpinspect-buffer-map buffer)))
+      (unless (eq map (phpinspect-buffer--last-indexed-bmap buffer))
+        (phpinspect-buffer-index-imports buffer (phpinspect-bmap-imports map))
+        (phpinspect-buffer-index-declarations buffer 
(phpinspect-bmap-declarations map))
+        (phpinspect-buffer-index-namespaces buffer (phpinspect-bmap-namespaces 
map))
+        (phpinspect-buffer-index-classes buffer (phpinspect-bmap-classes map))
+        (phpinspect-buffer-index-functions buffer (phpinspect-bmap-functions 
map))
+        (phpinspect-buffer-index-class-variables buffer 
(phpinspect-bmap-class-variables map))
+        (setf (phpinspect-buffer--last-indexed-bmap buffer) map)))))
+
 (defsubst phpinspect-buffer-parse-map (buffer)
   (phpinspect-buffer-parse buffer)
   (phpinspect-buffer-map buffer))
@@ -134,14 +456,6 @@ use."
 (cl-defmethod phpinspect-buffer-root-meta ((buffer phpinspect-buffer))
   (phpinspect-bmap-root-meta (phpinspect-buffer-map buffer)))
 
-(defun phpinspect-display-buffer-tree ()
-  (interactive)
-  (when phpinspect-current-buffer
-    (let ((buffer phpinspect-current-buffer))
-      (pop-to-buffer (generate-new-buffer "phpinspect-buffer-tree"))
-      (insert (pp-to-string (phpinspect-buffer-parse buffer 'no-interrupt)))
-      (read-only-mode))))
-
 (defun phpinspect-display-buffer-index ()
   (interactive)
   (when phpinspect-current-buffer
@@ -150,5 +464,8 @@ use."
       (insert (pp-to-string (phpinspect--index-tokens (phpinspect-buffer-parse 
buffer 'no-interrupt))))
       (read-only-mode))))
 
+(defun phpinspect-after-change-function (start end pre-change-length)
+  (when phpinspect-current-buffer
+    (phpinspect-buffer-register-edit phpinspect-current-buffer start end 
pre-change-length)))
 
 (provide 'phpinspect-buffer)
diff --git a/phpinspect-cache.el b/phpinspect-cache.el
index cdf89ee124..1b947744f5 100644
--- a/phpinspect-cache.el
+++ b/phpinspect-cache.el
@@ -30,14 +30,6 @@
 (defvar phpinspect-cache nil
   "An object used to store and access metadata of PHP projects.")
 
-(defun phpinspect--get-or-create-global-cache ()
-  "Get `phpinspect-cache'.
-If its value is nil, it is created and then returned."
-  (or phpinspect-cache
-      (setq phpinspect-cache (phpinspect--make-cache))))
-
-
-
 (cl-defstruct (phpinspect--cache (:constructor phpinspect--make-cache))
   (projects (make-hash-table :test 'equal :size 10)
             :type hash-table
@@ -45,6 +37,26 @@ If its value is nil, it is created and then returned."
             "A `hash-table` with the root directories of projects
 as keys and project caches as values."))
 
+(defun phpinspect--get-or-create-global-cache ()
+  "Get `phpinspect-cache'.
+If its value is nil, it is created and then returned."
+  (or phpinspect-cache
+      (setq phpinspect-cache (phpinspect--make-cache))))
+
+(defun phpinspect-purge-cache ()
+  "Assign a fresh, empty cache object to `phpinspect-cache'.
+This effectively purges any cached code information from all
+currently opened projects."
+  (interactive)
+  (when phpinspect-cache
+    ;; Allow currently known cached projects to cleanup after themselves
+    (maphash (lambda (_ project)
+               (phpinspect-project-purge project))
+             (phpinspect--cache-projects phpinspect-cache)))
+
+  ;; Assign a fresh cache object
+  (setq phpinspect-cache (phpinspect--make-cache)))
+
 (cl-defgeneric phpinspect--cache-getproject
     ((cache phpinspect--cache) (project-name string))
   "Get project by PROJECT-NAME that is located in CACHE.")
diff --git a/phpinspect-class.el b/phpinspect-class.el
index 923e5df510..d5e7221bf3 100644
--- a/phpinspect-class.el
+++ b/phpinspect-class.el
@@ -43,20 +43,23 @@
                   :documentation
                   "All static methods this class provides,
                   including those from extended classes.")
+  (name nil
+        :type phpinspect--type)
   (variables nil
              :type list
              :documentation
              "Variables that belong to this class.")
-  (extended-classes (make-hash-table :test 'eq)
-                    :type hash-table
+  (extended-classes nil
+                    :type list
                     :documentation
                     "All extended/implemented classes.")
-  (subscriptions nil
-                 :type list
+  (subscriptions (make-hash-table :test #'eq :size 10 :rehash-size 1.5)
+                 :type hash-table
                  :documentation
                  "A list of subscription functions that should be
                  called whenever anything about this class is
                  updated")
+  (declaration nil)
   (initial-index nil
                  :type bool
                  :documentation
@@ -64,13 +67,43 @@
                  has been indexed yet."))
 
 (cl-defmethod phpinspect--class-trigger-update ((class phpinspect--class))
-  (dolist (sub (phpinspect--class-subscriptions class))
+  (dolist (sub (hash-table-values (phpinspect--class-subscriptions class)))
     (funcall sub class)))
 
+(cl-defmethod phpinspect--class-update-extensions ((class phpinspect--class) 
extensions)
+  (setf (phpinspect--class-extended-classes class)
+        (seq-filter
+         #'phpinspect--class-p
+         (mapcar
+          (lambda (class-name)
+            (phpinspect-project-get-class-create (phpinspect--class-project 
class)
+                                                 class-name))
+          extensions)))
+
+  (dolist (extended (phpinspect--class-extended-classes class))
+    (phpinspect--class-incorporate class extended)))
+
+
 (cl-defmethod phpinspect--class-set-index ((class phpinspect--class)
                                            (index (head 
phpinspect--indexed-class)))
+  (setf (phpinspect--class-declaration class) (alist-get 'declaration index))
+  (setf (phpinspect--class-name class) (alist-get 'class-name index))
+
+  ;; Override methods when class seems syntactically correct (has balanced 
braces)
+  (when (alist-get 'complete index)
+    (let ((methods (phpinspect--class-methods class))
+          (static-methods (phpinspect--class-static-methods class)))
+
+      (dolist (method (hash-table-values methods))
+        (unless (phpinspect--function--inherited method)
+          (remhash (phpinspect--function-name-symbol method) methods)))
+      (dolist (method (hash-table-values static-methods))
+        (unless (phpinspect--function--inherited method)
+          (remhash (phpinspect--function-name-symbol method) 
static-methods)))))
+
   (setf (phpinspect--class-initial-index class) t)
   (setf (phpinspect--class-index class) index)
+
   (dolist (method (alist-get 'methods index))
     (phpinspect--class-update-method class method))
 
@@ -82,21 +115,21 @@
                 (alist-get 'constants index)
                 (alist-get 'static-variables index)))
 
-  (setf (phpinspect--class-extended-classes class)
-        (seq-filter
-         #'phpinspect--class-p
-         (mapcar
-          (lambda (class-name)
-            (phpinspect-project-get-class-create (phpinspect--class-project 
class)
-                                           class-name))
-          `(,@(alist-get 'implements index) ,@(alist-get 'extends index)))))
-
-  (dolist (extended (phpinspect--class-extended-classes class))
-    (phpinspect--class-incorporate class extended)
-    (phpinspect--class-subscribe class extended))
+  (phpinspect--class-update-extensions
+   class `(,@(alist-get 'implements index) ,@(alist-get 'extends index)))
 
   (phpinspect--class-trigger-update class))
 
+(cl-defmethod phpinspect--class-update-declaration
+  ((class phpinspect--class) declaration imports namespace-name)
+  (pcase-let ((`(,class-name ,extends ,implements ,_used-types)
+               (phpinspect--index-class-declaration
+                declaration (phpinspect--make-type-resolver
+                             (phpinspect--uses-to-types imports) nil 
namespace-name))))
+    (setf (phpinspect--class-name class) class-name)
+    (setf (phpinspect--class-declaration class) declaration)
+    (phpinspect--class-update-extensions class `(,@extends ,@implements))))
+
 (cl-defmethod phpinspect--class-get-method ((class phpinspect--class) 
(method-name symbol))
   (gethash method-name (phpinspect--class-methods class)))
 
@@ -110,6 +143,16 @@
       (when (string= variable-name (phpinspect--variable-name variable))
         (throw 'found variable)))))
 
+(cl-defmethod phpinspect--class-set-variable ((class phpinspect--class)
+                                              (var phpinspect--variable))
+  (push var (phpinspect--class-variables class)))
+
+(cl-defmethod phpinspect--class-delete-variable ((class phpinspect--class)
+                                                 (var phpinspect--variable))
+  (setf (phpinspect--class-variables class)
+        (seq-filter (lambda (clvar) (not (eq var clvar)))
+                    (phpinspect--class-variables class))))
+
 (cl-defmethod phpinspect--class-get-variables ((class phpinspect--class))
   (seq-filter #'phpinspect--variable-vanilla-p (phpinspect--class-variables 
class)))
 
@@ -140,16 +183,22 @@
                    (phpinspect--function-name method))
   (phpinspect--add-method-copy-to-map
    (phpinspect--class-methods class)
-   (alist-get 'class-name (phpinspect--class-index class))
+   (phpinspect--class-name class)
    method))
 
+
 (cl-defmethod phpinspect--class-set-static-method ((class phpinspect--class)
                                                    (method 
phpinspect--function))
-    (phpinspect--add-method-copy-to-map
+  (phpinspect--add-method-copy-to-map
    (phpinspect--class-static-methods class)
-   (alist-get 'class-name (phpinspect--class-index class))
+   (phpinspect--class-name class)
    method))
 
+
+(cl-defmethod phpinspect--class-delete-method ((class phpinspect--class) 
(method phpinspect--function))
+  (remhash (phpinspect--function-name-symbol method) 
(phpinspect--class-static-methods class))
+  (remhash (phpinspect--function-name-symbol method) 
(phpinspect--class-methods class)))
+
 (cl-defmethod phpinspect--class-get-method-return-type
   ((class phpinspect--class) (method-name symbol))
   (let ((method (phpinspect--class-get-method class method-name)))
@@ -171,7 +220,8 @@
 
 (cl-defmethod phpinspect--merge-method ((class-name phpinspect--type)
                                         (existing phpinspect--function)
-                                        (method phpinspect--function))
+                                        (method phpinspect--function)
+                                        &optional extended)
   (let ((new-return-type (phpinspect--resolve-late-static-binding
                           (phpinspect--function-return-type method)
                           class-name)))
@@ -180,49 +230,59 @@
       (setf (phpinspect--function-return-type existing)
             new-return-type))
 
+    (setf (phpinspect--function--inherited existing)
+          extended)
+
     (setf (phpinspect--function-arguments existing)
           (phpinspect--function-arguments method)))
   existing)
 
 (cl-defmethod phpinspect--class-update-static-method ((class phpinspect--class)
-                                                      (method 
phpinspect--function))
+                                                      (method 
phpinspect--function)
+                                                      &optional extended)
   (let ((existing (gethash (phpinspect--function-name-symbol method)
                            (phpinspect--class-static-methods class))))
     (if existing
         (phpinspect--merge-method
          (alist-get 'class-name (phpinspect--class-index class))
-         existing method)
+         existing method extended)
+      (setf (phpinspect--function--inherited method) extended)
       (phpinspect--class-set-static-method class method))))
 
 (cl-defmethod phpinspect--class-update-method ((class phpinspect--class)
-                                               (method phpinspect--function))
+                                               (method phpinspect--function)
+                                               &optional extended)
   (let* ((existing (gethash (phpinspect--function-name-symbol method)
                             (phpinspect--class-methods class))))
 
     (if existing
         (phpinspect--merge-method
          (alist-get 'class-name (phpinspect--class-index class))
-         existing method)
+         existing method extended)
+      (setf (phpinspect--function--inherited method) extended)
       (phpinspect--class-set-method class method))))
 
+;; FIXME: Remove inherited methods when they no longer exist in parent classes
+;; (and/or the current class in the case of abstract methods).
 (cl-defmethod phpinspect--class-incorporate ((class phpinspect--class)
                                              (other-class phpinspect--class))
-
   (dolist (method (phpinspect--class-get-method-list other-class))
-    (phpinspect--class-update-method class method))
-
-    (dolist (method (phpinspect--class-get-static-method-list other-class))
-      (phpinspect--class-update-static-method class method)))
+    (phpinspect--class-update-method class method 'extended))
 
+  (dolist (method (phpinspect--class-get-static-method-list other-class))
+    (phpinspect--class-update-static-method class method 'extended))
 
+    (phpinspect--class-subscribe class other-class))
 
 (cl-defmethod phpinspect--class-subscribe ((class phpinspect--class)
                                            (subscription-class 
phpinspect--class))
-  (let ((update-function
-         (lambda (new-class)
-           (phpinspect--class-incorporate class new-class)
-           (phpinspect--class-trigger-update class))))
-    (push update-function (phpinspect--class-subscriptions 
subscription-class))))
+  (unless (gethash subscription-class (phpinspect--class-subscriptions class))
+    (let ((update-function
+           (lambda (new-class)
+             (phpinspect--class-incorporate class new-class)
+             (phpinspect--class-trigger-update class))))
+      (puthash subscription-class update-function
+               (phpinspect--class-subscriptions subscription-class)))))
 
 (provide 'phpinspect-class)
 ;;; phpinspect-class.el ends here
diff --git a/phpinspect-completion.el b/phpinspect-completion.el
index 59b7d7d823..92e75ba857 100644
--- a/phpinspect-completion.el
+++ b/phpinspect-completion.el
@@ -213,6 +213,8 @@ Returns list of `phpinspect--completion'."
          (buffer-map (phpinspect-buffer-parse-map buffer))
          (rctx (phpinspect-get-resolvecontext buffer-map point))
          (completion-list (phpinspect--make-completion-list)))
+    (phpinspect-buffer-update-project-index buffer)
+
     (dolist (strategy phpinspect-completion-strategies)
       (when-let (region (phpinspect-comp-strategy-supports strategy query 
rctx))
         (setf (phpinspect--completion-list-completion-start completion-list)
diff --git a/phpinspect-eldoc.el b/phpinspect-eldoc.el
index 8efa9b7e72..45db02e34e 100644
--- a/phpinspect-eldoc.el
+++ b/phpinspect-eldoc.el
@@ -123,8 +123,7 @@ be implemented for return values of 
`phpinspect-eld-strategy-execute'")
            enclosing-token)
 
       (setq left-sibling (phpinspect-meta-find-child-before-recursively
-                          enclosing-token (phpinspect-eldoc-query-point q)))
-      (phpinspect-meta-overlaps-point left-sibling 
(phpinspect-eldoc-query-point q)))
+                          enclosing-token (phpinspect-eldoc-query-point q))))
     ;; Subject is inside an argument list
      ((and enclosing-token
                 (phpinspect-list-p (phpinspect-meta-token enclosing-token)))
@@ -250,6 +249,7 @@ also `phpinspect-eldoc-query-execute'.")
          (point (phpinspect-eldoc-query-point query))
          (buffer-map (phpinspect-buffer-parse-map buffer))
          (rctx (phpinspect-get-resolvecontext buffer-map point)))
+    (phpinspect-buffer-update-project-index buffer)
     (catch 'matched
       (dolist (strategy phpinspect-eldoc-strategies)
         (when (phpinspect-eld-strategy-supports strategy query rctx)
diff --git a/phpinspect-imports.el b/phpinspect-imports.el
index 5462f31299..df37ec6195 100644
--- a/phpinspect-imports.el
+++ b/phpinspect-imports.el
@@ -30,6 +30,7 @@
 (require 'phpinspect-autoload)
 (require 'phpinspect-buffer)
 (require 'phpinspect-cache)
+(require 'phpinspect-util)
 
 (defun phpinspect-insert-at-point (point data)
   (save-excursion
@@ -113,12 +114,6 @@ buffer position to insert the use statement at."
 (defalias 'phpinspect-fix-uses-interactive #'phpinspect-fix-imports
   "Alias for backwards compatibility")
 
-(defun phpinspect-namespace-name (namespace)
-  (or (and (phpinspect-namespace-p namespace)
-           (phpinspect-word-p (cadr namespace))
-           (cadadr namespace))
-      ""))
-
 (defun phpinspect-add-use-statements-for-missing-types (types buffer imports 
project parent-token)
   (let (namespace namespace-name)
     (dolist (type types)
diff --git a/phpinspect-index.el b/phpinspect-index.el
index 7dc8df1161..88e43bac41 100644
--- a/phpinspect-index.el
+++ b/phpinspect-index.el
@@ -27,6 +27,7 @@
 (require 'phpinspect-util)
 (require 'phpinspect-project)
 (require 'phpinspect-type)
+(require 'phpinspect-parser)
 
 (defun phpinspect--function-from-scope (scope)
   (cond ((and (phpinspect-static-p (cadr scope))
@@ -108,6 +109,7 @@ function (think \"new\" statements, return types etc.)."
 
     (phpinspect--make-function
      :scope `(,(car scope))
+     :token php-func
      :name (concat (if namespace (concat namespace "\\") "") (cadadr (cdr 
declaration)))
      :return-type (or type phpinspect--null-type)
      :arguments (phpinspect--index-function-arg-list
@@ -115,11 +117,16 @@ function (think \"new\" statements, return types etc.)."
                  (phpinspect-function-argument-list php-func)
                  add-used-types))))
 
+(define-inline phpinspect--safe-cadr (list)
+  (inline-letevals (list)
+    (inline-quote
+     (when (listp ,list) (cadr ,list)))))
+
 (defun phpinspect--index-const-from-scope (scope)
   (phpinspect--make-variable
    :scope `(,(car scope))
    :mutability `(,(caadr scope))
-   :name (cadr (cadr (cadr scope)))))
+   :name (phpinspect--safe-cadr (phpinspect--safe-cadr (phpinspect--safe-cadr 
scope)))))
 
 (defun phpinspect--var-annotations-from-token (token)
   (seq-filter #'phpinspect-var-annotation-p token))
@@ -184,6 +191,39 @@ function (think \"new\" statements, return types etc.)."
                   methods)))))
     methods))
 
+(defun phpinspect--index-class-declaration (decl type-resolver)
+  ;; Find out what the class extends or implements
+  (let (encountered-extends encountered-implements encountered-class
+        class-name extends implements used-types)
+    (dolist (word decl)
+      (if (phpinspect-word-p word)
+          (cond ((string= (cadr word) "extends")
+                 (phpinspect--log "Class %s extends other classes" class-name)
+                 (setq encountered-extends t))
+                ((string= (cadr word) "implements")
+                 (setq encountered-extends nil)
+                 (phpinspect--log "Class %s implements in interface" 
class-name)
+                 (setq encountered-implements t))
+                ((string= (cadr word) "class")
+                 (setq encountered-class t))
+                (t
+                 (phpinspect--log "Calling Resolver from index-class on %s" 
(cadr word))
+                 (cond (encountered-extends
+                        (push (funcall type-resolver (phpinspect--make-type
+                                                      :name (cadr word)))
+                              extends)
+                        (push (cadr word) used-types))
+                       (encountered-implements
+                        (push (funcall type-resolver (phpinspect--make-type
+                                                      :name (cadr word)))
+                              implements)
+                        (push (cadr word) used-types))
+                       (encountered-class
+                        (setq class-name (funcall type-resolver 
(phpinspect--make-type :name (cadr word)))
+                              encountered-class nil)))))))
+
+    (list class-name extends implements used-types)))
+
 
 (defun phpinspect--index-class (imports type-resolver location-resolver class 
&optional doc-block)
   "Create an alist with relevant attributes of a parsed class."
@@ -195,7 +235,7 @@ function (think \"new\" statements, return types etc.)."
         (constants)
         (extends)
         (implements)
-        (class-name (phpinspect--get-class-name-from-token class))
+        (class-name)
         ;; Keep track of encountered comments to be able to use type
         ;; annotations.
         (comment-before)
@@ -208,30 +248,9 @@ function (think \"new\" statements, return types etc.)."
                 (nconc used-types additional-used-types)
               (setq used-types additional-used-types))))
 
-    ;; Find out what the class extends or implements
-    (let ((enc-extends nil)
-          (enc-implements nil))
-      (dolist (word (cadr class))
-        (if (phpinspect-word-p word)
-            (cond ((string= (cadr word) "extends")
-                   (phpinspect--log "Class %s extends other classes" 
class-name)
-                   (setq enc-extends t))
-                  ((string= (cadr word) "implements")
-                   (setq enc-extends nil)
-                   (phpinspect--log "Class %s implements in interface" 
class-name)
-                   (setq enc-implements t))
-                  (t
-                   (phpinspect--log "Calling Resolver from index-class on %s" 
(cadr word))
-                   (cond (enc-extends
-                          (push (funcall type-resolver (phpinspect--make-type
-                                                        :name (cadr word)))
-                                extends)
-                          (push (cadr word) used-types))
-                         (enc-implements
-                          (push (funcall type-resolver (phpinspect--make-type
-                                                        :name (cadr word)))
-                                implements)
-                          (push (cadr word) used-types))))))))
+    (pcase-setq `(,class-name ,extends ,implements ,used-types)
+                (phpinspect--index-class-declaration (cadr class) 
type-resolver))
+
 
     (dolist (token (caddr class))
       (cond ((phpinspect-scope-p token)
@@ -330,21 +349,22 @@ function (think \"new\" statements, return types etc.)."
       (setq methods
             (nconc methods (phpinspect--index-method-annotations type-resolver 
doc-block))))
 
-    (let ((class-name (funcall type-resolver (phpinspect--make-type :name 
class-name))))
-      `(,class-name .
-                    (phpinspect--indexed-class
-                     (class-name . ,class-name)
-                     (location . ,(funcall location-resolver class))
-                     (imports . ,imports)
-                     (methods . ,methods)
-                     (static-methods . ,static-methods)
-                     (static-variables . ,static-variables)
-                     (variables . ,variables)
-                     (constants . ,constants)
-                     (extends . ,extends)
-                     (implements . ,implements)
-                     (used-types . ,(mapcar #'phpinspect-intern-name
-                                            (seq-uniq used-types 
#'string=))))))))
+    `(,class-name .
+                  (phpinspect--indexed-class
+                   (complete . ,(not (phpinspect-incomplete-class-p class)))
+                   (class-name . ,class-name)
+                   (declaration . ,(seq-find #'phpinspect-declaration-p class))
+                   (location . ,(funcall location-resolver class))
+                   (imports . ,imports)
+                   (methods . ,methods)
+                   (static-methods . ,static-methods)
+                   (static-variables . ,static-variables)
+                   (variables . ,variables)
+                   (constants . ,constants)
+                   (extends . ,extends)
+                   (implements . ,implements)
+                   (used-types . ,(mapcar #'phpinspect-intern-name
+                                          (seq-uniq used-types #'string=)))))))
 
 (defsubst phpinspect-namespace-body (namespace)
   "Return the nested tokens in NAMESPACE tokens' body.
@@ -503,7 +523,7 @@ Return value is a list of the types that are \"newed\"."
                              tokens type-resolver-factory imports))))))
      (t
       (phpinspect--log "phpinspect--index-tokens failed: %s. Enable 
debug-on-error for backtrace." err)
-      (when debug-on-error
+      (when (or debug-on-error phpinspect--debug)
         (require 'backtrace)
         (backtrace))
       nil))
diff --git a/phpinspect-meta.el b/phpinspect-meta.el
index e898371c9e..a66e5510b2 100644
--- a/phpinspect-meta.el
+++ b/phpinspect-meta.el
@@ -52,17 +52,40 @@
 (define-inline phpinspect-meta-whitespace-before (meta)
   (inline-quote (car (cddddr ,meta))))
 
-(defun phpinspect-meta-start (meta)
-  (if (phpinspect-meta-parent meta)
-      (+ (phpinspect-meta-start (phpinspect-meta-parent meta))
-         (phpinspect-meta-parent-offset meta))
-    (phpinspect-meta-absolute-start meta)))
+(define-inline phpinspect-meta-parent-start (meta)
+  "Calculate parent start position iteratively based on parent offsets."
+  (inline-letevals (meta)
+    (inline-quote
+     (let ((start (or (phpinspect-meta-parent-offset ,meta) 0))
+           (current ,meta))
+       (while (phpinspect-meta-parent current)
+         (setq current (phpinspect-meta-parent current)
+               start (+ start (or (phpinspect-meta-parent-offset current) 0))))
 
-(defun phpinspect-meta-end (meta)
-  (+ (phpinspect-meta-start meta) (phpinspect-meta-width meta)))
+       (+ (phpinspect-meta-absolute-start current) start)))))
 
-(defsubst phpinspect-meta-width (meta)
-  (- (phpinspect-meta-absolute-end meta) (phpinspect-meta-absolute-start 
meta)))
+(define-inline phpinspect-meta-start (meta)
+  "Calculate the start position of META."
+  (inline-quote
+     (if (phpinspect-meta-parent ,meta)
+           (+ (phpinspect-meta-parent-start (phpinspect-meta-parent ,meta))
+              (phpinspect-meta-parent-offset ,meta))
+       (phpinspect-meta-absolute-start ,meta))))
+
+(define-inline phpinspect-meta-width (meta)
+  (inline-letevals (meta)
+    (inline-quote
+     (- (phpinspect-meta-absolute-end ,meta) (phpinspect-meta-absolute-start 
,meta)))))
+
+(define-inline phpinspect-meta-end (meta)
+  (inline-letevals (meta)
+    (inline-quote
+     (+ (phpinspect-meta-start ,meta) (phpinspect-meta-width ,meta)))))
+
+(defsubst phpinspect-meta-find-root (meta)
+  (while (phpinspect-meta-parent meta)
+    (setq meta (phpinspect-meta-parent meta)))
+  meta)
 
 (defun phpinspect-meta-sort-width (meta1 meta2)
   (< (phpinspect-meta-width meta1) (phpinspect-meta-width meta2)))
@@ -73,9 +96,12 @@
 (define-inline phpinspect-meta-absolute-start (meta)
   (inline-quote (caddr ,meta)))
 
-(defsubst phpinspect-meta-overlaps-point (meta point)
-  (and (> (phpinspect-meta-end meta) point)
-       (<= (phpinspect-meta-start meta) point)))
+(define-inline phpinspect-meta-overlaps-point (meta point)
+  "Check if META's region overlaps POINT."
+  (inline-letevals (point meta)
+        (inline-quote
+         (and (> (phpinspect-meta-end ,meta) ,point)
+              (<= (phpinspect-meta-start ,meta) ,point)))))
 
 (defun phpinspect-meta-find-parent-matching-token (meta predicate)
   (if (funcall predicate (phpinspect-meta-token meta))
@@ -94,7 +120,9 @@
          (setf (phpinspect-meta-parent-offset ,meta)
                (- (phpinspect-meta-start ,meta) (phpinspect-meta-start 
,parent)))
          (phpinspect-meta-add-child ,parent ,meta))
-       (setcar (cdr ,meta) ,parent)))))
+       (setcar (cdr ,meta) ,parent)
+
+       ,meta))))
 
 ;; Note: using defsubst here causes a byte code overflow
 (defun phpinspect-meta-add-child (meta child)
@@ -138,7 +166,6 @@
     (phpinspect-splayt-find-smallest-after (phpinspect-meta-children 
(phpinspect-meta-parent meta))
                                            (phpinspect-meta-parent-offset 
meta))))
 
-
 (cl-defmethod phpinspect-meta-find-overlapping-child ((meta (head meta)) 
(point integer))
   (let ((child (phpinspect-splayt-find-largest-before
                 (phpinspect-meta-children meta) (phpinspect-meta--point-offset 
meta point))))
@@ -209,6 +236,5 @@
               (phpinspect-meta-start meta) (phpinspect-meta-end meta) 
(phpinspect-meta-token meta))
     "[nil]"))
 
-
 (provide 'phpinspect-meta)
 ;;; phpinspect-meta.el ends here
diff --git a/phpinspect-parse-context.el b/phpinspect-parse-context.el
index fb2ae38db2..bc0744416f 100644
--- a/phpinspect-parse-context.el
+++ b/phpinspect-parse-context.el
@@ -36,6 +36,7 @@ parsing. Usually used in combination with
 (cl-defstruct (phpinspect-pctx (:constructor phpinspect-make-pctx))
   "Parser Context"
   (incremental nil)
+  (meta-iterator nil)
   (interrupt-threshold (time-convert '(2 . 1000))
                        :documentation
                        "After how much time `interrupt-predicate'
diff --git a/phpinspect-parser.el b/phpinspect-parser.el
index d3d70a348b..451aa9850f 100644
--- a/phpinspect-parser.el
+++ b/phpinspect-parser.el
@@ -28,17 +28,21 @@
 (require 'phpinspect-bmap)
 (require 'phpinspect-meta)
 (require 'phpinspect-parse-context)
+(require 'phpinspect-token-predicates)
 
 (eval-when-compile
   (define-inline phpinspect--word-end-regex ()
     (inline-quote "\\([[:blank:]]\\|[^0-9a-zA-Z_]\\)")))
 
-(defsubst phpinspect--strip-word-end-space (string)
-  (when phpinspect-parse-context
-    (phpinspect-pctx-register-whitespace
-     phpinspect-parse-context
-     (substring string (- (length string) 1) (length string))))
-  (substring string 0 (- (length string) 1)))
+(define-inline phpinspect--strip-word-end-space (string)
+  (inline-letevals (string)
+    (inline-quote
+     (progn
+       (when phpinspect-parse-context
+         (phpinspect-pctx-register-whitespace
+          phpinspect-parse-context
+          (substring ,string (- (length ,string) 1) (length ,string))))
+       (substring ,string 0 (- (length ,string) 1))))))
 
 (defsubst phpinspect-munch-token-without-attribs (string token-keyword)
   "Return a token of type TOKEN-KEYWORD with STRING as value.
@@ -49,218 +53,6 @@ If STRING has text properties, they are stripped."
     (set-text-properties 0 length nil value)
     (list token-keyword value)))
 
-(defsubst phpinspect-token-type-p (object type)
-  "Returns t if OBJECT is a token of type TYPE.
-Type can be any of the token types returned by
-`phpinspect-parse-buffer-until-point`"
-  (and (listp object) (eq (car object) type)))
-
-(defsubst phpinspect-object-attrib-p (token)
-  (phpinspect-token-type-p token :object-attrib))
-
-(defsubst phpinspect-static-attrib-p (token)
-  (phpinspect-token-type-p token :static-attrib))
-
-(defsubst phpinspect-attrib-p (token)
-  (or (phpinspect-object-attrib-p token)
-      (phpinspect-static-attrib-p token)))
-
-(defun phpinspect-html-p (token)
-  (phpinspect-token-type-p token :html))
-
-(defun phpinspect-comma-p (token)
-  (phpinspect-token-type-p token :comma))
-
-(defsubst phpinspect-terminator-p (token)
-  (phpinspect-token-type-p token :terminator))
-
-(defsubst phpinspect-end-of-token-p (token)
-  (or (phpinspect-terminator-p token)
-      (phpinspect-comma-p token)
-      (phpinspect-html-p token)))
-
-(defsubst phpinspect-end-of-statement-p (token)
-  (or (phpinspect-end-of-token-p token)
-      (phpinspect-block-p token)))
-
-(defsubst phpinspect-incomplete-block-p (token)
-  (phpinspect-token-type-p token :incomplete-block))
-
-(defsubst phpinspect-block-p (token)
-  (or (phpinspect-token-type-p token :block)
-      (phpinspect-incomplete-block-p token)))
-
-(defun phpinspect-end-of-use-p (token)
-  (or (phpinspect-block-p token)
-      (phpinspect-end-of-token-p token)))
-
-(defun phpinspect-static-p (token)
-  (phpinspect-token-type-p token :static))
-
-(defsubst phpinspect-incomplete-const-p (token)
-  (phpinspect-token-type-p token :incomplete-const))
-
-(defsubst phpinspect-const-p (token)
-  (or (phpinspect-token-type-p token :const)
-      (phpinspect-incomplete-const-p token)))
-
-(defsubst phpinspect-scope-p (token)
-  (or (phpinspect-token-type-p token :public)
-      (phpinspect-token-type-p token :private)
-      (phpinspect-token-type-p token :protected)))
-
-(defsubst phpinspect-namespace-p (object)
-  (phpinspect-token-type-p object :namespace))
-
-(defun phpinspect-incomplete-class-p (token)
-  (and (phpinspect-class-p token)
-       (phpinspect-incomplete-block-p (car (last token)))))
-
-(defun phpinspect-incomplete-namespace-p (token)
-  (and (phpinspect-namespace-p token)
-       (or (phpinspect-incomplete-block-p (car (last token)))
-           (phpinspect-incomplete-class-p (car (last token))))))
-
-(defun phpinspect-function-p (token)
-  (phpinspect-token-type-p token :function))
-
-
-(defun phpinspect-class-p (token)
-  (phpinspect-token-type-p token :class))
-
-(defun phpinspect-incomplete-method-p (token)
-  (or (phpinspect-incomplete-function-p token)
-      (and (phpinspect-scope-p token)
-           (phpinspect-incomplete-function-p (car (last token))))
-      (and (phpinspect-scope-p token)
-           (phpinspect-static-p (car (last token)))
-           (phpinspect-incomplete-function-p (car (last (car (last token))))))
-      (and (phpinspect-scope-p token)
-           (phpinspect-function-p (car (last token))))))
-
-(defun phpinspect-incomplete-function-p (token)
-  (and (phpinspect-function-p token)
-       (phpinspect-incomplete-block-p (car (last token)))))
-
-(defsubst phpinspect-incomplete-list-p (token)
-  (phpinspect-token-type-p token :incomplete-list))
-
-(defsubst phpinspect-list-p (token)
-  (or (phpinspect-token-type-p token :list)
-      (phpinspect-incomplete-list-p token)))
-
-(defun phpinspect-declaration-p (token)
-  (phpinspect-token-type-p token :declaration))
-
-(defsubst phpinspect-assignment-p (token)
-  (phpinspect-token-type-p token :assignment))
-
-(defun phpinspect-function-argument-list (php-func)
-  "Get the argument list of a function"
-  (seq-find #'phpinspect-list-p (seq-find #'phpinspect-declaration-p php-func 
nil) nil))
-
-(defun phpinspect-annotation-p (token)
-  (phpinspect-token-type-p token :annotation))
-
-(defun phpinspect-method-annotation-p (token)
-  (phpinspect-token-type-p token :method-annotation))
-
-(defun phpinspect-var-annotation-p (token)
-  (phpinspect-token-type-p token :var-annotation))
-
-(defun phpinspect-return-annotation-p (token)
-  (phpinspect-token-type-p token :return-annotation))
-
-(defsubst phpinspect-variable-p (token)
-  (phpinspect-token-type-p token :variable))
-
-(defsubst phpinspect-word-p (token)
-  (phpinspect-token-type-p token :word))
-
-(defsubst phpinspect-incomplete-array-p (token)
-  (phpinspect-token-type-p token :incomplete-array))
-
-(defsubst phpinspect-array-p (token)
-  (or (phpinspect-token-type-p token :array)
-      (phpinspect-incomplete-array-p token)))
-
-(defsubst phpinspect-incomplete-root-p (token)
-  (and (phpinspect-root-p token)
-       (seq-find #'phpinspect-incomplete-token-p (cdr token))))
-
-(defsubst phpinspect-incomplete-token-p (token)
-  (or (phpinspect-incomplete-root-p token)
-      (phpinspect-incomplete-class-p token)
-      (phpinspect-incomplete-block-p token)
-      (phpinspect-incomplete-list-p token)
-      (phpinspect-incomplete-array-p token)
-      (phpinspect-incomplete-const-p token)
-      (phpinspect-incomplete-function-p token)
-      (phpinspect-incomplete-method-p token)
-      (phpinspect-incomplete-namespace-p token)))
-
-(defun phpinspect--static-terminator-p (token)
-  (or (phpinspect-function-p token)
-      (phpinspect-end-of-token-p token)))
-
-(defun phpinspect--scope-terminator-p (token)
-  (or (phpinspect-function-p token)
-      (phpinspect-end-of-token-p token)
-      (phpinspect-const-p token)
-      (phpinspect-static-p token)))
-
-(defsubst phpinspect-enclosing-token-p (token)
-  "Returns t when a token can enclose other tokens"
-  (or
-   (phpinspect-list-p token)
-   (phpinspect-block-p token)
-   (phpinspect-class-p token)
-   (phpinspect-function-p token)
-   (phpinspect-array-p token)
-   (phpinspect-scope-p token)
-   (phpinspect-static-p token)
-   (phpinspect-const-p token)))
-
-(defun phpinspect-namespace-keyword-p (token)
-  (and (phpinspect-word-p token) (string= (car (last token)) "namespace")))
-
-(defun phpinspect-use-keyword-p (token)
-  (and (phpinspect-word-p token) (string= (car (last token)) "use")))
-
-
-(defsubst phpinspect-root-p (object)
-  (phpinspect-token-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-token-type-p object :use))
-
-(defun phpinspect-comment-p (token)
-  (or (phpinspect-token-type-p token :comment)
-      (phpinspect-token-type-p token :doc-block)))
-
-(defsubst phpinspect-class-block (class)
-  (caddr class))
-
-(define-inline phpinspect-namespace-is-blocked-p (namespace)
-  (inline-letevals (namespace)
-    (inline-quote
-     (and (= (length ,namespace) 3) (phpinspect-block-p (caddr ,namespace))))))
-
-(defsubst phpinspect-namespace-block (namespace)
-  (when (phpinspect-namespace-is-blocked-p 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)))
-
 (defun phpinspect-handler-func-name (handler-name)
   (intern (concat "phpinspect--" (symbol-name handler-name) "-handler")))
 
@@ -715,6 +507,15 @@ parsing incrementally."
       (phpinspect-munch-token-without-attribs (match-string 0) :variable)
     (list :variable nil)))
 
+(phpinspect-defhandler class-variable (start-token &rest _ignored)
+  "Handler for tokens indicating reference to a variable"
+  ((regexp . "\\$"))
+  (forward-char (length start-token))
+  (if (looking-at (phpinspect-handler-regexp word))
+      (phpinspect-munch-token-without-attribs (match-string 0) :class-variable)
+    (list :class-variable nil)))
+
+
 (phpinspect-defhandler whitespace (whitespace &rest _ignored)
   "Handler that discards whitespace"
   ((regexp . "[[:blank:]\n]+"))
@@ -827,7 +628,7 @@ static keywords with the same meaning as in a class block."
 
 (phpinspect-defparser class-block
   :tree-keyword "block"
-  :handlers '(array tag equals list comma attribute-reference variable
+  :handlers '(array tag equals list comma attribute-reference class-variable
                     assignment-operator whitespace scope-keyword static-keyword
                     const-keyword use-keyword function-keyword word terminator
                     here-doc string comment block))
@@ -961,19 +762,19 @@ nature like argument lists"
 
 (phpinspect-defparser scope-public
   :tree-keyword "public"
-  :handlers '(function-keyword static-keyword const-keyword variable here-doc
+  :handlers '(function-keyword static-keyword const-keyword class-variable 
here-doc
                                string terminator tag comment)
   :delimiter-predicate #'phpinspect--scope-terminator-p)
 
 (phpinspect-defparser scope-private
   :tree-keyword "private"
-  :handlers '(function-keyword static-keyword const-keyword variable here-doc
+  :handlers '(function-keyword static-keyword const-keyword class-variable 
here-doc
                                string terminator tag comment)
   :delimiter-predicate #'phpinspect--scope-terminator-p)
 
 (phpinspect-defparser scope-protected
   :tree-keyword "protected"
-  :handlers '(function-keyword static-keyword const-keyword variable here-doc
+  :handlers '(function-keyword static-keyword const-keyword class-variable 
here-doc
                                string terminator tag comment)
   :delimiter-predicate #'phpinspect--scope-terminator-p)
 
@@ -991,7 +792,7 @@ nature like argument lists"
 
 (phpinspect-defparser static
   :tree-keyword "static"
-  :handlers '(comment function-keyword variable array word terminator tag)
+  :handlers '(comment function-keyword class-variable array word terminator 
tag)
   :delimiter-predicate #'phpinspect--static-terminator-p)
 
 (phpinspect-defhandler static-keyword (start-token max-point)
diff --git a/phpinspect-project.el b/phpinspect-project.el
index 0df08d9821..0eb3ac8ce8 100644
--- a/phpinspect-project.el
+++ b/phpinspect-project.el
@@ -56,6 +56,7 @@ indexed classes in the project")
                   :documentation
                   "A hash able that contains all of the currently indexed 
functions
 in the project")
+  (function-token-index (make-hash-table :test 'eq :size 100 :rehash-size 1.5))
   (fs nil
       :type phpinspect-fs
       :documentation
@@ -66,7 +67,10 @@ can be accessed.")
     :documentation
     "The autoload object through which this project's type
 definitions can be retrieved")
-  (worker (phpinspect-make-dynamic-worker)
+  (worker (progn
+            (unless (featurep 'phpinspect-worker)
+              (require 'phpinspect-worker))
+            (phpinspect-make-dynamic-worker))
           :type phpinspect-worker
           :documentation
           "The worker that this project may queue tasks for")
@@ -158,6 +162,10 @@ indexed by the absolute paths of the files they're 
watching."))
   ((project phpinspect-project) (name symbol))
   (gethash name (phpinspect-project-function-index project)))
 
+(cl-defmethod phpinspect-project-delete-function
+  ((project phpinspect-project) (name symbol))
+  (remhash name (phpinspect-project-function-index project)))
+
 (cl-defmethod phpinspect-project-get-functions ((project phpinspect-project))
   (let ((funcs))
     (maphash
@@ -173,6 +181,12 @@ indexed by the absolute paths of the files they're 
watching."))
       (phpinspect--log "Adding import to index queue: %s" import)
       (phpinspect-project-enqueue-if-not-present project (cdr import)))))
 
+(cl-defmethod phpinspect-project-delete-class ((project phpinspect-project) 
(class phpinspect--class))
+  (phpinspect-project-delete-class project (phpinspect--class-name class)))
+
+(cl-defmethod phpinspect-project-delete-class ((project phpinspect-project) 
(class-name phpinspect--type))
+  (remhash (phpinspect--type-name-symbol class-name) 
(phpinspect-project-class-index project)))
+
 (cl-defmethod phpinspect-project-add-class
   ((project phpinspect-project) (indexed-class (head 
phpinspect--indexed-class)) &optional index-imports)
   (let* ((class-name (phpinspect--type-name-symbol
@@ -203,11 +217,12 @@ indexed by the absolute paths of the files they're 
watching."))
     class))
 
 (cl-defmethod phpinspect-project-get-class-create
-  ((project phpinspect-project) (class-fqn phpinspect--type))
+  ((project phpinspect-project) (class-fqn phpinspect--type) &optional 
no-enqueue)
   (let ((class (phpinspect-project-get-class project class-fqn)))
     (unless class
       (setq class (phpinspect-project-create-class project class-fqn))
-      (phpinspect-project-enqueue-if-not-present project class-fqn))
+      (unless no-enqueue
+        (phpinspect-project-enqueue-if-not-present project class-fqn)))
     class))
 
 (defalias 'phpinspect-project-add-class-if-missing 
#'phpinspect-project-get-class-create)
diff --git a/phpinspect-serialize.el b/phpinspect-serialize.el
index 614dc21e68..dc72762cea 100644
--- a/phpinspect-serialize.el
+++ b/phpinspect-serialize.el
@@ -37,6 +37,7 @@
 (cl-defmethod phpinspect--serialize-function ((func phpinspect--function))
   `(phpinspect--make-function
     :name ,(phpinspect--function-name func)
+    :token (quote ,(phpinspect--function-token func))
     :scope (quote ,(phpinspect--function-scope func))
     :arguments ,(append '(list)
                         (mapcar (lambda (arg)
@@ -53,10 +54,11 @@
                                         (phpinspect--variable-type var)))
                               :scope (quote ,(phpinspect--variable-scope 
var))))
 
-
 (cl-defmethod phpinspect--serialize-indexed-class ((class (head 
phpinspect--indexed-class)))
   ``(phpinspect--indexed-class
+     (complete . ,,(alist-get 'complete class))
      (class-name . ,,(phpinspect--serialize-type (alist-get 'class-name 
class)))
+     (declaration . ,(quote ,(alist-get 'declaration class)))
      (imports . ,,(append '(list)
                           (mapcar #'phpinspect--serialize-import
                                   (alist-get 'imports class))))
diff --git a/phpinspect-splayt.el b/phpinspect-splayt.el
index aa3751b45f..4bba8744db 100644
--- a/phpinspect-splayt.el
+++ b/phpinspect-splayt.el
@@ -88,10 +88,10 @@ apeared to be a little more performant than using `let'."
 
 (define-inline phpinspect-make-splayt (&optional root-node)
   (inline-quote
-   (cons ,root-node nil)))
+   (cons 'phpinspect-splayt ,root-node)))
 
 (define-inline phpinspect-splayt-root-node (splayt)
-  (inline-quote (car ,splayt)))
+  (inline-quote (cdr ,splayt)))
 
 (define-inline phpinspect-splayt-empty-p (splayt)
   (inline-quote (not (phpinspect-splayt-root-node ,splayt))))
@@ -166,10 +166,6 @@ apeared to be a little more performant than using `let'."
        (when (and ,splayt (eq ,node (phpinspect-splayt-root-node ,splayt)))
          (setf (phpinspect-splayt-root-node ,splayt) 
(phpinspect-splayt-node-parent ,node)))))))
 
-(define-inline phpinspect-splayt-insert (splayt key value)
-  (inline-quote
-   (phpinspect-splayt-insert-node ,splayt (phpinspect-make-splayt-node ,key 
,value))))
-
 (define-inline phpinspect-splayt-node-grandparent (node)
   (inline-quote (phpinspect-splayt-node-parent (phpinspect-splayt-node-parent 
,node))))
 
@@ -261,7 +257,7 @@ apeared to be a little more performant than using `let'."
 
            (if ,reverse-sym
                (push ,current-sym ,stack-sym)
-             (setf ,place (phpinspect-splayt-node-value ,current-sym))
+             (setf ,place ,current-sym)
              ,@body)
 
            (when (phpinspect-splayt-node-right ,current-sym)
@@ -275,7 +271,7 @@ apeared to be a little more performant than using `let'."
          (when ,reverse-sym
            (while ,stack-sym
              (setq ,current-sym (pop ,stack-sym))
-             (setf ,place (phpinspect-splayt-node-value ,current-sym))
+             (setf ,place ,current-sym)
              ,@body))
 
          (setq ,reverse-sym (not ,reverse-sym)))
@@ -291,9 +287,15 @@ Traversal is breadth-first to take advantage of the splay 
trees
 main benefit: the most accessed interval of keys is likely to be
 near the top of the tee."
   (declare (indent 1))
-  `(phpinspect-splayt-node-traverse
-       (,(car place-and-splayt) (phpinspect-splayt-root-node ,(cadr 
place-and-splayt)))
-     ,@body))
+ (let* ((current (gensym))
+         (code `(phpinspect-splayt-node-traverse
+                    (,current (phpinspect-splayt-root-node ,(cadr 
place-and-splayt)))
+                  (setf ,(car place-and-splayt) (phpinspect-splayt-node-value 
,current))
+                  ,@body)))
+    (if (symbolp (car place-and-splayt))
+        `(let (,(car place-and-splayt))
+           ,code)
+      code)))
 
 (defmacro phpinspect-splayt-node-traverse-lr (place-and-node &rest body)
   (declare (indent 1))
@@ -309,7 +311,7 @@ near the top of the tee."
                (push ,current ,stack)
                (setq ,current (phpinspect-splayt-node-left ,current)))
            (setq ,current (pop ,stack))
-           (setf ,place (phpinspect-splayt-node-value ,current))
+           (setf ,place ,current)
            ,@body
            (setq ,current (phpinspect-splayt-node-right ,current)))))))
 
@@ -319,9 +321,16 @@ near the top of the tee."
 The car of PLACE-AND-SPLAYT is assigned the value of each node.
 The cadr of PLACE-AND-SPLAYT is expected to be a splay tree."
   (declare (indent 1))
-  `(phpinspect-splayt-node-traverse-lr
-       (,(car place-and-splayt) (phpinspect-splayt-root-node ,(cadr 
place-and-splayt)))
-     ,@body))
+  (let* ((current (gensym))
+         (code `(phpinspect-splayt-node-traverse-lr
+                    (,current (phpinspect-splayt-root-node ,(cadr 
place-and-splayt)))
+                  (setf ,(car place-and-splayt) (phpinspect-splayt-node-value 
,current))
+                  ,@body)))
+    (if (symbolp (car place-and-splayt))
+        `(let (,(car place-and-splayt))
+           ,code)
+      code)))
+
 
 (define-inline phpinspect-splayt-node-key-less-p (node key)
   (inline-quote (> ,key (phpinspect-splayt-node-key ,node))))
@@ -412,54 +421,89 @@ The cadr of PLACE-AND-SPLAYT is expected to be a splay 
tree."
 
        largest))))
 
-(defsubst phpinspect-splayt-find-all-after (splayt key)
+(defun phpinspect-splayt-find-all-after (splayt key)
   "Find all values in SPLAYT with a key higher than KEY."
-  (let ((first (phpinspect-splayt-find-smallest-node-after splayt key))
-        all)
+  (let* ((first (phpinspect-splayt-find-smallest-node-after splayt key))
+         (all (cons nil nil))
+         (all-rear all))
     (while first
-      (push (phpinspect-splayt-node-value first) all)
+      (setq all-rear (setcdr all-rear (cons (phpinspect-splayt-node-value 
first) nil)))
 
       (phpinspect-splayt-node-traverse (sibling (phpinspect-splayt-node-right 
first))
-        (setq all (nconc all (list sibling))))
+        (setq all-rear (setcdr all-rear (cons (phpinspect-splayt-node-value 
sibling) nil))))
 
       (if (and (phpinspect-splayt-node-parent first)
                (eq first (phpinspect-splayt-node-left 
(phpinspect-splayt-node-parent first))))
           (setq first (phpinspect-splayt-node-parent first))
         (setq first nil)))
-    all))
-
-(defsubst phpinspect-splayt-find-all-before (splayt key)
+    (cdr all)))
+
+(defun phpinspect-splayt-find-all-between (splayt key-min key-max)
+  (let* ((first (phpinspect-splayt-find-smallest-node-after splayt key-min))
+         (all (cons nil nil))
+         (all-rear all))
+    (catch 'return
+      (while first
+        (setq all-rear (setcdr all-rear (cons (phpinspect-splayt-node-value 
first) nil)))
+
+        (phpinspect-splayt-node-traverse-lr (sibling 
(phpinspect-splayt-node-right first))
+          (when (>= (phpinspect-splayt-node-key sibling) key-max)
+            (throw 'return all))
+          (setq all-rear (setcdr all-rear (cons (phpinspect-splayt-node-value 
sibling) nil))))
+
+        (if (and (phpinspect-splayt-node-parent first)
+                 (eq first (phpinspect-splayt-node-left 
(phpinspect-splayt-node-parent first))))
+            (setq first (phpinspect-splayt-node-parent first))
+          (setq first nil)))
+      (cdr all))))
+
+(defun phpinspect-splayt-find-all-before (splayt key)
   "Find all values in SPLAYT with a key higher than KEY."
-  (let ((first (phpinspect-splayt-find-largest-node-before splayt key))
-        all)
+  (let* ((first (phpinspect-splayt-find-largest-node-before splayt key))
+         (all (cons nil nil))
+         (all-rear all))
     (while first
-      (push (phpinspect-splayt-node-value first) all)
+      (setq all-rear (setcdr all-rear (cons (phpinspect-splayt-node-value 
first) nil)))
 
       (phpinspect-splayt-node-traverse (sibling (phpinspect-splayt-node-left 
first))
-        (setq all (nconc all (list sibling))))
+        (setq all-rear (setcdr all-rear (cons (phpinspect-splayt-node-value 
sibling) nil))))
 
       (if (and (phpinspect-splayt-node-parent first)
                (eq first (phpinspect-splayt-node-right 
(phpinspect-splayt-node-parent first))))
           (setq first (phpinspect-splayt-node-parent first))
         (setq first nil)))
-    all))
+    (cdr all)))
+
+(defun phpinspect-splayt-to-list (tree)
+  "Convert TREE to an ordered list."
+  (let* ((list (cons nil nil))
+         (rear list))
+    (phpinspect-splayt-traverse-lr (val tree)
+      (setq rear (setcdr rear (cons val nil))))
+
+    (cdr list)))
+
+(cl-defmethod seq-do (func (tree (head phpinspect-splayt)))
+  (phpinspect-splayt-traverse-lr (val tree)
+    (funcall func val)))
 
-(define-inline phpinspect-splayt-find-smallest-after (splayt key)
+(defun phpinspect-splayt-find-smallest-after (splayt key)
   "Find value of node with smallest key that is higher than KEY in SPLAYT."
-  (inline-quote
-   (phpinspect-splayt-node-value
-    (phpinspect-splay
-     ,splayt (phpinspect-splayt-find-smallest-node-after ,splayt ,key)))))
+  (phpinspect-splayt-node-value
+   (phpinspect-splay
+    splayt (phpinspect-splayt-find-smallest-node-after splayt key))))
 
-(define-inline phpinspect-splayt-find-largest-before (splayt key)
+(defun phpinspect-splayt-find-largest-before (splayt key)
   "Find value of node with smallest key that is higher than KEY in SPLAYT."
-  (inline-quote
    (phpinspect-splayt-node-value
     (phpinspect-splay
-     ,splayt (phpinspect-splayt-find-largest-node-before ,splayt ,key)))))
-
+     splayt (phpinspect-splayt-find-largest-node-before splayt key))))
 
-(defsubst phpinspect-splayt-find (splayt key)
+(defun phpinspect-splayt-find (splayt key)
   (phpinspect-splayt-node-value (phpinspect-splayt-find-node splayt key)))
 
+(defun phpinspect-splayt-insert (tree key value)
+  "Insert KEY as VALUE into TREE."
+  (phpinspect-splayt-insert-node tree (phpinspect-make-splayt-node key value)))
+
 (provide 'phpinspect-splayt)
diff --git a/phpinspect-toc.el b/phpinspect-toc.el
new file mode 100644
index 0000000000..f27f6dfda0
--- /dev/null
+++ b/phpinspect-toc.el
@@ -0,0 +1,82 @@
+;;; phpinspect-toc.el --- PHP parsing and completion package  -*- 
lexical-binding: t; -*-
+
+;; Copyright (C) 2021  Free Software Foundation, Inc
+
+;; Author: Hugo Thunnissen <de...@hugot.nl>
+;; Keywords: php, languages, tools, convenience
+;; Version: 0
+
+;; 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 'phpinspect-splayt)
+(require 'phpinspect-parser)
+
+(defun phpinspect-make-toc (&optional tree)
+  (let ((table (make-hash-table :test #'eq :size 20 :rehash-size 2.0)))
+    (if tree
+        (phpinspect-splayt-traverse-lr (meta tree)
+          (puthash (phpinspect-meta-token meta) meta table))
+      (setq tree (phpinspect-make-splayt)))
+
+    (list tree table)))
+
+(define-inline phpinspect-toc-register (toc meta)
+  (inline-letevals (toc meta)
+    (inline-quote
+     (progn
+       (phpinspect-splayt-insert (phpinspect-toc-tree ,toc) 
(phpinspect-meta-start ,meta) ,meta)
+       (puthash (phpinspect-meta-token ,meta) ,meta (phpinspect-toc-table 
,toc))))))
+
+(define-inline phpinspect-toc-tree (toc)
+  (inline-quote (car ,toc)))
+
+(define-inline phpinspect-toc-table (toc)
+  (inline-quote (cadr ,toc)))
+
+(defun phpinspect-toc-update (toc new-tree current-root)
+  (let ((current-tree (phpinspect-toc-tree toc))
+        (new-table (make-hash-table :test #'eq :size 20 :rehash-size 2.0))
+        new deleted)
+
+    (phpinspect-splayt-traverse-lr (meta new-tree)
+      (puthash (phpinspect-meta-token meta) meta new-table)
+      (push meta new))
+
+    (phpinspect-splayt-traverse-lr (meta current-tree)
+      (if (eq (phpinspect-meta-find-root meta) current-root)
+          (progn
+            (phpinspect-splayt-insert new-tree (phpinspect-meta-start meta) 
meta)
+            (puthash (phpinspect-meta-token meta) meta new-table))
+        (push meta deleted)))
+
+    (setf (phpinspect-toc-tree toc) new-tree)
+    (setf (phpinspect-toc-table toc) new-table)
+
+    (list new deleted)))
+
+(defun phpinspect-toc-token-at-point (toc point)
+  (let ((result (phpinspect-splayt-find-largest-before (phpinspect-toc-tree 
toc) (+ point 1))))
+    (and result (phpinspect-meta-overlaps-point result point) result)))
+
+(defun phpinspect-toc-token-at-or-after-point (toc point)
+  (phpinspect-splayt-find-smallest-after (phpinspect-toc-tree toc) (- point 
1)))
+
+(defun phpinspect-toc-tokens-in-region (toc start end)
+  (phpinspect-splayt-find-all-between (phpinspect-toc-tree toc) start end))
+
+(provide 'phpinspect-toc)
diff --git a/phpinspect-token-predicates.el b/phpinspect-token-predicates.el
new file mode 100644
index 0000000000..f03883deb2
--- /dev/null
+++ b/phpinspect-token-predicates.el
@@ -0,0 +1,250 @@
+;;; phpinspect-token-predicates.el --- Predicates for phpinspect-parser tokens 
types  -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2021  Free Software Foundation, Inc
+
+;; Author: Hugo Thunnissen <de...@hugot.nl>
+;; Keywords: php, languages, tools, convenience
+;; Version: 0
+
+;; 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:
+
+
+(define-inline phpinspect-token-type-p (object type)
+  "Returns t if OBJECT is a token of type TYPE.
+Type can be any of the token types returned by
+`phpinspect-parse-buffer-until-point`"
+  (inline-letevals (object)
+    (inline-quote
+     (and (listp ,object) (eq (car ,object) ,type)))))
+
+(defsubst phpinspect-object-attrib-p (token)
+  (phpinspect-token-type-p token :object-attrib))
+
+(defsubst phpinspect-static-attrib-p (token)
+  (phpinspect-token-type-p token :static-attrib))
+
+(defsubst phpinspect-attrib-p (token)
+  (or (phpinspect-object-attrib-p token)
+      (phpinspect-static-attrib-p token)))
+
+(defun phpinspect-html-p (token)
+  (phpinspect-token-type-p token :html))
+
+(defun phpinspect-comma-p (token)
+  (phpinspect-token-type-p token :comma))
+
+(defsubst phpinspect-terminator-p (token)
+  (phpinspect-token-type-p token :terminator))
+
+(defsubst phpinspect-end-of-token-p (token)
+  (or (phpinspect-terminator-p token)
+      (phpinspect-comma-p token)
+      (phpinspect-html-p token)))
+
+(defsubst phpinspect-end-of-statement-p (token)
+  (or (phpinspect-end-of-token-p token)
+      (phpinspect-block-p token)))
+
+(defsubst phpinspect-incomplete-block-p (token)
+  (phpinspect-token-type-p token :incomplete-block))
+
+(defsubst phpinspect-block-p (token)
+  (or (phpinspect-token-type-p token :block)
+      (phpinspect-incomplete-block-p token)))
+
+(defun phpinspect-end-of-use-p (token)
+  (or (phpinspect-block-p token)
+      (phpinspect-end-of-token-p token)))
+
+(defun phpinspect-static-p (token)
+  (phpinspect-token-type-p token :static))
+
+(defsubst phpinspect-incomplete-const-p (token)
+  (phpinspect-token-type-p token :incomplete-const))
+
+(defsubst phpinspect-const-p (token)
+  (or (phpinspect-token-type-p token :const)
+      (phpinspect-incomplete-const-p token)))
+
+(define-inline phpinspect-scope-p (token)
+  (inline-letevals (token)
+    (inline-quote
+     (or (phpinspect-token-type-p ,token :public)
+         (phpinspect-token-type-p ,token :private)
+         (phpinspect-token-type-p ,token :protected)))))
+
+(define-inline phpinspect-namespace-p (object)
+  (inline-quote
+   (phpinspect-token-type-p ,object :namespace)))
+
+(defun phpinspect-incomplete-class-p (token)
+  (and (phpinspect-class-p token)
+       (phpinspect-incomplete-block-p (car (last token)))))
+
+(defun phpinspect-incomplete-namespace-p (token)
+  (and (phpinspect-namespace-p token)
+       (or (phpinspect-incomplete-block-p (car (last token)))
+           (phpinspect-incomplete-class-p (car (last token))))))
+
+(define-inline phpinspect-function-p (token)
+  (inline-quote (phpinspect-token-type-p ,token :function)))
+
+(define-inline phpinspect-class-p (token)
+  (inline-quote (phpinspect-token-type-p ,token :class)))
+
+(defun phpinspect-incomplete-method-p (token)
+  (or (phpinspect-incomplete-function-p token)
+      (and (phpinspect-scope-p token)
+           (phpinspect-incomplete-function-p (car (last token))))
+      (and (phpinspect-scope-p token)
+           (phpinspect-static-p (car (last token)))
+           (phpinspect-incomplete-function-p (car (last (car (last token))))))
+      (and (phpinspect-scope-p token)
+           (phpinspect-function-p (car (last token))))))
+
+(defun phpinspect-incomplete-function-p (token)
+  (and (phpinspect-function-p token)
+       (phpinspect-incomplete-block-p (car (last token)))))
+
+(defsubst phpinspect-incomplete-list-p (token)
+  (phpinspect-token-type-p token :incomplete-list))
+
+(defsubst phpinspect-list-p (token)
+  (or (phpinspect-token-type-p token :list)
+      (phpinspect-incomplete-list-p token)))
+
+(define-inline phpinspect-declaration-p (token)
+  (inline-quote
+   (phpinspect-token-type-p ,token :declaration)))
+
+(defsubst phpinspect-assignment-p (token)
+  (phpinspect-token-type-p token :assignment))
+
+(defun phpinspect-function-argument-list (php-func)
+  "Get the argument list of a function"
+  (seq-find #'phpinspect-list-p (seq-find #'phpinspect-declaration-p php-func 
nil) nil))
+
+(defun phpinspect-annotation-p (token)
+  (phpinspect-token-type-p token :annotation))
+
+(defun phpinspect-method-annotation-p (token)
+  (phpinspect-token-type-p token :method-annotation))
+
+(defun phpinspect-var-annotation-p (token)
+  (phpinspect-token-type-p token :var-annotation))
+
+(defun phpinspect-return-annotation-p (token)
+  (phpinspect-token-type-p token :return-annotation))
+
+(define-inline phpinspect-class-variable-p (token)
+  (inline-quote (phpinspect-token-type-p ,token :class-variable)))
+
+(define-inline phpinspect-variable-p (token)
+  (inline-letevals (token)
+    (inline-quote
+     (or (phpinspect-token-type-p ,token :variable)
+         (phpinspect-token-type-p ,token :class-variable)))))
+
+(defsubst phpinspect-word-p (token)
+  (phpinspect-token-type-p token :word))
+
+(defsubst phpinspect-incomplete-array-p (token)
+  (phpinspect-token-type-p token :incomplete-array))
+
+(defsubst phpinspect-array-p (token)
+  (or (phpinspect-token-type-p token :array)
+      (phpinspect-incomplete-array-p token)))
+
+(defsubst phpinspect-incomplete-root-p (token)
+  (and (phpinspect-root-p token)
+       (seq-find #'phpinspect-incomplete-token-p (cdr token))))
+
+(defsubst phpinspect-incomplete-token-p (token)
+  (or (phpinspect-incomplete-root-p token)
+      (phpinspect-incomplete-class-p token)
+      (phpinspect-incomplete-block-p token)
+      (phpinspect-incomplete-list-p token)
+      (phpinspect-incomplete-array-p token)
+      (phpinspect-incomplete-const-p token)
+      (phpinspect-incomplete-function-p token)
+      (phpinspect-incomplete-method-p token)
+      (phpinspect-incomplete-namespace-p token)))
+
+(defun phpinspect--static-terminator-p (token)
+  (or (phpinspect-function-p token)
+      (phpinspect-end-of-token-p token)))
+
+(defun phpinspect--scope-terminator-p (token)
+  (or (phpinspect-function-p token)
+      (phpinspect-end-of-token-p token)
+      (phpinspect-const-p token)
+      (phpinspect-static-p token)))
+
+(defsubst phpinspect-enclosing-token-p (token)
+  "Returns t when a token can enclose other tokens"
+  (or
+   (phpinspect-list-p token)
+   (phpinspect-block-p token)
+   (phpinspect-class-p token)
+   (phpinspect-function-p token)
+   (phpinspect-array-p token)
+   (phpinspect-scope-p token)
+   (phpinspect-static-p token)
+   (phpinspect-const-p token)))
+
+(defun phpinspect-namespace-keyword-p (token)
+  (and (phpinspect-word-p token) (string= (car (last token)) "namespace")))
+
+(defun phpinspect-use-keyword-p (token)
+  (and (phpinspect-word-p token) (string= (car (last token)) "use")))
+
+
+(defsubst phpinspect-root-p (object)
+  (phpinspect-token-type-p object :root))
+
+(defsubst phpinspect-namespace-or-root-p (object)
+  (or (phpinspect-namespace-p object)
+      (phpinspect-root-p object)))
+
+(define-inline phpinspect-use-p (object)
+  (inline-quote (phpinspect-token-type-p ,object :use)))
+
+(defun phpinspect-comment-p (token)
+  (or (phpinspect-token-type-p token :comment)
+      (phpinspect-token-type-p token :doc-block)))
+
+(defsubst phpinspect-class-block (class)
+  (caddr class))
+
+(define-inline phpinspect-namespace-is-blocked-p (namespace)
+  (inline-letevals (namespace)
+    (inline-quote
+     (and (= (length ,namespace) 3) (phpinspect-block-p (caddr ,namespace))))))
+
+(defsubst phpinspect-namespace-block (namespace)
+  (when (phpinspect-namespace-is-blocked-p 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)))
+
+(provide 'phpinspect-token-predicates)
diff --git a/phpinspect-type.el b/phpinspect-type.el
index 8fc60064fa..fbc941524e 100644
--- a/phpinspect-type.el
+++ b/phpinspect-type.el
@@ -206,6 +206,15 @@ NAMESPACE may be nil, or a string with a namespace FQN."
                :type symbol
                :documentation
                "A symbol associated with the name of the function")
+  (token nil
+         :type phpinspect-function-p
+         :documentation
+         "The tokens with which this function was declared.")
+  (-inherited nil
+                 :type boolean
+                 :documentation
+                 "Whether this function has been incorporated into a class as
+method of an extended class.")
   (scope nil
          :type phpinspect-scope
          :documentation
diff --git a/phpinspect-util.el b/phpinspect-util.el
index 190bfec77f..ca5b971243 100644
--- a/phpinspect-util.el
+++ b/phpinspect-util.el
@@ -265,5 +265,16 @@ CONTINUE must be a condition-variable"
   (with-mutex mx (condition-wait continue))
   (phpinspect--log "Thread '%s' continuing execution" (thread-name 
(current-thread))))
 
+(defun phpinspect-namespace-name (namespace)
+  (or (and (phpinspect-namespace-p namespace)
+           (phpinspect-word-p (cadr namespace))
+           (cadadr namespace))
+      ""))
+
+
+(defsubst phpinspect-probably-token-p (token)
+  (and (listp token)
+       (keywordp (car token))))
+
 (provide 'phpinspect-util)
 ;;; phpinspect-util.el ends here
diff --git a/phpinspect-worker.el b/phpinspect-worker.el
index 0d42dd73bf..0a3e112efa 100644
--- a/phpinspect-worker.el
+++ b/phpinspect-worker.el
@@ -152,7 +152,7 @@ already present in the queue."
                           (phpinspect-worker-skip-next-pause worker))
                 (phpinspect-thread-pause phpinspect-worker-pause-time mx 
continue))
               (setf (phpinspect-worker-skip-next-pause worker) nil)))
-        (t (message "Phpinspect worker thread errored :%s" err))))
+        (t (phpinspect--log "Phpinspect worker thread errored :%s" err))))
     (phpinspect--log "Worker thread exiting")
     (message "phpinspect worker thread exited")))
 
diff --git a/phpinspect.el b/phpinspect.el
index e42f92c3e4..f52c98534c 100644
--- a/phpinspect.el
+++ b/phpinspect.el
@@ -79,10 +79,6 @@
 
       (phpinspect-pctx-bmap context))))
 
-(defun phpinspect-after-change-function (start end pre-change-length)
-  (when phpinspect-current-buffer
-    (phpinspect-buffer-register-edit phpinspect-current-buffer start end 
pre-change-length)))
-
 (defun phpinspect--init-mode ()
   "Initialize the phpinspect minor mode for the current buffer."
   (phpinspect-ensure-worker)
@@ -295,20 +291,6 @@ Example configuration for `company-mode':
     (phpinspect--completion-meta
               (phpinspect--completion-list-get-metadata 
phpinspect--last-completion-list arg)))))
 
-(defun phpinspect-purge-cache ()
-  "Assign a fresh, empty cache object to `phpinspect-cache'.
-This effectively purges any cached code information from all
-currently opened projects."
-  (interactive)
-  (when phpinspect-cache
-    ;; Allow currently known cached projects to cleanup after themselves
-    (maphash (lambda (_ project)
-               (phpinspect-project-purge project))
-             (phpinspect--cache-projects phpinspect-cache)))
-
-  ;; Assign a fresh cache object
-  (setq phpinspect-cache (phpinspect--make-cache)))
-
 (defsubst phpinspect-insert-file-contents (&rest args)
   "Call `phpinspect-insert-file-contents-function' with ARGS as arguments."
   (apply phpinspect-insert-file-contents-function args))
diff --git a/test/fixtures/IncompleteClass.eld 
b/test/fixtures/IncompleteClass.eld
index 67684e6872..19627636d9 100644
--- a/test/fixtures/IncompleteClass.eld
+++ b/test/fixtures/IncompleteClass.eld
@@ -1 +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
+(: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/IncompleteClassBlockedNamespace.eld 
b/test/fixtures/IncompleteClassBlockedNamespace.eld
index 2bf9eaa99a..96c2489efd 100644
--- a/test/fixtures/IncompleteClassBlockedNamespace.eld
+++ b/test/fixtures/IncompleteClassBlockedNamespace.eld
@@ -1 +1 @@
-(:root (:word "declare") (:list (:word "strict_types") (:assignment "=")) 
(:terminator ";") (:namespace (:word "App\\Controller") (:incomplete-block 
(: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
+(:root (:word "declare") (:list (:word "strict_types") (:assignment "=")) 
(:terminator ";") (:namespace (:word "App\\Controller") (:incomplete-block 
(: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/IncompleteClassMultipleNamespaces.eld 
b/test/fixtures/IncompleteClassMultipleNamespaces.eld
index f978752445..77449f51c5 100644
--- a/test/fixtures/IncompleteClassMultipleNamespaces.eld
+++ b/test/fixtures/IncompleteClassMultipleNamespaces.eld
@@ -1 +1 @@
-(:root (:word "declare") (:list (:word "strict_types") (:assignment "=")) 
(:terminator ";") (:namespace (:word "Circus\\Artist") (:block (: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") (:terminator  [...]
\ No newline at end of file
+(:root (:word "declare") (:list (:word "strict_types") (:assignment "=")) 
(:terminator ";") (:namespace (:word "Circus\\Artist") (:block (: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") (:terminator  [...]
\ No newline at end of file
diff --git a/test/fixtures/IndexClass1-indexed.eld 
b/test/fixtures/IndexClass1-indexed.eld
index e819b262f6..8f4f9e83d5 100644
--- a/test/fixtures/IndexClass1-indexed.eld
+++ b/test/fixtures/IndexClass1-indexed.eld
@@ -1 +1 @@
-`(phpinspect--root-index (imports \, (list)) (classes \, (list (cons 
(phpinspect--make-type :name "\\App\\Entity\\AuthToken" :collection nil 
:contains nil :fully-qualified t) `(phpinspect--indexed-class (class-name \, 
(phpinspect--make-type :name "\\App\\Entity\\AuthToken" :collection nil 
:contains nil :fully-qualified t)) (imports \, (list (cons 
(phpinspect-intern-name "ORM") (phpinspect--make-type :name 
"\\Doctrine\\ORM\\Mapping" :collection nil :contains nil :fully-qualified t)))) 
(me [...]
\ No newline at end of file
+`(phpinspect--root-index (imports \, (list)) (classes \, (list (cons 
(phpinspect--make-type :name "\\App\\Entity\\AuthToken" :collection nil 
:contains nil :fully-qualified t) `(phpinspect--indexed-class (complete \, t) 
(class-name \, (phpinspect--make-type :name "\\App\\Entity\\AuthToken" 
:collection nil :contains nil :fully-qualified t)) (declaration \, 
'(:declaration (:word "class") (:word "AuthToken"))) (imports \, (list (cons 
(phpinspect-intern-name "ORM") (phpinspect--make-type :nam [...]
\ No newline at end of file
diff --git a/test/fixtures/IndexClass1.eld b/test/fixtures/IndexClass1.eld
index bbecd1cc3a..b9fdeb1e44 100644
--- a/test/fixtures/IndexClass1.eld
+++ b/test/fixtures/IndexClass1.eld
@@ -1 +1 @@
-(:root (:word "declare") (:list (:word "strict_types") (:assignment "=")) 
(:terminator ";") (:namespace (:word "App\\Entity") (:terminator ";") (:use 
(:word "Doctrine\\ORM\\Mapping") (:word "as") (:word "ORM") (:terminator ";")) 
(:doc-block (:annotation "ORM\\Entity")) (:class (:declaration (:word "class") 
(:word "AuthToken")) (:block (:private (:variable "token") (:terminator ";")) 
(:doc-block (:var-annotation (:word "App\\\\Entity\\\\User"))) (:private 
(:variable "user") (:terminator " [...]
\ No newline at end of file
+(:root (:word "declare") (:list (:word "strict_types") (:assignment "=")) 
(:terminator ";") (:namespace (:word "App\\Entity") (:terminator ";") (:use 
(:word "Doctrine\\ORM\\Mapping") (:word "as") (:word "ORM") (:terminator ";")) 
(:doc-block (:annotation "ORM\\Entity")) (:class (:declaration (:word "class") 
(:word "AuthToken")) (:block (:private (:class-variable "token") (:terminator 
";")) (:doc-block (:var-annotation (:word "App\\\\Entity\\\\User"))) (:private 
(:class-variable "user") (: [...]
\ No newline at end of file
diff --git a/test/fixtures/IndexClass2-indexed.eld 
b/test/fixtures/IndexClass2-indexed.eld
index b67f78b5db..4edc18610c 100644
--- a/test/fixtures/IndexClass2-indexed.eld
+++ b/test/fixtures/IndexClass2-indexed.eld
@@ -1 +1 @@
-`(phpinspect--root-index (imports \, (list)) (classes \, (list (cons 
(phpinspect--make-type :name "\\App\\Entity\\AuthToken" :collection nil 
:contains nil :fully-qualified t) `(phpinspect--indexed-class (class-name \, 
(phpinspect--make-type :name "\\App\\Entity\\AuthToken" :collection nil 
:contains nil :fully-qualified t)) (imports \, (list (cons 
(phpinspect-intern-name "ORM") (phpinspect--make-type :name 
"\\Doctrine\\ORM\\Mapping" :collection nil :contains nil :fully-qualified t)))) 
(me [...]
\ No newline at end of file
+`(phpinspect--root-index (imports \, (list)) (classes \, (list (cons 
(phpinspect--make-type :name "\\App\\Entity\\AuthToken" :collection nil 
:contains nil :fully-qualified t) `(phpinspect--indexed-class (complete \, t) 
(class-name \, (phpinspect--make-type :name "\\App\\Entity\\AuthToken" 
:collection nil :contains nil :fully-qualified t)) (declaration \, 
'(:declaration (:word "class") (:word "AuthToken"))) (imports \, (list (cons 
(phpinspect-intern-name "ORM") (phpinspect--make-type :nam [...]
\ No newline at end of file
diff --git a/test/fixtures/IndexClass2.eld b/test/fixtures/IndexClass2.eld
index 37993cc5aa..85057367c8 100644
--- a/test/fixtures/IndexClass2.eld
+++ b/test/fixtures/IndexClass2.eld
@@ -1 +1 @@
-(:root (:word "declare") (:list (:word "strict_types") (:assignment "=")) 
(:terminator ";") (:namespace (:word "App\\Entity") (:terminator ";") (:use 
(:word "Doctrine\\ORM\\Mapping") (:word "as") (:word "ORM") (:terminator ";")) 
(:doc-block (:annotation "ORM\\Entity")) (:class (:declaration (:word "class") 
(:word "AuthToken")) (:block (:private (:variable "token") (:terminator ";")) 
(:private (:variable "extra") (:terminator ";")) (:doc-block (:var-annotation 
(:word "App\\\\Entity\\\\Use [...]
\ No newline at end of file
+(:root (:word "declare") (:list (:word "strict_types") (:assignment "=")) 
(:terminator ";") (:namespace (:word "App\\Entity") (:terminator ";") (:use 
(:word "Doctrine\\ORM\\Mapping") (:word "as") (:word "ORM") (:terminator ";")) 
(:doc-block (:annotation "ORM\\Entity")) (:class (:declaration (:word "class") 
(:word "AuthToken")) (:block (:private (:class-variable "token") (:terminator 
";")) (:private (:class-variable "extra") (:terminator ";")) (:doc-block 
(:var-annotation (:word "App\\\\E [...]
\ No newline at end of file
diff --git a/test/fixtures/NamespacedClass.eld 
b/test/fixtures/NamespacedClass.eld
index f536a1abe0..d8b6d51e24 100644
--- a/test/fixtures/NamespacedClass.eld
+++ b/test/fixtures/NamespacedClass.eld
@@ -1 +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
+(: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/phpinspect-test-env.el b/test/phpinspect-test-env.el
new file mode 100644
index 0000000000..6bb0aef0da
--- /dev/null
+++ b/test/phpinspect-test-env.el
@@ -0,0 +1,41 @@
+;;; phpinspect-test-env.el --- Unit tests for phpinspect.el  -*- 
lexical-binding: t; -*-
+
+(require 'phpinspect-worker)
+(require 'phpinspect-cache)
+
+;; Make sure that the worker is running. TODO: fully encapsulate the worker the
+;; data types that are used in tests so that we don't depend on some global
+;; worker object for tests.
+(phpinspect-ensure-worker)
+(phpinspect-purge-cache)
+
+(defvar phpinspect-test-directory
+  (file-name-directory
+   (or load-file-name
+       buffer-file-name))
+  "Directory that phpinspect tests reside in.")
+
+
+(defvar phpinspect-test-php-file-directory
+  (concat
+   (file-name-directory
+    (or load-file-name
+        buffer-file-name))
+   "/fixtures")
+  "Directory with syntax trees of example PHP files.")
+
+(defun phpinspect-test-read-fixture-data (name)
+  (with-temp-buffer
+    (insert-file-contents-literally (concat phpinspect-test-php-file-directory 
"/" name ".eld"))
+    (read (current-buffer))))
+
+(defun phpinspect-test-read-fixture-serialization (name)
+  (with-temp-buffer
+    (insert-file-contents-literally (concat phpinspect-test-php-file-directory 
"/" name ".eld"))
+    (eval (read (current-buffer)))))
+
+(defun phpinspect-test-parse-fixture-code (name)
+  (phpinspect-parse-file
+   (concat phpinspect-test-php-file-directory "/" name ".php")))
+
+(provide 'phpinspect-test-env)
diff --git a/test/phpinspect-test.el b/test/phpinspect-test.el
index a4e50bb233..88f2070ca0 100644
--- a/test/phpinspect-test.el
+++ b/test/phpinspect-test.el
@@ -26,40 +26,9 @@
 (require 'ert)
 (require 'phpinspect)
 
-;; Make sure that the worker is running. TODO: fully encapsulate the worker the
-;; data types that are used in tests so that we don't depend on some global
-;; worker object for tests.
-(phpinspect-ensure-worker)
-(phpinspect-purge-cache)
-
-(defvar phpinspect-test-directory
-  (file-name-directory
-   (or load-file-name
-       buffer-file-name))
-  "Directory that phpinspect tests reside in.")
-
-
-(defvar phpinspect-test-php-file-directory
-  (concat
-   (file-name-directory
-    (or load-file-name
-        buffer-file-name))
-   "/fixtures")
-  "Directory with syntax trees of example PHP files.")
-
-(defun phpinspect-test-read-fixture-data (name)
-  (with-temp-buffer
-    (insert-file-contents-literally (concat phpinspect-test-php-file-directory 
"/" name ".eld"))
-    (read (current-buffer))))
-
-(defun phpinspect-test-read-fixture-serialization (name)
-  (with-temp-buffer
-    (insert-file-contents-literally (concat phpinspect-test-php-file-directory 
"/" name ".eld"))
-    (eval (read (current-buffer)))))
-
-(defun phpinspect-test-parse-fixture-code (name)
-  (phpinspect-parse-file
-   (concat phpinspect-test-php-file-directory "/" name ".php")))
+(require 'phpinspect-test-env
+         (concat (file-name-directory (or load-file-name buffer-file-name))
+                 "phpinspect-test-env.el"))
 
 (ert-deftest phpinspect-get-variable-type-in-block ()
   (let* ((code "class Foo { function a(\\Thing $baz) { $foo = new 
\\DateTime(); $bar = $foo; Whatever comes after don't matter.")
@@ -544,6 +513,7 @@ class Thing
 (load-file (concat phpinspect-test-directory "/test-parse-context.el"))
 (load-file (concat phpinspect-test-directory "/test-splayt.el"))
 (load-file (concat phpinspect-test-directory "/test-pipeline.el"))
+(load-file (concat phpinspect-test-directory "/test-toc.el"))
 
 
 (provide 'phpinspect-test)
diff --git a/test/test-buffer.el b/test/test-buffer.el
index eee46922f4..53761523b9 100644
--- a/test/test-buffer.el
+++ b/test/test-buffer.el
@@ -26,7 +26,9 @@
 (require 'ert)
 (require 'phpinspect-parser)
 (require 'phpinspect-buffer)
-
+(require 'phpinspect-test-env
+         (concat (file-name-directory (or load-file-name buffer-file-name))
+                 "phpinspect-test-env.el"))
 
 (ert-deftest phpinspect-buffer-region-lookups ()
   (let* ((parsed)
@@ -285,3 +287,185 @@ class YYY {
     (setq parsed-after (phpinspect-buffer-parse buffer 'no-interrupt))
 
     (should (equal parsed parsed-after))))
+
+
+(ert-deftest phpinspect-buffer-index-classes ()
+  (let* ((buffer (phpinspect-make-buffer :project (phpinspect--make-project 
:autoload (phpinspect-make-autoloader))))
+         (namespaces (phpinspect-make-splayt))
+         (declarations (phpinspect-make-splayt))
+         (classes (phpinspect-make-splayt))
+         (root (phpinspect-make-meta nil 1 200 "" 'root)))
+    (phpinspect-splayt-insert
+     namespaces 1 (phpinspect-meta-set-parent
+                   (phpinspect-make-meta nil 1 100 "" '(:namespace (:word 
"TestNamespace") (:terminator ";")))
+                   root))
+    (phpinspect-splayt-insert
+     declarations 20
+     (phpinspect-make-meta nil 20 40 "" '(:declaration (:word "class") (:word 
"TestClass") (:word "extends") (:word "OtherTestClass"))))
+
+
+    (phpinspect-splayt-insert classes 20 (phpinspect-make-meta nil 20 80 "" 
'(:class (:comment "bla") '(:declaration (:word "class") (:word "TestClass") 
(:word "extends") (:word "OtherTestClass")))))
+
+    (phpinspect-buffer-index-declarations buffer declarations)
+    (phpinspect-buffer-index-namespaces buffer namespaces)
+    (phpinspect-buffer-index-classes buffer classes)
+
+    (should (phpinspect-project-get-class (phpinspect-buffer-project buffer) 
(phpinspect--make-type :name "\\TestNamespace\\TestClass")))
+
+    (should (= 2 (hash-table-count (phpinspect-project-class-index 
(phpinspect-buffer-project buffer)))))
+    (should (= 1 (length (phpinspect--class-extended-classes
+                          (phpinspect-project-get-class
+                           (phpinspect-buffer-project buffer)
+                           (phpinspect--make-type :name 
"\\TestNamespace\\TestClass"))))))
+
+    (let ((new-declarations (phpinspect-make-splayt))
+          (new-classes (phpinspect-make-splayt)))
+      (phpinspect-splayt-insert
+       new-declarations
+       20
+       (phpinspect-meta-set-parent
+        (phpinspect-make-meta nil 20 40 "" '(:declaration (:word "class") 
(:word "TestClass")))
+        root))
+
+      (phpinspect-splayt-insert
+       new-classes 20
+       (phpinspect-meta-set-parent
+        (phpinspect-make-meta nil 20 80 "" '(:class (:comment "bla") 
'(:declaration (:word "class") (:word "TestClass"))))
+        root))
+
+      (setf (phpinspect-buffer-map buffer) (phpinspect-make-bmap :-root-meta 
root))
+
+      (phpinspect-buffer-index-declarations buffer new-declarations)
+      (phpinspect-buffer-index-classes buffer new-classes)
+      (should (phpinspect-project-get-class
+               (phpinspect-buffer-project buffer)
+               (phpinspect--make-type :name "\\TestNamespace\\TestClass")))
+
+      (should (= 0 (length (phpinspect--class-extended-classes
+                          (phpinspect-project-get-class
+                           (phpinspect-buffer-project buffer)
+                           (phpinspect--make-type :name 
"\\TestNamespace\\TestClass")))))))
+
+    (let ((new-classes (phpinspect-make-splayt))
+          (new-root (phpinspect-make-meta nil 1 400 "" 'new-root)))
+      (setf (phpinspect-bmap--root-meta (phpinspect-buffer-map buffer)) 
new-root)
+      (phpinspect-buffer-index-classes buffer new-classes)
+
+      (should-not (phpinspect-project-get-class
+                   (phpinspect-buffer-project buffer)
+                   (phpinspect--make-type :name "\\TestNamespace\\TestClass")))
+
+      (should (= 1 (hash-table-count (phpinspect-project-class-index 
(phpinspect-buffer-project buffer))))))))
+
+(ert-deftest phpinspect-buffer-index-functions ()
+  (let ((buffer (phpinspect-make-buffer :project (phpinspect--make-project 
:autoload (phpinspect-make-autoloader))))
+        (namespaces (phpinspect-make-splayt))
+        (declarations  (phpinspect-make-splayt))
+        (classes (phpinspect-make-splayt))
+        (functions (phpinspect-make-splayt)))
+
+    (phpinspect-splayt-insert
+     namespaces 10
+     (phpinspect-make-meta nil 10 200 "" '(:namespace (:word "NS") 
(:terminator ";"))))
+
+
+    (phpinspect-splayt-insert
+     declarations 20
+     (phpinspect-make-meta nil 20 30 "" '(:declaration (:word "class") (:word 
"TestClass"))))
+    (phpinspect-splayt-insert
+     classes 20
+     (phpinspect-make-meta nil 20 70 "" '(:class (:declaration (:word "class") 
(:word "TestClass")))))
+
+
+    (phpinspect-splayt-insert
+     declarations 40
+     (phpinspect-make-meta nil 40 45 "" '(:declaration (:word "testMethod") 
(:list) (:word "RelativeType"))))
+
+    (phpinspect-splayt-insert
+     functions 40
+     (phpinspect-make-meta nil 40 50 "" '(:function (:declaration (:word 
"testMethod") (:list) (:word "RelativeType")))))
+
+    (phpinspect-buffer-index-declarations buffer declarations)
+    (phpinspect-buffer-index-namespaces buffer namespaces)
+    (phpinspect-buffer-index-classes buffer classes)
+
+    (phpinspect-buffer-index-functions buffer functions)
+
+    (should (phpinspect-project-get-class
+             (phpinspect-buffer-project buffer)
+             (phpinspect--make-type :name "\\NS\\TestClass")))
+
+    (should (= 1 (hash-table-count (phpinspect--class-methods
+                                    (phpinspect-project-get-class
+                                     (phpinspect-buffer-project buffer)
+                                     (phpinspect--make-type :name 
"\\NS\\TestClass"))))))
+
+    (setf (phpinspect-buffer-map buffer) (phpinspect-make-bmap :-root-meta 
(phpinspect-make-meta nil 1 400 "" 'root)))
+
+    (phpinspect-buffer-index-functions buffer (phpinspect-make-splayt))
+
+    (should (= 0 (hash-table-count (phpinspect--class-methods
+                                    (phpinspect-project-get-class
+                                     (phpinspect-buffer-project buffer)
+                                     (phpinspect--make-type :name 
"\\NS\\TestClass"))))))))
+
+(ert-deftest phpinspect-buffer-index-class-variables ()
+  (let ((buffer (phpinspect-make-buffer :project (phpinspect--make-project 
:autoload (phpinspect-make-autoloader))))
+        (namespaces (phpinspect-make-splayt))
+        (declarations  (phpinspect-make-splayt))
+        (classes (phpinspect-make-splayt))
+        (functions (phpinspect-make-splayt))
+        (variables (phpinspect-make-splayt)))
+
+    (phpinspect-splayt-insert
+     functions 60
+     (phpinspect-make-meta
+      nil 60 65 ""
+      (cadr (phpinspect-parse-string
+       "<?php function __construct(array $thing) { $this->banana = $thing; 
}"))))
+
+
+    (phpinspect-splayt-insert
+     declarations 20
+     (phpinspect-make-meta nil 20 30 "" '(:declaration (:word "class") (:word 
"TestClass"))))
+    (phpinspect-splayt-insert
+     classes 20
+     (phpinspect-make-meta nil 20 70 "" '(:class (:declaration (:word "class") 
(:word "TestClass")))))
+
+    (phpinspect-splayt-insert
+     variables 33
+     (phpinspect-make-meta nil 33 50 "" '(:class-variable "banana")))
+
+    (phpinspect-splayt-insert
+     variables 54
+     (phpinspect-make-meta nil 54 60 "" '(:const (:word "CONSTANT"))))
+
+    (phpinspect-buffer-index-declarations buffer declarations)
+    (phpinspect-buffer-index-namespaces buffer namespaces)
+    (phpinspect-buffer-index-classes buffer classes)
+    (phpinspect-buffer-index-functions buffer functions)
+
+    (phpinspect-buffer-index-class-variables buffer variables)
+
+    (should (phpinspect-project-get-class
+             (phpinspect-buffer-project buffer)
+             (phpinspect--make-type :name "\\TestClass")))
+
+    (should (= 2 (length (phpinspect--class-variables
+                          (phpinspect-project-get-class
+                           (phpinspect-buffer-project buffer)
+                           (phpinspect--make-type :name "\\TestClass"))))))
+
+
+    (should (= 1 (length (phpinspect--class-get-constants
+                          (phpinspect-project-get-class
+                           (phpinspect-buffer-project buffer)
+                           (phpinspect--make-type :name "\\TestClass"))))))
+
+    (should (phpinspect--type= (phpinspect--make-type :name "\\array")
+                              (phpinspect--variable-type
+                               (phpinspect--class-get-variable
+                                (phpinspect-project-get-class
+                                 (phpinspect-buffer-project buffer)
+                                 (phpinspect--make-type :name "\\TestClass"))
+                                "banana"))))))
diff --git a/test/test-class.el b/test/test-class.el
index 4d69e867b7..0dbfb88319 100644
--- a/test/test-class.el
+++ b/test/test-class.el
@@ -25,6 +25,11 @@
 
 (require 'ert)
 (require 'phpinspect-class)
+(require 'phpinspect-project)
+(require 'subr-x)
+(require 'phpinspect-worker)
+
+(phpinspect-ensure-worker)
 
 (ert-deftest phpinspect--merge-method-return-type ()
   (let* ((class-name (phpinspect--make-type :name "\\Something"))
@@ -40,3 +45,67 @@
                                (phpinspect--function-return-type result)))
     (should (phpinspect--type= (phpinspect--make-type :name "\\bool")
                                (phpinspect--function-return-type method1)))))
+
+(ert-deftest phpinspect-class-incorporate ()
+  (let ((class (phpinspect--make-class-generated))
+        (other-class (phpinspect--make-class-generated)))
+    (phpinspect--class-set-index class `(phpinspect--indexed-class (class-name 
. ,(phpinspect--make-type :name "Class"))))
+    (phpinspect--class-set-index other-class `(phpinspect--indexed-class 
(class-name . ,(phpinspect--make-type :name "OtherClass"))))
+    (phpinspect--class-update-method
+     class (phpinspect--make-function :name "test" :return-type 
phpinspect--null-type))
+
+    (phpinspect--class-update-method
+     other-class (phpinspect--make-function :name "other-test" :return-type 
phpinspect--null-type))
+
+    (phpinspect--class-incorporate class other-class)
+
+    (should (= 2 (length (hash-table-values (phpinspect--class-methods 
class)))))
+    (should (= 1 (length (hash-table-values (phpinspect--class-subscriptions 
other-class)))))
+
+    (phpinspect--class-set-index
+     class
+     `(phpinspect--indexed-class
+       (complete . t)
+       (class-name . ,(phpinspect--make-type :name "Class"))
+       (methods . ,(list (phpinspect--make-function :name "test" :return-type 
phpinspect--null-type)
+                         (phpinspect--make-function :name "foobar" 
:return-type phpinspect--null-type)))))
+
+    (should (= 3 (length (hash-table-values (phpinspect--class-methods 
class)))))
+
+    (phpinspect--class-incorporate class other-class)
+    (should (= 3 (length (hash-table-values (phpinspect--class-methods 
class)))))
+
+    (phpinspect--class-set-index
+     class
+     `(phpinspect--indexed-class
+       (complete . t)
+       (class-name . ,(phpinspect--make-type :name "Class"))
+       (methods . ,(list (phpinspect--make-function :name "foobar" 
:return-type phpinspect--null-type)))))
+
+    (should (= 2 (length (hash-table-values (phpinspect--class-methods 
class)))))
+    (should (phpinspect--class-get-method class (phpinspect-intern-name 
"other-test")))
+    (should (phpinspect--class-get-method class (phpinspect-intern-name 
"foobar")))
+
+    (phpinspect--class-set-index
+     class
+     `(phpinspect--indexed-class
+       (complete . t)
+       (class-name . ,(phpinspect--make-type :name "Class"))
+       (methods)))
+
+    (should (= 1 (length (hash-table-values (phpinspect--class-methods 
class)))))
+    (should (phpinspect--class-get-method class (phpinspect-intern-name 
"other-test")))
+
+    (phpinspect--class-incorporate class other-class)
+    (should (= 1 (length (hash-table-values (phpinspect--class-methods 
class)))))
+    (should (= 1 (length (hash-table-values (phpinspect--class-subscriptions 
other-class)))))))
+
+(ert-deftest phpinspect--class-update-declaration ()
+  (let ((class (phpinspect--make-class-generated :project 
(phpinspect--make-project))))
+    (phpinspect--class-update-declaration class '(:declaration (:word "class") 
(:word "TestClass")
+                                                               (:word 
"extends") (:word "OtherClass")
+                                                               (:word 
"implements") (:word "ImplClass"))
+                                          nil "NS")
+    (should (= 2 (length (phpinspect--class-extended-classes class))))
+    (should (phpinspect--type= (phpinspect--make-type :name "\\NS\\TestClass" 
:fully-qualified t)
+                               (phpinspect--class-name class)))))
diff --git a/test/test-edtrack.el b/test/test-edtrack.el
index 22db225886..b12ddc8360 100644
--- a/test/test-edtrack.el
+++ b/test/test-edtrack.el
@@ -1,3 +1,5 @@
+;;; test-edtrack.el --- Unit tests for phpinspect.el  -*- lexical-binding: t; 
-*-
+
 (require 'ert)
 (require 'phpinspect-edtrack)
 (require 'phpinspect-meta)
diff --git a/test/test-index.el b/test/test-index.el
index ab426f12ac..7bee75d9bd 100644
--- a/test/test-index.el
+++ b/test/test-index.el
@@ -46,13 +46,22 @@
             (classes
              (,(phpinspect--make-type :name"\\Potato" :fully-qualified t)
               phpinspect--indexed-class
+              (complete . t)
               (class-name . ,(phpinspect--make-type :name "\\Potato" 
:fully-qualified t))
+              (declaration . (:declaration (:word "class") (:word "Potato")))
               (location . (0 0))
               (imports)
               (methods)
               (static-methods . (,(phpinspect--make-function
                                    :name "staticMethod"
                                    :scope '(:public)
+                                   :token '(:function (:declaration (:word 
"function")
+                                        (:word "staticMethod")
+                                        (:list (:variable "untyped")
+                                               (:comma)
+                                               (:word "array")
+                                               (:variable "things")))
+                                                      (:block))
                                    :arguments `(("untyped" nil)
                                                 ("things" 
,(phpinspect--make-type :name "\\array"
                                                                                
   :collection t
diff --git a/test/test-meta.el b/test/test-meta.el
new file mode 100644
index 0000000000..f20e3ebe77
--- /dev/null
+++ b/test/test-meta.el
@@ -0,0 +1,67 @@
+;; test-meta.el --- Unit tests for phpinspect.el  -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2021 Free Software Foundation, Inc.
+
+;; 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-meta)
+
+(ert-deftest phpinspect-meta-start-relative-to-parent ()
+  (let ((meta (phpinspect-make-meta nil 10 20 "" 'token))
+        (parent1 (phpinspect-make-meta nil 9 22 "" 'token))
+        (parent2 (phpinspect-make-meta nil 0 100 "" 'token)))
+    (phpinspect-meta-set-parent meta parent1)
+    (phpinspect-meta-set-parent parent1 parent2)
+
+    (should (= 10 (phpinspect-meta-start meta)))
+
+    (phpinspect-meta-shift parent2  20)
+    (should (= 30 (phpinspect-meta-start meta)))
+
+    (should (phpinspect-meta-overlaps-point meta 30))))
+
+(ert-deftest phpinspect-meta-iterator ()
+  (let* ((meta (phpinspect-make-meta nil 10 20 "" 'token))
+         (firstchild (phpinspect-make-meta nil 10 12 "" 'token))
+         (secondchild (phpinspect-make-meta nil 13 15 "" 'token))
+         (parent1 (phpinspect-make-meta nil 9 22 "" 'token))
+         (sibling (phpinspect-make-meta nil 30 55 "" 'token))
+         (parent2 (phpinspect-make-meta nil 0 100 "" 'token))
+         iterator)
+    (phpinspect-meta-set-parent meta parent1)
+    (phpinspect-meta-set-parent parent1 parent2)
+    (phpinspect-meta-set-parent sibling parent2)
+    (phpinspect-meta-set-parent firstchild meta)
+    (phpinspect-meta-set-parent secondchild meta)
+
+    (setq iterator (phpinspect-make-meta-iterator parent2))
+
+    (should (eq meta (phpinspect-meta-iterator-token-at-point iterator 10)))
+    (should (eq sibling (phpinspect-meta-iterator-token-at-point iterator 30)))
+    (should (eq meta (phpinspect-meta-iterator-token-at-point iterator 10)))
+    (should (eq firstchild (phpinspect-meta-iterator-token-at-point iterator 
10)))
+    (should (eq secondchild (phpinspect-meta-iterator-token-at-point iterator 
13)))
+    (should (eq meta (phpinspect-meta-iterator-token-at-point iterator 10)))
+    (should (eq firstchild (phpinspect-meta-iterator-token-at-point iterator 
10)))
+    (should (eq sibling (phpinspect-meta-iterator-token-at-point iterator 
30)))))
diff --git a/test/test-splayt.el b/test/test-splayt.el
index 7eacf7d5ce..cf64dd51d4 100644
--- a/test/test-splayt.el
+++ b/test/test-splayt.el
@@ -163,3 +163,12 @@
 
     (should (equal (sort '("eight" "nine" "eleven" "twelve") #'string-lessp)
                    (sort (phpinspect-splayt-find-all-after tree 7) 
#'string-lessp)))))
+
+(ert-deftest phpinspect-splayt-to-list ()
+  (let ((tree (phpinspect-make-splayt)))
+    (phpinspect-splayt-insert tree 3 "three")
+    (phpinspect-splayt-insert tree 1 "one")
+    (phpinspect-splayt-insert tree 2 "two")
+
+
+    (should (equal '("one" "two" "three") (phpinspect-splayt-to-list tree)))))
diff --git a/test/test-toc.el b/test/test-toc.el
new file mode 100644
index 0000000000..99d758290b
--- /dev/null
+++ b/test/test-toc.el
@@ -0,0 +1,50 @@
+;;; test-edtrack.el --- Unit tests for phpinspect.el  -*- lexical-binding: t; 
-*-
+
+(require 'phpinspect-toc)
+(require 'phpinspect-splayt)
+
+(ert-deftest phpinspect-make-toc ()
+  (let ((tokens (phpinspect-make-splayt))
+        toc)
+    (phpinspect-splayt-insert tokens 1 (phpinspect-make-meta nil 1 20 "" 
'token1))
+    (phpinspect-splayt-insert tokens 40 (phpinspect-make-meta nil 40 45 "" 
'token2))
+    (phpinspect-splayt-insert tokens 55 (phpinspect-make-meta nil 55 70 "" 
'token3))
+
+    (setq toc (phpinspect-make-toc tokens))
+
+    (should (= 3 (hash-table-count (phpinspect-toc-table toc))))
+    (should (= 3 (length (phpinspect-splayt-to-list (phpinspect-toc-tree 
toc)))))))
+
+(ert-deftest phpinspect-update-toc ()
+  (let ((tokens (phpinspect-make-splayt))
+        (root (phpinspect-make-meta nil 1 200 "" 'root))
+        (new-root (phpinspect-make-meta nil 1 400 "" 'root))
+        (tok1 (phpinspect-make-meta nil 1 20 "" 'token1))
+        (tok2 (phpinspect-make-meta nil 40 45 "" 'token2))
+        (tok3 (phpinspect-make-meta nil 55 70 "" 'token3))
+        (tok4 (phpinspect-make-meta nil 71 91 "" 'token4))
+        new-tokens toc)
+
+    (phpinspect-meta-set-parent tok1 root)
+    (phpinspect-meta-set-parent tok2 root)
+    (phpinspect-meta-set-parent tok3 root)
+
+    (phpinspect-splayt-insert tokens 1 tok1)
+    (phpinspect-splayt-insert tokens 40 tok2)
+    (phpinspect-splayt-insert tokens 55 tok3)
+
+    (setq toc (phpinspect-make-toc tokens))
+
+    (phpinspect-meta-set-parent tok2 new-root)
+    (phpinspect-meta-set-parent tok3 new-root)
+    (phpinspect-meta-set-parent tok4 new-root)
+
+    (setq new-tokens (phpinspect-make-splayt))
+    (phpinspect-splayt-insert new-tokens 71 tok4)
+
+    (pcase-let ((`(,result-new ,result-deleted) (phpinspect-toc-update toc 
new-tokens new-root)))
+      (should (= 1 (length result-new)))
+      (should (= 1 (length result-deleted)))
+
+      (should (eq tok1 (car result-deleted)))
+      (should (eq tok4 (car result-new))))))

Reply via email to