Implement array member type inference

phpinspect now understands typed arrays!
WIP-incremental-parsing
Hugo Thunnissen 1 year ago
parent ae3acbdbe1
commit 224bbd7916

@ -41,14 +41,11 @@ as keys and project caches as values."))
((cache phpinspect--cache) (project-root string))
(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
((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)))
(unless project
(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
;; type of its members.
(let* ((is-collection
(when type
(member (phpinspect--type-name type) phpinspect-collection-types)))
(return-annotation-type
(when (or (phpinspect--should-prefer-return-annotation type) is-collection)
(cadadr
(seq-find #'phpinspect-return-annotation-p
comment-before)))))
(phpinspect--log "found return annotation %s when type is %s"
return-annotation-type
type)
(let* ((return-annotation-type
(cadadr (seq-find #'phpinspect-return-annotation-p comment-before)))
(is-collection
(and type
(phpinspect--type-is-collection type))))
(phpinspect--log "found return annotation %s in %s when type is %s"
return-annotation-type comment-before type)
(when (string-suffix-p "[]" return-annotation-type)
(setq is-collection t)
(setq return-annotation-type (string-trim-right return-annotation-type "\\[\\]")))
(when return-annotation-type
(cond ((phpinspect--should-prefer-return-annotation type)
@ -104,8 +104,7 @@ function (think \"new\" statements, return types etc.)."
(phpinspect--make-function
:scope `(,(car scope))
:name (cadadr (cdr declaration))
:return-type (if type (funcall type-resolver type)
phpinspect--null-type)
:return-type (or type phpinspect--null-type)
:arguments (phpinspect--index-function-arg-list
type-resolver
(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"
(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)
(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))
(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)
(caddr class))
@ -444,7 +448,8 @@ token is \";\", which marks the end of a statement in PHP."
(list-handler (phpinspect-handler 'list))
(list-regexp (phpinspect-handler-regexp 'list))
(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-regexp (phpinspect-handler-regexp 'variable))
(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
;; don't necessarily require the same handlers to parse.
(defsubst phpinspect-get-or-create-declaration-parser ()
(phpinspect-get-parser-create :declaration
'(comment word list terminator tag)
#'phpinspect-end-of-token-p))
(let ((parser (phpinspect-get-parser-create
:declaration
'(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)

@ -26,7 +26,8 @@
(require 'phpinspect-util)
(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."
(name-symbol nil
:type symbol
@ -97,6 +98,13 @@ See https://wiki.php.net/rfc/static_return_type ."
(when (phpinspect--type= type native)
(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))
(symbol-name (phpinspect--type-name-symbol type)))
@ -142,6 +150,8 @@ NAMESPACE may be nil, or a string with a namespace FQN."
type
(phpinspect--resolve-type-name types namespace (phpinspect--type-name type)))
(setf (phpinspect--type-fully-qualified type) t))
(when (phpinspect--type-is-collection type)
(setf (phpinspect--type-collection type) t))
type)
(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 "Disabled phpinspect logging.")))
(defsubst phpinspect--log (&rest args)
(when phpinspect--debug
(with-current-buffer (get-buffer-create "**phpinspect-logs**")
@ -60,7 +59,139 @@ PHP. Used to optimize string comparison.")
(set (make-local-variable 'window-point-insertion-type) t))
(goto-char (buffer-end 1))
(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))
;; (defmacro 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)
;; (and-statement))
;; (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 '*)
;; (push `(equal ,value (elt ,sequence ,sequence-pos)) and-statement)))
;; ((eq key :f)
;; (push `(,value (elt ,sequence ,sequence-pos)) and-statement))
;; (t (error (format "Invalid keyword: %s" key))))
;; (setq count (+ count 2)
;; sequence-pos (+ sequence-pos 1))))
;; `(when (= ,sequence-pos (length ,sequence)) (and ,@and-statement))))
(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)
;;; phpinspect-util.el ends here

@ -142,12 +142,11 @@ accompanied by all of its enclosing tokens."
(last-encountered-token (car
(phpinspect--resolvecontext-enclosing-tokens
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-block-p token))
;; When a class or function has been inserted already, its block
;; doesn't need to be added on top.
(phpinspect--resolvecontext-push-enclosing-token resolvecontext nil)
(phpinspect--resolvecontext-push-enclosing-token resolvecontext token))
(if (phpinspect-incomplete-token-p last-token)
@ -156,9 +155,6 @@ accompanied by all of its enclosing tokens."
(setf (phpinspect--resolvecontext-subject resolvecontext)
(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)))
@ -229,7 +225,6 @@ accompanied by all of its enclosing tokens."
(when method
(phpinspect--function-return-type method))))))
(defsubst phpinspect-get-cached-project-class-variable-type
(project-root class-fqn variable-name)
(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)
(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)
(current-sublist))
(dolist (thing list)
(if (funcall predicate thing)
(dolist (thing tokens)
(if (or (phpinspect-end-of-statement-p thing)
(when predicate (funcall predicate thing)))
(when current-sublist
(when (phpinspect-block-p thing)
(push thing current-sublist))
(push (nreverse current-sublist) sublists)
(setq current-sublist nil))
(push thing current-sublist)))
@ -394,6 +396,15 @@ TODO:
(phpinspect--format-type-name
(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)
(or (phpinspect-block-p token)
(phpinspect-list-p token)))
@ -405,74 +416,72 @@ TODO:
(cl-defgeneric phpinspect--find-assignments-in-token (token)
"Find any assignments that are in TOKEN, at top level or nested in blocks"
(when (keywordp (car token))
(setq token (cdr token)))
(let ((assignments)
(block-or-list)
(statements (phpinspect--split-list #'phpinspect-end-of-statement-p token)))
(blocks-or-lists)
(statements (phpinspect--split-statements token)))
(dolist (statement statements)
(cond ((seq-find #'phpinspect-assignment-p statement)
(phpinspect--log "Found assignment statement")
(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)
(setq assignments
(append
(phpinspect--find-assignments-in-token block-or-list)
assignments)))))
(when (seq-find #'phpinspect-maybe-assignment-p statement)
(phpinspect--log "Found assignment statement")
(push statement assignments))
(when (setq blocks-or-lists (seq-filter #'phpinspect-block-or-list-p statement))
(dolist (block-or-list blocks-or-lists)
(phpinspect--log "Found block or list %s" block-or-list)
(let ((local-assignments (phpinspect--find-assignments-in-token block-or-list)))
(dolist (local-assignment (nreverse local-assignments))
(push local-assignment assignments))))))
;; return
(phpinspect--log "Found assignments in token: %s" assignments)
(phpinspect--log "Found statements in token: %s" statements)
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)
"Inverse of applying `phpinspect-assignment-p to TOKEN."
(not (phpinspect-maybe-assignment-p token)))
(defun phpinspect--find-assignment-values-for-variable-in-token (variable-name token)
"Find all assignments of variable VARIABLE-NAME in TOKEN."
(defsubst phpinspect-not-comment-p (token)
(not (phpinspect-comment-p token)))
(defun phpinspect--find-assignments-by-predicate (token predicate)
(let ((variable-assignments)
(all-assignments (phpinspect--find-assignments-in-token token)))
(dolist (assignment all-assignments)
(let* ((is-loop-assignment nil)
(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
(cdr (seq-drop-while (lambda (elt)
(if (phpinspect-maybe-assignment-p elt)
(progn
(when (equal '(:word "as") elt)
(phpinspect--log "It's a loop assignment %s" elt)
(setq is-loop-assignment t))
nil)
t))
assignment))))
(seq-filter
#'phpinspect-not-comment-p
(cdr (seq-drop-while
(lambda (elt)
(if (phpinspect-maybe-assignment-p elt)
(progn
(when (equal '(:word "as") elt)
(phpinspect--log "It's a loop assignment %s" elt)
(setq is-loop-assignment t))
nil)
t))
assignment)))))
(if is-loop-assignment
(when (member `(:variable ,variable-name) right-of-assignment)
(push left-of-assignment variable-assignments))
(when (member `(:variable ,variable-name) left-of-assignment)
(push right-of-assignment variable-assignments)))))
(when (funcall predicate right-of-assignment)
;; Masquerade as an array access assignment
(setq left-of-assignment (append left-of-assignment '((:array))))
(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)))
;; (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)
(while (and statement (phpinspect-word-p (cadr statement)))
(pop statement))
@ -566,7 +575,11 @@ $variable = $variable->method();"
resolvecontext)
(funcall type-resolver previous-attribute-type)
(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)
;; Make sure to always return a FQN
(funcall type-resolver previous-attribute-type))))
@ -588,48 +601,168 @@ resolve types of function argument variables."
(phpinspect--log "Looking for assignments of variable %s in php block" variable-name)
(if (string= variable-name "this")
(funcall type-resolver (phpinspect--make-type :name "self"))
(phpinspect-get-pattern-type-in-block
resolvecontext (phpinspect--make-pattern :m `(:variable ,variable-name))
php-block type-resolver function-arg-list)))
;; else
(let* ((assignments
(phpinspect--find-assignment-values-for-variable-in-token variable-name php-block))
(last-assignment-value (when assignments (car (last assignments)))))
(phpinspect--log "Last assignment: %s" last-assignment-value)
(phpinspect--log "Current block: %s" php-block)
;; 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->attribute
(cond ((and (phpinspect-word-p (car last-assignment-value))
(string= (cadar last-assignment-value) "new"))
(funcall type-resolver (phpinspect--make-type :name (cadadr last-assignment-value))))
((and (> (length last-assignment-value) 1)
(seq-find #'phpinspect-attrib-p last-assignment-value))
(phpinspect--log "Variable was assigned with a derived statement")
(phpinspect-get-derived-statement-type-in-block resolvecontext
last-assignment-value
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 last-assignment-value))
(phpinspect--log "Variable was assigned with the value of another variable: %s"
last-assignment-value)
(or (when function-arg-list
(phpinspect-get-variable-type-in-function-arg-list (cadar last-assignment-value)
type-resolver
function-arg-list))
(phpinspect-get-variable-type-in-block resolvecontext
(cadar last-assignment-value)
php-block
type-resolver
function-arg-list)))
((not assignments)
(phpinspect--log "No assignments found for variable %s, checking function arguments"
variable-name)
(phpinspect-get-variable-type-in-function-arg-list variable-name
type-resolver
function-arg-list))))))
;; (let* ((assignments
;; (phpinspect--find-assignments-by-predicate
;; php-block (phpinspect--match-sequence-lambda
;; :m `(:variable ,variable-name))))
;; (last-assignment (when assignments (car (last assignments))))
;; (last-assignment-value (when last-assignment
;; (phpinspect--assignment-from last-assignment)))
;; (result))
;; (if (not assignments)
;; (progn
;; (phpinspect--log "No assignments found for variable %s, checking function arguments"
;; variable-name)
;; (setq result (phpinspect-get-variable-type-in-function-arg-list
;; variable-name type-resolver function-arg-list)))
;; (setq result
;; (phpinspect--interpret-expression-type-in-context
;; resolvecontext php-block type-resolver
;; last-assignment-value function-arg-list)))
;; (phpinspect--log "Type interpreted from last assignment expression of variable %s: %s"
;; variable-name result)
;; ;; 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 variable %s"
;; ", collection type: %s")
;; variable-name result)
;; (phpinspect--type-contains result))
;; result))))
(defun phpinspect-get-pattern-type-in-block
(resolvecontext pattern php-block type-resolver &optional function-arg-list)
"Find the type of PATTERN in PHP-BLOCK using TYPE-RESOLVER.
PATTERN must be an object of the type `phpinspect--pattern'.
Returns either a FQN or a relative type name, depending on
whether or not the root variable of the assignment value (right
side of assignment) needs to be extracted from FUNCTION-ARG-LIST.
When PHP-BLOCK belongs to a function, supply FUNCTION-ARG-LIST to
resolve types of function argument variables."
(let* ((assignments
(phpinspect--find-assignments-by-predicate
php-block (phpinspect--pattern-matcher pattern)))
(last-assignment (when assignments (car (last assignments))))
(last-assignment-value (when last-assignment
(phpinspect--assignment-from last-assignment)))
(pattern-code (phpinspect--pattern-code pattern))
(result))
(phpinspect--log "Looking for assignments of pattern %s in php block" pattern-code)
(if (not assignments)
(when (and (= (length pattern-code) 2) (phpinspect-variable-p (cadr pattern-code)))
(let ((variable-name (cadadr pattern-code)))
(progn
(phpinspect--log "No assignments found for variable %s, checking function arguments: %s"
variable-name function-arg-list)
(setq result (phpinspect-get-variable-type-in-function-arg-list
variable-name type-resolver function-arg-list)))))
(setq result
(phpinspect--interpret-expression-type-in-context
resolvecontext php-block type-resolver
last-assignment-value function-arg-list)))
(phpinspect--log "Type interpreted from last assignment expression of pattern %s: %s"
pattern-code result)
(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)
@ -1211,7 +1344,8 @@ before the search is executed."
(autoloader (phpinspect--project-autoload project)))
(when (eq index-new 'index-new)
(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)
;; Index new files and try again if not done already.
(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 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
;; worker object for tests.
(phpinspect-ensure-worker)
(phpinspect-purge-cache)
(defvar phpinspect-test-directory
(file-name-directory
@ -60,6 +61,104 @@
(phpinspect-parse-file
(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-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 ()
"Test phpinspect-parse on a namespaced class"
(should
@ -456,6 +555,29 @@ class Thing
(phpinspect--get-last-statement-in-token
(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-autoload.el"))
(load-file (concat phpinspect-test-directory "/test-fs.el"))
@ -464,7 +586,7 @@ class Thing
(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-type.el"))
(load-file (concat phpinspect-test-directory "/test-util.el"))
(provide 'phpinspect-test)
;;; phpinspect-test.el ends here

@ -55,6 +55,7 @@
:scope '(:public)
:arguments `(("untyped" nil)
("things" ,(phpinspect--make-type :name "\\array"
:collection t
:fully-qualified t)))
:return-type phpinspect--null-type)))
(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