Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion src/JETLS.jl
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,7 @@ function handler_concurrent_message(server::Server, @nospecialize msg)
end
elseif isdefined(msg, :id) && (id = msg.id; id isa String || id isa Int)
let cancel_flag = get!(()->CancelFlag(false), server.state.currently_handled, id)
Threads.@spawn :default @tryinvokelatest handle_request_message(server, msg, cancel_flag)
Threads.@spawn :default @tryinvokelatest handle_request_message_or_respond_error(server, msg, cancel_flag)
end
else
Threads.@spawn :default @tryinvokelatest handle_notification_message(server, msg)
Expand Down Expand Up @@ -455,6 +455,30 @@ function handle_response_message(
nothing
end

"""
handle_request_message_or_respond_error(server::Server, msg, cancel_flag::CancelFlag)

Run [`handle_request_message`](@ref), and if the handler throws, still send an
`ErrorCodes.InternalError` response for `msg.id`: every LSP request must be
answered or the client blocks on it until its own timeout. Sending the response
also enqueues the `HandledToken` that clears `msg.id` from
`server.state.currently_handled`.
"""
function handle_request_message_or_respond_error(server::Server, @nospecialize(msg), cancel_flag::CancelFlag)
try
handle_request_message(server, msg, cancel_flag)
catch err
@error "handle_request_message failed" id = msg.id
Base.display_error(stderr, err, catch_backtrace())
send(server,
ResponseMessage(;
id = msg.id,
result = nothing,
error = internal_error(sprint(showerror, err))))
end
nothing
end

function handle_request_message(server::Server, @nospecialize(msg), cancel_flag::CancelFlag)
if is_cancelled(cancel_flag)
send(server,
Expand Down
23 changes: 18 additions & 5 deletions src/analysis/Analyzer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -387,16 +387,27 @@ function CC.abstract_eval_globalref(
if saw_latestworld
return CC.RTEffects(Any, Any, CC.generic_getglobal_effects)
end
(valid_worlds, ret) = CC.scan_leaf_partitions(analyzer, g, sv.world) do analyzer::LSAnalyzer, binding::Core.Binding, partition::Core.BindingPartition
@static if hasfield(CC.InferenceState, :world)
curworld = sv.world.this
worldhint = sv.world
else
curworld = CC.get_inference_world(analyzer)
worldhint = CC.binding_world_hints(curworld, sv)
end
(valid_worlds, ret) = CC.scan_leaf_partitions(analyzer, g, worldhint) do analyzer::LSAnalyzer, binding::Core.Binding, partition::Core.BindingPartition
offset = report_offset(analyzer, sv)
if offset ≤ allowed_offset
if partition.min_world ≤ sv.world.this ≤ partition.max_world # XXX This should probably be fixed on the Julia side
if partition.min_world ≤ curworld ≤ partition.max_world # XXX This should probably be fixed on the Julia side
report_undef_global_var!(analyzer, sv, binding, partition, offset)
end
end
CC.abstract_eval_partition_load(analyzer, binding, partition)
end
CC.update_valid_age!(sv, valid_worlds)
@static if hasfield(CC.InferenceState, :world)
CC.update_valid_age!(sv, valid_worlds)
else
CC.update_valid_age!(sv, curworld, valid_worlds)
end
return ret
end

Expand Down Expand Up @@ -836,8 +847,10 @@ function report_method_error!(
arginfo::CC.ArgInfo, @nospecialize(atype)
)
info = call.info
if isa(info, CC.ConstCallInfo)
info = info.call
@static if isdefined(CC, :ConstCallInfo)
if isa(info, CC.ConstCallInfo)
info = info.call
end
end
if isa(info, CC.MethodMatchInfo)
report_method_error!(analyzer, sv, info, atype)
Expand Down
9 changes: 3 additions & 6 deletions src/analysis/Interpreter.jl
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,13 @@ end
JET.ToplevelAbstractAnalyzer(interp::LSInterpreter) = interp.analyzer
function JET.ToplevelAbstractAnalyzer(
interp::LSInterpreter, concretized::BitVector;
refresh_local_cache::Bool = true, # This option is used by JET v0.10. TODO We can remove this once we update JET to v0.11.
reset_report_target_modules::Bool = true, # LSInterpreter specific option
)
if reset_report_target_modules
reset_report_target_modules!(interp.analyzer, JET.InterpretationState(interp).res.analyzed_files)
end
return @invoke JET.ToplevelAbstractAnalyzer(
interp::JET.ConcreteInterpreter, concretized::BitVector;
refresh_local_cache)
interp::JET.ConcreteInterpreter, concretized::BitVector)
end

# overloads
Expand Down Expand Up @@ -180,8 +178,7 @@ function JET.analyze_from_definitions!(interp::LSInterpreter, config::JET.Toplev
# Create a new analyzer with fresh local caches (`inf_cache` and `analysis_results`)
# to avoid data races between concurrent signature analysis tasks
analyzer = JET.ToplevelAbstractAnalyzer(interp, JET.non_toplevel_concretized;
reset_report_target_modules = false,
refresh_local_cache = true)
reset_report_target_modules = false)
inf_world = CC.get_inference_world(analyzer)
match = Base._which(tt;
# NOTE use the latest world counter with `method_table(analyzer)` unwrapped,
Expand All @@ -195,7 +192,7 @@ function JET.analyze_from_definitions!(interp::LSInterpreter, config::JET.Toplev
# redirect keyword-slurping forwarders to the abstract keyword sorter,
# but only after the `entrypoint` check above sees the original method name
match = JETLS.redirect_keyword_slurp_match(analyzer, match, inf_world)
analyzer, result = JET.analyze_method_signature!(analyzer,
result = JET.analyze_method_signature!(analyzer,
match.method, match.spec_types, match.sparams)
reports = JET.get_reports(analyzer, result)
isempty(reports) || @lock progress.reports_lock append!(progress.reports, reports)
Expand Down
80 changes: 65 additions & 15 deletions src/analysis/TypeAnnotation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -191,11 +191,17 @@ mutable struct OCBodyAnnotationState
end
OCBodyAnnotationState() = OCBodyAnnotationState(nothing)

@static if isdefined(CC, :InferenceCache)
const ASTLocalInferenceCache = CC.InferenceCache
else
const ASTLocalInferenceCache = Vector{CC.InferenceResult}
end

struct ASTTypeAnnotator <: CC.AbstractInterpreter
world::UInt
inf_params::CC.InferenceParams
opt_params::CC.OptimizationParams
inf_cache::Vector{CC.InferenceResult}
inf_cache::ASTLocalInferenceCache

toptree::SyntaxTreeC
topmi::MethodInstance
Expand Down Expand Up @@ -230,7 +236,7 @@ struct ASTTypeAnnotator <: CC.AbstractInterpreter
aggressive_constant_propagation = true
),
opt_params::CC.OptimizationParams = CC.OptimizationParams(),
inf_cache::Vector{CC.InferenceResult} = CC.InferenceResult[],
inf_cache::ASTLocalInferenceCache = ASTLocalInferenceCache(),
oc_body_trees::IdDict{Method,SyntaxTreeC} = IdDict{Method,SyntaxTreeC}(),
oc_body_annotation_states::IdDict{Method,OCBodyAnnotationState} = IdDict{Method,OCBodyAnnotationState}(),
oc_argtype_observations::OCArgtypeTable = OCArgtypeTable(),
Expand Down Expand Up @@ -302,7 +308,26 @@ function CC.abstract_eval_partition_load(
)
res = @invoke CC.abstract_eval_partition_load(
interp::CC.AbstractInterpreter, binding::Core.Binding, partition::Core.BindingPartition)
return concretize_abstract_binding_state_load(interp, res)
res = concretize_abstract_binding_state_load(interp, res)
if res.rt === Any
# Non-const materialized globals load as `Any` (the binding itself is untyped);
# the recorded assignment type lives in the module value.
g = binding.globalref
world = Base.get_world_counter()
if Base.invoke_in_world(world, isdefinedglobal, g.mod, g.name)
val = Base.invoke_in_world(world, getglobal, g.mod, g.name)
if val isa JET.AbstractBindingState && isdefined(val, :typ)
(; exct, effects) = res
if val.maybeundef
⊔ = CC.join(CC.typeinf_lattice(interp))
exct = exct ⊔ UndefVarError
effects = CC.Effects(effects; nothrow = exct === Union{})
end
return CC.RTEffects(val.typ, exct, effects)
end
end
end
return res
end

# Script-mode full analysis may hand us a context module populated by JET's virtualprocess,
Expand Down Expand Up @@ -625,14 +650,19 @@ end
# - If the same basic block has a slot assignment that dominates `idx`,
# use the assigned RHS's type (`ssavaluetypes[pc_assign]`).
# - Otherwise fall back to the bb's entry varstate
# (`bb_vartables[bb][id]`), which CC's dataflow has already populated
# (`bb_states[bb].vartable[id]`), which CC's dataflow has already populated
# with cross-bb branch narrowing.
function slot_type_at(slot::SlotNumber, idx::Int, frame::CC.InferenceState)
pc_assign = CC.find_dominating_assignment(slot.id, idx, frame)
pc_assign === nothing || return frame.ssavaluetypes[pc_assign]
bb = CC.block_for_inst(frame.cfg, idx)
entry = @something frame.bb_vartables[bb] return frame.src.slottypes[slot.id]
return entry[slot.id].typ
@static if hasfield(CC.InferenceState, :bb_states)
entry = @something frame.bb_states[bb] return frame.src.slottypes[slot.id]
return entry.vartable[slot.id].typ
else
entry = @something frame.bb_vartables[bb] return frame.src.slottypes[slot.id]
return entry[slot.id].typ
end
end

# Extract the matched methods for a call site from CC's per-stmt `CallInfo`.
Expand All @@ -655,10 +685,14 @@ function collect_call_matches!(matches::Vector{Core.MethodMatch}, @nospecialize
for sub in info.split
collect_call_matches!(matches, sub)
end
elseif info isa CC.ConstCallInfo
# `ConstCallInfo` wraps the underlying dispatch info with a
# const-prop'd result; the same matching methods live one level down.
collect_call_matches!(matches, info.call)
else
@static if isdefined(CC, :ConstCallInfo)
if info isa CC.ConstCallInfo
# `ConstCallInfo` wraps the underlying dispatch info with a
# const-prop'd result; the same matching methods live one level down.
collect_call_matches!(matches, info.call)
end
end
end
return matches
end
Expand Down Expand Up @@ -809,8 +843,24 @@ function annotate_types!(
end
end

@static if hasmethod(CC.finishinfer!,
Tuple{CC.InferenceState, CC.AbstractInterpreter, Int, IdDict{MethodInstance, CodeInstance}})
function CC.finishinfer!(frame::CC.InferenceState, interp::ASTTypeAnnotator, cycleid::Int,
opt_cache::IdDict{MethodInstance, CodeInstance})
ret = @invoke CC.finishinfer!(frame::CC.InferenceState, interp::CC.AbstractInterpreter,
cycleid::Int, opt_cache::IdDict{MethodInstance, CodeInstance})
finishinfer_annotate!(frame, interp)
return ret
end
else
function CC.finishinfer!(frame::CC.InferenceState, interp::ASTTypeAnnotator, cycleid::Int)
ret = @invoke CC.finishinfer!(frame::CC.InferenceState, interp::CC.AbstractInterpreter, cycleid::Int)
finishinfer_annotate!(frame, interp)
return ret
end
end

function finishinfer_annotate!(frame::CC.InferenceState, interp::ASTTypeAnnotator)
if frame.linfo === interp.topmi
annotate_types!(interp.toptree[1], frame, interp.filter)
else
Expand All @@ -819,7 +869,7 @@ function CC.finishinfer!(frame::CC.InferenceState, interp::ASTTypeAnnotator, cyc
record_oc_body_annotation_candidate!(interp, def, frame)
end
end
return ret
return nothing
end

# The pending `def` entry marks the dynamic extent of the eager `check=false`
Expand Down Expand Up @@ -974,7 +1024,7 @@ function _infer_toplevel_tree(
world::UInt = Base.get_world_counter()
)
filter = SyntheticFilter(st0, ctx3.bindings)
inf_cache = CC.InferenceResult[]
inf_cache = ASTLocalInferenceCache()
interp = refinements = nothing
for _ = 1:MAX_OC_REFINEMENT_PASSES
observations = OCArgtypeTable()
Expand Down Expand Up @@ -1048,7 +1098,7 @@ function infer_lowered_tree(
ctx3::JL.VariableAnalysisContext, inferrable_tree3::SyntaxTreeC,
context_module::Module, world::UInt, filter::SyntheticFilter,
observations::OCArgtypeTable, refinements::Union{Nothing,OCArgtypeTable},
inf_cache::Vector{CC.InferenceResult}
inf_cache::ASTLocalInferenceCache
)
inferrable_tree = try
# Route single-method local closures through `OpaqueClosure` instead of
Expand Down Expand Up @@ -1090,7 +1140,7 @@ function infer_thunk!(
tree::SyntaxTreeC, src::CodeInfo, context_module::Module,
argtypes::Union{Nothing,Vector{Any}}, world::UInt, filter::SyntheticFilter,
observations::OCArgtypeTable, refinements::Union{Nothing,OCArgtypeTable},
inf_cache::Vector{CC.InferenceResult}
inf_cache::ASTLocalInferenceCache
)
strip_latestworld!(src)
mi = construct_toplevel_mi(src, context_module)
Expand Down Expand Up @@ -1152,7 +1202,7 @@ end
function infer_method_defs!(
inferred::SyntaxTreeC, src::CodeInfo, context_module::Module, world::UInt, filter::SyntheticFilter,
observations::OCArgtypeTable, refinements::Union{Nothing,OCArgtypeTable},
inf_cache::Vector{CC.InferenceResult}
inf_cache::ASTLocalInferenceCache
)
block = inferred[1]
nstmts = JS.numchildren(block)
Expand Down
2 changes: 1 addition & 1 deletion src/analysis/full-analysis.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1063,7 +1063,7 @@ function analyze_package_with_revise(
JET.AnalyzerState(JET.AnalyzerState(analyzer), #=refresh_local_cache=#true))
match = signature_analysis_match(task_analyzer, siginfo.sig, inf_world)
if match !== nothing
task_analyzer, result = JET.analyze_method_signature!(task_analyzer,
result = JET.analyze_method_signature!(task_analyzer,
match.method, match.spec_types, match.sparams)
@atomic progress.analyzed += 1
reports = JET.get_reports(task_analyzer, result)
Expand Down
20 changes: 14 additions & 6 deletions src/completions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -586,11 +586,14 @@ function add_emoji_latex_completions!(
newText = val))
end

emojionly || foreach(REPL.REPLCompletions.latex_symbols) do (key, val)
items[key] = create_ci(key, val, false)
end
foreach(REPL.REPLCompletions.emoji_symbols) do (key, val)
items[key] = create_ci(key, val, true)
if emojionly
foreach(REPL.REPLCompletions.emoji_symbols) do (key, val)
items[key] = create_ci(key, val, true)
end
else
foreach(REPL.REPLCompletions.latex_symbols) do (key, val)
items[key] = create_ci(key, val, false)
end
end

# if we reached here, we have added all emoji and latex completions
Expand Down Expand Up @@ -623,7 +626,12 @@ const KEYWORD_DOCS = Dict{String,MarkupContent}()
let keywords = Set{String}()
union!(keywords, REPL.REPLCompletions.sorted_keywords, REPL.REPLCompletions.sorted_keyvals)
for keyword in keywords
documentation = get_keyword_doc(Symbol(keyword))
keysym = Symbol(keyword)
documentation = if keysym in (Symbol("true"), Symbol("false")) || haskey(Base.Docs.keywords, keysym)
get_keyword_doc(keysym)
else
nothing
end
KEYWORD_COMPLETIONS[keyword] = CompletionItem(;
label = keyword,
labelDetails = CompletionItemLabelDetails(; description = "keyword"),
Expand Down
2 changes: 1 addition & 1 deletion src/document-symbol.jl
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ function extract_module_symbol!(
parent_context_module::Module
)
JS.numchildren(st0) ≥ 3 || return nothing
name_node = st0[2]
name_node = st0[end-1]
name = @something get_name_val(name_node) return nothing
context_module = parent_context_module
if invokelatest(isdefinedglobal, parent_context_module, Symbol(name))
Expand Down
Loading
Loading