Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OTP 26 Incremental dialyzer #1081

Merged
merged 15 commits into from
Mar 15, 2024
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -306,8 +306,9 @@ With Dialyzer integration enabled, ElixirLS will build an index of symbols (modu
Below is a list of configuration options supported by the ElixirLS language server. Please refer to your editor's documentation to determine how to configure language servers.

<dl>
<dt>elixirLS.autoBuild</dt><dd>Trigger ElixirLS build when code is saved.</dd>
<dt>elixirLS.dialyzerEnabled</dt><dd>Run ElixirLS's rapid Dialyzer when code is saved.</dd>
<dt>elixirLS.autoBuild</dt><dd>Trigger ElixirLS build when code is saved</dd>
<dt>elixirLS.dialyzerEnabled</dt><dd>Run ElixirLS's rapid Dialyzer when code is saved</dd>
<dt>elixirLS.incrementalDialyzer</dt><dd>Use OTP incremental dialyzer (available on OTP 26+)</dd>
<dt>elixirLS.dialyzerWarnOpts</dt><dd>Dialyzer options to enable or disable warnings - See Dialyzer's documentation for options. Note that the <code>race_conditions</code> option is unsupported.</dd>
<dt>elixirLS.dialyzerFormat</dt><dd>Formatter to use for Dialyzer warnings</dd>
<dt>elixirLS.envVariables</dt><dd>Environment variables to use for compilation</dd>
Expand Down
22 changes: 11 additions & 11 deletions apps/language_server/lib/language_server/dialyzer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ defmodule ElixirLS.LanguageServer.Dialyzer do
deps_path: deps_path
}

trigger_analyze(state)
maybe_trigger_analyze(state)
else
state
end
Expand Down Expand Up @@ -268,8 +268,8 @@ defmodule ElixirLS.LanguageServer.Dialyzer do
}
end

defp trigger_analyze(%{analysis_pid: nil} = state), do: do_analyze(state)
defp trigger_analyze(state), do: state
defp maybe_trigger_analyze(%{analysis_pid: nil} = state), do: do_analyze(state)
defp maybe_trigger_analyze(state), do: state

defp update_stale(md5, removed_files, file_changes, timestamp, project_dir, build_path) do
prev_paths = Map.keys(md5) |> MapSet.new()
Expand Down Expand Up @@ -607,7 +607,7 @@ defmodule ElixirLS.LanguageServer.Dialyzer do
%Diagnostics{
compiler_name: "ElixirLS Dialyzer",
file: source_file,
position: normalize_postion(position),
position: normalize_position(position),
message: warning_message(data, warning_format),
severity: :warning,
details: data
Expand All @@ -617,25 +617,25 @@ defmodule ElixirLS.LanguageServer.Dialyzer do

# up until OTP 23 position was line :: non_negative_integer
# starting from OTP 24 it is erl_anno:location() :: line | {line, column}
defp normalize_postion({line, column}) when line > 0 do
def normalize_position({line, column}) when line > 0 do
{line, column}
end

# 0 means unknown line
defp normalize_postion(line) when line >= 0 do
def normalize_position(line) when line >= 0 do
line
end

defp normalize_postion(position) do
def normalize_position(position) do
Logger.warning(
"[ElixirLS Dialyzer] dialyzer returned warning with invalid position #{inspect(position)}"
)

0
end

defp warning_message({_, _, {warning_name, args}} = raw_warning, warning_format)
when warning_format in ["dialyxir_long", "dialyxir_short"] do
def warning_message({_, _, {warning_name, args}} = raw_warning, warning_format)
when warning_format in ["dialyxir_long", "dialyxir_short"] do
format_function =
case warning_format do
"dialyxir_long" -> :format_long
Expand All @@ -652,11 +652,11 @@ defmodule ElixirLS.LanguageServer.Dialyzer do
end
end

defp warning_message(raw_warning, "dialyzer") do
def warning_message(raw_warning, "dialyzer") do
dialyzer_raw_warning_message(raw_warning)
end

defp warning_message(raw_warning, warning_format) do
def warning_message(raw_warning, warning_format) do
Logger.info(
"[ElixirLS Dialyzer] Unrecognized dialyzerFormat setting: #{inspect(warning_format)}" <>
", falling back to \"dialyzer\""
Expand Down
72 changes: 50 additions & 22 deletions apps/language_server/lib/language_server/dialyzer/analyzer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,33 +9,41 @@ defmodule ElixirLS.LanguageServer.Dialyzer.Analyzer do
# TODO remove this comment when OTP >= 25 is required

# default warns taken from
# https://github.com/erlang/otp/blob/4ed7957623e5ccbd420a09a506bd6bc9930fe93c/lib/dialyzer/src/dialyzer_options.erl#L34
# macros defined in https://github.com/erlang/otp/blob/4ed7957623e5ccbd420a09a506bd6bc9930fe93c/lib/dialyzer/src/dialyzer.hrl#L36
# as of OTP 25
# https://github.com/erlang/otp/blob/928d03e6da416208fce7b9a7dbbfbb4f25d26c37/lib/dialyzer/src/dialyzer_options.erl#L34
# macros defined in https://github.com/erlang/otp/blob/928d03e6da416208fce7b9a7dbbfbb4f25d26c37/lib/dialyzer/src/dialyzer.hrl#L36
# as of OTP 26
@default_warns [
:warn_behaviour,
:warn_bin_construction,
:warn_callgraph,
:warn_contract_range,
:warn_contract_syntax,
:warn_contract_types,
:warn_failing_call,
:warn_fun_app,
:warn_map_construction,
:warn_matching,
:warn_non_proper_list,
:warn_not_called,
:warn_opaque,
:warn_return_no_exit,
:warn_undefined_callbacks
]
:warn_behaviour,
:warn_bin_construction,
:warn_callgraph,
:warn_contract_range,
:warn_contract_syntax,
:warn_contract_types,
:warn_failing_call,
:warn_fun_app,
:warn_map_construction,
:warn_matching,
:warn_non_proper_list,
:warn_not_called,
:warn_opaque,
:warn_return_no_exit,
:warn_undefined_callbacks
] ++
(if String.to_integer(System.otp_release()) >= 26 do
[
# warn_unknown is enabled by default since OTP 26
:warn_unknown
]
else
[]
end)

@non_default_warns [
:warn_contract_not_equal,
:warn_contract_subtype,
:warn_contract_supertype,
:warn_return_only_exit,
:warn_umatched_return,
:warn_unknown
:warn_umatched_return
] ++
(if String.to_integer(System.otp_release()) >= 25 do
[
Expand All @@ -45,6 +53,17 @@ defmodule ElixirLS.LanguageServer.Dialyzer.Analyzer do
]
else
[]
end) ++
(if String.to_integer(System.otp_release()) >= 26 do
[
# OTP >= 26 options
:warn_overlapping_contract
]
else
[
# warn_unknown is enabled by default since OTP 26
:warn_unknown
]
end)
@log_cache_length 10

Expand Down Expand Up @@ -162,7 +181,16 @@ defmodule ElixirLS.LanguageServer.Dialyzer.Analyzer do
end

def matching_tags(warn_opts) do
:dialyzer_options.build_warnings(warn_opts, @default_warns)
default_warns =
unless :persistent_term.get(:language_server_test_mode, false) do
@default_warns
else
# do not include warn_unknown in tests
# we build small PLT and this results in lots of warnings
@default_warns -- [:warn_unknown]
end

:dialyzer_options.build_warnings(warn_opts, default_warns)
end

defp main_loop(%__MODULE__{backend_pid: backend_pid} = state) do
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ defmodule ElixirLS.LanguageServer.Dialyzer.Manifest do
end
end

defp otp_vsn() do
def otp_vsn() do
major = :erlang.system_info(:otp_release) |> List.to_string()
vsn_file = Path.join([:code.root_dir(), "releases", major, "OTP_VERSION"])

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ defmodule ElixirLS.LanguageServer.Dialyzer.SuccessTypings do
|> :dialyzer_plt.all_modules()
|> :sets.to_list()

# TODO filter by apps?
for mod <- modules,
file = source(mod),
file in files,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
defmodule ElixirLS.LanguageServer.Dialyzer.Supervisor do
alias ElixirLS.LanguageServer.Dialyzer
alias ElixirLS.LanguageServer.{Dialyzer, DialyzerIncremental}
use Supervisor

def start_link(parent \\ self(), root_path) do
Expand All @@ -10,7 +10,8 @@ defmodule ElixirLS.LanguageServer.Dialyzer.Supervisor do
def init({parent, root_path}) do
Supervisor.init(
[
{Dialyzer, {parent, root_path}}
{Dialyzer, {parent, root_path}},
{DialyzerIncremental, {parent, root_path}}
],
strategy: :one_for_one
)
Expand Down
1 change: 1 addition & 0 deletions apps/language_server/lib/language_server/dialyzer/utils.ex
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ defmodule ElixirLS.LanguageServer.Dialyzer.Utils do
defp module_references(mod) do
try do
for form <- read_forms(mod),
# TODO does import create remote call?
{:call, _, {:remote, _, {:atom, _, module}, _}, _} <- form,
uniq: true,
do: module
Expand Down
Loading
Loading