Skip to content

Commit 72430b5

Browse files
committed
hover: Prototype type on hover
1 parent e355af5 commit 72430b5

File tree

4 files changed

+74
-33
lines changed

4 files changed

+74
-33
lines changed

src/hover.jl

Lines changed: 52 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -55,15 +55,35 @@ function local_binding_hover_info(fi::FileInfo, uri::URI, definitions::JS.Syntax
5555
return String(take!(io))
5656
end
5757

58+
function type_hover_for_binding(
59+
fi::FileInfo, mod::Module, postprocessor::LSPostProcessor,
60+
ctx3::JL.VariableAnalysisContext, inferrable_tree::JS.SyntaxTree,
61+
inferrable_binding::JS.SyntaxTree
62+
)
63+
range = jsobj_to_range(inferrable_binding, fi)
64+
fallback_hover = Hover(;
65+
contents = MarkupContent(; kind = MarkupKind.Markdown, value = "`[Unknown type]`"),
66+
range)
67+
inferred_tree = @something infer_toplevel_tree(ctx3, inferrable_tree, mod) return fallback_hover
68+
typ = @something get_type_for_range(inferred_tree, JS.byte_range(inferrable_binding)) return fallback_hover
69+
typstr = postprocessor(string(typ)::String)
70+
contents = MarkupContent(;
71+
kind = MarkupKind.Markdown,
72+
value = "```julia\n$typstr\n```")
73+
return Hover(; contents, range)
74+
end
75+
5876
function handle_HoverRequest(
59-
server::Server, msg::HoverRequest, cancel_flag::CancelFlag)
77+
server::Server, msg::HoverRequest, cancel_flag::CancelFlag
78+
)
6079
state = server.state
6180
uri = msg.params.textDocument.uri
6281
pos = adjust_position(state, uri, msg.params.position)
6382

83+
fallback_response = HoverResponse(; id = msg.id, result = null)
6484
result = get_file_info(state, uri, cancel_flag)
6585
if isnothing(result)
66-
return send(server, HoverResponse(; id = msg.id, result = null))
86+
return send(server, fallback_response)
6787
elseif result isa ResponseError
6888
return send(server, HoverResponse(; id = msg.id, result = nothing, error = result))
6989
end
@@ -73,15 +93,20 @@ function handle_HoverRequest(
7393
offset = xy_to_offset(fi, pos)
7494
(; mod, analyzer, postprocessor) = get_context_info(state, uri, pos)
7595

76-
local_hover = local_binding_hover(state, fi, uri, st0_top, offset, mod)
77-
isnothing(local_hover) || return send(server, HoverResponse(;
78-
id = msg.id,
79-
result = local_hover))
96+
inferrable_binding = select_inferrable_binding(st0_top, offset, mod)
97+
if !isnothing(inferrable_binding)
98+
(; ctx3, st3, binding) = inferrable_binding
99+
if is_from_user_ast(binding)
100+
binfo = JL.get_binding(ctx3, binding)::JL.BindingInfo
101+
if binfo.kind === :local || binfo.kind === :argument
102+
result = type_hover_for_binding(fi, mod, postprocessor, ctx3, st3, binding)
103+
return send(server, HoverResponse(; id = msg.id, result))
104+
end
105+
end
106+
end
80107

81108
node = @something select_target_identifier(st0_top, offset) begin
82-
tok = @something token_at_offset(fi, pos) begin
83-
return send(server, HoverResponse(; id = msg.id, result = null))
84-
end
109+
tok = @something token_at_offset(fi, pos) return send(server, fallback_response)
85110
byterng = JS.byte_range(tok)
86111
tokstr = String(fi.parsed_stream.textbuf[byterng])
87112
if haskey(KEYWORD_DOCS, tokstr)
@@ -90,43 +115,43 @@ function handle_HoverRequest(
90115
start = offset_to_xy(fi, first(byterng)),
91116
var"end" = offset_to_xy(fi, last(byterng)+1))
92117
range, _ = unadjust_range(state, uri, range)
93-
return send(server, HoverResponse(;
94-
id = msg.id,
95-
result = Hover(; contents, range)))
118+
result = Hover(; contents, range)
119+
return send(server, HoverResponse(; id = msg.id, result))
96120
end
97-
return send(server, HoverResponse(; id = msg.id, result = null))
121+
return send(server, fallback_response)
98122
end
99123

100-
parentmod = mod
101-
identifier_node = node
102-
103124
# TODO replace this AST hack with a proper abstract interpretation to resolve binding information
104-
if JS.kind(node) === JS.K"." && JS.numchildren(node) 2
105-
dotprefix = node[1]
125+
identifier_node = node
126+
if JS.kind(identifier_node) === JS.K"." && JS.numchildren(identifier_node) 2
127+
dotprefix = identifier_node[1]
106128
dotprefixtyp = resolve_type(analyzer, mod, dotprefix)
107129
if dotprefixtyp isa Core.Const
108130
dotprefixval = dotprefixtyp.val
109131
if dotprefixval isa Module
110-
parentmod = dotprefixval
111-
identifier_node = node[2]
132+
mod = dotprefixval
133+
identifier_node = identifier_node[2]
112134
# EST wraps the RHS of dot expressions in `K"inert"`
113135
if JS.kind(identifier_node) === JS.K"inert" && JS.numchildren(identifier_node) 1
114136
identifier_node = identifier_node[1]
115137
end
116138
end
117139
end
118140
end
119-
if !JS.is_identifier(identifier_node)
120-
return send(server, HoverResponse(; id = msg.id, result = null))
141+
142+
JS.is_identifier(identifier_node) || return send(server, fallback_response)
143+
identifier = try
144+
JL.est_to_expr(identifier_node)
145+
catch
146+
return send(server, fallback_response)
121147
end
122-
identifier = Symbol(identifier_node.name_val)
123-
documentation = @invokelatest(Base.Docs.doc(DocsBinding(parentmod, identifier)))::Markdown.MD
148+
identifier isa Symbol || return send(server, fallback_response)
149+
150+
documentation = @invokelatest(Base.Docs.doc(DocsBinding(mod, identifier)))::Markdown.MD
124151
value = postprocessor(documentation)
125152
contents = MarkupContent(; kind = MarkupKind.Markdown, value)
126153
range, _ = unadjust_range(state, uri, jsobj_to_range(node, fi))
127-
return send(server, HoverResponse(;
128-
id = msg.id,
129-
result = Hover(; contents, range)))
154+
return send(server, HoverResponse(; id = msg.id, result = Hover(; contents, range)))
130155
end
131156

132157
@eval function DocsBinding(parentmod::Module, identifier::Symbol)

src/utils/ast.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -965,6 +965,7 @@ end
965965

966966
"""
967967
is_from_user_ast(provs::JS.SyntaxList) -> Bool
968+
is_from_user_ast(st3::JS.SyntaxTree) -> Bool
968969
969970
Determine whether a binding with the given provenances originates from user-written code.
970971
@@ -986,6 +987,7 @@ function is_from_user_ast(provs::JS.SyntaxList)
986987
JS.sourcefile(lprov) == JS.sourcefile(fprov) || return false
987988
return JS.byte_range(lprov) JS.byte_range(fprov)
988989
end
990+
is_from_user_ast(st3::JS.SyntaxTree) = is_from_user_ast(JS.flattened_provenance(st3))
989991

990992
function is_throw_call(ctx3::JL.VariableAnalysisContext, st3::JS.SyntaxTree)
991993
JS.kind(st3) === JS.K"call" || return false

src/utils/binding.jl

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,3 +366,19 @@ function _lookup_binding_definitions!(sl::JS.SyntaxList, st3::JS.SyntaxTree, bin
366366
end
367367
return reverse!(deduplicate_syntaxlist(sl))
368368
end
369+
370+
function select_inferrable_binding(
371+
st0_top::JS.SyntaxTree, offset::Int, mod::Module;
372+
caller::AbstractString = "select_inferrable_target"
373+
)
374+
st0 = @something greatest_local(st0_top, offset) return nothing # nothing we can lower
375+
(; ctx3, st3) = try
376+
jl_lower_for_scope_resolution(mod, st0; trim_error_nodes=false, recover_from_macro_errors=false)
377+
catch err
378+
JETLS_DEBUG_LOWERING && @warn "Error in lowering ($caller)" err
379+
JETLS_DEBUG_LOWERING && Base.show_backtrace(stderr, catch_backtrace())
380+
return nothing
381+
end
382+
binding = @something __select_target_binding(ctx3, st3, offset) return nothing
383+
return (; ctx3, st3, binding)
384+
end

src/utils/inference.jl

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
11
function get_type_for_range(inferred_tree::JL.SyntaxTree, rng::UnitRange{<:Integer})
22
typ = Ref{Any}(nothing)
3-
traverse(inferred_tree) do st::JL.SyntaxTree
4-
if JS.byte_range(st) == rng
5-
if hasproperty(st, :type)
6-
ntyp = st.type
3+
traverse(inferred_tree) do st5::JL.SyntaxTree
4+
if is_from_user_ast(JS.flattened_provenance(st5)) && JS.byte_range(st5) == rng
5+
if hasproperty(st5, :type)
6+
ntyp = st5.type
77
if typ[] === nothing
88
typ[] = ntyp
99
else
1010
typ[] = CC.tmerge(ntyp, typ[])
1111
end
12-
else
13-
return nothing
1412
end
1513
end
1614
end

0 commit comments

Comments
 (0)