branch: externals/phpinspect commit a66e35f57ca48289a22b8c15b1d2a62dfff10070 Author: Hugo Thunnissen <de...@hugot.nl> Commit: Hugo Thunnissen <de...@hugot.nl>
Test and improve typedef index reactivity and lazy loading + fix a bunch of bugs --- phpinspect-completion.el | 3 +- phpinspect-index.el | 12 ++-- phpinspect-method-cell.el | 64 ++++++++++++++-------- phpinspect-pipeline.el | 4 +- phpinspect-project.el | 69 +++++++++++------------ phpinspect-resolve.el | 4 +- phpinspect-suggest.el | 2 +- phpinspect-type.el | 7 +++ phpinspect-typedef.el | 81 ++++++++++++++++++++++----- test/test-project.el | 101 ++++++++++++++++++++++++++++++++++ test/test-typedef.el | 136 ++++++++++++++++++++++++++++++++++++++++++++++ 11 files changed, 400 insertions(+), 83 deletions(-) diff --git a/phpinspect-completion.el b/phpinspect-completion.el index 15a585ec64..a5c0821e57 100644 --- a/phpinspect-completion.el +++ b/phpinspect-completion.el @@ -180,7 +180,8 @@ belonging to a token that conforms with `phpinspect-attrib-p'" (phpinspect-completion-query-buffer q) (phpinspect-completion-query-point q) #'phpinspect-static-attrib-p))) - (phpinspect--log "Returning region for attribute access subject %s" (phpinspect-meta-string subject)) + (phpinspect--log "[comp-static-attribute] Returning region for attribute access subject %s" + (phpinspect-meta-string subject)) (list (phpinspect-attrib-start subject) (phpinspect-meta-end subject)))) (cl-defmethod phpinspect-comp-strategy-execute diff --git a/phpinspect-index.el b/phpinspect-index.el index 59f55aa374..60a5e45411 100644 --- a/phpinspect-index.el +++ b/phpinspect-index.el @@ -160,7 +160,8 @@ function (think \"new\" statements, return types etc.)." (cadadr (seq-find #'phpinspect-return-annotation-p comment-before)))) (if type (phpinspect--apply-annotation-type return-annotation-type type type-resolver) - (setq type (funcall type-resolver (phpinspect--make-type :name return-annotation-type))))) + (when (stringp return-annotation-type) + (setq type (funcall type-resolver (phpinspect--make-type :name return-annotation-type)))))) (when-let ((throw-annotations (seq-filter #'phpinspect-throws-annotation-p comment-before))) (dolist (tr throw-annotations) @@ -410,7 +411,6 @@ SCOPE should be a scope token (`phpinspect-scope-p')." add-used-types) methods)) ((phpinspect-doc-block-p token) - (phpinspect--log "setting comment-before %s" token) (setq comment-before token)) ;; Prevent comments from sticking around too long @@ -420,7 +420,6 @@ SCOPE should be a scope token (`phpinspect-scope-p')." (nconc trait-config (phpinspect--index-trait-use token type-resolver add-used-types)))) (t - (phpinspect--log "Unsetting comment-before") (setq comment-before nil)))) ;; Dirty hack that assumes the constructor argument names to be the same as the object @@ -437,7 +436,7 @@ SCOPE should be a scope token (`phpinspect-scope-p')." constructor-sym)) methods))) (when constructor - (phpinspect--log "Constructor was found") + (phpinspect--log "Constructor was found for %s" class-name) (dolist (variable variables) (when (not (phpinspect--variable-type variable)) (phpinspect--log "Looking for variable type in constructor arguments (%s)" @@ -460,7 +459,7 @@ SCOPE should be a scope token (`phpinspect-scope-p')." `(,class-name . (phpinspect--indexed-class (complete . ,(not (phpinspect-incomplete-class-p class))) - (trait-confg . ,trait-config) + (trait-config . ,trait-config) (class-name . ,class-name) (declaration . ,(seq-find #'phpinspect-declaration-p class)) (location . ,(funcall location-resolver class)) @@ -765,7 +764,8 @@ Returns a list of type name strings." (push `(alias ,(cadadr (cadr block?)) ,(cadr (nth 3 block?))) (cdr t-config)))) - (setq block? (nthcdr 4 block?)))))) + (setq block? (nthcdr 4 block?))) + (t (pop block?))))) (when add-used-types (funcall add-used-types used-types)) diff --git a/phpinspect-method-cell.el b/phpinspect-method-cell.el index 0fe412fe24..59c06aa799 100644 --- a/phpinspect-method-cell.el +++ b/phpinspect-method-cell.el @@ -37,6 +37,8 @@ :type phpinspect-name) (origin-type nil :type phpinspect--type) + (-return-type nil + :type phpinspect--type) (definition nil :type phpinspect--function)) @@ -123,27 +125,44 @@ Also updates all members of MCOL with the same origin-type." list)) (defun phpi-method-set-return-type (method type) - (setf (phpinspect--function-return-type (phpi-method-definition method)) - type)) + (setf (phpi-method--return-type method) type)) + +(defun phpi-method-original-return-type (method) + (phpinspect--function-return-type (phpi-method-definition method))) + +(defun phpi-method-does-late-static-binding (method) + (when-let ((og-type (phpi-method-original-return-type method))) + (phpinspect--type-does-late-static-binding og-type))) + +(defun phpi-method-resolve-late-static-binding (method home-type) + "Destructively modify METHOD to return HOME-TYPE if late static binding." + (when-let ((og-type (phpi-method-original-return-type method))) + (phpi-method-set-return-type + method (phpinspect--resolve-late-static-binding og-type home-type)))) (defun phpi-mc-set (cell home-type origin-type method) - (if (phpinspect--type= home-type origin-type) - ;; Method belongs to home type - (progn - (when (and method - (phpi-method-return-type method) - (phpinspect--type-does-late-static-binding (phpi-method-return-type method))) - (phpi-method-set-return-type - method (phpinspect--resolve-late-static-binding - (phpi-method-return-type method) home-type))) - - (setf (phpi-mc-own cell) method)) - ;; Method is from a trait, interface or inherited - (pcase (phpinspect--type-category origin-type) - ('trait (setf (phpi-mc-trait cell) method)) - ('interface (setf (phpi-mc-interface cell) method)) - ;; class or abstract class - (_ (setf (phpi-mc-inherited cell) method))))) + (let ((late-static-binding (and method (phpi-method-does-late-static-binding method)))) + (if (phpinspect--type= home-type origin-type) + ;; Method belongs to home type + (progn + (when late-static-binding + (phpi-method-resolve-late-static-binding method home-type)) + + (setf (phpi-mc-own cell) method)) + + ;; resolve late static binding + (when late-static-binding + ;; Copy on write (we don't want to change the return type for the + ;; original typedef as well). + (setq method (phpi-copy-method method)) + (phpi-method-resolve-late-static-binding method home-type)) + + ;; Method is from a trait, interface or inherited + (pcase (phpinspect--type-category origin-type) + ('trait (setf (phpi-mc-trait cell) method)) + ('interface (setf (phpi-mc-interface cell) method)) + ;; class or abstract class + (_ (setf (phpi-mc-inherited cell) method)))))) (defun phpi-mc-get-for-type-category (cell home-type type) (if (phpinspect--type= home-type type) @@ -198,7 +217,8 @@ https://www.php.net/manual/en/language.oop5.traits.php" (phpi-try-method-return-type (phpi-mc-interface cell)))) (defun phpi-method-return-type (method) - (phpinspect--function-return-type (phpi-method-definition method))) + (or (phpi-method--return-type method) + (phpinspect--function-return-type (phpi-method-definition method)))) (defun phpi-mcol-delete-for-type (mcol type &optional name) "Delete from MCOL all methods that originate from TYPE. @@ -261,10 +281,10 @@ return types in extended classes, traits and interfaces." (phpinspect--function-arguments fn)) (cl-defmethod phpi-fn-return-type ((fn phpinspect--function)) - (phpinspect--function-return-type fn)) + (or (phpinspect--function-return-type fn) phpinspect--unknown-type)) (cl-defmethod phpi-fn-return-type ((method phpinspect-method)) - (phpi-method-return-type method)) + (or (phpi-method-return-type method) phpinspect--unknown-type)) (cl-defmethod phpi-fn-argument-type ((fn phpinspect--function) argument-name) (phpinspect--function-argument-type fn argument-name)) diff --git a/phpinspect-pipeline.el b/phpinspect-pipeline.el index b09029ab1c..c71bf82713 100644 --- a/phpinspect-pipeline.el +++ b/phpinspect-pipeline.el @@ -176,7 +176,7 @@ directories." (phpinspect-pipeline--register-wakeup-function ,inc-queue) (while ,continue-running - (condition-case-unless-debug err + (condition-case err (progn (phpinspect-pipeline-pause) ;; Prevent quitting during step execution, as this could @@ -208,7 +208,7 @@ directories." (quit (ignore-error phpinspect-pipeline-incoming (phpinspect-pipeline-pause))) (phpinspect-pipeline-incoming) - (t (phpinspect--log "Pipeline thread errored: %s" err) + (t (phpinspect-message "Pipeline thread errored: %s" err) (setq ,end (phpinspect-make-pipeline-end :thread (current-thread) :error err)) (setq ,continue-running nil) (phpinspect-pipeline-ctx-register-end ,pctx-sym ,end) diff --git a/phpinspect-project.el b/phpinspect-project.el index ef38af5245..0aaf24c592 100644 --- a/phpinspect-project.el +++ b/phpinspect-project.el @@ -101,27 +101,18 @@ serious performance hits. Enable at your own risk (:") (phpinspect-worker-enqueue (phpinspect-project-worker project) (phpinspect-make-index-task project type)))))))) -(cl-defmethod phpinspect-project-add-typedef-attribute-types-to-index-queue - ((project phpinspect-project) (typedef phpinspect-typedef)) - (phpinspect-project-edit project - (phpinspect-project-add-return-types-to-index-queueue - project - (phpi-typedef-get-methods typedef)) - (phpinspect-project-add-return-types-to-index-queueue - project - (phpi-typedef-get-static-methods typedef)) - (phpinspect-project-add-variable-types-to-index-queue - project - (phpi-typedef-variables typedef)))) +(defun phpinspect-project-enqueue-types (project types) + (dolist (type types) + (phpinspect-project-enqueue-if-not-present project type))) (cl-defmethod phpinspect-project-add-index - ((project phpinspect-project) (index (head phpinspect--root-index)) &optional index-imports) + ((project phpinspect-project) (index (head phpinspect--root-index)) &optional index-dependencies) (phpinspect-project-edit project - (when index-imports + (when index-dependencies (phpinspect-project-enqueue-imports project (alist-get 'imports (cdr index)))) (dolist (indexed-typedef (alist-get 'classes (cdr index))) - (phpinspect-project-add-typedef project (cdr indexed-typedef) index-imports)) + (phpinspect-project-add-typedef project (cdr indexed-typedef) index-dependencies)) (dolist (func (alist-get 'functions (cdr index))) (phpinspect-project-set-function project func)))) @@ -193,7 +184,7 @@ serious performance hits. Enable at your own risk (:") (remhash (phpinspect--type-name-symbol typedef-name) (phpinspect-project-typedef-index project)))) (cl-defmethod phpinspect-project-add-typedef - ((project phpinspect-project) (indexed-typedef (head phpinspect--indexed-class)) &optional index-imports) + ((project phpinspect-project) (indexed-typedef (head phpinspect--indexed-class)) &optional index-dependencies) (phpinspect-project-edit project (if (not (alist-get 'class-name (cdr indexed-typedef))) (phpinspect--log "Error: Typedef with declaration %s does not have a name" (alist-get 'declaration indexed-typedef)) @@ -203,16 +194,15 @@ serious performance hits. Enable at your own risk (:") (typedef (gethash typedef-name (phpinspect-project-typedef-index project)))) (unless typedef - (setq typedef (phpinspect-make-typedef typedef-type-name (phpinspect-project-make-typedef-retriever project)))) - ;; :typedef-retriever (phpinspect-project-make-typedef-retriever project)))) - - (when index-imports - (phpinspect-project-enqueue-imports - project (alist-get 'imports (cdr indexed-typedef)))) + (setq typedef (phpinspect-make-typedef + typedef-type-name (phpinspect-project-make-typedef-retriever project)))) (phpi-typedef-set-index typedef indexed-typedef) - (puthash typedef-name typedef (phpinspect-project-typedef-index project)) - (phpinspect-project-add-typedef-attribute-types-to-index-queue project typedef))))) + + (when index-dependencies + (phpinspect-project-enqueue-types project (phpi-typedef-get-dependencies typedef))) + + (puthash typedef-name typedef (phpinspect-project-typedef-index project)))))) (cl-defmethod phpinspect-project-set-typedef ((project phpinspect-project) (typedef-fqn phpinspect--type) (typedef phpinspect-typedef)) @@ -249,11 +239,8 @@ indexation, but indexed synchronously before returning." no-enqueue (phpi-typedef-initial-index typedef)) (phpinspect-project-edit project - (when (and no-enqueue (phpi-typedef-initial-index typedef)) - (phpinspect--log "Indexing type file for %s" typedef-fqn) - (phpinspect-project-add-index - project - (phpinspect-project-index-type-file project typedef-fqn)))) + (when no-enqueue + (phpinspect-project-ensure-index-typedef-and-dependencies project typedef))) typedef)) (cl-defmethod phpinspect-project-get-typedef-extra-or-create @@ -261,6 +248,22 @@ indexation, but indexed synchronously before returning." (or (phpinspect-project-get-typedef-or-extra project typedef-fqn no-enqueue) (phpinspect-project-get-typedef-create project typedef-fqn no-enqueue))) +(defun phpinspect-project-ensure-index-typedef-and-dependencies (project typedef) + (unless (phpi-typedef-initial-index typedef) + (phpinspect-project-add-index + project + (phpinspect-project-index-type-file project (phpi-typedef-name typedef)))) + + (dolist (dep (phpi-typedef-get-dependencies typedef)) + (unless (phpi-typedef-initial-index + (phpinspect-project-get-typedef-create project dep)) + (phpinspect-project-add-index + project + (phpinspect-project-index-type-file project dep)))) + + (dolist (extended (phpi-typedef-subscribed-to-types typedef)) + (phpinspect-project-ensure-index-typedef-and-dependencies + project (phpinspect-project-get-typedef-create project extended)))) (cl-defmethod phpinspect-project-get-typedef ((project phpinspect-project) (typedef-fqn phpinspect--type) &optional index) @@ -272,11 +275,9 @@ indexation, but indexed synchronously before returning." (not (phpi-typedef-read-only-p typedef))) (setf (phpi-typedef-read-only-p typedef) t)) - (when (and index (not (phpi-typedef-initial-index typedef))) - (phpinspect-project-add-index - project - (phpinspect-project-index-type-file project typedef-fqn)))) + (when index + (phpinspect-project-ensure-index-typedef-and-dependencies project typedef))) typedef)) (cl-defmethod phpinspect-project-get-typedef-or-extra @@ -404,7 +405,7 @@ before the search is executed." (let* ((type (phpinspect-index-task-type task)) (root-index (phpinspect-project-index-type-file project type))) (when root-index - (phpinspect-project-add-index project root-index))))))) + (phpinspect-project-add-index project root-index 'index-dependencies))))))) ;;; INDEX FILE TASK (cl-defstruct (phpinspect-index-dir-task (:constructor phpinspect-make-index-dir-task)) diff --git a/phpinspect-resolve.el b/phpinspect-resolve.el index 01c05ea26d..f27cc4bcb6 100644 --- a/phpinspect-resolve.el +++ b/phpinspect-resolve.el @@ -561,8 +561,8 @@ type. (`phpinspect--type-p')" (setq type-resolver (phpinspect--make-type-resolver-for-resolvecontext resolvecontext))) - (phpinspect--log "Looking for type of statement: %s in nested token" - (phpinspect--resolvecontext-subject resolvecontext)) + (phpinspect--log "Looking for type of statement: %s in nested token, assume-derived is: %s" + (phpinspect--resolvecontext-subject resolvecontext) assume-derived) ;; Find all enclosing tokens that aren't classes. Classes do not contain variable ;; assignments which have effect in the current scope, which is what we're trying ;; to find here to infer the statement type. diff --git a/phpinspect-suggest.el b/phpinspect-suggest.el index 1e2d93202e..d6f8c56240 100644 --- a/phpinspect-suggest.el +++ b/phpinspect-suggest.el @@ -147,7 +147,7 @@ static variables and static methods." static)) (statement-type (phpinspect-resolve-type-from-context resolvecontext - type-resolver))) + type-resolver static))) (phpinspect--log "Statement type: %s" statement-type) (when statement-type diff --git a/phpinspect-type.el b/phpinspect-type.el index e3cb085004..304b8ee626 100644 --- a/phpinspect-type.el +++ b/phpinspect-type.el @@ -85,6 +85,7 @@ that the collection is expected to contain") (defvar phpinspect--self-type (phpinspect--make-type :name "\\self" :fully-qualified t)) (defvar phpinspect--this-type (phpinspect--make-type :name "\\this" :fully-qualified t)) (defvar phpinspect--null-type (phpinspect--make-type :name "\\null" :fully-qualified t)) +(defvar phpinspect--unknown-type (phpinspect--make-type :name "unknown-type")) (defun phpinspect-define-standard-types () (setq phpinspect-native-types @@ -243,6 +244,12 @@ NAMESPACE may be nil, or a string with a namespace FQN." self) 'face 'font-lock-type-face))) +(cl-defmethod phpinspect--display-format-type-name (type) + (cl-assert (not type)) + + (phpinspect--display-format-type-name "unknown-type")) + + (cl-defstruct (phpinspect--function (:constructor phpinspect--make-function-generated) (:copier phpinspect--copy-function)) "A PHP function." diff --git a/phpinspect-typedef.el b/phpinspect-typedef.el index 1015159841..a3f827c539 100644 --- a/phpinspect-typedef.el +++ b/phpinspect-typedef.el @@ -142,13 +142,24 @@ structure returned by `phpinspect--index-trait-use'." (cl-defmethod phpi-typedef-get-static-method ((def phpinspect-typedef) (method-name string)) (phpi-typedef-get-static-method def (phpinspect-intern-name method-name))) -(cl-defmethod phpi-typedef-set-method ((def phpinspect-typedef) (fn phpinspect--function)) - (let ((method (phpinspect-make-method (phpi-typedef-name def) fn))) - (phpi-ma-add (phpi-typedef-method-adder def) - (phpi-typedef-methods def) - method 'overwrite))) +(cl-defmethod phpi-typedef-set-method ((def phpinspect-typedef) (m phpinspect-method) &optional no-propagate) + (phpi-ma-add (phpi-typedef-method-adder def) (phpi-typedef-methods def) m 'overwrite) + (unless no-propagate + (phpi-typedef-trigger-subscriber-method-update def m))) + +(cl-defmethod phpi-typedef-set-method ((def phpinspect-typedef) (fn phpinspect--function) &optional no-propagate) + (phpi-typedef-set-method def (phpinspect-make-method (phpi-typedef-name def) fn))) + +(cl-defmethod phpi-typedef-set-static-method ((def phpinspect-typedef) (m phpinspect-method) &optional no-propagate) + (phpi-ma-add (phpi-typedef-method-adder def) (phpi-typedef-static-methods def) m 'overwrite) + (unless no-propagate + (phpi-typedef-trigger-subscriber-method-update def m 'static))) + +(cl-defmethod phpi-typedef-set-static-method ((def phpinspect-typedef) (fn phpinspect--function) &optional no-propagate) + (phpi-typedef-set-static-method def (phpinspect-make-method (phpi-typedef-name def) fn))) -(cl-defmethod phpi-typedef-set-static-method ((def phpinspect-typedef) (fn phpinspect--function)) + +(cl-defmethod phpi-typedef-set-static-method ((def phpinspect-typedef) (fn phpinspect--function) &optional no-propagate) (let ((method (phpinspect-make-method (phpi-typedef-name def) fn))) (phpi-ma-add (phpi-typedef-method-adder def) (phpi-typedef-static-methods def) @@ -158,9 +169,14 @@ structure returned by `phpinspect--index-trait-use'." (cl-defmethod phpi-typedef-delete-method ((def phpinspect-typedef) (name (head phpinspect-name))) (phpi-mcol-delete-for-type (phpi-typedef-methods def) (phpi-typedef-name def) name) + (phpi-typedef-trigger-subscriber-method-delete def (phpi-typedef-name def) name) (phpi-mcol-delete-for-type - (phpi-typedef-static-methods def) (phpi-typedef-name def) name)) + (phpi-typedef-static-methods def) (phpi-typedef-name def) name) + (phpi-typedef-trigger-subscriber-method-delete def (phpi-typedef-name def) name 'static)) + +(cl-defmethod phpi-typedef-delete-method ((def phpinspect-typedef) (name string)) + (phpi-typedef-delete-method def (phpinspect-intern-name name))) (cl-defmethod phpi-typedef-delete-method ((def phpinspect-typedef) (fn phpinspect--function)) (phpi-typedef-delete-method def (phpinspect--function-name-symbol fn))) @@ -179,7 +195,7 @@ structure returned by `phpinspect--index-trait-use'." (progn (phpi-ma-set-config (phpi-typedef-method-adder def) config) (dolist (cons config) - (push (car config) types)))) + (push (car cons) types)))) ;; return all types that were in config types))) @@ -190,7 +206,7 @@ structure returned by `phpinspect--index-trait-use'." (phpi-ma-add (phpi-typedef-method-adder def) (phpi-typedef-methods def) method)) (dolist (method (phpi-typedef-get-static-methods foreign-def)) - (phpi-ma-add (phpi-typedef-method-adder def) (phpi-typedef-methods def) method)) + (phpi-ma-add (phpi-typedef-method-adder def) (phpi-typedef-static-methods def) method)) (dolist (var (phpi-typedef-variables foreign-def)) (phpi-typedef-set-variable def var)))) @@ -222,7 +238,25 @@ extended classes, used traits or implemented interfaces." "Incorporate DEF into subscribed typedefs." (dolist (type (phpi-typedef-subscribed-types def)) (when-let ((foreign-def (phpi--typedef-get-foreign-type def type))) - (phpi-typedef-add-foreign-members foreign-def def)))) + (phpi-typedef-add-foreign-members foreign-def def) + ;; Notify subscribers of upstream changes + (phpi-typedef-trigger-subscriber-update foreign-def)))) + +(defun phpi-typedef-trigger-subscriber-method-update (def method &optional static) + (dolist (type (phpi-typedef-subscribed-types def)) + (when-let ((foreign-def (phpi--typedef-get-foreign-type def type))) + (if static + (phpi-typedef-set-static-method foreign-def method) + (phpi-typedef-set-method foreign-def method))))) + +(defun phpi-typedef-trigger-subscriber-method-delete (def type method-name &optional static) + (dolist (subtype (phpi-typedef-subscribed-types def)) + (when-let ((foreign-def (phpi--typedef-get-foreign-type def subtype))) + (phpi-mcol-delete-for-type + (if static (phpi-typedef-static-methods foreign-def) (phpi-typedef-methods foreign-def)) + type method-name) + + (phpi-typedef-trigger-subscriber-method-delete foreign-def type method-name static)))) (defun phpi-typedef-set-name (def type) "Set the TYPE name of DEF. @@ -235,7 +269,8 @@ TYPE must be a structure of type `phpinspect--type'." ;; Only update if type name is actually different (unless (phpinspect--type= existing type) (setf (phpi-typedef-name def) type) - (phpi-mcol-set-home-type (phpi-typedef-methods def) type))))) + (phpi-mcol-set-home-type (phpi-typedef-methods def) type) + (phpi-mcol-set-home-type (phpi-typedef-static-methods def) type))))) (defun phpi-typedef-update-declaration (def declaration imports namespace-name trait-config) (phpi--typedef-edit def @@ -267,7 +302,7 @@ TYPE must be a structure of type `phpinspect--type'." #'phpinspect-typedef-p (mapcar (lambda (class-name) - (funcall (phpi-typedef-retriever def) class-name)) + (phpi--typedef-get-foreign-type def class-name)) extensions)))) (dolist (fd foreign-defs) @@ -279,8 +314,6 @@ TYPE must be a structure of type `phpinspect--type'." (phpi--typedef-edit def (setf (phpi-typedef-declaration def) (alist-get 'declaration index)) - - (let ((ma (phpi-typedef-method-adder def)) (mcol (phpi-typedef-methods def)) (stmcol (phpi-typedef-static-methods def)) @@ -307,7 +340,7 @@ TYPE must be a structure of type `phpinspect--type'." (phpi-typedef-update-extensions def `(,@(alist-get 'implements index) ,@(alist-get 'extends index) - ,@(phpi-typedef-set-trait-config def (alist-get 'trait-usage index)))) + ,@(phpi-typedef-set-trait-config def (alist-get 'trait-config index)))) (phpi-typedef-trigger-subscriber-update def)))) @@ -351,5 +384,23 @@ TYPE must be a structure of type `phpinspect--type'." (when (string= variable-name (phpinspect--variable-name variable)) (throw 'found variable))))) +(defun phpi-typedef-get-dependencies (def) + (let ((deps (phpi-typedef-subscribed-to-types def))) + (dolist (method (phpi-typedef-get-methods def)) + (when (phpi-method-return-type method) + (push (phpi-method-return-type method) deps))) + + + (dolist (method (phpi-typedef-get-static-methods def)) + (when (phpi-method-return-type method) + (push (phpi-method-return-type method) deps))) + + (dolist (var (phpi-typedef-variables def)) + (when (phpinspect--variable-type var) + (push (phpinspect--variable-type var) deps))) + + (seq-uniq deps #'phpinspect--type=))) + + (provide 'phpinspect-typedef) ;;; phpinspect-typedef.el ends here diff --git a/test/test-project.el b/test/test-project.el index 719ad15774..432d153008 100644 --- a/test/test-project.el +++ b/test/test-project.el @@ -26,6 +26,11 @@ (require 'ert) (require 'phpinspect-project) +(require 'phpinspect-test-env + (expand-file-name "phpinspect-test-env.el" + (file-name-directory (macroexp-file-name)))) + + (ert-deftest phpinspect-project-purge () (let ((project (phpinspect--make-project))) (phpinspect-project-purge project) @@ -42,3 +47,99 @@ (phpinspect-project-purge project) (should (= 0 (length (hash-table-values (phpinspect-project-file-watchers project))))))) + +(defun phpinspect--make-dummy-composer-project-with-code () + (let ((fs (phpinspect-make-virtual-fs))) + (phpinspect-virtual-fs-set-file + fs + "/project/root/composer.json" + "{ \"autoload\": { \"psr-4\": {\"App\\\\\": [\"src/\", \"lib\"]}}}") + + (phpinspect-virtual-fs-set-file fs + "/project/root/src/Foo.php" + "<?php namespace App; trait Foo { public function do(): static {} public static function dont(): Baz {} }") + + (phpinspect-virtual-fs-set-file fs + "/project/root/src/Baz.php" + "<?php namespace App; class Baz { public function amBaz(): bool {} }") + + (phpinspect-virtual-fs-set-file fs + "/project/root/src/Bar.php" + "<?php namespace App; class Bar { use Foo; public function foo(): Foo {} }") + + (phpinspect-virtual-fs-set-file fs + "/project/root/src/Harry.php" + "<?php namespace App; class Harry { public function amBarry(): bool {} }") + + + (phpinspect-virtual-fs-set-file fs + "/project/root/src/Barry.php" + "<?php namespace App; class Barry { public function getHarry(): Harry {} }") + + + (let* ((project (phpinspect--make-dummy-project fs "/project/root")) + (autoload (phpinspect-project-autoload project)) + result error) + + (phpinspect-autoloader-refresh autoload (lambda (res err) + (setq result res error err))) + + (while (not (or result error)) + (thread-yield)) + + project))) + + +(ert-deftest phpinspect-project-no-enqueue () + (let* ((project (phpinspect--make-dummy-composer-project-with-code)) + (bar (phpinspect-project-get-typedef-extra-or-create + project (phpinspect--make-type :name "\\App\\Bar") 'no-enqueue)) + ;; Fetch foo without passing no-enqueue + (foo (phpinspect-project-get-typedef-extra-or-create + project (phpinspect--make-type :name "\\App\\Foo"))) + (baz (phpinspect-project-get-typedef-extra-or-create + project (phpinspect--make-type :name "\\App\\Baz"))) + (barry (phpinspect-project-get-typedef-extra-or-create + project (phpinspect--make-type :name "\\App\\Barry"))) + (harry (phpinspect-project-get-typedef-extra-or-create + project (phpinspect--make-type :name "\\App\\Harry")))) + ;; Bar includes foo's method + (should bar) + (should (= 2 (length (phpi-typedef-get-methods bar)))) + + ;; Foo is loaded/indexed + (should foo) + (should (= 1 (length (phpi-typedef-get-methods foo)))) + + ;; Baz should be loaded as dependency of foo + (should baz) + (should (phpi-typedef-initial-index baz)) + + ;; barry and harry are not loaded (worker is null, so they are not indexed + ;; in the background either). + (should harry) + (should-not (phpi-typedef-initial-index harry)) + (should barry) + (should-not (phpi-typedef-initial-index barry)) + + ;; Trigger index of barry + (phpinspect-project-get-typedef-extra-or-create + project (phpinspect--make-type :name "\\App\\Barry") 'no-enqueue) + + ;; barry and harry should be loaded now (harry as dependency of barry) + (should (phpi-typedef-initial-index barry)) + (should (phpi-typedef-initial-index harry)))) + +(ert-deftest phpinspect-project-late-static-binding () + (let* ((project (phpinspect--make-dummy-composer-project-with-code)) + (bar (phpinspect-project-get-typedef-extra-or-create + project (phpinspect--make-type :name "\\App\\Bar") 'no-enqueue))) + + (should bar) + (should (= 2 (length (phpi-typedef-get-methods bar)))) + + (let ((method (phpi-typedef-get-method bar "do"))) + (should (phpinspect--type= (phpinspect--make-type :name "\\App\\Bar") + (phpi-method-return-type method))) + (should (phpinspect--type= (phpinspect--make-type :name "\\App\\Bar") + (phpi-fn-return-type method)))))) diff --git a/test/test-typedef.el b/test/test-typedef.el index 76adbd33db..63344ed89c 100644 --- a/test/test-typedef.el +++ b/test/test-typedef.el @@ -59,6 +59,108 @@ (setq method (phpi-typedef-get-method def1 (phpinspect-intern-name "test"))) (should-not method)))) +(ert-deftest phpinspect-typedef-subscribe-static () + (let* ((def1 (phpinspect-make-typedef (phpinspect--make-type :name "\\A"))) + (def2 (phpinspect-make-typedef (phpinspect--make-type :name "\\B"))) + (retriever (lambda (type) + (cond ((phpinspect--type= type (phpinspect--make-type :name "\\A")) + def1) + ((phpinspect--type= type (phpinspect--make-type :name "\\B")) + def2))))) + + (setf (phpi-typedef-retriever def1) retriever + (phpi-typedef-retriever def2) retriever) + (phpi-typedef-set-static-method def2 (phpinspect--make-function :name "test")) + + (phpi-typedef-update-extensions def1 (list (phpi-typedef-name def2))) + + (let ((method (phpi-typedef-get-static-method def1 (phpinspect-intern-name "test")))) + (should method) + (should-not (phpi-method-return-type method)) + + (phpi-typedef-set-static-method def1 (phpinspect--make-function + :name "test" + :return-type (phpinspect--make-type :name "\\aaa"))) + + (setq method (phpi-typedef-get-static-method def1 (phpinspect-intern-name "test"))) + (should method) + (should (phpi-method-return-type method)) + + (phpi-typedef-delete-method def1 (phpinspect-intern-name "test")) + + (setq method (phpi-typedef-get-static-method def1 (phpinspect-intern-name "test"))) + (should method) + (should-not (phpi-method-return-type method)) + + (phpi-typedef-update-extensions def1 nil) + + (setq method (phpi-typedef-get-static-method def1 (phpinspect-intern-name "test"))) + (should-not method)))) + +(ert-deftest phpinspect-typedef-subscribe-multi-ancestor () + (let* ((def1 (phpinspect-make-typedef (phpinspect--make-type :name "\\A"))) + (def2 (phpinspect-make-typedef (phpinspect--make-type :name "\\B"))) + (def3 (phpinspect-make-typedef (phpinspect--make-type :name "\\C"))) + (retriever (lambda (type) + (cond ((phpinspect--type= type (phpi-typedef-name def1)) + def1) + ((phpinspect--type= type (phpi-typedef-name def2)) + def2) + ((phpinspect--type= type (phpi-typedef-name def3)) + def3))))) + + (setf (phpi-typedef-retriever def1) retriever + (phpi-typedef-retriever def2) retriever + (phpi-typedef-retriever def3) retriever) + + + (phpi-typedef-update-extensions def1 (list (phpi-typedef-name def2))) + (phpi-typedef-update-extensions def2 (list (phpi-typedef-name def3))) + + (phpi-typedef-set-method def3 (phpinspect--make-function :name "test")) + (phpi-typedef-trigger-subscriber-update def3) + + + (let ((method (phpi-typedef-get-method def1 (phpinspect-intern-name "test")))) + (should method) + (should-not (phpi-method-return-type method))))) + +(ert-deftest phpinspect-typedef-subscribe-multi-ancestor-no-manual-trigger () + (let* ((def1 (phpinspect-make-typedef (phpinspect--make-type :name "\\A"))) + (def2 (phpinspect-make-typedef (phpinspect--make-type :name "\\B"))) + (def3 (phpinspect-make-typedef (phpinspect--make-type :name "\\C"))) + (retriever (lambda (type) + (cond ((phpinspect--type= type (phpi-typedef-name def1)) + def1) + ((phpinspect--type= type (phpi-typedef-name def2)) + def2) + ((phpinspect--type= type (phpi-typedef-name def3)) + def3))))) + + (setf (phpi-typedef-retriever def1) retriever + (phpi-typedef-retriever def2) retriever + (phpi-typedef-retriever def3) retriever) + + + (phpi-typedef-update-extensions def1 (list (phpi-typedef-name def2))) + (phpi-typedef-update-extensions def2 (list (phpi-typedef-name def3))) + + ;; Set method, don't trigger update (expect automatic single method propagation) + (phpi-typedef-set-method def3 (phpinspect--make-function :name "test")) + + (let ((method (phpi-typedef-get-method def1 (phpinspect-intern-name "test")))) + (should method) + (should-not (phpi-method-return-type method)) + + (phpi-typedef-delete-method def3 "test") + + (setq method (phpi-typedef-get-method def1 (phpinspect-intern-name "test"))) + + (should-not method)))) + + + + (ert-deftest phpinspect-typedef-variables () (let ((def (phpinspect-make-typedef (phpinspect--make-type :name "\\test")))) @@ -73,3 +175,37 @@ (should (phpi-typedef-get-variable def "test2")) (should (string= "test2" (phpinspect--variable-name test2)))))) + + +(ert-deftest phpinspect-typedef-set-index-trait () + (let* ((code "class A { use \\AAA; function B(): C {} }") + (index (phpinspect--index-tokens (phpinspect-parse-string code))) + (class (cdar (alist-get 'classes index))) + (def1 (phpinspect-make-typedef (alist-get 'class-name class))) + (def2 (phpinspect-make-typedef (phpinspect--make-type :name "\\AAA"))) + (retriever (lambda (type) + (cond ((phpinspect--type= type (phpi-typedef-name def1)) + def1) + ((phpinspect--type= type (phpi-typedef-name def2)) + def2))))) + + (setf (phpi-typedef-retriever def1) retriever + (phpi-typedef-retriever def2) retriever) + + (phpi-typedef-set-static-method def2 (phpinspect--make-function + :name "test" + :return-type (phpinspect--make-type :name "\\aaa"))) + + (phpi-typedef-set-index def1 class) + + (should (phpi-typedef-get-methods def1)) + (should (= 1 (length (phpi-typedef-get-methods def1)))) + (should (= 1 (length (phpi-typedef-get-static-methods def1)))) + + (should (eq (phpinspect-intern-name "B") + (phpi-method-name (car (phpi-typedef-get-methods def1))))) + + (let ((static-method (phpi-typedef-get-static-method def1 (phpinspect-intern-name "test")))) + (should static-method) + (should (eq (phpinspect-intern-name "test") + (phpi-method-name static-method))))))