Move more functionalities from main file to separate modules
parent
1f145665ef
commit
d1d34a4249
@ -0,0 +1,187 @@
|
||||
;;; phpinspect-type.el --- PHP parsing and completion package -*- lexical-binding: t; -*-
|
||||
|
||||
;; Copyright (C) 2021 Free Software Foundation, Inc
|
||||
|
||||
;; Author: Hugo Thunnissen <devel@hugot.nl>
|
||||
;; Keywords: php, languages, tools, convenience
|
||||
;; Version: 0
|
||||
|
||||
;; 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:
|
||||
|
||||
(require 'phpinspect-bmap)
|
||||
(require 'phpinspect-buffer)
|
||||
(require 'phpinspect-resolvecontext)
|
||||
(require 'phpinspect-suggest)
|
||||
|
||||
(defvar phpinspect--last-completion-list nil
|
||||
"Used internally to save metadata about completion options
|
||||
between company backend calls")
|
||||
|
||||
(cl-defstruct (phpinspect--completion
|
||||
(:constructor phpinspect--construct-completion))
|
||||
"Contains a possible completion value with all it's attributes."
|
||||
(value nil :type string)
|
||||
(meta nil :type string)
|
||||
(annotation nil :type string)
|
||||
(kind nil :type symbol))
|
||||
|
||||
(cl-defgeneric phpinspect--make-completion (completion-candidate)
|
||||
"Creates a `phpinspect--completion` for a possible completion
|
||||
candidate. Candidates can be indexed functions and variables.")
|
||||
|
||||
(cl-defstruct (phpinspect--completion-list
|
||||
(:constructor phpinspect--make-completion-list))
|
||||
"Contains all data for a completion at point"
|
||||
(completions (obarray-make)
|
||||
:type obarray
|
||||
:documentation
|
||||
"A list of completion strings"))
|
||||
|
||||
(cl-defgeneric phpinspect--completion-list-add
|
||||
(comp-list completion)
|
||||
"Add a completion to a completion-list.")
|
||||
|
||||
(cl-defmethod phpinspect--completion-list-add
|
||||
((comp-list phpinspect--completion-list) (completion phpinspect--completion))
|
||||
(unless (intern-soft (phpinspect--completion-value completion)
|
||||
(phpinspect--completion-list-completions comp-list))
|
||||
(set (intern (phpinspect--completion-value completion)
|
||||
(phpinspect--completion-list-completions comp-list))
|
||||
completion)))
|
||||
|
||||
(cl-defmethod phpinspect--completion-list-get-metadata
|
||||
((comp-list phpinspect--completion-list) (completion-name string))
|
||||
(let ((comp-sym (intern-soft completion-name
|
||||
(phpinspect--completion-list-completions comp-list))))
|
||||
(when comp-sym
|
||||
(symbol-value comp-sym))))
|
||||
|
||||
|
||||
(cl-defmethod phpinspect--completion-list-strings
|
||||
((comp-list phpinspect--completion-list))
|
||||
(let ((strings))
|
||||
(obarray-map (lambda (sym) (push (symbol-name sym) strings))
|
||||
(phpinspect--completion-list-completions comp-list))
|
||||
strings))
|
||||
|
||||
(cl-defstruct (phpinspect-completion-query (:constructor phpinspect-make-completion-query))
|
||||
(completion-point 0
|
||||
:type integer
|
||||
:documentation
|
||||
"Position in the buffer from where the resolvecontext is determined.")
|
||||
(point 0
|
||||
:type integer
|
||||
:documentation "Position in buffer for which to provide completions")
|
||||
(buffer nil
|
||||
:type phpinspect-buffer))
|
||||
|
||||
(cl-defmethod phpinspect-completion-query-execute ((query phpinspect-completion-query))
|
||||
"Execute QUERY.
|
||||
|
||||
Returns list of `phpinspect--completion'."
|
||||
(let* ((buffer (phpinspect-completion-query-buffer query))
|
||||
(point (phpinspect-completion-query-point query))
|
||||
(buffer-map (phpinspect-buffer-parse-map buffer))
|
||||
(rctx (phpinspect-get-resolvecontext buffer-map point))
|
||||
(candidates))
|
||||
(dolist (strategy phpinspect-completion-strategies)
|
||||
(when (phpinspect-comp-strategy-supports strategy query rctx)
|
||||
(phpinspect--log "Found matching completion strategy. Executing...")
|
||||
(nconc candidates (phpinspect-comp-strategy-execute strategy query rctx))))
|
||||
|
||||
(mapcar #'phpinspect--make-completion candidates)))
|
||||
|
||||
(cl-defgeneric phpinspect-comp-strategy-supports (strategy (query phpinspect-completion-query) (context phpinspect--resolvecontext))
|
||||
"Should return non-nil if STRATEGY should be deployed for QUERY
|
||||
and CONTEXT. All strategies must implement this method.")
|
||||
|
||||
(cl-defgeneric phpinspect-comp-strategy-execute (strategy (query phpinspect-completion-query) (context phpinspect--resolvecontext))
|
||||
"Should return a list of objects for which `phpinspect--make-completion' is implemented.")
|
||||
|
||||
(cl-defstruct (phpinspect-comp-sigil (:constructor phpinspect-make-comp-sigil))
|
||||
"Completion strategy for the sigil ($) character.")
|
||||
|
||||
(cl-defmethod phpinspect-comp-strategy-supports
|
||||
((strat phpinspect-comp-sigil) (q phpinspect-completion-query)
|
||||
(rctx phpinspect--resolvecontext))
|
||||
(and (= (phpinspect-completion-query-completion-point q)
|
||||
(phpinspect-completion-query-point q))
|
||||
(phpinspect-variable-p
|
||||
(phpinspect-meta-token
|
||||
(phpinspect-bmap-last-token-starting-before-point
|
||||
(phpinspect-buffer-parse-map (phpinspect-completion-query-buffer q))
|
||||
(phpinspect-completion-query-point q))))))
|
||||
|
||||
(cl-defmethod phpinspect-comp-strategy-execute
|
||||
((strat phpinspect-comp-sigil) (q phpinspect-completion-query)
|
||||
(rctx phpinspect--resolvecontext))
|
||||
(phpinspect-suggest-variables-at-point rctx))
|
||||
|
||||
(cl-defstruct (phpinspect-comp-attribute (:constructor phpinspect-make-comp-attribute))
|
||||
"Completion strategy for object attributes")
|
||||
|
||||
(cl-defmethod phpinspect-comp-strategy-supports
|
||||
((strat phpinspect-comp-attribute) (q phpinspect-completion-query)
|
||||
(context phpinspect--resolvecontext))
|
||||
(phpinspect-object-attrib-p (car (last (phpinspect--resolvecontext-subject rctx)))))
|
||||
|
||||
(cl-defmethod phpinspect-comp-strategy-execute
|
||||
((strat phpinspect-comp-sigil) (q phpinspect-completion-query)
|
||||
(rctx phpinspect--resolvecontext))
|
||||
(phpinspect-suggest-variables-at-point rctx))
|
||||
|
||||
(cl-defstruct (phpinspect-comp-static-attribute (:constructor phpinspect-make-comp-static-attribute))
|
||||
"Completion strategy for static attributes")
|
||||
|
||||
(cl-defstruct (phpinspect-comp-bareword (:constructor phpinspect-make-comp-bareword))
|
||||
"Completion strategy for bare words")
|
||||
|
||||
|
||||
(cl-defmethod phpinspect--make-completion
|
||||
((completion-candidate phpinspect--function))
|
||||
"Create a `phpinspect--completion` for COMPLETION-CANDIDATE."
|
||||
(phpinspect--construct-completion
|
||||
:value (phpinspect--function-name completion-candidate)
|
||||
:meta (concat "(" (mapconcat (lambda (arg)
|
||||
(concat (phpinspect--format-type-name (cadr arg)) " "
|
||||
"$" (if (> (length (car arg)) 8)
|
||||
(truncate-string-to-width (car arg) 8 nil)
|
||||
(car arg))))
|
||||
(phpinspect--function-arguments completion-candidate)
|
||||
", ")
|
||||
") "
|
||||
(phpinspect--format-type-name (phpinspect--function-return-type completion-candidate)))
|
||||
:annotation (concat " "
|
||||
(phpinspect--type-bare-name
|
||||
(phpinspect--function-return-type completion-candidate)))
|
||||
:kind 'function))
|
||||
|
||||
(cl-defmethod phpinspect--make-completion
|
||||
((completion-candidate phpinspect--variable))
|
||||
(phpinspect--construct-completion
|
||||
:value (phpinspect--variable-name completion-candidate)
|
||||
:meta (phpinspect--format-type-name
|
||||
(or (phpinspect--variable-type completion-candidate)
|
||||
phpinspect--null-type))
|
||||
:annotation (concat " "
|
||||
(phpinspect--type-bare-name
|
||||
(or (phpinspect--variable-type completion-candidate)
|
||||
phpinspect--null-type)))
|
||||
:kind 'variable))
|
||||
|
||||
(provide 'phpinspect-completion)
|
@ -0,0 +1,508 @@
|
||||
;;; phpinspect-resolve.el --- PHP parsing and completion package -*- lexical-binding: t; -*-
|
||||
|
||||
;; Copyright (C) 2021 Free Software Foundation, Inc
|
||||
|
||||
;; Author: Hugo Thunnissen <devel@hugot.nl>
|
||||
;; Keywords: php, languages, tools, convenience
|
||||
;; Version: 0
|
||||
|
||||
;; 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:
|
||||
|
||||
(require 'phpinspect-resolvecontext)
|
||||
(require 'phpinspect-type)
|
||||
(require 'phpinspect-parser)
|
||||
|
||||
(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)))
|
||||
|
||||
(defsubst phpinspect-maybe-assignment-p (token)
|
||||
"Like `phpinspect-assignment-p', but includes \"as\" barewords as possible tokens."
|
||||
(or (phpinspect-assignment-p token)
|
||||
(equal '(:word "as") token)))
|
||||
|
||||
(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)
|
||||
(blocks-or-lists)
|
||||
(statements (phpinspect--split-statements token)))
|
||||
(dolist (statement statements)
|
||||
(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))
|
||||
|
||||
(defsubst phpinspect-not-assignment-p (token)
|
||||
"Inverse of applying `phpinspect-assignment-p to TOKEN."
|
||||
(not (phpinspect-maybe-assignment-p 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-filter #'phpinspect-not-comment-p
|
||||
(seq-take-while #'phpinspect-not-assignment-p assignment)))
|
||||
(right-of-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 (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)))
|
||||
|
||||
(defsubst phpinspect-drop-preceding-barewords (statement)
|
||||
(while (and statement (phpinspect-word-p (cadr statement)))
|
||||
(pop statement))
|
||||
statement)
|
||||
|
||||
;; TODO: the use of this function and similar ones should be replaced with code
|
||||
;; that uses locally injected project objects in stead of retrieving the project
|
||||
;; object through global variables.
|
||||
(defsubst phpinspect-get-cached-project-class (project-root class-fqn)
|
||||
(when project-root
|
||||
(phpinspect-project-get-class
|
||||
(phpinspect--cache-get-project-create (phpinspect--get-or-create-global-cache)
|
||||
project-root)
|
||||
class-fqn)))
|
||||
|
||||
(defun phpinspect-get-cached-project-class-methods (project-root class-fqn &optional static)
|
||||
(phpinspect--log "Getting cached project class methods for %s (%s)"
|
||||
project-root class-fqn)
|
||||
(when project-root
|
||||
(let ((class (phpinspect-get-or-create-cached-project-class
|
||||
project-root
|
||||
class-fqn)))
|
||||
(when class
|
||||
(phpinspect--log "Retrieved class index, starting method collection %s (%s)"
|
||||
project-root class-fqn)
|
||||
(if static
|
||||
(phpinspect--class-get-static-method-list class)
|
||||
(phpinspect--class-get-method-list class))))))
|
||||
|
||||
(defmacro phpinspect-find-function-in-list (method-name list)
|
||||
(let ((break-sym (gensym))
|
||||
(method-name-sym (gensym)))
|
||||
`(let ((,method-name-sym (phpinspect-intern-name ,method-name)))
|
||||
(catch (quote ,break-sym)
|
||||
(dolist (func ,list)
|
||||
(when (eq (phpinspect--function-name-symbol func)
|
||||
,method-name-sym)
|
||||
(throw (quote ,break-sym) func)))))))
|
||||
|
||||
(defsubst phpinspect-get-cached-project-class-method-type
|
||||
(project-root class-fqn method-name)
|
||||
(when project-root
|
||||
(let* ((class (phpinspect-get-or-create-cached-project-class project-root class-fqn))
|
||||
(method))
|
||||
(when class
|
||||
(setq method
|
||||
(phpinspect--class-get-method class (phpinspect-intern-name method-name)))
|
||||
(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)"
|
||||
project-root class-fqn variable-name)
|
||||
(when project-root
|
||||
(let ((found-variable
|
||||
(phpinspect--class-get-variable
|
||||
(phpinspect-get-or-create-cached-project-class project-root class-fqn)
|
||||
variable-name)))
|
||||
(when found-variable
|
||||
(phpinspect--variable-type found-variable)))))
|
||||
|
||||
(defsubst phpinspect-get-cached-project-class-static-method-type
|
||||
(project-root class-fqn method-name)
|
||||
(when project-root
|
||||
(let* ((class (phpinspect-get-or-create-cached-project-class project-root class-fqn))
|
||||
(method))
|
||||
(when class
|
||||
(setq method
|
||||
(phpinspect--class-get-static-method
|
||||
class
|
||||
(phpinspect-intern-name method-name)))
|
||||
(when method
|
||||
(phpinspect--function-return-type method))))))
|
||||
|
||||
(defun phpinspect-get-derived-statement-type-in-block
|
||||
(resolvecontext statement php-block type-resolver &optional function-arg-list)
|
||||
"Get type of RESOLVECONTEXT subject in PHP-BLOCK.
|
||||
|
||||
Use TYPE-RESOLVER and FUNCTION-ARG-LIST in the process.
|
||||
|
||||
An example of a derived statement would be the following php code:
|
||||
$variable->attribute->method();
|
||||
$variable->attribute;
|
||||
$variable->method();
|
||||
self::method();
|
||||
ClassName::method();
|
||||
$variable = ClassName::method();
|
||||
$variable = $variable->method();"
|
||||
;; A derived statement can be an assignment itself.
|
||||
(when (seq-find #'phpinspect-assignment-p statement)
|
||||
(phpinspect--log "Derived statement is an assignment: %s" statement)
|
||||
(setq statement (cdr (seq-drop-while #'phpinspect-not-assignment-p statement))))
|
||||
(phpinspect--log "Get derived statement type in block: %s" statement)
|
||||
(let* ((first-token (pop statement))
|
||||
(current-token)
|
||||
(previous-attribute-type))
|
||||
;; No first token means we were passed an empty list.
|
||||
(when (and first-token
|
||||
(setq previous-attribute-type
|
||||
(or
|
||||
;; Statements starting with a bare word can indicate a static
|
||||
;; method call. These could be statements with "return" or
|
||||
;; another bare-word at the start though, so we drop preceding
|
||||
;; barewords when they are present.
|
||||
(when (phpinspect-word-p first-token)
|
||||
(when (phpinspect-word-p (car statement))
|
||||
(setq statement (phpinspect-drop-preceding-barewords
|
||||
statement))
|
||||
(setq first-token (pop statement)))
|
||||
(funcall type-resolver (phpinspect--make-type
|
||||
:name (cadr first-token))))
|
||||
|
||||
;; No bare word, assume we're dealing with a variable.
|
||||
(phpinspect-get-variable-type-in-block
|
||||
resolvecontext
|
||||
(cadr first-token)
|
||||
php-block
|
||||
type-resolver
|
||||
function-arg-list))))
|
||||
|
||||
(phpinspect--log "Statement: %s" statement)
|
||||
(phpinspect--log "Starting attribute type: %s" previous-attribute-type)
|
||||
(while (setq current-token (pop statement))
|
||||
(phpinspect--log "Current derived statement token: %s" current-token)
|
||||
(cond ((phpinspect-object-attrib-p current-token)
|
||||
(let ((attribute-word (cadr current-token)))
|
||||
(when (phpinspect-word-p attribute-word)
|
||||
(if (phpinspect-list-p (car statement))
|
||||
(progn
|
||||
(pop statement)
|
||||
(setq previous-attribute-type
|
||||
(or
|
||||
(phpinspect-get-cached-project-class-method-type
|
||||
(phpinspect--resolvecontext-project-root
|
||||
resolvecontext)
|
||||
(funcall type-resolver previous-attribute-type)
|
||||
(cadr attribute-word))
|
||||
previous-attribute-type)))
|
||||
(setq previous-attribute-type
|
||||
(or
|
||||
(phpinspect-get-cached-project-class-variable-type
|
||||
(phpinspect--resolvecontext-project-root
|
||||
resolvecontext)
|
||||
(funcall type-resolver previous-attribute-type)
|
||||
(cadr attribute-word))
|
||||
previous-attribute-type))))))
|
||||
((phpinspect-static-attrib-p current-token)
|
||||
(let ((attribute-word (cadr current-token)))
|
||||
(phpinspect--log "Found attribute word: %s" attribute-word)
|
||||
(phpinspect--log "checking if next token is a list. Token: %s"
|
||||
(car statement))
|
||||
(when (phpinspect-word-p attribute-word)
|
||||
(if (phpinspect-list-p (car statement))
|
||||
(progn
|
||||
(pop statement)
|
||||
(setq previous-attribute-type
|
||||
(or
|
||||
(phpinspect-get-cached-project-class-static-method-type
|
||||
(phpinspect--resolvecontext-project-root
|
||||
resolvecontext)
|
||||
(funcall type-resolver previous-attribute-type)
|
||||
(cadr attribute-word))
|
||||
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))))
|
||||
|
||||
;;;;
|
||||
;; TODO: since we're passing type-resolver to all of the get-variable-type functions now,
|
||||
;; we may as well always return FQNs in stead of relative type names.
|
||||
;;;;
|
||||
(defun phpinspect-get-variable-type-in-block
|
||||
(resolvecontext variable-name php-block type-resolver &optional function-arg-list)
|
||||
"Find the type of VARIABLE-NAME in PHP-BLOCK using TYPE-RESOLVER.
|
||||
|
||||
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) can be found in FUNCTION-ARG-LIST.
|
||||
|
||||
When PHP-BLOCK belongs to a function, supply FUNCTION-ARG-LIST to
|
||||
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)))
|
||||
|
||||
(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))))
|
||||
|
||||
; return
|
||||
result))
|
||||
|
||||
(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 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)))
|
||||
(when current-sublist
|
||||
(push (nreverse current-sublist) sublists))
|
||||
(nreverse sublists)))
|
||||
|
||||
(defun phpinspect-get-variable-type-in-function-arg-list (variable-name type-resolver arg-list)
|
||||
"Infer VARIABLE-NAME's type from typehints in
|
||||
ARG-LIST. ARG-LIST should be a list token as returned by
|
||||
`phpinspect--list-handler` (see also `phpinspect-list-p`)"
|
||||
(let ((arg-no (seq-position arg-list
|
||||
variable-name
|
||||
(lambda (token variable-name)
|
||||
(and (phpinspect-variable-p token)
|
||||
(string= (car (last token)) variable-name))))))
|
||||
(if (and arg-no
|
||||
(> arg-no 0))
|
||||
(let ((arg (elt arg-list (- arg-no 1))))
|
||||
(if (phpinspect-word-p arg)
|
||||
(funcall type-resolver
|
||||
(phpinspect--make-type :name (car (last arg))))
|
||||
nil)))))
|
||||
|
||||
(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 (lambda (part) (or (phpinspect-attrib-p part)
|
||||
(phpinspect-array-p part)))
|
||||
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 &optional type-resolver)
|
||||
(unless type-resolver
|
||||
(setq type-resolver
|
||||
(phpinspect--make-type-resolver-for-resolvecontext resolvecontext)))
|
||||
(phpinspect--log "Looking for type of statement: %s in nested token"
|
||||
(phpinspect--resolvecontext-subject resolvecontext))
|
||||
;; Find all enclosing tokens that aren't classes. Classes do not contain variable
|
||||
;; assignments which have effect in the current scope, which is what we're trying
|
||||
;; to find here to infer the statement type.
|
||||
(let ((enclosing-tokens (seq-filter #'phpinspect-not-class-p
|
||||
(phpinspect--resolvecontext-enclosing-tokens
|
||||
resolvecontext)))
|
||||
(enclosing-token)
|
||||
(type))
|
||||
(while (and enclosing-tokens (not type))
|
||||
;;(phpinspect--log "Trying to find type in %s" enclosing-token)
|
||||
(setq enclosing-token (pop enclosing-tokens))
|
||||
|
||||
(setq type
|
||||
(cond ((phpinspect-namespace-p enclosing-token)
|
||||
(phpinspect-get-derived-statement-type-in-block
|
||||
resolvecontext
|
||||
(phpinspect--resolvecontext-subject
|
||||
resolvecontext)
|
||||
(or (phpinspect-namespace-block enclosing-token)
|
||||
enclosing-token)
|
||||
type-resolver))
|
||||
((or (phpinspect-block-p enclosing-token)
|
||||
(phpinspect-root-p enclosing-token))
|
||||
(phpinspect-get-derived-statement-type-in-block
|
||||
resolvecontext
|
||||
(phpinspect--resolvecontext-subject
|
||||
resolvecontext)
|
||||
enclosing-token
|
||||
type-resolver))
|
||||
((phpinspect-function-p enclosing-token)
|
||||
(phpinspect-get-derived-statement-type-in-block
|
||||
resolvecontext
|
||||
(phpinspect--resolvecontext-subject
|
||||
resolvecontext)
|
||||
(phpinspect-function-block enclosing-token)
|
||||
type-resolver
|
||||
(phpinspect-function-argument-list enclosing-token))))))
|
||||
type))
|
||||
|
||||
(provide 'phpinspect-resolve)
|
@ -0,0 +1,139 @@
|
||||
;;; phpinspect-suggest.el --- PHP parsing and completion package -*- lexical-binding: t; -*-
|
||||
|
||||
;; Copyright (C) 2021 Free Software Foundation, Inc
|
||||
|
||||
;; Author: Hugo Thunnissen <devel@hugot.nl>
|
||||
;; Keywords: php, languages, tools, convenience
|
||||
;; Version: 0
|
||||
|
||||
;; 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:
|
||||
|
||||
(require 'phpinspect-resolvecontext)
|
||||
(require 'phpinspect-resolve)
|
||||
(require 'phpinspect-parser)
|
||||
(require 'phpinspect-type)
|
||||
(require 'phpinspect-project)
|
||||
(require 'phpinspect-class)
|
||||
|
||||
(defun phpinspect-suggest-variables-at-point (resolvecontext)
|
||||
(phpinspect--log "Suggesting variables at point")
|
||||
(let ((variables))
|
||||
(dolist (token (phpinspect--resolvecontext-enclosing-tokens resolvecontext))
|
||||
(when (phpinspect-not-class-p token)
|
||||
(let ((token-list token)
|
||||
(potential-variable))
|
||||
(while token-list
|
||||
(setq potential-variable (pop token-list))
|
||||
(cond ((phpinspect-variable-p potential-variable)
|
||||
(phpinspect--log "Pushing variable %s" potential-variable)
|
||||
(push (phpinspect--make-variable
|
||||
:name (cadr potential-variable)
|
||||
:type phpinspect--null-type)
|
||||
variables))
|
||||
((phpinspect-function-p potential-variable)
|
||||
(push (phpinspect-function-block potential-variable) token-list)
|
||||
(dolist (argument (phpinspect-function-argument-list potential-variable))
|
||||
(when (phpinspect-variable-p argument)
|
||||
(push (phpinspect--make-variable
|
||||
:name (cadr argument)
|
||||
:type phpinspect--null-type)
|
||||
variables))))
|
||||
((phpinspect-block-p potential-variable)
|
||||
(dolist (nested-token (cdr potential-variable))
|
||||
(push nested-token token-list))))))))
|
||||
|
||||
;; Only return variables that have a name. Unnamed variables are just dollar
|
||||
;; signs (:
|
||||
(seq-filter #'phpinspect--variable-name variables)))
|
||||
|
||||
(defun phpinspect-get-cached-project-class-methods (project-root class-fqn &optional static)
|
||||
(phpinspect--log "Getting cached project class methods for %s (%s)"
|
||||
project-root class-fqn)
|
||||
(when project-root
|
||||
(let ((class (phpinspect-get-or-create-cached-project-class
|
||||
project-root
|
||||
class-fqn)))
|
||||
(when class
|
||||
(phpinspect--log "Retrieved class index, starting method collection %s (%s)"
|
||||
project-root class-fqn)
|
||||
(if static
|
||||
(phpinspect--class-get-static-method-list class)
|
||||
(phpinspect--class-get-method-list class))))))
|
||||
|
||||
(defun phpinspect--get-methods-for-class
|
||||
(resolvecontext buffer-classes class &optional static)
|
||||
"Extract all possible methods for a class from `buffer-classes` and the class index.
|
||||
`buffer-classes` will be preferred because their data should be
|
||||
more recent"
|
||||
(let ((methods (phpinspect-get-cached-project-class-methods
|
||||
(phpinspect--resolvecontext-project-root
|
||||
resolvecontext)
|
||||
class
|
||||
static))
|
||||
(buffer-index (alist-get class buffer-classes nil nil #'phpinspect--type=)))
|
||||
(phpinspect--log "Getting methods for class (%s)" class)
|
||||
(when buffer-index
|
||||
(dolist (method (alist-get (if static 'static-methods 'methods)
|
||||
buffer-index))
|
||||
(push method methods)))
|
||||
(unless methods
|
||||
(phpinspect--log "Failed to find methods for class %s :(" class))
|
||||
methods))
|
||||
|
||||
(defun phpinspect--get-variables-for-class (buffer-classes class-name &optional static)
|
||||
(let ((class (phpinspect-get-or-create-cached-project-class
|
||||
(phpinspect-current-project-root)
|
||||
class-name)))
|
||||
;; TODO return static variables/constants when static is set
|
||||
(when class
|
||||
(phpinspect--class-variables class))))
|
||||
|
||||
(defun phpinspect--make-method-lister (resolvecontext buffer-classes &optional static)
|
||||
(lambda (fqn)
|
||||
(phpinspect--get-methods-for-class resolvecontext buffer-classes fqn static)))
|
||||
|
||||
(defun phpinspect-suggest-attributes-at-point
|
||||
(resolvecontext &optional static)
|
||||
"Suggest object or class attributes at point.
|
||||
|
||||
RESOLVECONTEXT must be a structure of the type
|
||||
`phpinspect--resolvecontext'. The PHP type of its subject is
|
||||
resolved to provide completion candidates.
|
||||
|
||||
If STATIC is non-nil, candidates are provided for constants,
|
||||
static variables and static methods."
|
||||
(let* ((buffer-index phpinspect--buffer-index)
|
||||
(buffer-classes (alist-get 'classes (cdr buffer-index)))
|
||||
(type-resolver (phpinspect--make-type-resolver-for-resolvecontext
|
||||
resolvecontext))
|
||||
(method-lister (phpinspect--make-method-lister
|
||||
resolvecontext
|
||||
buffer-classes
|
||||
static)))
|
||||
(let ((statement-type (phpinspect-resolve-type-from-context
|
||||
resolvecontext
|
||||
type-resolver)))
|
||||
(when statement-type
|
||||
(let ((type (funcall type-resolver statement-type)))
|
||||
(append (phpinspect--get-variables-for-class
|
||||
buffer-classes
|
||||
type
|
||||
static)
|
||||
(funcall method-lister type)))))))
|
||||
|
||||
(provide 'phpinspect-suggest)
|
Loading…
Reference in New Issue