Implement resolvecontext derivation using token metadata tree

WIP-incremental-parsing
Hugo Thunnissen 10 months ago
parent 292b4ca123
commit 92ae43fe6e

@ -233,6 +233,18 @@ Type can be any of the token types returned by
(phpinspect-const-p token)
(phpinspect-static-p token)))
(defsubst phpinspect-enclosing-token-p (token)
"Returns t when a token can enclose other tokens"
(or
(phpinspect-list-p token)
(phpinspect-block-p token)
(phpinspect-class-p token)
(phpinspect-function-p token)
(phpinspect-array-p token)
(phpinspect-scope-p token)
(phpinspect-static-p token)
(phpinspect-const-p token)))
(defun phpinspect-namespace-keyword-p (token)
(and (phpinspect-word-p token) (string= (car (last token)) "namespace")))

@ -100,10 +100,11 @@ list it was called on."
(let ((,list (phpinspect-slice-start ,(cadr place-and-slice)))
(,slice-end (phpinspect-llnode-right
(phpinspect-slice-end ,(cadr place-and-slice)))))
(while (and ,list (not (eq ,slice-end ,list)))
(let ((,(car place-and-slice) (phpinspect-llnode-value ,list)))
,@body)
(setq ,list (,normal-next-function ,list)))))))
(when (phpinspect-llnode-value ,list)
(while (and ,list (not (eq ,slice-end ,list)))
(let ((,(car place-and-slice) (phpinspect-llnode-value ,list)))
,@body)
(setq ,list (,normal-next-function ,list))))))))
(cl-defmethod seq-reverse ((slice phpinspect-slice))
(setf (phpinspect-slice-reversed slice) (not (phpinspect-slice-reversed slice)))
@ -188,7 +189,13 @@ belongs to. Return resulting linked list."
(or (phpinspect-llnode-right list) list))
(cl-defmethod phpinspect-ll-link ((list phpinspect-llnode) value)
(gethash value (phpinspect-llnode-link-map list)))
(or (gethash value (phpinspect-llnode-link-map list))
(catch 'found
(while list
(when (eq value (phpinspect-llnode-value list))
(phpinspect-ll-register-link list)
(throw 'found list))
(setq list (phpinspect-llnode-right list))))))
(cl-defmethod phpinspect-ll-relink ((list phpinspect-llnode) value)
(phpinspect-ll-unregister-link list)
@ -436,6 +443,12 @@ belongs to. Return resulting linked list."
(and (= 0 (phpinspect-tree-start tree))
(= 0 (phpinspect-tree-end tree))))
(cl-defmethod phpinspect-tree-find-last-child-before-point ((tree phpinspect-tree) (point integer))
(catch 'found
(seq-doseq (child (seq-reverse (seq-into (phpinspect-tree-children tree) 'slice)))
(when (<= (phpinspect-tree-end child) point)
(throw 'found child)))))
(cl-defmethod phpinspect-tree-insert-node ((tree phpinspect-tree) (node phpinspect-tree))
"Insert a new NODE into TREE.
@ -553,9 +566,18 @@ Returns the newly inserted node."
Returns list of values from overlapping trees, sorted by interval
width with the smallest interval as car."
(when (phpinspect-tree-overlaps tree point)
(let* ((overlapper
(seq-find (lambda (child) (phpinspect-tree-overlaps child point))
(phpinspect-tree-children tree))))
(let* ((from-end (- (phpinspect-tree-end tree) point))
(from-start (- point (phpinspect-tree-start tree)))
(overlapper
(catch 'found
(let ((children (seq-into (phpinspect-tree-children tree) 'slice)))
(when (> from-start from-end)
(setq children (seq-reverse children)))
(phpinspect-doslice (child children)
(when (phpinspect-tree-overlaps child point)
(throw 'found child)))))))
(if overlapper
`(,@(phpinspect-tree-traverse-overlapping overlapper point) ,(phpinspect-tree-value tree))
`(,(phpinspect-tree-value tree))))))
@ -626,6 +648,9 @@ with its value as argument."
(let ((found? (phpinspect-tree-find-node-starting-at child point)))
(when found? (throw 'found found?))))))))
(cl-defmethod phpinspect-tree-width ((tree phpinspect-tree))
(- (phpinspect-tree-start tree) (phpinspect-tree-end tree)))
(cl-defmethod phpinspect-tree-find-smallest-overlapping-set ((tree phpinspect-tree) region)
"Traverse TREE for smallest set of intervals overlapping REGION,
@ -681,7 +706,9 @@ Returns the newly created and inserted node."
(let ((parent-link (phpinspect-ll-link (phpinspect-tree-children parent)
tree)))
(unless parent-link
(phpinspect--log "No parent link for node, trying to find it manually")
(phpinspect--log "No parent link for node, trying to find it manually")
(message "No parent link for tree of %s" (phpinspect-tree-meta-token tree))
(message "Parent: %s" (phpinspect-tree-meta-token parent))
(setq parent-link
(seq-find (lambda (child) (eq child tree))
(phpinspect-tree-children parent))))

@ -134,38 +134,60 @@ candidate. Candidates can be indexed functions and variables.")
(let ((previous-siblings))
(catch 'break
(seq-doseq (child children)
(when (phpinspect-end-of-statement-p (phpinspect-tree-meta-token child))
(throw 'break nil))
(when (< (phpinspect-tree-start child) point)
(when (phpinspect-end-of-statement-p (phpinspect-tree-meta-token child))
(throw 'break nil))
(push child previous-siblings))))
previous-siblings)))
(cl-defmethod phpinspect-get-resolvecontext
((tree phpinspect-tree) (point integer))
(let* ((enclosing (phpinspect-tree-traverse-overlapping
tree (phpinspect-make-region (- point 1) point)))
(subject (car enclosing))
(let* ((enclosing (phpinspect-tree-traverse-overlapping tree point))
(enclosing-tokens)
;; When there are no enclosing tokens, point is probably at the absolute
;; end of the buffer, so we find the last child before point.
(subject (or (car enclosing) (phpinspect-tree-find-last-child-before-point tree point)))
(subject-token)
(siblings))
(when enclosing
(phpinspect--log "Initial resolvecontext subject: %s" (phpinspect-meta-token subject))
(let ((parent (phpinspect-tree-meta-token (phpinspect-tree-parent (phpinspect-meta-tree subject)))))
(phpinspect--log "Parent: %s" parent))
(if (phpinspect-block-or-list-p (phpinspect-meta-token subject))
(setq subject (mapcar #'phpinspect-tree-meta-token
(phpinspect-find-statement-before-point
(phpinspect-meta-tree subject) point)))
(setq subject (mapcar #'phpinspect-tree-meta-token
(phpinspect-find-statement-before-point
(phpinspect-tree-parent
(phpinspect-meta-tree subject))
point)))))
(phpinspect--make-resolvecontext :subject subject
:enclosing-tokens (mapcar #'phpinspect-meta-token enclosing)
:project-root (phpinspect-current-project-root))))
(when subject
(when (phpinspect-tree-p subject) (setq subject (phpinspect-tree-value subject)))
;; Dig down through tokens that can contain statements
(catch 'break
(while (phpinspect-enclosing-token-p (phpinspect-meta-token subject))
(let ((new-subject (phpinspect-tree-find-last-child-before-point
(phpinspect-meta-tree subject) point)))
(if new-subject
(setq subject (phpinspect-tree-value new-subject))
(throw 'break nil)))))
(phpinspect--log "Initial resolvecontext subject token: %s" (phpinspect-meta-token subject))
(setq subject-token (mapcar #'phpinspect-tree-meta-token
(phpinspect-find-statement-before-point
(phpinspect-tree-parent
(phpinspect-meta-tree subject))
point)))
(phpinspect--log "Ultimate resolvecontext subject token: %s" subject-token))
;; Iterate through subject parents to build stack of enclosing tokens
(let ((parent (phpinspect-tree-parent (phpinspect-meta-tree subject))))
(while (and parent (phpinspect-tree-value parent))
(let ((granny (phpinspect-tree-parent parent)))
(unless (and (phpinspect-block-p (phpinspect-tree-meta-token parent))
(or (not granny) (not (phpinspect-tree-value granny))
(phpinspect-function-p (phpinspect-tree-meta-token granny))
(phpinspect-class-p (phpinspect-tree-meta-token granny))))
(push (phpinspect-tree-meta-token parent) enclosing-tokens))
(setq parent (phpinspect-tree-parent parent)))))
(phpinspect--make-resolvecontext
:subject (phpinspect--get-last-statement-in-token subject-token)
:enclosing-tokens (nreverse enclosing-tokens)
:project-root (phpinspect-current-project-root))))
(defun phpinspect-subject-at-point ()
@ -175,7 +197,7 @@ candidate. Candidates can be indexed functions and variables.")
(phpinspect-get-resolvecontext (phpinspect-buffer-parse-tree phpinspect-current-buffer) (point)))
(cl-defmethod phpinspect--get-resolvecontext (token &optional resolvecontext)
(defun phpinspect--get-resolvecontext (token &optional resolvecontext)
"Find the deepest nested incomplete token in TOKEN.
If RESOLVECONTEXT is nil, it is created. Returns RESOLVECONTEXT
of type `phpinspect--resolvecontext' containing the last
@ -320,6 +342,19 @@ accompanied by all of its enclosing tokens."
(current-buffer)
(point-max)))
(defun phpinspect-parse-string-to-tree (string)
(with-temp-buffer
(insert string)
(let ((context (phpinspect-make-pctx :incremental t
:tree (phpinspect-make-tree :start (point-min)
:end (point-max)
:grow-root t))))
(phpinspect-with-parse-context context
(phpinspect-parse-current-buffer))
(seq-elt (phpinspect-tree-children (phpinspect-pctx-tree context)) 0))))
(defun phpinspect-parse-string (string)
(with-temp-buffer
(insert string)
@ -373,19 +408,20 @@ TODO:
"
(let* ((token-tree (phpinspect-buffer-parse-tree phpinspect-current-buffer))
(resolvecontext (phpinspect-get-resolvecontext token-tree (point)))
(incomplete-token (car (phpinspect--resolvecontext-enclosing-tokens
resolvecontext)))
(enclosing-token (cadr (phpinspect--resolvecontext-enclosing-tokens
resolvecontext)))
(statement (phpinspect--get-last-statement-in-token
enclosing-token))
(statement (if (phpinspect-list-p (car (phpinspect--resolvecontext-enclosing-tokens
resolvecontext)))
(phpinspect--get-last-statement-in-token enclosing-token)
(phpinspect--resolvecontext-subject resolvecontext)))
(arg-list (seq-find #'phpinspect-list-p (reverse statement)))
(type-resolver (phpinspect--make-type-resolver-for-resolvecontext
resolvecontext))
(static))
(phpinspect--log "Enclosing token: %s" enclosing-token)
(phpinspect--log "reference token: %s" (car (last statement 2)))
(phpinspect--log "Eldoc statement: %s" statement)
(when (and (phpinspect-incomplete-list-p incomplete-token)
(when (and (phpinspect-list-p arg-list)
enclosing-token
(or (phpinspect-object-attrib-p (car (last statement 2)))
(setq static (phpinspect-static-attrib-p (car (last statement 2))))))
@ -416,7 +452,7 @@ TODO:
(when method
(let ((arg-count -1)
(comma-count
(length (seq-filter #'phpinspect-comma-p incomplete-token))))
(length (seq-filter #'phpinspect-comma-p arg-list))))
(concat (truncate-string-to-width
(phpinspect--function-name method) phpinspect-eldoc-word-width) ": ("
(mapconcat
@ -874,7 +910,9 @@ Assuming that files are only changed from within Emacs, this
keeps the cache valid. If changes are made outside of Emacs,
users will have to use \\[phpinspect-purge-cache]."
(when (and (boundp 'phpinspect-mode) phpinspect-mode)
(setq phpinspect--buffer-index (phpinspect-index-current-buffer))
(setq phpinspect--buffer-index
(phpinspect--index-tokens
(phpinspect-buffer-reparse phpinspect-current-buffer)))
(let ((imports (alist-get 'imports phpinspect--buffer-index))
(project (phpinspect--cache-get-project-create
(phpinspect--get-or-create-global-cache)

@ -62,8 +62,11 @@
(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;"))
(let* ((code "class Foo { function a(\\Thing $baz) { $foo = new \\DateTime(); $bar = $foo; Whatever comes after don't matter.")
(tree (phpinspect-parse-string-to-tree code))
(tokens (phpinspect-parse-string "class Foo { function a(\\Thing $baz) { $foo = new \\DateTime(); $bar = $foo;"))
(context (phpinspect--get-resolvecontext tokens))
(tree-context (phpinspect-get-resolvecontext tree (- (length code) 36)))
(project-root "could never be a real project root")
(phpinspect-project-root-function
(lambda (&rest _ignored) project-root))
@ -78,9 +81,17 @@
context "foo"
(phpinspect-function-block
(car (phpinspect--resolvecontext-enclosing-tokens context)))
(phpinspect--make-type-resolver-for-resolvecontext context))))
(phpinspect--make-type-resolver-for-resolvecontext context)))
(tree-result (phpinspect-get-variable-type-in-block
tree-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)))))
result))
(should (phpinspect--type= (phpinspect--make-type :name "\\DateTime")
tree-result)))))
(ert-deftest phpinspect-get-pattern-type-in-block ()
(let* ((tokens (phpinspect-parse-string "class Foo { function a(\\Thing $baz) { $foo = new \\DateTime(); $this->potato = $foo;"))
@ -104,9 +115,25 @@
(should (phpinspect--type= (phpinspect--make-type :name "\\DateTime")
result)))))
(ert-deftest phpinspect-get-resolvecontext-multi-strategy ()
(let* ((code1 "class Foo { function a(\\Thing $baz) { $foo = []; $foo[] = $baz; $bar = $foo[0]; $bork = [$foo[0]]; $bark = $bork[0]; $borknest = [$bork]; $barknest = $borknest[0][0]; }}")
(code2 "class Foo { function a(\\Thing $baz) { $foo = []; $foo[] = $baz; $bar = $foo[0]; $bork = [$foo[0]]; $bark = $bork[0]; $borknest = [$bork]; $barknest = $borknest[0][0]")
(tree (phpinspect-parse-string-to-tree code1))
(tokens (phpinspect-parse-string code2))
(context1 (phpinspect-get-resolvecontext tree (- (length code1) 4)))
(context2 (phpinspect--get-resolvecontext tokens)))
(should (equal (phpinspect--resolvecontext-subject context1)
(phpinspect--resolvecontext-subject context2)))
(should (= (length (phpinspect--resolvecontext-enclosing-tokens context1))
(length (phpinspect--resolvecontext-enclosing-tokens context2))))))
(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]; $bork = [$foo[0]]; $bark = $bork[0]; $borknest = [$bork]; $barknest = $borknest[0][0]"))
(context (phpinspect--get-resolvecontext tokens))
(let* ((code "class Foo { function a(\\Thing $baz) { $foo = []; $foo[] = $baz; $bar = $foo[0]; $bork = [$foo[0]]; $bark = $bork[0]; $borknest = [$bork]; $barknest = $borknest[0][0]; }}")
(tokens (phpinspect-parse-string-to-tree code))
(context (phpinspect-get-resolvecontext tokens (- (length code) 4)))
(project-root "could never be a real project root")
(phpinspect-project-root-function
(lambda (&rest _ignored) project-root))
@ -465,7 +492,7 @@ class Thing
function doStuff()
{
$this->getThis(")
$this->getThis(new \DateTime(), bla)")
(tokens (phpinspect-parse-string php-code))
(index (phpinspect--index-tokens tokens))
(phpinspect-project-root-function (lambda () "phpinspect-test"))
@ -478,6 +505,7 @@ class Thing
(should (string= "getThis: ($moment DateTime, $thing Thing, $other): Thing"
(with-temp-buffer
(insert php-code)
(backward-char)
(setq-local phpinspect-current-buffer
(phpinspect-make-buffer :buffer (current-buffer)))
(phpinspect-eldoc-function))))))

@ -112,6 +112,18 @@
(should (equal '("a" "b" "c" "d") (seq-into list 'list)))))
(ert-deftest phpinspect-slice-reverse ()
(let ((list (phpinspect-make-ll)))
(phpinspect-ll-push "d" list)
(phpinspect-ll-push "c" list)
(phpinspect-ll-push "b" list)
(phpinspect-ll-push "a" list)
(let ((slice (seq-into list 'slice)))
(should (equal '("a" "b" "c" "d") (seq-into slice 'list)))
(should (equal '("d" "c" "b" "a") (seq-into (seq-reverse slice) 'list))))))
(ert-deftest phpinspect-ll-seq-take-while ()
(let ((list (phpinspect-make-ll))
(result))
@ -268,7 +280,9 @@ the start of the list."
(setq detached-list (phpinspect-slice-detach slice))
(should (string= "a" (apply #'concat (seq-into slice 'list))))
(should (string= "a" (apply #'concat (seq-into detached-list 'list))))
(should (seq-empty-p list))))
(should (seq-empty-p list))
(should-not (phpinspect-ll-link list val1))
(should (phpinspect-ll-link detached-list val1))))
(ert-deftest phpinspect-tree-insert-enclosing-node ()
@ -444,4 +458,18 @@ the node iteself if it has been stored intact)."
(let* ((tree (phpinspect-make-tree :start 5 :end 10))
(node (phpinspect-tree-insert-node tree (phpinspect-make-tree :start 5 :end 10))))
(should (eq node (seq-elt (phpinspect-tree-children tree) 0)))))
(should (eq node (seq-elt (phpinspect-tree-children tree) 0)))
(should-not (phpinspect-tree-parent tree))
(should (eq tree (phpinspect-tree-parent node)))))
(ert-deftest phpinspect-tree-insert-sorted ()
(let ((tree (phpinspect-make-tree :start 0 :end 1000 :value 'root)))
(phpinspect-tree-insert tree 1 33 'one)
(phpinspect-tree-insert tree 50 60 'three)
(phpinspect-tree-insert tree 40 50 'two)
(phpinspect-tree-insert tree 71 90 'five)
(phpinspect-tree-insert tree 60 70 'four)
(should (equal '(one two three four five)
(mapcar #'phpinspect-tree-value
(seq-into (phpinspect-tree-children tree) 'list))))))

Loading…
Cancel
Save