diff --git a/phpinspect-index.el b/phpinspect-index.el index 6484c5f..faa28ec 100644 --- a/phpinspect-index.el +++ b/phpinspect-index.el @@ -172,6 +172,22 @@ function (think \"new\" statements, return types etc.)." (defun phpinspect--var-annotations-from-token (token) (seq-filter #'phpinspect-var-annotation-p token)) +(define-inline phpinspect-var-annotation-variable (annotation) + (inline-quote (cadr (caddr ,annotation)))) + +(define-inline phpinspect-var-annotation-type (annotation) + (inline-quote (cadadr ,annotation))) + +(defun phpinspect--find-var-annotation-for-variable (annotation-list variable) + (catch 'return + (dolist (annotation annotation-list) + (when (and (phpinspect-var-annotation-p annotation) + (phpinspect-var-annotation-variable annotation) + (string= (phpinspect-var-annotation-variable annotation) + variable)) + (throw 'return annotation))) + nil)) + (defun phpinspect--variable-type-string-from-comment (comment variable-name) (let* ((var-annotations (phpinspect--var-annotations-from-token comment)) (type (if var-annotations diff --git a/phpinspect-resolve.el b/phpinspect-resolve.el index 46b24ad..785a8bb 100644 --- a/phpinspect-resolve.el +++ b/phpinspect-resolve.el @@ -60,33 +60,48 @@ (cl-defstruct (phpinspect--assignment-context (:constructor phpinspect--make-assignment-context) (:conc-name phpinspect--actx-)) + (annotations nil + :type list + :documentation "List of var annotations available in context") (tokens nil :type list) (preceding-assignments nil :type list)) -(defun phpinspect--find-assignment-ctxs-in-token (token &optional assignments-before) +(defun phpinspect--find-assignment-ctxs-in-token (token &optional assignments-before var-annotations) (when (keywordp (car token)) (setq token (cdr token))) - (let ((assignments) - (blocks-or-lists) - (statements (phpinspect--split-statements token))) + (setq var-annotations (or var-annotations (cons nil nil))) + + (let ((statements (phpinspect--split-statements token)) + assignments blocks-or-lists) (dolist (statement statements) (phpinspect--log "Finding assignment in statement '%s'" statement) (when (seq-find #'phpinspect-maybe-assignment-p statement) (phpinspect--log "Found assignment statement") (push (phpinspect--make-assignment-context + :annotations var-annotations :tokens statement :preceding-assignments assignments-before) assignments) (setq assignments-before assignments)) + ;; Find all var annotations in statement. + (when-let* ((comments (seq-filter #'phpinspect-comment-p statement))) + (dolist (comment comments) + (dolist (token comment) + (when (phpinspect-var-annotation-p token) + ;; Intentionally destructively modify annotation list so that all + ;; assignments have the same annotations available to them. + (push token (cdr var-annotations)))))) + (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-assignment-ctxs-in-token block-or-list assignments-before))) + (phpinspect--find-assignment-ctxs-in-token + block-or-list assignments-before var-annotations))) (dolist (local-assignment (nreverse local-assignments)) (push local-assignment assignments)) (setq assignments-before assignments))))) @@ -133,8 +148,6 @@ Destructively removes tokens from the end of ASSIGNMENT-TOKENS." :to left-of-assignment :ctx actx))))))) nil)) - ;; (phpinspect--log "Returning the thing %s" variable-assignments) - ;; (nreverse variable-assignments))) (defsubst phpinspect-drop-preceding-barewords (statement) (while (and statement (phpinspect-word-p (cadr statement))) @@ -385,6 +398,18 @@ resolve types of function argument variables." (phpinspect--log "Type interpreted from last assignment expression of pattern %s: %s" pattern-code result) + ;; Unable to resolve a type from the code. When the pattern is for a + ;; variable, attempt to find a @var annotation for the specified variable. + (when (and (not result) last-assignment + (and (= (phpinspect--pattern-length pattern) 1) + (phpinspect-variable-p (cadr pattern-code)))) + (when-let* ((annotation (phpinspect--find-var-annotation-for-variable + (phpinspect--actx-annotations + (phpinspect--assignment-ctx last-assignment)) + (cadadr pattern-code))) + (annotation-type (phpinspect-var-annotation-type annotation))) + (setq result (funcall type-resolver (phpinspect--make-type :name annotation-type))))) + (when (and result (phpinspect--type-collection result) (not (phpinspect--type-contains result))) (phpinspect--log (concat "Interpreted type %s is a collection type, but 'contains'" @@ -400,7 +425,6 @@ resolve types of function argument variables." (phpinspect--get-pattern-type-from-assignments resolvecontext concat-pattern php-block assignments type-resolver function-arg-list)))) - ;; return result)) diff --git a/test/test-resolve.el b/test/test-resolve.el index 38aa096..7acdd45 100644 --- a/test/test-resolve.el +++ b/test/test-resolve.el @@ -119,3 +119,27 @@ (should (phpinspect--type= (phpinspect--make-type :name "\\string") (phpinspect-resolve-type-from-context context))))))) + + +(ert-deftest phpinspect-get-variable-type-in-block-var-annotation () + (let ((base-code "/* @var \\Foo $banana */ $banana = $undefined;") + (paths (list "$banana->bar" + "if ($baz = $banana) { $bar = $banana; } $bar->bar" + "$baz = new \\DateTime(); /** @var \\DateTime $pear */} $banana->bar" + "if ($baz = $banana->bar) { $baz")) + (project (phpinspect--make-dummy-project))) + + (phpinspect-project-add-index + project + (phpinspect--index-tokens + (phpinspect-parse-string "class Foo { public string $bar; }"))) + + (dolist (path paths) + (let* ((code (concat base-code path)) + (bmap (phpinspect-parse-string-to-bmap code)) + (context (phpinspect-get-resolvecontext project bmap (length code))) + (result (phpinspect-resolve-type-from-context context))) + + (should result) + (should (phpinspect--type= (phpinspect--make-type :name "\\string") + result))))))