branch: externals/phpinspect
commit 341afd42da277c71b151564034c2d5f55961159f
Author: Hugo Thunnissen <[email protected]>
Commit: Hugo Thunnissen <[email protected]>
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.