[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[elpa] externals/phpinspect 0af7d88fda 5/5: Improve performance of parse
From: |
ELPA Syncer |
Subject: |
[elpa] externals/phpinspect 0af7d88fda 5/5: Improve performance of parser (and phpinspect-meta) |
Date: |
Fri, 27 Sep 2024 21:58:42 -0400 (EDT) |
branch: externals/phpinspect
commit 0af7d88fda438ea5d6b91f4419d1e7d144f98123
Author: Hugo Thunnissen <devel@hugot.nl>
Commit: Hugo Thunnissen <devel@hugot.nl>
Improve performance of parser (and phpinspect-meta)
- Add `phpinspect--recycle-token' which attempts to re-use all eligible
righthand siblings.
- Change important search functions of phpinspect-meta to inline functions
and
optimize their code.
- Introduce `phpinspect-meta--point-offset-base' which see docstring.
---
phpinspect-eldoc.el | 8 +--
phpinspect-imports.el | 6 +-
phpinspect-meta.el | 156 ++++++++++++++++++++++++++++++++++----------------
phpinspect-parser.el | 132 +++++++++++++++++++++++++++++++++---------
test/test-meta.el | 11 ++++
5 files changed, 229 insertions(+), 84 deletions(-)
diff --git a/phpinspect-eldoc.el b/phpinspect-eldoc.el
index feb62e2edd..ee81d60434 100644
--- a/phpinspect-eldoc.el
+++ b/phpinspect-eldoc.el
@@ -167,8 +167,8 @@ be implemented for return values of
`phpinspect-eld-strategy-execute'")
(cond
;; Method call
((setq match-result (phpinspect--match-sequence (last statement 2)
- :f (phpinspect-meta-wrap-token-pred
#'phpinspect-attrib-p)
- :f (phpinspect-meta-wrap-token-pred
#'phpinspect-list-p)))
+ :f (phpinspect-meta-token-predicate
#'phpinspect-attrib-p)
+ :f (phpinspect-meta-token-predicate
#'phpinspect-list-p)))
(phpinspect--log "Eldoc context is a method call")
(setq arg-list (car (last match-result))
@@ -197,8 +197,8 @@ be implemented for return values of
`phpinspect-eld-strategy-execute'")
(when method
(phpinspect-make-function-doc :fn method :arg-pos arg-pos))))
((setq match-result (phpinspect--match-sequence (last statement 2)
- :f (phpinspect-meta-wrap-token-pred
#'phpinspect-word-p)
- :f (phpinspect-meta-wrap-token-pred
#'phpinspect-list-p)))
+ :f (phpinspect-meta-token-predicate
#'phpinspect-word-p)
+ :f (phpinspect-meta-token-predicate
#'phpinspect-list-p)))
(phpinspect--log "Eldoc context is a function call")
(setq arg-list (car (last match-result))
diff --git a/phpinspect-imports.el b/phpinspect-imports.el
index 7031d9d8a2..0d4088ce9b 100644
--- a/phpinspect-imports.el
+++ b/phpinspect-imports.el
@@ -54,7 +54,7 @@ resulting in an unjust removal."
(phpinspect-namespace-is-blocked-p (phpinspect-meta-token
token-meta)))
(phpinspect-find-first-use (phpinspect-meta-last-child token-meta))
(phpinspect-meta-find-first-child-matching
- token-meta (phpinspect-meta-wrap-token-pred #'phpinspect-use-p))))
+ token-meta (phpinspect-meta-token-predicate #'phpinspect-use-p))))
(defun phpinspect-add-use (fqn buffer &optional namespace-meta)
"Add use statement for FQN to BUFFER.
@@ -80,12 +80,12 @@ buffer position to insert the use statement at."
(phpinspect-insert-at-point
(phpinspect-meta-end
(phpinspect-meta-find-first-child-matching
- namespace-meta (phpinspect-meta-wrap-token-pred
#'phpinspect-terminator-p)))
+ namespace-meta (phpinspect-meta-token-predicate
#'phpinspect-terminator-p)))
(format "%c%cuse %s;%c" ?\n ?\n fqn ?\n)))))
;; else
(let ((existing-use (phpinspect-meta-find-first-child-matching
(phpinspect-buffer-root-meta buffer)
- (phpinspect-meta-wrap-token-pred #'phpinspect-use-p))))
+ (phpinspect-meta-token-predicate #'phpinspect-use-p))))
(if existing-use
(phpinspect-insert-at-point
(phpinspect-meta-start existing-use)
diff --git a/phpinspect-meta.el b/phpinspect-meta.el
index a99ed95c21..2a989fb2b2 100644
--- a/phpinspect-meta.el
+++ b/phpinspect-meta.el
@@ -53,10 +53,30 @@
(require 'phpinspect-splayt)
+(eval-and-compile
+ (defvar phpinspect-meta--point-offset-base nil
+ "This variable overrides `phpinspect-meta-start'. Normally,
+metadata objects derive their start position relative to their
+parents. This is at some performance cost because the start
+position is re-calculated each time it is accessed.
+
+There are scenarios, most notably when parsing incrementally,
+where the start position of the token is already known while
+interacting with the metadata object. In such cases this variable
+can be set to override the start position, preventing any
+additional calculations.
+
+Note: Use this sparingly in scenarios where performance is of
+significant importance. The code that uses this will be harder to
+maintain. It requires a lot of mental tracing to know when to
+set/unset this variable."))
+
(define-inline phpinspect-make-meta
- (parent start end whitespace-before token &optional overlay children
parent-offset deleted)
- (inline-quote (list 'meta ,parent ,start ,end ,whitespace-before ,token
,overlay
- (or ,children (phpinspect-make-splayt)) ,parent-offset
,deleted)))
+ (parent start end whitespace-before token &optional overlay children
parent-offset)
+ "Create a metadata object for TOKEN."
+ (inline-letevals (start end)
+ (inline-quote (list 'meta ,parent ,start ,end ,whitespace-before ,token
,overlay
+ (or ,children (phpinspect-make-splayt))
,parent-offset))))
(define-inline phpinspect-meta-p (meta)
(inline-quote (eq 'meta (car-safe ,meta))))
@@ -82,9 +102,6 @@
(define-inline phpinspect-meta-whitespace-before (meta)
(inline-quote (car (cddddr ,meta))))
-(define-inline phpinspect-meta-deleted (meta)
- (inline-quote (car (nthcdr 9 ,meta))))
-
(define-inline phpinspect-meta-parent-start (meta)
"Calculate parent start position iteratively based on parent offsets."
(inline-letevals (meta)
@@ -97,13 +114,22 @@
(+ (phpinspect-meta-absolute-start current) start)))))
-(define-inline phpinspect-meta-start (meta)
+(define-inline phpinspect-meta--start (meta)
"Calculate the start position of META."
- (inline-quote
+ (inline-letevals (meta)
+ (inline-quote
(if (phpinspect-meta-parent ,meta)
- (+ (phpinspect-meta-parent-start (phpinspect-meta-parent ,meta))
- (phpinspect-meta-parent-offset ,meta))
- (phpinspect-meta-absolute-start ,meta))))
+ (+ (phpinspect-meta-parent-start (phpinspect-meta-parent ,meta))
+ (phpinspect-meta-parent-offset ,meta))
+ (phpinspect-meta-absolute-start ,meta)))))
+
+(define-inline phpinspect-meta-start (meta)
+ "Start position of META.
+
+Either calculates relative to parent or returns
+`phpinspect-meta--point-offset-base' when that variable is set."
+ (inline-quote
+ (or phpinspect-meta--point-offset-base (phpinspect-meta--start ,meta))))
(define-inline phpinspect-meta-width (meta)
(inline-letevals (meta)
@@ -115,7 +141,8 @@
(inline-quote
(+ (phpinspect-meta-start ,meta) (phpinspect-meta-width ,meta)))))
-(defsubst phpinspect-meta-find-root (meta)
+(defun phpinspect-meta-find-root (meta)
+ "Find the root node of META's tree."
(while (phpinspect-meta-parent meta)
(setq meta (phpinspect-meta-parent meta)))
meta)
@@ -154,8 +181,7 @@
(setf (phpinspect-meta-parent-offset ,meta)
(- (phpinspect-meta-start ,meta) (phpinspect-meta-start
,parent)))
(phpinspect-meta-add-child ,parent ,meta))
- (setcar (cdr ,meta) ,parent)
-
+ (setf (phpinspect-meta-parent ,meta) ,parent)
,meta))))
;; Note: using defsubst here causes a byte code overflow
@@ -179,8 +205,15 @@
(setf (phpinspect-meta-parent ,meta) nil)))))
(defun phpinspect-meta-shift (meta delta)
+ "Move META by DELTA characters.
+
+Negative DELTA will move to the left. Positive DELTA will move to the right.
+
+All children are moved together (as they calculate their
+positions based on the parent)."
(setf (phpinspect-meta-absolute-start meta) (+ (phpinspect-meta-start meta)
delta))
- (setf (phpinspect-meta-absolute-end meta) (+ (phpinspect-meta-end meta)
delta)))
+ (setf (phpinspect-meta-absolute-end meta) (dlet
((phpinspect-meta--point-offset-base nil))
+ (+ (phpinspect-meta-end meta)
delta))))
(defun phpinspect-meta-right-siblings (meta)
(sort
@@ -196,6 +229,7 @@
(cdr tokens)))
(defun phpinspect-meta-token-with-left-siblings (meta)
+ "Return a list containing tokens of META and all of its left siblings."
(nconc (phpinspect-meta-left-sibling-tokens meta) (list
(phpinspect-meta-token meta))))
(defun phpinspect-meta-left-siblings (meta)
@@ -204,30 +238,30 @@
(phpinspect-meta-children (phpinspect-meta-parent meta))
(phpinspect-meta-parent-offset meta))
#'phpinspect-meta-sort-start))
-(defun phpinspect-meta-wrap-token-pred (predicate)
- (lambda (meta) (funcall predicate (phpinspect-meta-token meta))))
-
(define-inline phpinspect-meta--point-offset (meta point)
(inline-quote
- (- ,point (phpinspect-meta-start ,meta))))
+ (- ,point (or phpinspect-meta--point-offset-base (phpinspect-meta-start
,meta)))))
(cl-defmethod phpinspect-meta-find-left-sibling ((meta (head meta)))
(when (phpinspect-meta-parent meta)
(phpinspect-splayt-find-largest-before (phpinspect-meta-children
(phpinspect-meta-parent meta))
(phpinspect-meta-parent-offset
meta))))
-(cl-defmethod phpinspect-meta-find-right-sibling ((meta (head meta)))
- (when (phpinspect-meta-parent meta)
- (phpinspect-splayt-find-smallest-after (phpinspect-meta-children
(phpinspect-meta-parent meta))
- (phpinspect-meta-parent-offset
meta))))
+(define-inline phpinspect-meta-find-right-sibling (meta)
+ (inline-letevals (meta)
+ (inline-quote
+ (when (phpinspect-meta-parent ,meta)
+ (phpinspect-splayt-find-smallest-after (phpinspect-meta-children
(phpinspect-meta-parent ,meta))
+ (phpinspect-meta-parent-offset
,meta))))))
-(cl-defmethod phpinspect-meta-find-overlapping-child ((meta (head meta))
(point integer))
- (let ((child (phpinspect-splayt-find-largest-before
- (phpinspect-meta-children meta)
- ;; Use point +1 as a child starting at point still overlaps
- (+ (phpinspect-meta--point-offset meta point) 1))))
- (when (and child (phpinspect-meta-overlaps-point child point))
- child)))
+(define-inline phpinspect-meta-find-overlapping-child (meta point)
+ (inline-letevals (meta point)
+ (inline-quote
+ (if-let ((child (phpinspect-splayt-find-largest-before
+ (phpinspect-meta-children ,meta)
+ ;; Use point +1 as a child starting at point still
overlaps
+ (+ (phpinspect-meta--point-offset ,meta ,point) 1))))
+ (and (phpinspect-meta-overlaps-point child ,point) child)))))
(cl-defmethod phpinspect-meta-find-overlapping-children ((meta (head meta))
(point integer))
(let ((child meta)
@@ -237,18 +271,30 @@
(push child children))
children))
-(cl-defmethod phpinspect-meta-find-child-starting-at ((meta (head meta))
(point integer))
- (phpinspect-splayt-find (phpinspect-meta-children meta)
(phpinspect-meta--point-offset meta point)))
-
-(cl-defmethod phpinspect-meta-find-child-starting-at-recursively ((meta (head
meta)) (point integer))
- (let ((child (phpinspect-meta-find-child-starting-at meta point)))
- (if child
- child
- (setq child (phpinspect-meta-find-overlapping-child meta point))
- (when child
- (phpinspect-meta-find-child-starting-at-recursively child point)))))
+(define-inline phpinspect-meta-find-child-starting-at (meta point)
+ (inline-letevals (meta point)
+ (inline-quote
+ (phpinspect-splayt-find (phpinspect-meta-children ,meta)
(phpinspect-meta--point-offset ,meta ,point)))))
-(cl-defmethod phpinspect-meta-find-child-before ((meta (head meta)) (point
integer))
+(define-inline phpinspect-meta-find-child-starting-at-recursively (meta point)
+ (inline-letevals (point)
+ (inline-quote
+ (catch 'phpinspect--return
+ (dlet ((phpinspect-meta--point-offset-base (phpinspect-meta-start
,meta)))
+ (let ((meta ,meta))
+ (while meta
+ (if-let ((child (phpinspect-meta-find-child-starting-at meta
,point)))
+ (throw 'phpinspect--return child)
+ (setq meta
+ (phpinspect-splayt-find-largest-before
+ (phpinspect-meta-children meta)
+ (phpinspect-meta--point-offset meta ,point))
+ phpinspect-meta--point-offset-base
+ (if meta (+ (phpinspect-meta-parent-offset meta)
phpinspect-meta--point-offset-base)
+ phpinspect-meta--point-offset-base))))))))))
+
+(defun phpinspect-meta-find-child-before (meta point)
+ "Find the last child of META before POINT."
(phpinspect-splayt-find-largest-before
(phpinspect-meta-children meta) (phpinspect-meta--point-offset meta point)))
@@ -256,6 +302,26 @@
(phpinspect-splayt-find-smallest-after
(phpinspect-meta-children meta) (phpinspect-meta--point-offset meta point)))
+(define-inline phpinspect-meta-find-child-after-recursively (meta point)
+ (inline-letevals (point)
+ (inline-quote
+ (catch 'phpinspect--return
+ (dlet ((phpinspect-meta--point-offset-base (phpinspect-meta-start
,meta)))
+ (let ((meta ,meta))
+ (while meta
+ (if-let ((child (phpinspect-meta-find-child-after meta ,point)))
+ (throw 'phpinspect--return child)
+ (setq meta
+ (or (phpinspect-splayt-find-smallest-after
+ (phpinspect-meta-children meta)
+ (phpinspect-meta--point-offset meta ,point))
+ (phpinspect-splayt-find-largest-before
+ (phpinspect-meta-children meta)
+ (phpinspect-meta--point-offset meta ,point)))
+ phpinspect-meta--point-offset-base
+ (if meta (+ (phpinspect-meta-parent-offset meta)
phpinspect-meta--point-offset-base)
+ phpinspect-meta--point-offset-base))))))))))
+
(cl-defmethod phpinspect-meta-find-child-before-recursively ((meta (head
meta)) (point integer))
(let ((child meta)
last)
@@ -295,7 +361,6 @@
(push child result)))
result))
-
(defun phpinspect-meta-flatten (meta)
"Flatten META and all its children into an unordered list."
(when meta
@@ -309,13 +374,6 @@
result)))
-(defun phpinspect-meta-delete (meta)
- "Mark META and all of its children as deleted."
- (named-let meta-delete ((meta meta))
- (setf (phpinspect-meta-deleted meta) t)
- (phpinspect-splayt-traverse-lr (child (phpinspect-meta-children meta))
- (meta-delete child))))
-
(cl-defmethod phpinspect-meta-last-child ((meta (head meta)))
(phpinspect-meta-find-child-before meta (phpinspect-meta-end meta)))
diff --git a/phpinspect-parser.el b/phpinspect-parser.el
index 2c8a3418bb..f84dd264f3 100644
--- a/phpinspect-parser.el
+++ b/phpinspect-parser.el
@@ -127,15 +127,71 @@ You can purge the parser cache with
\\[phpinspect-purge-parser-cache]."
,(if (alist-get 'inline attribute-alist)
`(define-inline ,inline-name (,@arguments)
,docstring
+ (declare (speed 3))
,@body)
`(defsubst ,inline-name (,@arguments)
,docstring
+ (declare (speed 3))
,@body))
(put (quote ,inline-name) 'phpinspect--handler t)
(put (quote ,inline-name) 'definition-name (quote ,name))
(put (quote ,regexp-inline-name) 'definition-name (quote ,name)))))
+(defun phpinspect--recycle-token (context taint-iterator point original-point
token-meta tokens-rear &optional delimiter-predicate)
+ "Attempt to re-use TOKEN-META and any of its eligible righthand siblings."
+ (declare (speed 3))
+
+ (when-let ((bmap (phpinspect-pctx-bmap context))
+ (first-iteration t))
+ (catch 'phpinspect--return
+ ;; Use while loop instead of recursion for better performance.
+ (while t
+ ;; Set point-offset-base for more efficient execution of
+ ;; `phpinspect-meta-start' and related functions.
+ (dlet ((phpinspect-meta--point-offset-base original-point))
+ (if (or (not token-meta)
+ (phpinspect-taint-iterator-token-is-tainted-p taint-iterator
token-meta))
+ ;; If the first passed token is tainted. Return tainted symbol to
+ ;; signal failure to parser loop. Otherwise return re-used
tokens.
+ (throw 'phpinspect--return (if first-iteration 'tainted
tokens-rear))
+
+ ;; Token is eligible for re-use.
+ (let ((parent-offset (phpinspect-meta-parent-offset token-meta))
+ (delta (- point original-point))
+ (current-end-position (+ point (phpinspect-meta-width
token-meta)))
+ (token (phpinspect-meta-token token-meta))
+ (right-sibling (phpinspect-meta-find-right-sibling
token-meta)))
+ ;; Note: recycling the token will update its parent and
+ ;; positions. Its properties should no longer be queried (aside
+ ;; from its width, which always stays the same)
+ (phpinspect-bmap-recycle
+ bmap token-meta delta (phpinspect-pctx-consume-whitespace
context))
+
+ (goto-char current-end-position)
+ (setq tokens-rear (setcdr tokens-rear (cons token nil)))
+
+ ;; Override point-offset-base again, but this time for
+ ;; right-sibling
+ (dlet ((phpinspect-meta--point-offset-base nil))
+ (if-let (((not (and delimiter-predicate (funcall
delimiter-predicate token))))
+ (right-sibling right-sibling)
+ ((setq phpinspect-meta--point-offset-base
+ (+ original-point (-
(phpinspect-meta-parent-offset right-sibling)
+ parent-offset))))
+ ((not (phpinspect-taint-iterator-region-is-tainted-p
+ taint-iterator current-end-position (+ delta
(phpinspect-meta-start right-sibling))))))
+ (progn
+ ;; There was a right sibling and it is eligible for
+ ;; re-use. Set token-meta and "recurse".
+ (setq first-iteration nil
+ token-meta right-sibling
+ point (+ delta (phpinspect-meta-start
right-sibling))
+ original-point (phpinspect-meta-start
right-sibling)))
+
+ ;; No eligible right sibling, break.
+ (throw 'phpinspect--return tokens-rear))))))))))
+
(eval-when-compile
(defun phpinspect-make-parser-function (name tree-type handlers &optional
delimiter-predicate delimiter-condition)
"Create a parser function using the handlers by names defined in
HANDLER-LIST.
@@ -157,6 +213,7 @@ delimiter predicate and have parsing stop when the last
parsed
token is \";\", which marks the end of a statement in PHP."
(cl-assert (symbolp delimiter-predicate))
`(defun ,(phpinspect-parser-func-name name "simple") (buffer max-point
&optional skip-over continue-condition &rest _ignored)
+ (declare (speed 3))
(with-current-buffer buffer
(let* ((tokens (cons ,tree-type nil))
(tokens-rear tokens)
@@ -182,19 +239,18 @@ token is \";\", which marks the end of a statement in
PHP."
;; Return
tokens))))
-
- (defun phpinspect-make-incremental-parser-function
+ (defun phpinspect-make-incremental-parser-function
(name tree-type handlers &optional delimiter-predicate
delimiter-condition)
"Like `phpinspect-make-parser-function', but returned function
is able to reuse an already parsed tree."
(cl-assert (symbolp delimiter-predicate))
`(defun ,(phpinspect-parser-func-name name "incremental")
(context buffer max-point &optional skip-over continue-condition root)
+ (declare (speed 3))
(with-current-buffer buffer
(let* ((tokens (cons ,tree-type nil))
(tokens-rear tokens)
(root-start (point))
- (bmap (phpinspect-pctx-bmap context))
(previous-bmap (phpinspect-pctx-previous-bmap context))
(edtrack (phpinspect-pctx-edtrack context))
(taint-iterator (when edtrack
(phpinspect-edtrack-make-taint-iterator edtrack)))
@@ -203,9 +259,6 @@ is able to reuse an already parsed tree."
;; Loop variables
(start-position)
(original-position)
- (current-end-position)
- (existing-meta)
- (delta)
(token))
(when skip-over (forward-char skip-over))
@@ -225,30 +278,52 @@ is able to reuse an already parsed tree."
(phpinspect-pctx-check-interrupt context))
(setq start-position (point))
- (cond ((and previous-bmap edtrack
- (setq existing-meta
- (phpinspect-bmap-token-starting-at
- previous-bmap
- (setq original-position
-
(phpinspect-edtrack-original-position-at-point edtrack start-position))))
- (not (or (phpinspect-root-p (phpinspect-meta-token
existing-meta))
-
(phpinspect-taint-iterator-token-is-tainted-p taint-iterator existing-meta))))
- (setq delta (- start-position original-position)
- current-end-position (+ (phpinspect-meta-end
existing-meta) delta)
- token (phpinspect-meta-token existing-meta))
-
- ;;(message "Reusing token %s at point %s"
(phpinspect-meta-string existing-meta) (point))
- ;; Re-register existing token
- (phpinspect-bmap-overlay
- bmap previous-bmap existing-meta delta
- (phpinspect-pctx-consume-whitespace context))
-
- (goto-char current-end-position)
+
+ (cond ((and-let*
+ ((edtrack)
+ (previous-bmap)
+ (result
+ ;; Look for an already parsted token at POINT to
+ ;; adopt into new tree.
+ (or (phpinspect--recycle-token
+ context
+ taint-iterator
+ start-position
+ (setq original-position
+
(phpinspect-edtrack-original-position-at-point edtrack start-position))
+ (phpinspect-bmap-token-starting-at
previous-bmap original-position)
+ tokens-rear
+ ,(if delimiter-predicate `(quote
,delimiter-predicate) 'nil))
+ ;; There is no token at POINT exactly. Attempt
+ ;; to find any other adoptable token after
+ ;; POINT.
+ (when-let
+ ((token-after
(phpinspect-bmap-token-starting-after previous-bmap original-position))
+ (start (phpinspect-meta-start
token-after))
+
+ ((not
(phpinspect-taint-iterator-region-is-tainted-p
+ taint-iterator original-position
start)))
+ (current-start (+ start-position (- start
original-position))))
+ (goto-char current-start)
+ (phpinspect--recycle-token
+ context taint-iterator current-start start
token-after tokens-rear
+ ,(if delimiter-predicate `(quote
,delimiter-predicate) 'nil)))))
+
+ ;; `phpinspect--recycle-token' will return the
symbol
+ ;; 'tainted' when the token that it tried to reuse
+ ;; was tainted. When this happens, we know that we
+ ;; can't re-use the token and we should continue
+ ;; parsing.
+ ((not (eq result 'tainted)))
+
+ ;; Re-using tokens was a success, update tokens-rear
+ ((setq tokens-rear result))))
;; Skip over whitespace after so that we don't do a full
;; run down all of the handlers during the next iteration
- (when (looking-at (phpinspect-handler-regexp whitespace))
- (,(phpinspect-handler-func-name 'whitespace)
(match-string 0))))
+ (when (looking-at
(phpinspect--whitespace-handler-regexp))
+ (phpinspect--whitespace-handler (match-string 0))))
+
,@(mapcar
(lambda (handler)
`((looking-at (,(phpinspect-handler-regexp-func-name
handler)))
@@ -785,7 +860,8 @@ static keywords with the same meaning as in a class block."
(parsed (phpinspect--parse-class-block
(current-buffer) max-point (length start-token)
continue-condition)))
(if complete-block
- (goto-char (+ complete-block 1))
+ (progn
+ (goto-char (+ complete-block 1)))
(setcar parsed :incomplete-block))
parsed))
diff --git a/test/test-meta.el b/test/test-meta.el
index 465413744b..3f07c07304 100644
--- a/test/test-meta.el
+++ b/test/test-meta.el
@@ -52,3 +52,14 @@
(phpinspect-meta-detach-parent meta)
(should (length= (phpinspect-meta-flatten parent) 1))))
+
+(ert-deftest phpinspect-meta-find-child-starting-at ()
+ (let ((grandchild (phpinspect-make-meta nil 14 16 "" 'grandchild))
+ (meta (phpinspect-make-meta nil 10 20 "" 'child))
+ (parent (phpinspect-make-meta nil 9 22 "" 'parent)))
+ (phpinspect-meta-set-parent meta parent)
+ (phpinspect-meta-set-parent grandchild meta)
+
+ (should (phpinspect-meta-find-child-starting-at parent 10))
+
+ (should (eq grandchild (phpinspect-meta-find-child-starting-at-recursively
parent 14)))))