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

    WIP: Index types in the background using collaborative threading.
    
    Created queue for types to be indexed in separate thread and tried to limit
    lock-ups as much as possible by using idle timers.
---
 phpinspect-cache.el   |   2 +-
 phpinspect-index.el   | 204 +++++++++++++++++++++++++++++++++++++++-----------
 phpinspect-project.el |  54 +++++++------
 phpinspect-type.el    |  16 +++-
 phpinspect.el         |  17 +++--
 5 files changed, 216 insertions(+), 77 deletions(-)

diff --git a/phpinspect-cache.el b/phpinspect-cache.el
index f546068b0a..6260b662b7 100644
--- a/phpinspect-cache.el
+++ b/phpinspect-cache.el
@@ -56,7 +56,7 @@ then returned.")
   ((cache phpinspect--cache) (project-root string))
   (or (phpinspect--cache-getproject cache project-root)
       (puthash project-root
-               (phpinspect--make-project-cache)
+               (phpinspect--make-project-cache :root project-root)
                (phpinspect--cache-projects cache))))
 
 (provide 'phpinspect-cache)
diff --git a/phpinspect-index.el b/phpinspect-index.el
index c1fd2c9350..077091e72b 100644
--- a/phpinspect-index.el
+++ b/phpinspect-index.el
@@ -338,12 +338,6 @@ namespace if not provided"
   ;; TODO: Implement function indexation
   )
 
-;; (defun phpinspect--get-or-create-index-for-class-file (class-fqn)
-;;   (phpinspect--log "Getting or creating index for %s" class-fqn)
-;;   (phpinspect-get-or-create-cached-project-class
-;;    (phpinspect-project-root)
-;;    class-fqn))
-
 (defun phpinspect-index-file (file-name)
   (phpinspect--index-tokens (phpinspect-parse-file file-name)))
 
@@ -354,34 +348,6 @@ namespace if not provided"
                     project-root)))
       (phpinspect--project-get-class-create project class-fqn))))
 
-    ;; (let ((existing-index (phpinspect-get-cached-project-class
-    ;;                        project-root
-    ;;                        class-fqn)))
-    ;;   (or
-    ;;    existing-index
-    ;;    (progn
-    ;;      (let* ((class-file (phpinspect-class-filepath class-fqn))
-    ;;             (visited-buffer (when class-file (find-buffer-visiting 
class-file)))
-    ;;             (new-index))
-
-    ;;        (phpinspect--log "No existing index for FQN: %s" class-fqn)
-    ;;        (phpinspect--log "filepath: %s" class-file)
-    ;;        (when class-file
-    ;;          (if visited-buffer
-    ;;              (setq new-index (with-current-buffer visited-buffer
-    ;;                                (phpinspect--index-current-buffer)))
-    ;;            (setq new-index (phpinspect-index-file class-file)))
-    ;;          (dolist (class (alist-get 'classes new-index))
-    ;;            (when class
-    ;;              (phpinspect-cache-project-class
-    ;;               project-root
-    ;;               (cdr class))))
-    ;;          (alist-get class-fqn (alist-get 'classes new-index)
-    ;;                     nil
-    ;;                     nil
-    ;;                     #'phpinspect--type=))))))))
-
-
 (defun phpinspect--index-current-buffer ()
   (phpinspect--index-tokens (phpinspect-parse-current-buffer)))
 
@@ -389,15 +355,167 @@ namespace if not provided"
   "Index a PHP file for classes and the methods they have"
   (phpinspect--index-tokens (phpinspect-parse-current-buffer)))
 
-;; (defun phpinspect--get-variables-for-class (buffer-classes class &optional 
static)
-;;   (let ((class-index (or (assoc-default class buffer-classes 
#'phpinspect--type=)
-;;                          (phpinspect--get-or-create-index-for-class-file 
class))))
-;;     (when class-index
-;;       (if static
-;;           (append (alist-get 'static-variables class-index)
-;;                   (alist-get 'constants class-index))
-;;         (alist-get 'variables class-index)))))
-
+(cl-defstruct (phpinspect--queue-item
+               (:constructor phpinspect--make-queue-item))
+  (next nil
+        :type phpinspect--queue-item
+        :documentation
+        "The next item in the queue")
+  (thing nil
+         :type any
+         :documentation
+         "The thing stored in the queue")
+  (previous nil
+            :type phpinspect--queue-item
+            :documentation
+            "The previous item in the queue"))
+
+(cl-defmethod phpinspect--queue-last ((item phpinspect--queue-item))
+  (if (phpinspect--queue-item-next item)
+      (phpinspect--queue-last (phpinspect--queue-item-next item))
+    item))
+
+(cl-defmethod phpinspect--queue-first ((item phpinspect--queue-item))
+  (if (phpinspect--queue-item-previous item)
+      (phpinspect--queue-first (phpinspect--queue-item-previous item))
+    item))
+
+(cl-defmethod phpinspect--queue-enqueue ((item phpinspect--queue-item) thing)
+  (let ((last (phpinspect--queue-last item)))
+    (if (not (phpinspect--queue-item-thing last))
+        (setf (phpinspect--queue-item-thing last) thing)
+  (setf (phpinspect--queue-item-next last)
+        (phpinspect--make-queue-item :previous last :thing thing)))))
+
+(cl-defmethod phpinspect--queue-dequeue ((item phpinspect--queue-item))
+  (let* ((first (phpinspect--queue-first item))
+         (thing (phpinspect--queue-item-thing first))
+         (next (phpinspect--queue-item-next first)))
+    (when next (setf (phpinspect--queue-item-previous next) nil))
+    (cond ((and (eq item first) (not next))
+           (setf (phpinspect--queue-item-thing item)
+                 nil))
+          ((eq item first)
+
+           (setf (phpinspect--queue-item-thing item)
+                 (phpinspect--queue-item-thing next))
+           (setf (phpinspect--queue-item-next item)
+                 (phpinspect--queue-item-next next))))
+    thing))
+
+(cl-defmethod phpinspect--queue-find
+  ((item phpinspect--queue-item) thing comparison-func)
+  (setq item (phpinspect--queue-first item))
+
+  (let ((found))
+    (while (and item (not found))
+      (when (and (phpinspect--queue-item-thing item)
+                 (funcall comparison-func thing (phpinspect--queue-item-thing 
item)))
+        (setq found item))
+
+      (setq item (phpinspect--queue-item-next item)))
+
+    found))
+
+(cl-defmethod phpinspect--queue-enqueue-noduplicate
+  ((item phpinspect--queue-item) thing comparison-func)
+
+  (when (not (phpinspect--queue-find item thing comparison-func))
+    (phpinspect--queue-enqueue item thing)))
+
+(defvar phpinspect--index-queue (phpinspect--make-queue-item)
+  "Queue with indexation tasks. Each task is a list, the car of
+  which is a project directory path and the cadr of which is an
+  instance of `phpinspect--type`.")
+
+(defvar phpinspect--index-thread nil
+  "Thread that executes index tasks from `phpinspect--index-queue`.")
+
+(defvar phpinspect--index-thread-running nil
+  "Thread that executes index tasks from `phpinspect--index-queue`.")
+
+(defun phpinspect--index-thread-function ()
+  (while phpinspect--index-thread-running
+    (let* ((task (phpinspect--queue-dequeue phpinspect--index-queue))
+           (mx (make-mutex))
+           (continue (make-condition-variable mx))
+           (skip-pause))
+      (when task
+        (phpinspect--log "Indexing class %s for project in %s from index 
thread"
+                         (phpinspect--index-task-type task)
+                         (phpinspect--index-task-project-root task))
+
+        (let ((project (phpinspect--cache-get-project-create
+                         (phpinspect--get-or-create-global-cache)
+                         (phpinspect--index-task-project-root task)))
+              (is-native-type (phpinspect--type-is-native
+                               (phpinspect--index-task-type task))))
+          (if is-native-type
+              (progn
+                (phpinspect--log "Skipping indexation of native type %s"
+                                 (phpinspect--index-task-type task))
+                (setq skip-pause t))
+            (let ((type-index (phpinspect--index-type
+                                 project
+                                 (phpinspect--index-task-type task))))
+              (when type-index (phpinspect--project-add-class project 
type-index))))))
+
+          (unless skip-pause
+            (phpinspect--index-thread-pause 1 mx continue))
+          (setq skip-pause nil)))
+  (phpinspect--log "Index thread exiting")
+  (message "phpinspect index thread exited"))
+
+(defun phpinspect--index-thread-pause (pause-time mx continue)
+  (phpinspect--log "Index thead is paused for %d seconds" pause-time)
+  (run-with-idle-timer
+   pause-time
+   nil
+   (lambda () (with-mutex mx (condition-notify continue))))
+  (with-mutex mx (condition-wait continue))
+  (phpinspect--log "Index thread continuing"))
+
+(defun phpinspect--ensure-index-thread ()
+  (interactive)
+  (when (or (not phpinspect--index-thread)
+            (not (thread-alive-p phpinspect--index-thread)))
+    (setq phpinspect--index-thread-running t)
+    (setq phpinspect--index-thread
+          (make-thread #'phpinspect--index-thread-function 
"phpinspect-index-thread"))))
+
+(defun phpinspect--stop-index-thread ()
+  (interactive)
+  (setq phpinspect--index-thread-running nil))
+
+(defalias 'phpinspect--index-task-project-root #'car)
+(defalias 'phpinspect--index-task-type #'cadr)
+
+(defun phpinspect--index-task= (task1 task2)
+  (and (phpinspect--type= (phpinspect--index-task-type task1)
+                          (phpinspect--index-task-type task2))
+       (string= (phpinspect--index-task-project-root task1)
+                (phpinspect--index-task-project-root task2))))
+
+(defsubst phpinspect--make-index-task (project-root type)
+  (list project-root type))
+
+(cl-defmethod phpinspect--index-type ((project phpinspect--project)
+                                      (type phpinspect--type))
+  (let* ((class-file (with-temp-buffer
+                       (cd (phpinspect--project-root project))
+                       (phpinspect-type-filepath type)))
+         (visited-buffer (when class-file (find-buffer-visiting class-file)))
+         (new-index)
+         (class-index))
+    (when class-file
+      (if visited-buffer
+          (setq new-index (with-current-buffer visited-buffer
+                            (phpinspect--index-current-buffer)))
+        (setq new-index (phpinspect-index-file class-file)))
+            (alist-get type (alist-get 'classes new-index)
+                       nil
+                       nil
+                       #'phpinspect--type=))))
 
 (provide 'phpinspect-index)
 ;;; phpinspect-index.el ends here
diff --git a/phpinspect-project.el b/phpinspect-project.el
index 2dfa5efce1..478379433e 100644
--- a/phpinspect-project.el
+++ b/phpinspect-project.el
@@ -31,12 +31,26 @@
                :type hash-table
                :documentation
                "A `hash-table` that contains all of the currently
-indexed classes in the project"))
+indexed classes in the project")
+  (root nil
+        :type string
+        :documentation
+        "The root directory of this project"))
 
 (cl-defgeneric phpinspect--project-add-class
     ((project phpinspect--project) (class (head phpinspect--indexed-class)))
   "Add an indexed CLASS to PROJECT.")
 
+(cl-defmethod phpinspect--project-add-return-types-to-index-queueue
+  ((project phpinspect--project) methods)
+  (dolist (method methods)
+    (when (not (phpinspect--project-get-class project 
(phpinspect--function-return-type method)))
+      (phpinspect--queue-enqueue-noduplicate phpinspect--index-queue
+                                             (phpinspect--make-index-task
+                                              (phpinspect--project-root 
project)
+                                              
(phpinspect--function-return-type method))
+                                             #'phpinspect--index-task=))))
+
 (cl-defmethod phpinspect--project-add-class
   ((project phpinspect--project) (indexed-class (head 
phpinspect--indexed-class)))
   (let* ((class-name (phpinspect--type-name-symbol
@@ -44,10 +58,17 @@ indexed classes in the project"))
          (existing-class (gethash class-name
                                   (phpinspect--project-class-index project))))
     (if existing-class
-        (phpinspect--class-set-index existing-class indexed-class)
+        (progn
+          (phpinspect--class-set-index existing-class indexed-class)
+          (phpinspect--project-add-return-types-to-index-queueue
+           project
+           (phpinspect--class-get-method-list existing-class)))
       (let ((new-class (phpinspect--make-class-generated :project project)))
         (phpinspect--class-set-index new-class indexed-class)
-        (puthash class-name new-class (phpinspect--project-class-index 
project))))))
+        (puthash class-name new-class (phpinspect--project-class-index 
project))
+        (phpinspect--project-add-return-types-to-index-queueue
+         project
+         (phpinspect--class-get-method-list new-class))))))
 
 (cl-defgeneric phpinspect--project-get-class
     ((project phpinspect--project) (class-fqn phpinspect--type))
@@ -61,28 +82,15 @@ indexed classes in the project"))
       (puthash (phpinspect--type-name-symbol class-fqn)
                class
                (phpinspect--project-class-index project))
-
-      (let* ((class-file (phpinspect-class-filepath class-fqn))
-             (visited-buffer (when class-file (find-buffer-visiting 
class-file)))
-             (new-index)
-             (class-index))
-
-        (phpinspect--log "No existing index for FQN: %s" class-fqn)
-        (phpinspect--log "filepath: %s" class-file)
-        (when class-file
-          (if visited-buffer
-              (setq new-index (with-current-buffer visited-buffer
-                                (phpinspect--index-current-buffer)))
-            (setq new-index (phpinspect-index-file class-file)))
-          (setq class-index
-                (alist-get class-fqn (alist-get 'classes new-index)
-                           nil
-                           nil
-                           #'phpinspect--type=))
-          (when class-index
-            (phpinspect--class-set-index class class-index)))))
+      (phpinspect--queue-enqueue-noduplicate
+       phpinspect--index-queue
+       (phpinspect--make-index-task (phpinspect--project-root project)
+                                    class-fqn)
+       #'phpinspect--index-task=))
     class))
 
+(defalias 'phpinspect--project-add-class-if-missing 
#'phpinspect--project-get-class-create)
+
 (cl-defmethod phpinspect--project-get-class
   ((project phpinspect--project) (class-fqn phpinspect--type))
   (gethash (phpinspect--type-name-symbol class-fqn)
diff --git a/phpinspect-type.el b/phpinspect-type.el
index e9f628652c..2cfa88cf2d 100644
--- a/phpinspect-type.el
+++ b/phpinspect-type.el
@@ -49,11 +49,11 @@
   `(phpinspect--make-type-generated
     ,@(phpinspect--wrap-plist-name-in-symbol property-list)))
 
-(defun phpinspect--make-types (&rest type-names)
+(defun phpinspect--make-types (type-names)
   (mapcar (lambda (name) (phpinspect--make-type :name name))
           type-names))
 
-(defconst phpinspect-native-types
+(defconst phpinspect-native-typenames
   ;; self, parent and resource are not valid type name.
   ;; see https://www.php.net/manual/ja/language.types.declarations.php
   ;;;
@@ -61,6 +61,10 @@
   ;; list of type names that we should not attempt to resolve relatively.
   '("array" "bool" "callable" "float" "int" "iterable" "mixed" "object" 
"string" "void" "self" "static" "this"))
 
+(defconst phpinspect-native-types
+  (phpinspect--make-types (mapcar (lambda (name) (concat "\\" name))
+                                  phpinspect-native-typenames)))
+
 (defvar phpinspect-collection-types
   (phpinspect--make-types '("\\array" "\\iterable" "\\SplObjectCollection" 
"\\mixed"))
   "FQNs of types that should be treated as collecitons when inferring types.")
@@ -80,6 +84,12 @@ See https://wiki.php.net/rfc/static_return_type ."
   (or (phpinspect--type= type phpinspect--static-type)
       (phpinspect--type= type phpinspect--this-type)))
 
+(defsubst phpinspect--type-is-native (type)
+  (catch 'found
+    (dolist (native phpinspect-native-types)
+      (when (phpinspect--type= type native)
+        (throw 'found t)))))
+
 (cl-defmethod phpinspect--type-name ((type phpinspect--type))
   (symbol-name (phpinspect--type-name-symbol type)))
 
@@ -103,7 +113,7 @@ NAMESPACE may be nil, or a string with a namespace FQN."
          type)
 
         ;; Native type
-        ((member type phpinspect-native-types)
+        ((member type phpinspect-native-typenames)
          (concat "\\" type))
 
         ;; Relative FQN
diff --git a/phpinspect.el b/phpinspect.el
index 20dc8a84f7..ae44c45434 100644
--- a/phpinspect.el
+++ b/phpinspect.el
@@ -1,4 +1,4 @@
-;;; phpinspect.el --- PHP parsing and completion package  -*- lexical-binding: 
t; -*-
+;; phpinspect.el --- PHP parsing and completion package  -*- lexical-binding: 
t; -*-
 
 ;; Copyright (C) 2021  Free Software Foundation, Inc
 
@@ -36,6 +36,7 @@
 (require 'phpinspect-util)
 (require 'phpinspect-type)
 (require 'phpinspect-index)
+(require 'phpinspect-class)
 
 (defvar-local phpinspect--buffer-index nil
   "The result of the last successfull parse + index action
@@ -51,7 +52,7 @@ phpinspect")
 (defvar phpinspect-project-root-function #'phpinspect--find-project-root
   "Function that phpinspect uses to find the root directory of a project.")
 
-(defvar phpinspect-class-filepath-function #'phpinspect-get-class-filepath
+(defvar phpinspect-type-filepath-function #'phpinspect-get-class-filepath
   "Function that phpinspect uses to find the filepath of a class by its FQN.")
 
 (defvar phpinspect-project-root-file-list
@@ -711,6 +712,8 @@ more recent"
   (eldoc-add-command 'c-electric-backspace)
 
   (phpinspect--after-save-action)
+  (phpinspect--ensure-index-thread)
+
   (add-hook 'after-save-hook #'phpinspect--after-save-action nil 'local))
 
 (defun phpinspect--after-save-action ()
@@ -1222,7 +1225,7 @@ available FQNs in a project.  This may require
 project directory before it can be used."
   (interactive (list (phpinspect--make-type
                       :name (completing-read "Class: " 
(phpinspect-get-all-fqns)))))
-  (find-file (phpinspect-class-filepath fqn)))
+  (find-file (phpinspect-type-filepath fqn)))
 
 (defun phpinspect-find-own-class-file (fqn)
   "`phpinspect-find-class-file', but for non-vendored classes.
@@ -1233,11 +1236,11 @@ located in \"vendor\" folder."
   (interactive (list (phpinspect--make-type
                       :name
                       (completing-read "Class: " (phpinspect-get-all-fqns 
"uses_own")))))
-  (find-file (phpinspect-class-filepath fqn)))
+  (find-file (phpinspect-type-filepath fqn)))
 
-(defsubst phpinspect-class-filepath (fqn)
-  "Call `phpinspect-class-filepath-function' with FQN as argument."
-  (funcall phpinspect-class-filepath-function fqn))
+(defsubst phpinspect-type-filepath (fqn)
+  "Call `phpinspect-type-filepath-function' with FQN as argument."
+  (funcall phpinspect-type-filepath-function fqn))
 
 (defun phpinspect-get-class-filepath (class &optional index-new)
   "Retrieve filepath to CLASS definition file.

Reply via email to