-
Notifications
You must be signed in to change notification settings - Fork 15
Expand file tree
/
Copy pathbinding.jl
More file actions
202 lines (181 loc) · 8.04 KB
/
binding.jl
File metadata and controls
202 lines (181 loc) · 8.04 KB
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
"""
Heuristic for showing completions. A binding is relevant when all are true:
- it isn't generated by the compiler
- if nonglobal, it's defined before the cursor
- (if global) it doesn't contain or immediately precede the cursor
"""
function is_relevant(ctx::JL.AbstractLoweringContext,
binding::JL.BindingInfo,
cursor::Int)
(;start, stop) = JS.byte_range(JL.binding_ex(ctx, binding.id))
!binding.is_internal &&
!in(cursor, start:(stop+1)) &&
(binding.kind === :global
# || we could relax this for locals defined before the end of the
# largest for/while containing the cursor
|| cursor > start)
end
let lowering_module = Module()
global function jl_lower_for_scope_resolution2(st0)
ctx1, st1 = JL.expand_forms_1(lowering_module, remove_macrocalls(st0));
ctx2, st2 = JL.expand_forms_2(ctx1, st1);
ctx3, st3 = JL.resolve_scopes(ctx2, st2);
return ctx3, st2
end
global function jl_lower_for_scope_resolution3(st0)
ctx1, st1 = JL.expand_forms_1(lowering_module, remove_macrocalls(st0));
ctx2, st2 = JL.expand_forms_2(ctx1, st1);
ctx3, st3 = JL.resolve_scopes(ctx2, st2);
@assert !isnothing(st3)
return ctx3, st3
end
end
"""
Find the list of (BindingInfo, SyntaxTree, distance::Int) to suggest as
completions given a parsed SyntaxTree and a cursor position.
JuliaLowering throws away the mapping from scopes to bindings (scopes are stored
as an ephemeral stack.) We work around this by taking all available bindings
and filtering out any that aren't declared in a scope containing the cursor.
"""
function cursor_bindings(st0_top::JL.SyntaxTree, b_top::Int)
st0, b = greatest_local(st0_top, b_top)
if isnothing(st0)
return nothing # nothing we can lower
end
ctx3, st2 = try
jl_lower_for_scope_resolution2(st0)
catch # err
# JETLS_DEV_MODE && @warn "Error in lowering" err
return nothing # lowering failed, e.g. because of incomplete input
end
# Note that ctx.bindings are only available after resolve_scopes, and
# scope-blocks are not present in st3 after resolve_scopes.
binfos = filter(binfo -> is_relevant(ctx3, binfo, b), ctx3.bindings.info)
# for each binding: binfo, all syntaxtrees containing it, and the scope it belongs to
bscopeinfos = Tuple{JL.BindingInfo, JL.SyntaxList, Union{JL.SyntaxTree, Nothing}}[]
for binfo in binfos
# TODO: find tree parents instead of byte parents?
bas = byte_ancestors(st2, JS.byte_range(JL.binding_ex(ctx3, binfo.id)))
# find the innermost hard scope containing this binding decl. we shouldn't
# be in multiple overlapping scopes that are not direct ancestors; that
# should indicate a provenance failure
i = findfirst(ba -> JS.kind(ba) in JS.KSet"scope_block lambda module toplevel", bas)
push!(bscopeinfos, (binfo, bas, isnothing(i) ? nothing : bas[i]))
end
cursor_scopes = byte_ancestors(st2, b)
# ignore scopes we aren't in
filter!(((_, _, bs),) -> isnothing(bs) || bs._id in cursor_scopes.ids,
bscopeinfos)
# Now eliminate duplicates by name.
# - Prefer any local binding belonging to a tighter scope (lower bdistance)
# - If a static parameter and a local of the same name exist in the same
# scope (impossible in julia), the local is internal and should be ignored
bdistances = map(((_, _, bs),) -> if isnothing(bs)
lastindex(cursor_scopes.ids) + 1
else
findfirst(cs -> bs._id === cs, cursor_scopes.ids)
end,
bscopeinfos)
seen = Dict{String, Int}()
for i in eachindex(bscopeinfos)
(binfo, _, _) = bscopeinfos[i]
prev = get(seen, binfo.name, nothing)
if (isnothing(prev)
|| bdistances[i] < bdistances[prev]
|| binfo.kind === :static_parameter)
seen[binfo.name] = i
elseif JETLS_DEV_MODE
@info "Found two bindings with the same name:" binfo bscopeinfos[prev][1]
end
end
return map(values(seen)) do i
(binfo, _, _) = bscopeinfos[i]
# distance from the cursor
dist = abs(b - JS.last_byte(JL.binding_ex(ctx3, binfo.id)))
return (binfo, JL.binding_ex(ctx3, binfo.id), dist)
end
end
"""
select_target_binding(ctx3::JL.VariableAnalysisContext, st3::JL.SyntaxTree, offset::Int) -> target_binding::Union{Nothing,JL.SyntaxTree}
For the same purpose as [`select_target_node`](@ref), returns the `target_binding::JL.SyntaxTree`
closest to the cursor at the `offset` position.
Note that `st3::JL.SyntaxTree` has been processed by `JL.resolve_scopes` and corresponds to
`ctx3::JL.VariableAnalysisContext`.
It is guaranteed that `target_binding` satisfies `JS.kind(target_binding) === JS.K"BindingId"`.
"""
function select_target_binding(ctx3::JL.VariableAnalysisContext, st3::JL.SyntaxTree, offset::Int)
function select(st::JL.SyntaxTree)
JS.kind(st) === JS.K"BindingId" || return false
binfo = JL.lookup_binding(ctx3, st)
return !binfo.is_internal
end
bas = byte_ancestors(st3, offset)
i = findfirst(select, bas)
if isnothing(i)
offset > 0 || return nothing
# Support cases like `var│`, `func│(5)`
bas = byte_ancestors(st3, offset - 1)
i = findfirst(select, bas)
if isnothing(i)
return nothing
end
end
return bas[i]
end
"""
select_target_binding_definitions(st0_top::JL.SyntaxTree, offset::Int) ->
nothing or (binding::JL.SyntaxTree, definitions::JL.SyntaxList)
Find the binding at the cursor position and return all of its definition sites.
Returns `nothing` if lowering fails, no binding is found at the cursor, or the binding
has no definitions. Otherwise returns a tuple of `(binding, definitions)` where:
- `binding` is the `JL.SyntaxTree` node representing the binding at the cursor
- `definitions` is a `JL.SyntaxList` containing all definition sites for that binding
"""
function select_target_binding_definitions(st0_top::JL.SyntaxTree, offset::Int)
st0, b = greatest_local(st0_top, offset)
isnothing(st0) && return nothing
ctx3, st3 = try
jl_lower_for_scope_resolution3(st0)
catch
return nothing
end
binding = select_target_binding(ctx3, st3, b)
isnothing(binding) && return nothing
binfo = JL.lookup_binding(ctx3, binding)
definitions = lookup_binding_definitions(st3, binfo)
isempty(definitions) && return nothing
return binding, definitions
end
is_same_binding(x::JL.SyntaxTree, id::Int) = JS.kind(x) === JS.K"BindingId" && id == JL._binding_id(x)
"""
lookup_binding_definitions(st3::JL.SyntaxTree, binfo::JL.BindingInfo) -> definitions::JL.SyntaxList
Find all definition sites for a given binding in the syntax tree. Returns a `JL.SyntaxList`
containing the syntax nodes where the binding may be defined.
This function traverses the syntax tree to collect `definitions` that tracks all the
assignment expressions (`=`) and function declarations where the binding may be defined.
For `:argument` bindings, `definitions` also includes the argument declaration itself.
"""
function lookup_binding_definitions(st3::JL.SyntaxTree, binfo::JL.BindingInfo)
if binfo.kind === :argument
sl = JL.SyntaxList(JL.syntax_graph(st3), [binfo.node_id])
else
sl = JL.SyntaxList(st3)
end
return _lookup_binding_definitions!(sl, st3, binfo.id)
end
function _lookup_binding_definitions!(sl::JL.SyntaxList, st3::JL.SyntaxTree, binding_id::Int)
traverse(st3) do st::JL.SyntaxTree
if JS.kind(st) === JS.K"=" && JS.numchildren(st) ≥ 2
lhs = st[1]
if is_same_binding(lhs, binding_id)
push!(sl, lhs)
end
elseif JS.kind(st) === JS.K"function_decl" && JS.numchildren(st) ≥ 1
func = st[1]
if is_same_binding(func, binding_id)
push!(sl, func)
end
end
end
return reverse!(deduplicate_syntaxlist(sl))
end