Compare commits

...

3 Commits

Author SHA1 Message Date
Hugo Thunnissen 0c0c1ca381 Add test for phpinspect-get-pattern-type-in-block
continuous-integration/drone/push Build was killed Details
1 year ago
Hugo Thunnissen 8cd4dc2025 Remove commented code + tidy some formatting 1 year ago
Hugo Thunnissen 224bbd7916 Implement array member type inference
phpinspect now understands typed arrays!
1 year ago

@ -41,14 +41,11 @@ as keys and project caches as values."))
((cache phpinspect--cache) (project-root string)) ((cache phpinspect--cache) (project-root string))
(gethash project-root (phpinspect--cache-projects cache))) (gethash project-root (phpinspect--cache-projects cache)))
(cl-defgeneric phpinspect--cache-get-project-create
((cache phpinspect--cache) (project-root string))
"Get a project that is located in PROJECT-ROOT from CACHE.
If no such project exists in the cache yet, it is created and
then returned.")
(cl-defmethod phpinspect--cache-get-project-create (cl-defmethod phpinspect--cache-get-project-create
((cache phpinspect--cache) (project-root string)) ((cache phpinspect--cache) (project-root string))
"Get a project that is located in PROJECT-ROOT from CACHE.
If no such project exists in the cache yet, it is created and
then returned."
(let ((project (phpinspect--cache-getproject cache project-root))) (let ((project (phpinspect--cache-getproject cache project-root)))
(unless project (unless project
(setq project (puthash project-root (setq project (puthash project-root

@ -72,17 +72,17 @@ function (think \"new\" statements, return types etc.)."
;; @return annotation. When dealing with a collection, we want to store the ;; @return annotation. When dealing with a collection, we want to store the
;; type of its members. ;; type of its members.
(let* ((is-collection (let* ((return-annotation-type
(when type (cadadr (seq-find #'phpinspect-return-annotation-p comment-before)))
(member (phpinspect--type-name type) phpinspect-collection-types))) (is-collection
(return-annotation-type (and type
(when (or (phpinspect--should-prefer-return-annotation type) is-collection) (phpinspect--type-is-collection type))))
(cadadr (phpinspect--log "found return annotation %s in %s when type is %s"
(seq-find #'phpinspect-return-annotation-p return-annotation-type comment-before type)
comment-before)))))
(phpinspect--log "found return annotation %s when type is %s" (when (string-suffix-p "[]" return-annotation-type)
return-annotation-type (setq is-collection t)
type) (setq return-annotation-type (string-trim-right return-annotation-type "\\[\\]")))
(when return-annotation-type (when return-annotation-type
(cond ((phpinspect--should-prefer-return-annotation type) (cond ((phpinspect--should-prefer-return-annotation type)
@ -104,8 +104,7 @@ function (think \"new\" statements, return types etc.)."
(phpinspect--make-function (phpinspect--make-function
:scope `(,(car scope)) :scope `(,(car scope))
:name (cadadr (cdr declaration)) :name (cadadr (cdr declaration))
:return-type (if type (funcall type-resolver type) :return-type (or type phpinspect--null-type)
phpinspect--null-type)
:arguments (phpinspect--index-function-arg-list :arguments (phpinspect--index-function-arg-list
type-resolver type-resolver
(phpinspect-function-argument-list php-func) (phpinspect-function-argument-list php-func)

@ -173,6 +173,9 @@ Type can be any of the token types returned by
"Get the argument list of a function" "Get the argument list of a function"
(seq-find #'phpinspect-list-p (seq-find #'phpinspect-declaration-p php-func nil) nil)) (seq-find #'phpinspect-list-p (seq-find #'phpinspect-declaration-p php-func nil) nil))
(defun phpinspect-function-block (token)
(cadr token))
(defun phpinspect-annotation-p (token) (defun phpinspect-annotation-p (token)
(phpinspect-token-type-p token :annotation)) (phpinspect-token-type-p token :annotation))
@ -236,7 +239,8 @@ Type can be any of the token types returned by
(phpinspect-token-type-p object :use)) (phpinspect-token-type-p object :use))
(defun phpinspect-comment-p (token) (defun phpinspect-comment-p (token)
(phpinspect-token-type-p token :comment)) (or (phpinspect-token-type-p token :comment)
(phpinspect-token-type-p token :doc-block)))
(defsubst phpinspect-class-block (class) (defsubst phpinspect-class-block (class)
(caddr class)) (caddr class))
@ -444,7 +448,8 @@ token is \";\", which marks the end of a statement in PHP."
(list-handler (phpinspect-handler 'list)) (list-handler (phpinspect-handler 'list))
(list-regexp (phpinspect-handler-regexp 'list)) (list-regexp (phpinspect-handler-regexp 'list))
(word-handler (phpinspect-handler 'word)) (word-handler (phpinspect-handler 'word))
(word-regexp (phpinspect-handler-regexp 'word)) ;; Return annotations may end with "[]" for collections.
(word-regexp (concat (phpinspect-handler-regexp 'word) "\\(\\[\\]\\)?"))
(variable-handler (phpinspect-handler 'variable)) (variable-handler (phpinspect-handler 'variable))
(variable-regexp (phpinspect-handler-regexp 'variable)) (variable-regexp (phpinspect-handler-regexp 'variable))
(annotation-regexp (phpinspect-handler-regexp 'annotation))) (annotation-regexp (phpinspect-handler-regexp 'annotation)))
@ -740,9 +745,15 @@ nature like argument lists"
;; TODO: Look into using different names for function and class :declaration tokens. They ;; TODO: Look into using different names for function and class :declaration tokens. They
;; don't necessarily require the same handlers to parse. ;; don't necessarily require the same handlers to parse.
(defsubst phpinspect-get-or-create-declaration-parser () (defsubst phpinspect-get-or-create-declaration-parser ()
(phpinspect-get-parser-create :declaration (let ((parser (phpinspect-get-parser-create
'(comment word list terminator tag) :declaration
#'phpinspect-end-of-token-p)) '(comment word list terminator tag)
#'phpinspect-end-of-token-p)))
(lambda (&rest arguments)
(let ((result (apply parser arguments)))
(if (phpinspect-terminator-p (car (last result)))
(butlast result)
result)))))
(phpinspect-defhandler function-keyword (start-token max-point) (phpinspect-defhandler function-keyword (start-token max-point)

@ -26,7 +26,8 @@
(require 'phpinspect-util) (require 'phpinspect-util)
(cl-defstruct (phpinspect--type (cl-defstruct (phpinspect--type
(:constructor phpinspect--make-type-generated)) (:constructor phpinspect--make-type-generated)
(:copier phpinspect--copy-type))
"Represents an instance of a PHP type in the phpinspect syntax tree." "Represents an instance of a PHP type in the phpinspect syntax tree."
(name-symbol nil (name-symbol nil
:type symbol :type symbol
@ -97,6 +98,13 @@ See https://wiki.php.net/rfc/static_return_type ."
(when (phpinspect--type= type native) (when (phpinspect--type= type native)
(throw 'found t))))) (throw 'found t)))))
(defsubst phpinspect--type-is-collection (type)
(catch 'found
(dolist (collection phpinspect-collection-types)
(when (phpinspect--type= type collection)
(throw 'found t)))))
(cl-defmethod phpinspect--type-name ((type phpinspect--type)) (cl-defmethod phpinspect--type-name ((type phpinspect--type))
(symbol-name (phpinspect--type-name-symbol type))) (symbol-name (phpinspect--type-name-symbol type)))
@ -142,6 +150,8 @@ NAMESPACE may be nil, or a string with a namespace FQN."
type type
(phpinspect--resolve-type-name types namespace (phpinspect--type-name type))) (phpinspect--resolve-type-name types namespace (phpinspect--type-name type)))
(setf (phpinspect--type-fully-qualified type) t)) (setf (phpinspect--type-fully-qualified type) t))
(when (phpinspect--type-is-collection type)
(setf (phpinspect--type-collection type) t))
type) type)
(defun phpinspect--make-type-resolver (types &optional token-tree namespace) (defun phpinspect--make-type-resolver (types &optional token-tree namespace)

@ -52,7 +52,6 @@ PHP. Used to optimize string comparison.")
(message "Enabled phpinspect logging.") (message "Enabled phpinspect logging.")
(message "Disabled phpinspect logging."))) (message "Disabled phpinspect logging.")))
(defsubst phpinspect--log (&rest args) (defsubst phpinspect--log (&rest args)
(when phpinspect--debug (when phpinspect--debug
(with-current-buffer (get-buffer-create "**phpinspect-logs**") (with-current-buffer (get-buffer-create "**phpinspect-logs**")
@ -60,7 +59,94 @@ PHP. Used to optimize string comparison.")
(set (make-local-variable 'window-point-insertion-type) t)) (set (make-local-variable 'window-point-insertion-type) t))
(goto-char (buffer-end 1)) (goto-char (buffer-end 1))
(insert (concat "[" (format-time-string "%H:%M:%S") "]: " (insert (concat "[" (format-time-string "%H:%M:%S") "]: "
(apply #'format args) "\n"))))) (apply #'format args) "\n")))))
(cl-defstruct (phpinspect--pattern
(:constructor phpinspect--make-pattern-generated))
"An object that can be used to match lists to a given
pattern. See `phpinspect--match-sequence'."
(matcher nil
:type lambda
:documentation "The function used to match sequences")
(code nil
:type list
:documentation "The original code list used to create this pattern"))
(defsubst phpinspect--make-pattern (&rest pattern)
(phpinspect--make-pattern-generated
:matcher (apply #'phpinspect--match-sequence-lambda pattern)
:code pattern))
(defun phpinspect--match-sequence-lambda (&rest pattern)
(lambda (sequence)
(apply #'phpinspect--match-sequence sequence pattern)))
(cl-defmethod phpinspect--pattern-match ((pattern phpinspect--pattern) sequence)
"Match SEQUENCE to PATTERN."
(funcall (phpinspect--pattern-matcher pattern) sequence))
(defun phpinspect--match-sequence (sequence &rest pattern)
"Match SEQUENCE to positional matchers defined in PATTERN.
PATTERN is a plist with the allowed keys being :m and :f. Each
key-value pair in the plist defines a match operation that is
applied to the corresponding index of SEQUENCE (so for ex.: key 0
is applied to pos. 0 of SEQUENCE, key 1 to pos. 1, and so on).
Possible match operations:
:m - This key can be used to match a list element to the literal
value supplied for it, using the `equal' comparison function. For
example, providing `(\"foobar\") as value will result in the
comparison (equal (elt SEQUENCE pos) `(\"foobar\")). There is one
exception to this rule: using the symbol * as value for the :m
key will match anything, essentially skipping comparison for the
element at this position in SEQUENCE.
:f - This key can be used to match a list element by executing
the function provided as value. The function is executed with the
list element as argument, and will be considered as matching if
it evaluates to a non-nil value."
(let* ((pattern-length (length pattern))
(count 0)
(sequence-pos 0)
(sequence-length (/ pattern-length 2)))
(and (= sequence-length (length sequence))
(catch 'found
(while (< count pattern-length)
(let ((key (elt pattern count))
(value (elt pattern (+ count 1))))
(unless (keywordp key)
(error (format "Invalid, expected keyword, got %s" key)))
(cond ((eq key :m)
(unless (eq value '*)
(unless (equal value (elt sequence sequence-pos))
(throw 'found nil))))
((eq key :f)
(unless (funcall value (elt sequence sequence-pos))
(throw 'found nil)))
(t (error (format "Invalid keyword: %s" key))))
(setq count (+ count 2)
sequence-pos (+ sequence-pos 1))))
(throw 'found t)))))
(defun phpinspect--pattern-concat (pattern1 pattern2)
(let* ((pattern1-sequence-length (/ (length (phpinspect--pattern-code pattern1)) 2)))
(phpinspect--make-pattern-generated
:matcher (lambda (sequence)
(unless (< (length sequence) pattern1-sequence-length)
(and (phpinspect--pattern-match
pattern1
(butlast sequence (- (length sequence) pattern1-sequence-length)))
(phpinspect--pattern-match
pattern2
(last sequence (- (length sequence) pattern1-sequence-length))))))
:code (append (phpinspect--pattern-code pattern1)
(phpinspect--pattern-code pattern2)))))
(provide 'phpinspect-util) (provide 'phpinspect-util)
;;; phpinspect-util.el ends here ;;; phpinspect-util.el ends here

@ -142,12 +142,11 @@ accompanied by all of its enclosing tokens."
(last-encountered-token (car (last-encountered-token (car
(phpinspect--resolvecontext-enclosing-tokens (phpinspect--resolvecontext-enclosing-tokens
resolvecontext)))) resolvecontext))))
(if (and (or (phpinspect-function-p last-encountered-token) (unless (and (or (phpinspect-function-p last-encountered-token)
(phpinspect-class-p last-encountered-token)) (phpinspect-class-p last-encountered-token))
(phpinspect-block-p token)) (phpinspect-block-p token))
;; When a class or function has been inserted already, its block ;; When a class or function has been inserted already, its block
;; doesn't need to be added on top. ;; doesn't need to be added on top.
(phpinspect--resolvecontext-push-enclosing-token resolvecontext nil)
(phpinspect--resolvecontext-push-enclosing-token resolvecontext token)) (phpinspect--resolvecontext-push-enclosing-token resolvecontext token))
(if (phpinspect-incomplete-token-p last-token) (if (phpinspect-incomplete-token-p last-token)
@ -156,9 +155,6 @@ accompanied by all of its enclosing tokens."
(setf (phpinspect--resolvecontext-subject resolvecontext) (setf (phpinspect--resolvecontext-subject resolvecontext)
(phpinspect--get-last-statement-in-token token)) (phpinspect--get-last-statement-in-token token))
;; Delete all occurences of nil caused higher up in the function.
(cl-delete nil (phpinspect--resolvecontext-enclosing-tokens
resolvecontext))
resolvecontext))) resolvecontext)))
@ -229,7 +225,6 @@ accompanied by all of its enclosing tokens."
(when method (when method
(phpinspect--function-return-type method)))))) (phpinspect--function-return-type method))))))
(defsubst phpinspect-get-cached-project-class-variable-type (defsubst phpinspect-get-cached-project-class-variable-type
(project-root class-fqn variable-name) (project-root class-fqn variable-name)
(phpinspect--log "Getting cached project class variable type for %s (%s::%s)" (phpinspect--log "Getting cached project class variable type for %s (%s::%s)"
@ -283,12 +278,19 @@ accompanied by all of its enclosing tokens."
(insert string) (insert string)
(phpinspect-parse-current-buffer))) (phpinspect-parse-current-buffer)))
(defun phpinspect--split-list (predicate list) (defun phpinspect--split-statements (tokens &optional predicate)
"Split TOKENS into separate statements.
If PREDICATE is provided, it is used as additional predicate to
determine whether a token delimits a statement."
(let ((sublists) (let ((sublists)
(current-sublist)) (current-sublist))
(dolist (thing list) (dolist (thing tokens)
(if (funcall predicate thing) (if (or (phpinspect-end-of-statement-p thing)
(when predicate (funcall predicate thing)))
(when current-sublist (when current-sublist
(when (phpinspect-block-p thing)
(push thing current-sublist))
(push (nreverse current-sublist) sublists) (push (nreverse current-sublist) sublists)
(setq current-sublist nil)) (setq current-sublist nil))
(push thing current-sublist))) (push thing current-sublist)))
@ -394,6 +396,15 @@ TODO:
(phpinspect--format-type-name (phpinspect--format-type-name
(phpinspect--function-return-type method))))))))) (phpinspect--function-return-type method)))))))))
(cl-defstruct (phpinspect--assignment
(:constructor phpinspect--make-assignment))
(to nil
:type phpinspect-variable
:documentation "The variable that is assigned to")
(from nil
:type phpinspect-token
:documentation "The token that is assigned from"))
(defsubst phpinspect-block-or-list-p (token) (defsubst phpinspect-block-or-list-p (token)
(or (phpinspect-block-p token) (or (phpinspect-block-p token)
(phpinspect-list-p token))) (phpinspect-list-p token)))
@ -405,74 +416,72 @@ TODO:
(cl-defgeneric phpinspect--find-assignments-in-token (token) (cl-defgeneric phpinspect--find-assignments-in-token (token)
"Find any assignments that are in TOKEN, at top level or nested in blocks" "Find any assignments that are in TOKEN, at top level or nested in blocks"
(when (keywordp (car token))
(setq token (cdr token)))
(let ((assignments) (let ((assignments)
(block-or-list) (blocks-or-lists)
(statements (phpinspect--split-list #'phpinspect-end-of-statement-p token))) (statements (phpinspect--split-statements token)))
(dolist (statement statements) (dolist (statement statements)
(cond ((seq-find #'phpinspect-assignment-p statement) (when (seq-find #'phpinspect-maybe-assignment-p statement)
(phpinspect--log "Found assignment statement") (phpinspect--log "Found assignment statement")
(push statement assignments)) (push statement assignments))
((setq block-or-list (seq-find #'phpinspect-block-or-list-p statement))
(phpinspect--log "Found block or list %s" block-or-list) (when (setq blocks-or-lists (seq-filter #'phpinspect-block-or-list-p statement))
(setq assignments (dolist (block-or-list blocks-or-lists)
(append (phpinspect--log "Found block or list %s" block-or-list)
(phpinspect--find-assignments-in-token block-or-list) (let ((local-assignments (phpinspect--find-assignments-in-token block-or-list)))
assignments))))) (dolist (local-assignment (nreverse local-assignments))
(push local-assignment assignments))))))
;; return ;; return
(phpinspect--log "Found assignments in token: %s" assignments) (phpinspect--log "Found assignments in token: %s" assignments)
(phpinspect--log "Found statements in token: %s" statements) (phpinspect--log "Found statements in token: %s" statements)
assignments)) assignments))
(cl-defmethod phpinspect--find-assignments-in-token ((token (head :list)))
"Find assignments that are in a list token."
(phpinspect--log "looking for assignments in list %s" token)
(seq-filter
(lambda (statement)
(phpinspect--log "checking statement %s" statement)
(seq-find #'phpinspect-maybe-assignment-p statement))
(phpinspect--split-list #'phpinspect-end-of-statement-p (cdr token))))
(defsubst phpinspect-not-assignment-p (token) (defsubst phpinspect-not-assignment-p (token)
"Inverse of applying `phpinspect-assignment-p to TOKEN." "Inverse of applying `phpinspect-assignment-p to TOKEN."
(not (phpinspect-maybe-assignment-p token))) (not (phpinspect-maybe-assignment-p token)))
(defun phpinspect--find-assignment-values-for-variable-in-token (variable-name token) (defsubst phpinspect-not-comment-p (token)
"Find all assignments of variable VARIABLE-NAME in TOKEN." (not (phpinspect-comment-p token)))
(defun phpinspect--find-assignments-by-predicate (token predicate)
(let ((variable-assignments) (let ((variable-assignments)
(all-assignments (phpinspect--find-assignments-in-token token))) (all-assignments (phpinspect--find-assignments-in-token token)))
(dolist (assignment all-assignments) (dolist (assignment all-assignments)
(let* ((is-loop-assignment nil) (let* ((is-loop-assignment nil)
(left-of-assignment (left-of-assignment
(seq-take-while #'phpinspect-not-assignment-p assignment)) (seq-filter #'phpinspect-not-comment-p
(seq-take-while #'phpinspect-not-assignment-p assignment)))
(right-of-assignment (right-of-assignment
(cdr (seq-drop-while (lambda (elt) (seq-filter
(if (phpinspect-maybe-assignment-p elt) #'phpinspect-not-comment-p
(progn (cdr (seq-drop-while
(when (equal '(:word "as") elt) (lambda (elt)
(phpinspect--log "It's a loop assignment %s" elt) (if (phpinspect-maybe-assignment-p elt)
(setq is-loop-assignment t)) (progn
nil) (when (equal '(:word "as") elt)
t)) (phpinspect--log "It's a loop assignment %s" elt)
assignment)))) (setq is-loop-assignment t))
nil)
t))
assignment)))))
(if is-loop-assignment (if is-loop-assignment
(when (member `(:variable ,variable-name) right-of-assignment) (when (funcall predicate right-of-assignment)
(push left-of-assignment variable-assignments)) ;; Masquerade as an array access assignment
(when (member `(:variable ,variable-name) left-of-assignment) (setq left-of-assignment (append left-of-assignment '((:array))))
(push right-of-assignment variable-assignments))))) (push (phpinspect--make-assignment :to right-of-assignment
:from left-of-assignment)
variable-assignments))
(when (funcall predicate left-of-assignment)
(push (phpinspect--make-assignment :from right-of-assignment
:to left-of-assignment)
variable-assignments)))))
(phpinspect--log "Returning the thing %s" variable-assignments)
(nreverse variable-assignments))) (nreverse variable-assignments)))
;; (if (or (member `(:variable ,variable-name)
;; (seq-take-while #'phpinspect-not-assignment-p
;; assignment))5
;; (and (phpinspect-list-p (car assignment))
;; (member `(:variable ,variable-name) (car assignment)))
;; (and (member '(:word "as") assignment)
;; (member `(:variable ,variable-name)
;; (seq-drop-while (lambda (elt)
;; (not (equal '(:word "as") elt)))))))
;; (push assignment variable-assignments)))
;; (nreverse variable-assignments)))
(defsubst phpinspect-drop-preceding-barewords (statement) (defsubst phpinspect-drop-preceding-barewords (statement)
(while (and statement (phpinspect-word-p (cadr statement))) (while (and statement (phpinspect-word-p (cadr statement)))
(pop statement)) (pop statement))
@ -566,7 +575,11 @@ $variable = $variable->method();"
resolvecontext) resolvecontext)
(funcall type-resolver previous-attribute-type) (funcall type-resolver previous-attribute-type)
(cadr attribute-word)) (cadr attribute-word))
previous-attribute-type))))))))) previous-attribute-type)))))))
((and previous-attribute-type (phpinspect-array-p current-token))
(setq previous-attribute-type
(or (phpinspect--type-contains previous-attribute-type)
previous-attribute-type)))))
(phpinspect--log "Found derived type: %s" previous-attribute-type) (phpinspect--log "Found derived type: %s" previous-attribute-type)
;; Make sure to always return a FQN ;; Make sure to always return a FQN
(funcall type-resolver previous-attribute-type)))) (funcall type-resolver previous-attribute-type))))
@ -588,48 +601,133 @@ resolve types of function argument variables."
(phpinspect--log "Looking for assignments of variable %s in php block" variable-name) (phpinspect--log "Looking for assignments of variable %s in php block" variable-name)
(if (string= variable-name "this") (if (string= variable-name "this")
(funcall type-resolver (phpinspect--make-type :name "self")) (funcall type-resolver (phpinspect--make-type :name "self"))
;; else (phpinspect-get-pattern-type-in-block
(let* ((assignments resolvecontext (phpinspect--make-pattern :m `(:variable ,variable-name))
(phpinspect--find-assignment-values-for-variable-in-token variable-name php-block)) php-block type-resolver function-arg-list)))
(last-assignment-value (when assignments (car (last assignments)))))
(defun phpinspect-get-pattern-type-in-block
(phpinspect--log "Last assignment: %s" last-assignment-value) (resolvecontext pattern php-block type-resolver &optional function-arg-list)
(phpinspect--log "Current block: %s" php-block) "Find the type of PATTERN in PHP-BLOCK using TYPE-RESOLVER.
;; When the right of an assignment is more than $variable; or "string";(so
;; (:variable "variable") (:terminator ";") or (:string "string") (:terminator ";") PATTERN must be an object of the type `phpinspect--pattern'.
;; in tokens), we're likely working with a derived assignment like $object->method()
;; or $object->attribute Returns either a FQN or a relative type name, depending on
(cond ((and (phpinspect-word-p (car last-assignment-value)) whether or not the root variable of the assignment value (right
(string= (cadar last-assignment-value) "new")) side of assignment) needs to be extracted from FUNCTION-ARG-LIST.
(funcall type-resolver (phpinspect--make-type :name (cadadr last-assignment-value))))
((and (> (length last-assignment-value) 1) When PHP-BLOCK belongs to a function, supply FUNCTION-ARG-LIST to
(seq-find #'phpinspect-attrib-p last-assignment-value)) resolve types of function argument variables."
(phpinspect--log "Variable was assigned with a derived statement") (let* ((assignments
(phpinspect-get-derived-statement-type-in-block resolvecontext (phpinspect--find-assignments-by-predicate
last-assignment-value php-block (phpinspect--pattern-matcher pattern)))
php-block (last-assignment (when assignments (car (last assignments))))
type-resolver (last-assignment-value (when last-assignment
function-arg-list)) (phpinspect--assignment-from last-assignment)))
;; If the right of an assignment is just $variable;, we can check if it is a (pattern-code (phpinspect--pattern-code pattern))
;; function argument and otherwise recurse to find the type of that variable. (result))
((phpinspect-variable-p (car last-assignment-value)) (phpinspect--log "Looking for assignments of pattern %s in php block" pattern-code)
(phpinspect--log "Variable was assigned with the value of another variable: %s"
last-assignment-value) (if (not assignments)
(or (when function-arg-list (when (and (= (length pattern-code) 2) (phpinspect-variable-p (cadr pattern-code)))
(phpinspect-get-variable-type-in-function-arg-list (cadar last-assignment-value) (let ((variable-name (cadadr pattern-code)))
type-resolver (progn
function-arg-list)) (phpinspect--log "No assignments found for variable %s, checking function arguments: %s"
(phpinspect-get-variable-type-in-block resolvecontext variable-name function-arg-list)
(cadar last-assignment-value) (setq result (phpinspect-get-variable-type-in-function-arg-list
php-block variable-name type-resolver function-arg-list)))))
type-resolver (setq result
function-arg-list))) (phpinspect--interpret-expression-type-in-context
((not assignments) resolvecontext php-block type-resolver
(phpinspect--log "No assignments found for variable %s, checking function arguments" last-assignment-value function-arg-list)))
variable-name)
(phpinspect-get-variable-type-in-function-arg-list variable-name (phpinspect--log "Type interpreted from last assignment expression of pattern %s: %s"
type-resolver pattern-code result)
function-arg-list))))))
(when (and result (phpinspect--type-collection result) (not (phpinspect--type-contains result)))
(phpinspect--log (concat
"Interpreted type %s is a collection type, but 'contains'"
"attribute is not set. Attempting to infer type from context")
result)
(setq result (phpinspect--copy-type result))
(let ((concat-pattern
(phpinspect--pattern-concat
pattern (phpinspect--make-pattern :f #'phpinspect-array-p))))
(phpinspect--log "Inferring type of concatenated pattern %s"
(phpinspect--pattern-code concat-pattern))
(setf (phpinspect--type-contains result)
(phpinspect-get-pattern-type-in-block
resolvecontext concat-pattern php-block
type-resolver function-arg-list))))
;; Detect array access
(if (and last-assignment-value result
(< 1 (length last-assignment-value))
(phpinspect-array-p (car (last last-assignment-value))))
(progn
(phpinspect--log (concat
"Detected array access in last assignment of pattern %s"
", collection type: %s")
pattern-code result)
(phpinspect--type-contains result))
result)))
(defun phpinspect--interpret-expression-type-in-context
(resolvecontext php-block type-resolver expression &optional function-arg-list)
"Infer EXPRESSION's type from provided context.
Use RESOLVECONTEXT, PHP-BLOCK, TYPE-RESOLVER and
FUNCTION-ARG-LIST as contextual information to infer type of
EXPRESSION."
;; When the right of an assignment is more than $variable; or "string";(so
;; (:variable "variable") (:terminator ";") or (:string "string") (:terminator ";")
;; in tokens), we're likely working with a derived assignment like $object->method()
;; or $object->attributen
(cond ((phpinspect-array-p (car expression))
(let ((collection-contains)
(collection-items (phpinspect--split-statements (cdr (car expression))))
(count 0))
(phpinspect--log "Checking collection items: %s" collection-items)
(while (and (< count (length collection-items))
(not collection-contains))
(setq collection-contains
(phpinspect--interpret-expression-type-in-context
resolvecontext php-block type-resolver
(elt collection-items count) function-arg-list)
count (+ count 1)))
(phpinspect--log "Collection contained: %s" collection-contains)
(phpinspect--make-type :name "\\array"
:fully-qualified t
:collection t
:contains collection-contains)))
((and (phpinspect-word-p (car expression))
(string= (cadar expression) "new"))
(funcall
type-resolver (phpinspect--make-type :name (cadadr expression))))
((and (> (length expression) 1)
(seq-find #'phpinspect-attrib-p expression))
(phpinspect--log "Variable was assigned with a derived statement")
(phpinspect-get-derived-statement-type-in-block
resolvecontext expression php-block
type-resolver function-arg-list))
;; If the right of an assignment is just $variable;, we can check if it is a
;; function argument and otherwise recurse to find the type of that variable.
((phpinspect-variable-p (car expression))
(phpinspect--log "Variable was assigned with the value of another variable: %s"
expression)
(or (when function-arg-list
(phpinspect-get-variable-type-in-function-arg-list
(cadar expression)
type-resolver function-arg-list))
(phpinspect-get-variable-type-in-block resolvecontext
(cadar expression)
php-block
type-resolver
function-arg-list)))))
(defun phpinspect-resolve-type-from-context (resolvecontext type-resolver) (defun phpinspect-resolve-type-from-context (resolvecontext type-resolver)
@ -1211,7 +1309,8 @@ before the search is executed."
(autoloader (phpinspect--project-autoload project))) (autoloader (phpinspect--project-autoload project)))
(when (eq index-new 'index-new) (when (eq index-new 'index-new)
(phpinspect-autoloader-refresh autoloader)) (phpinspect-autoloader-refresh autoloader))
(let* ((result (phpinspect-autoloader-resolve autoloader (phpinspect--type-name-symbol class)))) (let* ((result (phpinspect-autoloader-resolve
autoloader (phpinspect--type-name-symbol class))))
(if (not result) (if (not result)
;; Index new files and try again if not done already. ;; Index new files and try again if not done already.
(if (eq index-new 'index-new) (if (eq index-new 'index-new)

@ -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") (:terminator ";")) (:use (:word "Doctrine\\ORM\\EntityManagerInterface") (:terminator ";")) (:use (:word "Twig\\Environment") (:terminator ";")) (:use (:word "Symfony\\Component\\HttpFoundation\\Request") (:terminator ";")) (:use (:word "Symfony\\Component\\Routing\\Annotation\\Route") (:terminator ";")) (:class (:declaration (:word "class") (:word "AddressController")) (:incomplete-block (:const (:word "A_CONSTANT_FOR_THE_SAKE_OF_HAVING_ONE") (:assignment "=") (:string "a value") (:terminator ";")) (:public (:const (:word "ARRAY_CONSTANT") (:assignment "=") (:array (:string "key") (:fat-arrow "=>") (:string "value") (:comma ",") (:string "key") (:fat-arrow "=>")) (:terminator ";"))) (:private (:variable "repo") (:terminator ";")) (:private (:variable "user_repo") (:terminator ";")) (:private (:variable "twig") (:terminator ";")) (:private (:variable "em") (:terminator ";")) (:public (:function (:declaration (:word "function") (:word "__construct") (:list (:word "AddressRepository") (:variable "repo") (:comma ",") (:word "UserRepository") (:variable "user_repo") (:comma ",") (:word "Environment") (:variable "twig") (:comma ",") (:word "EntityManagerInterface") (:variable "em"))) (:block (:variable "this") (:object-attrib (:word "repo")) (:assignment "=") (:variable "repo") (:terminator ";") (:variable "this") (:object-attrib (:word "user_repo")) (:assignment "=") (:variable "user_repo") (:terminator ";") (:variable "this") (:object-attrib (:word "twig")) (:assignment "=") (:variable "twig") (:terminator ";") (:variable "this") (:object-attrib (:word "em")) (:assignment "=") (:variable "em") (:terminator ";")))) (:doc-block (:annotation "Route")) (:public (:function (:declaration (:word "function") (:word "addAddressPage") (:list (:word "Request") (:variable "req")) (:word "Response")) (:block (:variable "user") (:assignment "=") (:variable "this") (:object-attrib (:word "user_repo")) (:object-attrib (:word "findOne")) (:list (:variable "req") (:object-attrib (:word "get")) (:list (:string "user"))) (:terminator ";") (:word "return") (:word "new") (:word "Response") (:list (:variable "this") (:object-attrib (:word "twig")) (:object-attrib (:word "render")) (:list (:string "address/create.html.twig") (:comma ",") (:array (:string "user") (:fat-arrow "=>") (:variable "user") (:comma ",")))) (:terminator ";")))) (:doc-block (:annotation "Route")) (:public (:function (:declaration (:word "function") (:word "addAddressAction") (:list (:word "Request") (:variable "req")) (:word "Response")) (:block (:variable "user") (:assignment "=") (:variable "this") (:object-attrib (:word "user_repo")) (:object-attrib (:word "findOne")) (:list (:variable "req") (:object-attrib (:word "request")) (:object-attrib (:word "get")) (:list (:string "user"))) (:terminator ";") (:variable "address_string") (:assignment "=") (:variable "req") (:object-attrib (:word "request")) (:object-attrib (:word "get")) (:list (:string "address")) (:terminator ";") (:variable "address") (:assignment "=") (:word "new") (:word "Address") (:list (:variable "user") (:comma ",") (:variable "address_string")) (:terminator ";") (:variable "this") (:object-attrib (:word "em")) (:object-attrib (:word "persist")) (:list (:variable "address")) (:terminator ";") (:variable "this") (:object-attrib (:word "em")) (:object-attrib (:word "flush")) (:list) (:terminator ";") (:word "return") (:word "new") (:word "RedirectResponse") (:list (:string "/user/") (:variable "user") (:object-attrib (:word "getLoginName")) (:list) (:string "/manage")) (:terminator ";")))) (:doc-block (:annotation "Route")) (:public (:function (:declaration (:word "function") (:word "deleteAddressAction") (:list (:word "Request") (:variable "req")) (:word "Response")) (:incomplete-block (:variable "address") (:assignment "=") (:variable "this") (:object-attrib (:word "repo")) (:object-attrib (:word "find")) (:list (:variable "req") (:object-attrib (:word "request")) (:object-attrib (:word "get")) (:list (:string "address"))) (:terminator ";") (:comment) (:comment) (:variable "this") (:object-attrib (:word "em")) (:object-attrib (:word "remove")) (:incomplete-list (:variable "this") (:object-attrib (:word "em")) (:object-attrib nil)))))))))) (: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") (:terminator ";")) (:use (:word "Doctrine\\ORM\\EntityManagerInterface") (:terminator ";")) (:use (:word "Twig\\Environment") (:terminator ";")) (:use (:word "Symfony\\Component\\HttpFoundation\\Request") (:terminator ";")) (:use (:word "Symfony\\Component\\Routing\\Annotation\\Route") (:terminator ";")) (:word "class") (:word "AddressController") (:incomplete-block (:const (:word "A_CONSTANT_FOR_THE_SAKE_OF_HAVING_ONE") (:assignment "=") (:string "a value") (:terminator ";")) (:public (:const (:word "ARRAY_CONSTANT") (:assignment "=") (:array (:string "key") (:fat-arrow "=>") (:string "value") (:comma ",") (:string "key") (:fat-arrow "=>")) (:terminator ";"))) (:private (:variable "repo") (:terminator ";")) (:private (:variable "user_repo") (:terminator ";")) (:private (:variable "twig") (:terminator ";")) (:private (:variable "em") (:terminator ";")) (:public (:function (:declaration (:word "function") (:word "__construct") (:list (:word "AddressRepository") (:variable "repo") (:comma ",") (:word "UserRepository") (:variable "user_repo") (:comma ",") (:word "Environment") (:variable "twig") (:comma ",") (:word "EntityManagerInterface") (:variable "em"))) (:block (:variable "this") (:object-attrib (:word "repo")) (:assignment "=") (:variable "repo") (:terminator ";") (:variable "this") (:object-attrib (:word "user_repo")) (:assignment "=") (:variable "user_repo") (:terminator ";") (:variable "this") (:object-attrib (:word "twig")) (:assignment "=") (:variable "twig") (:terminator ";") (:variable "this") (:object-attrib (:word "em")) (:assignment "=") (:variable "em") (:terminator ";")))) (:doc-block (:annotation "Route")) (:public (:function (:declaration (:word "function") (:word "addAddressPage") (:list (:word "Request") (:variable "req")) (:word "Response")) (:block (:variable "user") (:assignment "=") (:variable "this") (:object-attrib (:word "user_repo")) (:object-attrib (:word "findOne")) (:list (:variable "req") (:object-attrib (:word "get")) (:list (:string "user"))) (:terminator ";") (:word "return") (:word "new") (:word "Response") (:list (:variable "this") (:object-attrib (:word "twig")) (:object-attrib (:word "render")) (:list (:string "address/create.html.twig") (:comma ",") (:array (:string "user") (:fat-arrow "=>") (:variable "user") (:comma ",")))) (:terminator ";")))) (:doc-block (:annotation "Route")) (:public (:function (:declaration (:word "function") (:word "addAddressAction") (:list (:word "Request") (:variable "req")) (:word "Response")) (:block (:variable "user") (:assignment "=") (:variable "this") (:object-attrib (:word "user_repo")) (:object-attrib (:word "findOne")) (:list (:variable "req") (:object-attrib (:word "request")) (:object-attrib (:word "get")) (:list (:string "user"))) (:terminator ";") (:variable "address_string") (:assignment "=") (:variable "req") (:object-attrib (:word "request")) (:object-attrib (:word "get")) (:list (:string "address")) (:terminator ";") (:variable "address") (:assignment "=") (:word "new") (:word "Address") (:list (:variable "user") (:comma ",") (:variable "address_string")) (:terminator ";") (:variable "this") (:object-attrib (:word "em")) (:object-attrib (:word "persist")) (:list (:variable "address")) (:terminator ";") (:variable "this") (:object-attrib (:word "em")) (:object-attrib (:word "flush")) (:list) (:terminator ";") (:word "return") (:word "new") (:word "RedirectResponse") (:list (:string "/user/") (:variable "user") (:object-attrib (:word "getLoginName")) (:list) (:string "/manage")) (:terminator ";")))) (:doc-block (:annotation "Route")) (:public (:function (:declaration (:word "function") (:word "deleteAddressAction") (:list (:word "Request") (:variable "req")) (:word "Response")) (:incomplete-block (:variable "address") (:assignment "=") (:variable "this") (:object-attrib (:word "repo")) (:object-attrib (:word "find")) (:list (:variable "req") (:object-attrib (:word "request")) (:object-attrib (:word "get")) (:list (:string "address"))) (:terminator ";") (:comment) (:comment) (:variable "this") (:object-attrib (:word "em")) (:object-attrib (:word "remove")) (:incomplete-list (:variable "this") (:object-attrib (:word "em")) (:object-attrib nil)))))))))

File diff suppressed because one or more lines are too long

@ -1 +1 @@
`(phpinspect--root-index (imports \, (list)) (classes ,(list (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)))) (methods \, (list (phpinspect--make-function :name "getCreationTime" :scope '(:public) :arguments (list) :return-type (phpinspect--make-type :name "\\DateTime" :collection nil :contains nil :fully-qualified t)) (phpinspect--make-function :name "isValid" :scope '(:public) :arguments (list) :return-type (phpinspect--make-type :name "\\bool" :collection nil :contains nil :fully-qualified t)) (phpinspect--make-function :name "hasStudentRole" :scope '(:public) :arguments (list) :return-type (phpinspect--make-type :name "\\bool" :collection nil :contains nil :fully-qualified t)) (phpinspect--make-function :name "getUser" :scope '(:public) :arguments (list) :return-type (phpinspect--make-type :name "\\App\\Entity\\User" :collection nil :contains nil :fully-qualified t)) (phpinspect--make-function :name "getToken" :scope '(:public) :arguments (list) :return-type (phpinspect--make-type :name "\\string" :collection nil :contains nil :fully-qualified t)) (phpinspect--make-function :name "__construct" :scope '(:public) :arguments (list (list "token" (phpinspect--make-type :name "\\string" :collection nil :contains nil :fully-qualified t)) (list "user" (phpinspect--make-type :name "\\App\\Entity\\User" :collection nil :contains nil :fully-qualified t)) (list "valid" (phpinspect--make-type :name "\\bool" :collection nil :contains nil :fully-qualified t)) (list "creation_time" (phpinspect--make-type :name "\\DateTime" :collection nil :contains nil :fully-qualified t))) :return-type (phpinspect--make-type :name "\\null" :collection nil :contains nil :fully-qualified t)))) (static-methods \, (list)) (static-variables \, (list)) (variables \, (list (phpinspect--make-variable :name "creation_time" :type (phpinspect--make-type :name "\\DateTime" :collection nil :contains nil :fully-qualified t) :scope '(:private)) (phpinspect--make-variable :name "valid" :type (phpinspect--make-type :name "\\bool" :collection nil :contains nil :fully-qualified t) :scope '(:private)) (phpinspect--make-variable :name "user" :type (phpinspect--make-type :name "\\App\\Entity\\App\\\\Entity\\\\User" :collection nil :contains nil :fully-qualified t) :scope '(:private)) (phpinspect--make-variable :name "token" :type (phpinspect--make-type :name "\\string" :collection nil :contains nil :fully-qualified t) :scope '(:private)))) (constants \, (list)) (extends \, (list)) (implements \, (list))))) (functions \, (list))) `(phpinspect--root-index (imports \, (list)) (classes ,(list (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)))) (methods \, (list (phpinspect--make-function :name "arrayReturn" :scope '(:public) :arguments (list) :return-type (phpinspect--make-type :name "\\array" :collection t :contains (phpinspect--make-type :name "\\App\\Entity\\DateTime" :collection nil :contains nil :fully-qualified t) :fully-qualified t)) (phpinspect--make-function :name "getCreationTime" :scope '(:public) :arguments (list) :return-type (phpinspect--make-type :name "\\DateTime" :collection nil :contains nil :fully-qualified t)) (phpinspect--make-function :name "isValid" :scope '(:public) :arguments (list) :return-type (phpinspect--make-type :name "\\bool" :collection nil :contains nil :fully-qualified t)) (phpinspect--make-function :name "hasStudentRole" :scope '(:public) :arguments (list) :return-type (phpinspect--make-type :name "\\bool" :collection nil :contains nil :fully-qualified t)) (phpinspect--make-function :name "getUser" :scope '(:public) :arguments (list) :return-type (phpinspect--make-type :name "\\App\\Entity\\User" :collection nil :contains nil :fully-qualified t)) (phpinspect--make-function :name "getToken" :scope '(:public) :arguments (list) :return-type (phpinspect--make-type :name "\\string" :collection nil :contains nil :fully-qualified t)) (phpinspect--make-function :name "__construct" :scope '(:public) :arguments (list (list "token" (phpinspect--make-type :name "\\string" :collection nil :contains nil :fully-qualified t)) (list "user" (phpinspect--make-type :name "\\App\\Entity\\User" :collection nil :contains nil :fully-qualified t)) (list "valid" (phpinspect--make-type :name "\\bool" :collection nil :contains nil :fully-qualified t)) (list "creation_time" (phpinspect--make-type :name "\\DateTime" :collection nil :contains nil :fully-qualified t))) :return-type (phpinspect--make-type :name "\\null" :collection nil :contains nil :fully-qualified t)))) (static-methods \, (list)) (static-variables \, (list)) (variables \, (list (phpinspect--make-variable :name "creation_time" :type (phpinspect--make-type :name "\\DateTime" :collection nil :contains nil :fully-qualified t) :scope '(:private)) (phpinspect--make-variable :name "valid" :type (phpinspect--make-type :name "\\bool" :collection nil :contains nil :fully-qualified t) :scope '(:private)) (phpinspect--make-variable :name "user" :type (phpinspect--make-type :name "\\App\\Entity\\App\\\\Entity\\\\User" :collection nil :contains nil :fully-qualified t) :scope '(:private)) (phpinspect--make-variable :name "token" :type (phpinspect--make-type :name "\\string" :collection nil :contains nil :fully-qualified t) :scope '(:private)))) (constants \, (list)) (extends \, (list)) (implements \, (list))))) (functions \, (list)))

@ -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 ";")) (:doc-block (:var-annotation (:word "bool"))) (:private (:variable "valid") (:terminator ";")) (:doc-block (:var-annotation (:word "\\DateTime"))) (:private (:variable "creation_time") (:terminator ";")) (:public (:function (:declaration (:word "function") (:word "__construct") (:list (:word "string") (:variable "token") (:comma ",") (:word "User") (:variable "user") (:comma ",") (:word "bool") (:variable "valid") (:assignment "=") (:word "false") (:comma ",") (:word "\\DateTime") (:variable "creation_time") (:assignment "=") (:word "null"))) (:block (:variable "this") (:object-attrib (:word "token")) (:assignment "=") (:variable "token") (:terminator ";") (:variable "this") (:object-attrib (:word "user")) (:assignment "=") (:variable "user") (:terminator ";") (:variable "this") (:object-attrib (:word "valid")) (:assignment "=") (:variable "valid") (:terminator ";") (:variable "this") (:object-attrib (:word "creation_time")) (:assignment "=") (:variable "creation_time") (:word "new") (:word "\\DateTime") (:list) (:terminator ";")))) (:public (:function (:declaration (:word "function") (:word "getToken") (:list) (:word "string")) (:block (:word "return") (:variable "this") (:object-attrib (:word "token")) (:terminator ";")))) (:public (:function (:declaration (:word "function") (:word "getUser") (:list) (:word "User")) (:block (:word "return") (:variable "this") (:object-attrib (:word "user")) (:terminator ";")))) (:public (:function (:declaration (:word "function") (:word "hasStudentRole") (:list) (:word "bool")) (:block (:word "return") (:variable "this") (:object-attrib (:word "role_student")) (:terminator ";")))) (:public (:function (:declaration (:word "function") (:word "isValid") (:list) (:word "bool")) (:block (:word "return") (:variable "this") (:object-attrib (:word "valid")) (:terminator ";")))) (:public (:function (:declaration (:word "function") (:word "getCreationTime") (:list) (:word "\\DateTime")) (:block (:word "return") (:variable "this") (:object-attrib (:word "creation_time")) (:terminator ";")))))))) (: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 ";")) (:doc-block (:var-annotation (:word "bool"))) (:private (:variable "valid") (:terminator ";")) (:doc-block (:var-annotation (:word "\\DateTime"))) (:private (:variable "creation_time") (:terminator ";")) (:public (:function (:declaration (:word "function") (:word "__construct") (:list (:word "string") (:variable "token") (:comma ",") (:word "User") (:variable "user") (:comma ",") (:word "bool") (:variable "valid") (:assignment "=") (:word "false") (:comma ",") (:word "\\DateTime") (:variable "creation_time") (:assignment "=") (:word "null"))) (:block (:variable "this") (:object-attrib (:word "token")) (:assignment "=") (:variable "token") (:terminator ";") (:variable "this") (:object-attrib (:word "user")) (:assignment "=") (:variable "user") (:terminator ";") (:variable "this") (:object-attrib (:word "valid")) (:assignment "=") (:variable "valid") (:terminator ";") (:variable "this") (:object-attrib (:word "creation_time")) (:assignment "=") (:variable "creation_time") (:word "new") (:word "\\DateTime") (:list) (:terminator ";")))) (:public (:function (:declaration (:word "function") (:word "getToken") (:list) (:word "string")) (:block (:word "return") (:variable "this") (:object-attrib (:word "token")) (:terminator ";")))) (:public (:function (:declaration (:word "function") (:word "getUser") (:list) (:word "User")) (:block (:word "return") (:variable "this") (:object-attrib (:word "user")) (:terminator ";")))) (:public (:function (:declaration (:word "function") (:word "hasStudentRole") (:list) (:word "bool")) (:block (:word "return") (:variable "this") (:object-attrib (:word "role_student")) (:terminator ";")))) (:public (:function (:declaration (:word "function") (:word "isValid") (:list) (:word "bool")) (:block (:word "return") (:variable "this") (:object-attrib (:word "valid")) (:terminator ";")))) (:public (:function (:declaration (:word "function") (:word "getCreationTime") (:list) (:word "\\DateTime")) (:block (:word "return") (:variable "this") (:object-attrib (:word "creation_time")) (:terminator ";")))) (:doc-block (:return-annotation (:word "DateTime[]"))) (:public (:function (:declaration (:word "function") (:word "arrayReturn") (:list) (:word "array")) (:block (:word "return") (:array (:word "new") (:word "\\DateTime") (:list)) (:terminator ";"))))))))

@ -63,4 +63,12 @@ class AuthToken
{ {
return $this->creation_time; return $this->creation_time;
} }
/**
* @return DateTime[]
*/
public function arrayReturn(): array
{
return [ new \DateTime() ];
}
} }

@ -30,6 +30,7 @@
;; data types that are used in tests so that we don't depend on some global ;; data types that are used in tests so that we don't depend on some global
;; worker object for tests. ;; worker object for tests.
(phpinspect-ensure-worker) (phpinspect-ensure-worker)
(phpinspect-purge-cache)
(defvar phpinspect-test-directory (defvar phpinspect-test-directory
(file-name-directory (file-name-directory
@ -60,6 +61,126 @@
(phpinspect-parse-file (phpinspect-parse-file
(concat phpinspect-test-php-file-directory "/" name ".php"))) (concat phpinspect-test-php-file-directory "/" name ".php")))
(ert-deftest phpinspect-get-variable-type-in-block ()
(let* ((tokens (phpinspect-parse-string "class Foo { function a(\\Thing $baz) { $foo = new \\DateTime(); $bar = $foo;"))
(context (phpinspect--get-resolvecontext tokens))
(project-root "could never be a real project root")
(phpinspect-project-root-function
(lambda (&rest _ignored) project-root))
(project (phpinspect--make-project
:fs (phpinspect-make-virtual-fs)
:root project-root
:worker (phpinspect-make-worker))))
(puthash project-root project (phpinspect--cache-projects phpinspect-cache))
(let ((result (phpinspect-get-variable-type-in-block
context "foo"
(phpinspect-function-block
(car (phpinspect--resolvecontext-enclosing-tokens context)))
(phpinspect--make-type-resolver-for-resolvecontext context))))
(should (phpinspect--type= (phpinspect--make-type :name "\\DateTime")
result)))))
(ert-deftest phpinspect-get-pattern-type-in-block ()
(let* ((tokens (phpinspect-parse-string "class Foo { function a(\\Thing $baz) { $foo = new \\DateTime(); $this->potato = $foo;"))
(context (phpinspect--get-resolvecontext tokens))
(project-root "could never be a real project root")
(phpinspect-project-root-function
(lambda (&rest _ignored) project-root))
(project (phpinspect--make-project
:fs (phpinspect-make-virtual-fs)
:root project-root
:worker (phpinspect-make-worker))))
(puthash project-root project (phpinspect--cache-projects phpinspect-cache))
(let ((result (phpinspect-get-pattern-type-in-block
context (phpinspect--make-pattern :m `(:variable "this")
:m `(:object-attrib (:word "potato")))
(phpinspect-function-block
(car (phpinspect--resolvecontext-enclosing-tokens context)))
(phpinspect--make-type-resolver-for-resolvecontext context))))
(should (phpinspect--type= (phpinspect--make-type :name "\\DateTime")
result)))))
(ert-deftest phpinspect-get-variable-type-in-block-array-access ()
(let* ((tokens (phpinspect-parse-string "class Foo { function a(\\Thing $baz) { $foo = []; $foo[] = $baz; $bar = $foo[0];"))
(context (phpinspect--get-resolvecontext tokens))
(project-root "could never be a real project root")
(phpinspect-project-root-function
(lambda (&rest _ignored) project-root))
(project (phpinspect--make-project
:fs (phpinspect-make-virtual-fs)
:root project-root
:worker (phpinspect-make-worker))))
(puthash project-root project (phpinspect--cache-projects phpinspect-cache))
(let* ((function-token (car (phpinspect--resolvecontext-enclosing-tokens context)))
(result (phpinspect-get-variable-type-in-block
context "bar"
(phpinspect-function-block function-token)
(phpinspect--make-type-resolver-for-resolvecontext context)
(phpinspect-function-argument-list function-token))))
(should (phpinspect--type= (phpinspect--make-type :name "\\Thing")
result)))))
(ert-deftest phpinspect-get-variable-type-in-block-array-foreach ()
(let* ((tokens (phpinspect-parse-string "class Foo { function a(\\Thing $baz) { $foo = []; $foo[] = $baz; foreach ($foo as $bar) {$bar->"))
(context (phpinspect--get-resolvecontext tokens))
(project-root "could never be a real project root")
(phpinspect-project-root-function
(lambda (&rest _ignored) project-root))
(project (phpinspect--make-project
:fs (phpinspect-make-virtual-fs)
:root project-root
:worker (phpinspect-make-worker))))
(puthash project-root project (phpinspect--cache-projects phpinspect-cache))
(let* ((function-token (seq-find #'phpinspect-function-p
(phpinspect--resolvecontext-enclosing-tokens context)))
(result (phpinspect-get-variable-type-in-block
context "bar"
(phpinspect-function-block function-token)
(phpinspect--make-type-resolver-for-resolvecontext context)
(phpinspect-function-argument-list function-token))))
(should (phpinspect--type= (phpinspect--make-type :name "\\Thing")
result)))))
(ert-deftest phpinspect--find-assignments-in-token ()
(let* ((tokens (cadr
(phpinspect-parse-string "{ $foo = ['nr 1']; $bar = $nr2; if (true === ($foo = $nr3)) { $foo = $nr4; $notfoo = $nr5; if ([] === ($foo = [ $nr6 ])){ $foo = [ $nr7 ];}}}")))
(expected '(((:variable "foo")
(:assignment "=")
(:array
(:variable "nr7")))
((:variable "foo")
(:assignment "=")
(:array
(:variable "nr6")))
((:variable "notfoo")
(:assignment "=")
(:variable "nr5"))
((:variable "foo")
(:assignment "=")
(:variable "nr4"))
((:variable "foo")
(:assignment "=")
(:variable "nr3"))
((:variable "bar")
(:assignment "=")
(:variable "nr2"))
((:variable "foo")
(:assignment "=")
(:array
(:string "nr 1")))))
(assignments (phpinspect--find-assignments-in-token tokens)))
(should (equal expected assignments))))
(ert-deftest phpinspect-parse-namespaced-class () (ert-deftest phpinspect-parse-namespaced-class ()
"Test phpinspect-parse on a namespaced class" "Test phpinspect-parse on a namespaced class"
(should (should
@ -456,6 +577,29 @@ class Thing
(phpinspect--get-last-statement-in-token (phpinspect--get-last-statement-in-token
(phpinspect-parse-string php-code-bare)))))) (phpinspect-parse-string php-code-bare))))))
(ert-deftest phpinspect--find-assignments-by-predicate ()
(let* ((token '(:block
(:variable "bam") (:object-attrib "boom") (:assignment "=")
(:variable "beng") (:terminator)
(:variable "notbam") (:word "nonsense") (:assignment "=") (:string) (:terminator)
(:variable "bam") (:comment) (:object-attrib "boom") (:assignment "=")
(:variable "wat") (:object-attrib "call") (:terminator)))
(result (phpinspect--find-assignments-by-predicate
token
(phpinspect--match-sequence-lambda :m `(:variable "bam") :m `(:object-attrib "boom")))))
(should (= 2 (length result)))
(dolist (assignment result)
(should (equal '((:variable "bam") (:object-attrib "boom"))
(phpinspect--assignment-to assignment))))
(should (equal '((:variable "beng"))
(phpinspect--assignment-from (cadr result))))
(should (equal '((:variable "wat") (:object-attrib "call"))
(phpinspect--assignment-from (car result))))))
(load-file (concat phpinspect-test-directory "/test-worker.el")) (load-file (concat phpinspect-test-directory "/test-worker.el"))
(load-file (concat phpinspect-test-directory "/test-autoload.el")) (load-file (concat phpinspect-test-directory "/test-autoload.el"))
(load-file (concat phpinspect-test-directory "/test-fs.el")) (load-file (concat phpinspect-test-directory "/test-fs.el"))
@ -464,7 +608,7 @@ class Thing
(load-file (concat phpinspect-test-directory "/test-index.el")) (load-file (concat phpinspect-test-directory "/test-index.el"))
(load-file (concat phpinspect-test-directory "/test-class.el")) (load-file (concat phpinspect-test-directory "/test-class.el"))
(load-file (concat phpinspect-test-directory "/test-type.el")) (load-file (concat phpinspect-test-directory "/test-type.el"))
(load-file (concat phpinspect-test-directory "/test-util.el"))
(provide 'phpinspect-test) (provide 'phpinspect-test)
;;; phpinspect-test.el ends here ;;; phpinspect-test.el ends here

@ -55,6 +55,7 @@
:scope '(:public) :scope '(:public)
:arguments `(("untyped" nil) :arguments `(("untyped" nil)
("things" ,(phpinspect--make-type :name "\\array" ("things" ,(phpinspect--make-type :name "\\array"
:collection t
:fully-qualified t))) :fully-qualified t)))
:return-type phpinspect--null-type))) :return-type phpinspect--null-type)))
(static-variables) (static-variables)

@ -0,0 +1,47 @@
;; test-util.el --- Unit tests for phpinspect.el -*- lexical-binding: t; -*-
;; Copyright (C) 2021 Free Software Foundation, Inc.
;; Author: Hugo Thunnissen <devel@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:
(ert-deftest phpinspect--pattern ()
(let* ((a "a")
(pattern1 (phpinspect--make-pattern :m `(,a) :m '* :m "b"))
(pattern2 (phpinspect--make-pattern :f #'listp :m '* :m "b")))
(should (equal '(:m ("a") :m * :m "b") (phpinspect--pattern-code pattern1)))
(should (equal '(:f listp :m * :m "b") (phpinspect--pattern-code pattern2)))
(dolist (pattern `(,pattern1 ,pattern2))
(should (phpinspect--pattern-match pattern '(("a") "c" "b")))
(should (phpinspect--pattern-match pattern '(("a") nil "b")))
(should-not (phpinspect--pattern-match pattern '(1 nil "b")))
(should-not (phpinspect--pattern-match pattern '(("a") nil "b" "c"))))))
(ert-deftest phpinspect--pattern-concat ()
(let* ((pattern1 (phpinspect--make-pattern :m "a" :m '* :m "b"))
(pattern2 (phpinspect--make-pattern :f #'stringp :m '* :m "D"))
(result (phpinspect--pattern-concat pattern1 pattern2)))
(should (equal '(:m "a" :m * :m "b" :f stringp :m * :m "D") (phpinspect--pattern-code result)))
(should (phpinspect--pattern-match result '("a" "anything" "b" "astring" nil "D")))))
Loading…
Cancel
Save