Skip to content

Commit

Permalink
reuse once loaded PLT
Browse files Browse the repository at this point in the history
properly clean up PLT when a new one is loaded
  • Loading branch information
lukaszsamson committed May 22, 2024
1 parent 59040c4 commit 5ac0190
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -227,8 +227,7 @@ defmodule ElixirLS.LanguageServer.Dialyzer.Manifest do
])
end

defp transfer_plt(active_plt, pid) do
# FIXME: matching against opaque type
def transfer_plt(active_plt, pid) do
plt(
info: info,
types: types,
Expand Down
57 changes: 36 additions & 21 deletions apps/language_server/lib/language_server/dialyzer_incremental.ex
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ defmodule ElixirLS.LanguageServer.DialyzerIncremental do
:warning_format,
:apps_paths,
:project_dir,
:next_build
:next_build,
:plt
]

Record.defrecordp(:iplt_info, [
Expand Down Expand Up @@ -95,11 +96,9 @@ defmodule ElixirLS.LanguageServer.DialyzerIncremental do
end

@impl GenServer
def handle_call({:suggest_contracts, files}, _from, state) do
def handle_call({:suggest_contracts, files}, _from, state = %{plt: plt}) when plt != nil do
specs =
try do
# TODO maybe store plt in state?
plt = :dialyzer_iplt.from_file(elixir_incremental_plt_path())
SuccessTypings.suggest_contracts(plt, files)
catch
:throw = kind, {:dialyzer_error, message} = payload ->
Expand Down Expand Up @@ -145,10 +144,15 @@ defmodule ElixirLS.LanguageServer.DialyzerIncremental do

{opts, warning_modules_to_apps} = build_dialyzer_opts()

if state.plt do
:dialyzer_plt.delete(state.plt)
end

{:ok, pid} =
Task.start_link(fn ->
warnings = do_analyze(opts, warning_modules_to_apps)
send(parent, {:analysis_finished, warnings, build_ref})
{warnings, plt} = do_analyze(opts, warning_modules_to_apps)
Manifest.transfer_plt(plt, parent)
send(parent, {:analysis_finished, warnings, build_ref, plt})
end)

%{
Expand All @@ -157,7 +161,8 @@ defmodule ElixirLS.LanguageServer.DialyzerIncremental do
warning_format: warning_format,
apps_paths: apps_paths,
project_dir: project_dir,
analysis_pid: pid
analysis_pid: pid,
plt: nil
}
else
state
Expand All @@ -175,7 +180,7 @@ defmodule ElixirLS.LanguageServer.DialyzerIncremental do

@impl GenServer
def handle_info(
{:analysis_finished, warnings_map, build_ref},
{:analysis_finished, warnings_map, build_ref, plt},
state
) do
diagnostics =
Expand All @@ -188,14 +193,21 @@ defmodule ElixirLS.LanguageServer.DialyzerIncremental do
)

Server.dialyzer_finished(state.parent, diagnostics, build_ref)
state = %{state | analysis_pid: nil}
state = %{state | analysis_pid: nil, plt: plt}

case state.next_build do
nil -> {:noreply, state}
msg -> handle_cast(msg, %{state | next_build: nil})
end
end

def handle_info(
{:"ETS-TRANSFER", _, _, _},
state
) do
{:noreply, state}
end

defp build_dialyzer_opts() do
# assume that all required apps has been loaded during build
# notable exception is erts which is not loaded by default but we load it manually during startup
Expand Down Expand Up @@ -326,7 +338,7 @@ defmodule ElixirLS.LanguageServer.DialyzerIncremental do
# warnings returned by dialyzer public api are stripped to https://www.erlang.org/doc/man/dialyzer#type-dial_warning
# file paths are app relative but we need to know which umbrella app they come from
# we load PLT info directly and read raw warnings
{us, {_dialyzer_plt, plt_info}} =
{us, {dialyzer_plt, plt_info}} =
:timer.tc(fn ->
:dialyzer_iplt.plt_and_info_from_file(elixir_incremental_plt_path())
end)
Expand All @@ -335,17 +347,20 @@ defmodule ElixirLS.LanguageServer.DialyzerIncremental do

iplt_info(warning_map: warning_map) = plt_info
# filter by modules from project app/umbrella apps
warning_map
|> Map.take(Map.keys(warning_modules_to_apps))
|> Enum.group_by(
fn {module, _warnings} ->
Map.fetch!(warning_modules_to_apps, module)
end,
fn {module, warnings} ->
# raw warnings may be duplicated
{module, Enum.uniq(warnings)}
end
)
warnings =
warning_map
|> Map.take(Map.keys(warning_modules_to_apps))
|> Enum.group_by(
fn {module, _warnings} ->
Map.fetch!(warning_modules_to_apps, module)
end,
fn {module, warnings} ->
# raw warnings may be duplicated
{module, Enum.uniq(warnings)}
end
)

{warnings, dialyzer_plt}
catch
:throw = kind, {:dialyzer_error, message} = payload ->
{_payload, stacktrace} = Exception.blame(kind, payload, __STACKTRACE__)
Expand Down

0 comments on commit 5ac0190

Please sign in to comment.