-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathphpinspect-resolvecontext.el
258 lines (219 loc) · 10.7 KB
/
phpinspect-resolvecontext.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
;;; phpinspect-resolvecontext.el --- PHP parsing and completion package -*- lexical-binding: t; -*-
;; Copyright (C) 2021-2023 Free Software Foundation, Inc
;; Author: Hugo Thunnissen <[email protected]>
;; Keywords: php, languages, tools, convenience
;; Version: 2.1.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-cache)
(require 'phpinspect-project)
(require 'phpinspect-token-predicates)
(require 'phpinspect-type)
(require 'phpinspect-meta)
(require 'phpinspect-util)
(defsubst phpinspect-blocklike-p (token)
(or (phpinspect-block-p token)
(phpinspect-function-p token)
(phpinspect-class-p token)
(phpinspect-namespace-p token)))
(defsubst phpinspect-return-p (token)
(and (phpinspect-word-p token)
(string= "return" (cadr token))))
(define-inline phpinspect-statement-introduction-p (token)
(inline-letevals (token)
(inline-quote
(or (phpinspect-return-p ,token)
(phpinspect-end-of-statement-p ,token)
(phpinspect-string-concatenator-p ,token)
(phpinspect-use-p ,token)
(phpinspect-function-p ,token)))))
(cl-defstruct (phpinspect--resolvecontext
(:constructor phpinspect--make-resolvecontext)
(:copier phpinspect--copy-resolvecontext))
(subject nil
:type phpinspect--token
:documentation
"The statement we're trying to resolve the type of.")
(project nil
:type phpinspect-project
:documentation
"The project we're resolving types for.")
(enclosing-metadata nil
:type list
:documentation
"Metadata of tokens that enclose the subject.")
(enclosing-tokens nil
:type list
:documentation
"Tokens that enclose the subject."))
(defun phpinspect--repurpose-resolvecontext (rctx &optional enclosing-tokens subject)
"Copy RCTX, optionally replacing the enclosing tokens and subject.
Note: if ENCLOSING-TOKENS are provided, the repurposed
resolvecontext will have it's enclosing token metadata unset as
it would no longer be valid for the new enclosing tokens."
(let ((rctx (phpinspect--copy-resolvecontext rctx)))
(when subject
(setf (phpinspect--resolvecontext-subject rctx) subject))
(when enclosing-tokens
;; Unset enclosing-metadata as it is no longer valid.
(setf (phpinspect--resolvecontext-enclosing-metadata rctx) nil)
(setf (phpinspect--resolvecontext-enclosing-tokens rctx) enclosing-tokens))
rctx))
(cl-defmethod phpinspect--resolvecontext-push-enclosing-token
((resolvecontext phpinspect--resolvecontext) enclosing-token)
"Add ENCLOSING-TOKEN to RESOLVECONTEXTs enclosing token stack."
(push enclosing-token (phpinspect--resolvecontext-enclosing-tokens
resolvecontext)))
(defun phpinspect-find-statement-before-point (bmap meta point)
(let ((children (reverse (phpinspect-meta-find-children-before meta point)))
token previous-siblings)
(catch 'return
(dolist (child children)
(setq token (phpinspect-meta-token child))
(when (< (phpinspect-meta-start child) point)
(if (and (not previous-siblings) (phpinspect-blocklike-p token))
(progn
(throw 'return (phpinspect-find-statement-before-point bmap child point)))
(when (phpinspect-statement-introduction-p token)
(throw 'return previous-siblings))
(unless (phpinspect-comment-p token)
(push child previous-siblings))))))
previous-siblings))
(defun phpinspect--get-last-statement-in-token (token)
(setq token (cond ((phpinspect-function-p token)
(phpinspect-function-block token))
((phpinspect-namespace-p token)
(phpinspect-namespace-block token))
(t token)))
(nreverse
(seq-take-while
(let ((keep-taking t) (last-test nil))
(lambda (elt)
(when last-test
(setq keep-taking nil))
(setq last-test (phpinspect-variable-p elt))
(and keep-taking
(not (phpinspect-end-of-statement-p elt))
(listp elt))))
(reverse token))))
(cl-defmethod phpinspect-get-resolvecontext
((project phpinspect-project) (bmap phpinspect-bmap) (point integer))
"Construct resolvecontext for BMAP, orienting around POINT."
(let* ((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 (phpinspect-bmap-last-token-before-point bmap point))
(subject-token))
(phpinspect--log "Last token before point: %s, right siblings: %s, parent: %s"
(phpinspect-meta-string subject)
(mapcar #'phpinspect-meta-token (phpinspect-meta-right-siblings subject))
(phpinspect-meta-string (phpinspect-meta-parent subject)))
(let ((next-sibling (car (phpinspect-meta-right-siblings subject))))
;; When the right sibling of the last ending token overlaps point, this is
;; our actual subject.
(when (and next-sibling
(phpinspect-meta-overlaps-point next-sibling point))
(setq subject next-sibling)))
;; Dig down through tokens that can contain statements
(let (new-subject)
(catch 'break
(while (and subject
(phpinspect-enclosing-token-p (phpinspect-meta-token subject))
(cdr (phpinspect-meta-token subject)))
(setq new-subject (phpinspect-meta-find-child-before subject point))
(if new-subject
(setq subject new-subject)
(throw 'break nil)))))
(phpinspect--log "Initial resolvecontext subject token: %s"
(phpinspect-meta-token subject))
(when subject
(setq subject-token
(mapcar #'phpinspect-meta-token
(phpinspect-find-statement-before-point
bmap (phpinspect-meta-parent subject) point)))
(phpinspect--log "Ultimate resolvecontext subject token: %s. Parent: %s"
subject-token (phpinspect-meta-token
(phpinspect-meta-parent subject)))
;; Iterate through subject parents to build stack of enclosing tokens
(let ((parent (phpinspect-meta-parent subject)))
(while parent
(let ((granny (phpinspect-meta-parent parent)))
(unless (and (phpinspect-block-p (phpinspect-meta-token parent))
(or (not granny)
(phpinspect-function-p (phpinspect-meta-token granny))
(phpinspect-class-p (phpinspect-meta-token granny))))
(push parent enclosing-tokens))
(setq parent (phpinspect-meta-parent parent))))))
(phpinspect--make-resolvecontext
:subject (phpinspect--get-last-statement-in-token subject-token)
:enclosing-tokens (nreverse (mapcar #'phpinspect-meta-token enclosing-tokens))
:enclosing-metadata (nreverse enclosing-tokens)
:project project)))
(defun phpinspect--get-resolvecontext (project 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
statement of the innermost incomplete token as subject
accompanied by all of its enclosing tokens."
(unless resolvecontext
(setq resolvecontext (phpinspect--make-resolvecontext
:project project)))
(let ((last-token (car (last token)))
(last-encountered-token (car
(phpinspect--resolvecontext-enclosing-tokens
resolvecontext))))
(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 token))
(if (phpinspect-incomplete-token-p last-token)
(phpinspect--get-resolvecontext project last-token resolvecontext)
;; else
(setf (phpinspect--resolvecontext-subject resolvecontext)
(phpinspect--get-last-statement-in-token token))
resolvecontext)))
(defun phpinspect-rctx-get-typedef (rctx class-fqn &optional no-enqueue)
(cl-assert (phpinspect--resolvecontext-p rctx))
(let ((project (phpinspect--resolvecontext-project rctx)))
(phpinspect-project-get-typedef-extra-or-create project class-fqn no-enqueue)))
(defun phpinspect-rctx-get-function-return-type (rctx function-name)
(cl-assert (phpinspect--resolvecontext-p rctx))
(let ((project (phpinspect--resolvecontext-project rctx)))
(phpinspect-project-get-function-return-type project function-name)))
(defun phpinspect--make-type-resolver-for-resolvecontext
(resolvecontext)
(let ((namespace-or-root
(seq-find #'phpinspect-namespace-or-root-p
(phpinspect--resolvecontext-enclosing-tokens
resolvecontext)))
(namespace-name))
(when (phpinspect-namespace-p namespace-or-root)
(setq namespace-name (cadadr namespace-or-root))
(setq namespace-or-root (phpinspect-namespace-body namespace-or-root)))
(phpinspect--make-type-resolver
(phpinspect--uses-to-types
(seq-filter #'phpinspect-use-p namespace-or-root))
(seq-find #'phpinspect-class-p
(phpinspect--resolvecontext-enclosing-tokens
resolvecontext))
namespace-name)))
(cl-defmethod phpinspect--resolvecontext-pop-meta ((rctx phpinspect--resolvecontext))
"Remove the first element of enclosing token metadata and
return it. Pops enclosing tokens to keep both in sync."
(pop (phpinspect--resolvecontext-enclosing-tokens rctx))
(pop (phpinspect--resolvecontext-enclosing-metadata rctx)))
(provide 'phpinspect-resolvecontext)
;;; phpinspect-resolvecontext.el ends here