Skip to content

Commit 06178f5

Browse files
authored
OTP 26 Incremental dialyzer (#1081)
* fix typo * PoC incremental dialyzer * acquire build lock trigger next analysis * implement suggest contracts * apply some improvements introduced since the initial PoC * add error reporting * add env * update readme * remove not needed erts app * fix suggest contracts * fix diagnostic emit * we no longer consolidate protocols * make old dialyzer tests pass * add tests * add OTP 26 dialyzer options
1 parent a6f5d8a commit 06178f5

File tree

11 files changed

+886
-117
lines changed

11 files changed

+886
-117
lines changed

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -306,8 +306,9 @@ With Dialyzer integration enabled, ElixirLS will build an index of symbols (modu
306306
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.
307307

308308
<dl>
309-
<dt>elixirLS.autoBuild</dt><dd>Trigger ElixirLS build when code is saved.</dd>
310-
<dt>elixirLS.dialyzerEnabled</dt><dd>Run ElixirLS's rapid Dialyzer when code is saved.</dd>
309+
<dt>elixirLS.autoBuild</dt><dd>Trigger ElixirLS build when code is saved</dd>
310+
<dt>elixirLS.dialyzerEnabled</dt><dd>Run ElixirLS's rapid Dialyzer when code is saved</dd>
311+
<dt>elixirLS.incrementalDialyzer</dt><dd>Use OTP incremental dialyzer (available on OTP 26+)</dd>
311312
<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>
312313
<dt>elixirLS.dialyzerFormat</dt><dd>Formatter to use for Dialyzer warnings</dd>
313314
<dt>elixirLS.envVariables</dt><dd>Environment variables to use for compilation</dd>

apps/language_server/lib/language_server/dialyzer.ex

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ defmodule ElixirLS.LanguageServer.Dialyzer do
200200
deps_path: deps_path
201201
}
202202

203-
trigger_analyze(state)
203+
maybe_trigger_analyze(state)
204204
else
205205
state
206206
end
@@ -268,8 +268,8 @@ defmodule ElixirLS.LanguageServer.Dialyzer do
268268
}
269269
end
270270

271-
defp trigger_analyze(%{analysis_pid: nil} = state), do: do_analyze(state)
272-
defp trigger_analyze(state), do: state
271+
defp maybe_trigger_analyze(%{analysis_pid: nil} = state), do: do_analyze(state)
272+
defp maybe_trigger_analyze(state), do: state
273273

274274
defp update_stale(md5, removed_files, file_changes, timestamp, project_dir, build_path) do
275275
prev_paths = Map.keys(md5) |> MapSet.new()
@@ -607,7 +607,7 @@ defmodule ElixirLS.LanguageServer.Dialyzer do
607607
%Diagnostics{
608608
compiler_name: "ElixirLS Dialyzer",
609609
file: source_file,
610-
position: normalize_postion(position),
610+
position: normalize_position(position),
611611
message: warning_message(data, warning_format),
612612
severity: :warning,
613613
details: data
@@ -617,25 +617,25 @@ defmodule ElixirLS.LanguageServer.Dialyzer do
617617

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

624624
# 0 means unknown line
625-
defp normalize_postion(line) when line >= 0 do
625+
def normalize_position(line) when line >= 0 do
626626
line
627627
end
628628

629-
defp normalize_postion(position) do
629+
def normalize_position(position) do
630630
Logger.warning(
631631
"[ElixirLS Dialyzer] dialyzer returned warning with invalid position #{inspect(position)}"
632632
)
633633

634634
0
635635
end
636636

637-
defp warning_message({_, _, {warning_name, args}} = raw_warning, warning_format)
638-
when warning_format in ["dialyxir_long", "dialyxir_short"] do
637+
def warning_message({_, _, {warning_name, args}} = raw_warning, warning_format)
638+
when warning_format in ["dialyxir_long", "dialyxir_short"] do
639639
format_function =
640640
case warning_format do
641641
"dialyxir_long" -> :format_long
@@ -652,11 +652,11 @@ defmodule ElixirLS.LanguageServer.Dialyzer do
652652
end
653653
end
654654

655-
defp warning_message(raw_warning, "dialyzer") do
655+
def warning_message(raw_warning, "dialyzer") do
656656
dialyzer_raw_warning_message(raw_warning)
657657
end
658658

659-
defp warning_message(raw_warning, warning_format) do
659+
def warning_message(raw_warning, warning_format) do
660660
Logger.info(
661661
"[ElixirLS Dialyzer] Unrecognized dialyzerFormat setting: #{inspect(warning_format)}" <>
662662
", falling back to \"dialyzer\""

apps/language_server/lib/language_server/dialyzer/analyzer.ex

Lines changed: 50 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,33 +9,41 @@ defmodule ElixirLS.LanguageServer.Dialyzer.Analyzer do
99
# TODO remove this comment when OTP >= 25 is required
1010

1111
# default warns taken from
12-
# https://github.com/erlang/otp/blob/4ed7957623e5ccbd420a09a506bd6bc9930fe93c/lib/dialyzer/src/dialyzer_options.erl#L34
13-
# macros defined in https://github.com/erlang/otp/blob/4ed7957623e5ccbd420a09a506bd6bc9930fe93c/lib/dialyzer/src/dialyzer.hrl#L36
14-
# as of OTP 25
12+
# https://github.com/erlang/otp/blob/928d03e6da416208fce7b9a7dbbfbb4f25d26c37/lib/dialyzer/src/dialyzer_options.erl#L34
13+
# macros defined in https://github.com/erlang/otp/blob/928d03e6da416208fce7b9a7dbbfbb4f25d26c37/lib/dialyzer/src/dialyzer.hrl#L36
14+
# as of OTP 26
1515
@default_warns [
16-
:warn_behaviour,
17-
:warn_bin_construction,
18-
:warn_callgraph,
19-
:warn_contract_range,
20-
:warn_contract_syntax,
21-
:warn_contract_types,
22-
:warn_failing_call,
23-
:warn_fun_app,
24-
:warn_map_construction,
25-
:warn_matching,
26-
:warn_non_proper_list,
27-
:warn_not_called,
28-
:warn_opaque,
29-
:warn_return_no_exit,
30-
:warn_undefined_callbacks
31-
]
16+
:warn_behaviour,
17+
:warn_bin_construction,
18+
:warn_callgraph,
19+
:warn_contract_range,
20+
:warn_contract_syntax,
21+
:warn_contract_types,
22+
:warn_failing_call,
23+
:warn_fun_app,
24+
:warn_map_construction,
25+
:warn_matching,
26+
:warn_non_proper_list,
27+
:warn_not_called,
28+
:warn_opaque,
29+
:warn_return_no_exit,
30+
:warn_undefined_callbacks
31+
] ++
32+
(if String.to_integer(System.otp_release()) >= 26 do
33+
[
34+
# warn_unknown is enabled by default since OTP 26
35+
:warn_unknown
36+
]
37+
else
38+
[]
39+
end)
40+
3241
@non_default_warns [
3342
:warn_contract_not_equal,
3443
:warn_contract_subtype,
3544
:warn_contract_supertype,
3645
:warn_return_only_exit,
37-
:warn_umatched_return,
38-
:warn_unknown
46+
:warn_umatched_return
3947
] ++
4048
(if String.to_integer(System.otp_release()) >= 25 do
4149
[
@@ -45,6 +53,17 @@ defmodule ElixirLS.LanguageServer.Dialyzer.Analyzer do
4553
]
4654
else
4755
[]
56+
end) ++
57+
(if String.to_integer(System.otp_release()) >= 26 do
58+
[
59+
# OTP >= 26 options
60+
:warn_overlapping_contract
61+
]
62+
else
63+
[
64+
# warn_unknown is enabled by default since OTP 26
65+
:warn_unknown
66+
]
4867
end)
4968
@log_cache_length 10
5069

@@ -162,7 +181,16 @@ defmodule ElixirLS.LanguageServer.Dialyzer.Analyzer do
162181
end
163182

164183
def matching_tags(warn_opts) do
165-
:dialyzer_options.build_warnings(warn_opts, @default_warns)
184+
default_warns =
185+
unless :persistent_term.get(:language_server_test_mode, false) do
186+
@default_warns
187+
else
188+
# do not include warn_unknown in tests
189+
# we build small PLT and this results in lots of warnings
190+
@default_warns -- [:warn_unknown]
191+
end
192+
193+
:dialyzer_options.build_warnings(warn_opts, default_warns)
166194
end
167195

168196
defp main_loop(%__MODULE__{backend_pid: backend_pid} = state) do

apps/language_server/lib/language_server/dialyzer/manifest.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ defmodule ElixirLS.LanguageServer.Dialyzer.Manifest do
201201
end
202202
end
203203

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

apps/language_server/lib/language_server/dialyzer/success_typings.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ defmodule ElixirLS.LanguageServer.Dialyzer.SuccessTypings do
1010
|> :dialyzer_plt.all_modules()
1111
|> :sets.to_list()
1212

13+
# TODO filter by apps?
1314
for mod <- modules,
1415
file = source(mod),
1516
file in files,

apps/language_server/lib/language_server/dialyzer/supervisor.ex

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
defmodule ElixirLS.LanguageServer.Dialyzer.Supervisor do
2-
alias ElixirLS.LanguageServer.Dialyzer
2+
alias ElixirLS.LanguageServer.{Dialyzer, DialyzerIncremental}
33
use Supervisor
44

55
def start_link(parent \\ self(), root_path) do
@@ -10,7 +10,8 @@ defmodule ElixirLS.LanguageServer.Dialyzer.Supervisor do
1010
def init({parent, root_path}) do
1111
Supervisor.init(
1212
[
13-
{Dialyzer, {parent, root_path}}
13+
{Dialyzer, {parent, root_path}},
14+
{DialyzerIncremental, {parent, root_path}}
1415
],
1516
strategy: :one_for_one
1617
)

apps/language_server/lib/language_server/dialyzer/utils.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ defmodule ElixirLS.LanguageServer.Dialyzer.Utils do
5151
defp module_references(mod) do
5252
try do
5353
for form <- read_forms(mod),
54+
# TODO does import create remote call?
5455
{:call, _, {:remote, _, {:atom, _, module}, _}, _} <- form,
5556
uniq: true,
5657
do: module

0 commit comments

Comments
 (0)