Skip to content

Commit 8c35947

Browse files
authored
Elixir 1.16 support (#1032)
* fix warnings * Mix.Dep.load_on_environment no longer exists on 1.16 * update elixir_sense api * rescue MismatchedDelimiterError added in 1.16 * remove diagnostic message normalisation as it breaks 1.16 and 1.15 message format * handle updated message format * sanitize paths passed to wildcards * fix dialyzer error * format * fix warning * fix test * fix flaky test revert group leader change * add missing alias * fix race conditions during config reload project reload wasn't under build lock and it could execute in parallel with build this made the tests flaky * format on 1.15 * use List.improper? when it makes sense * bump elixir_sense * make the sample more broken * attempt to fix errors on <= 1.14 * run formatter
1 parent 2d6c4b8 commit 8c35947

File tree

21 files changed

+320
-246
lines changed

21 files changed

+320
-246
lines changed

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.17.10
1+
0.18.0

apps/elixir_ls_debugger/lib/debugger/server.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1028,7 +1028,7 @@ defmodule ElixirLS.Debugger.Server do
10281028
_ -> -1
10291029
end
10301030

1031-
stack_frames = Enum.slice(paused_process.stack, start_frame..end_frame)
1031+
stack_frames = Enum.slice(paused_process.stack, start_frame..end_frame//1)
10321032
{state, frame_ids} = ensure_frame_ids(state, pid, stack_frames)
10331033

10341034
stack_frames_json =

apps/elixir_ls_debugger/lib/debugger/variables.ex

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,11 @@ defmodule ElixirLS.Debugger.Variables do
1212
if Keyword.keyword?(var) do
1313
:named
1414
else
15-
:indexed
16-
17-
try do
18-
# this call will raise ArgumentError for improper list, no better way to check it
19-
_ = length(var)
15+
if List.improper?(var) do
16+
# improper list has head and tail
17+
:named
18+
else
2019
:indexed
21-
rescue
22-
ArgumentError ->
23-
# improper list has head and tail
24-
:named
2520
end
2621
end
2722
end
@@ -48,7 +43,7 @@ defmodule ElixirLS.Debugger.Variables do
4843
start = start || 0
4944

5045
try do
51-
# this call will raise ArgumentError for improper list, no better way to check it
46+
# this call will raise ArgumentError for improper list
5247
max_count = length(var)
5348
count = count || max_count
5449

@@ -137,6 +132,7 @@ defmodule ElixirLS.Debugger.Variables do
137132

138133
def num_children(var) when is_list(var) do
139134
try do
135+
# this call will raise ArgumentError for improper list
140136
length(var)
141137
rescue
142138
ArgumentError ->
@@ -202,13 +198,10 @@ defmodule ElixirLS.Debugger.Variables do
202198
if Keyword.keyword?(var) and var != [] do
203199
"keyword"
204200
else
205-
try do
206-
# this call will raise ArgumentError for improper list, no better way to check it
207-
_ = length(var)
201+
if List.improper?(var) do
202+
"improper list"
203+
else
208204
"list"
209-
rescue
210-
ArgumentError ->
211-
"improper list"
212205
end
213206
end
214207
end

apps/elixir_ls_debugger/test/debugger_test.exs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,15 @@ defmodule ElixirLS.Debugger.ServerTest do
1212

1313
setup do
1414
{:ok, packet_capture} = ElixirLS.Utils.PacketCapture.start_link(self())
15+
default_group_leader = Process.info(Process.whereis(ElixirLS.Debugger.Output))[:group_leader]
1516
Process.group_leader(Process.whereis(ElixirLS.Debugger.Output), packet_capture)
1617

1718
{:ok, _} = start_supervised(BreakpointCondition)
1819
{:ok, _} = start_supervised({ModuleInfoCache, %{}})
1920
{:ok, server} = Server.start_link(name: Server)
2021

2122
on_exit(fn ->
23+
Process.group_leader(Process.whereis(ElixirLS.Debugger.Output), default_group_leader)
2224
for mod <- :int.interpreted(), do: :int.nn(mod)
2325
:int.auto_attach(false)
2426
:int.no_break()

apps/language_server/lib/language_server/build.ex

Lines changed: 150 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -4,140 +4,171 @@ defmodule ElixirLS.LanguageServer.Build do
44
require Logger
55

66
def build(parent, root_path, opts) when is_binary(root_path) do
7-
spawn_monitor(fn ->
8-
with_build_lock(fn ->
9-
{us, result} =
10-
:timer.tc(fn ->
11-
Logger.info("Starting build with MIX_ENV: #{Mix.env()} MIX_TARGET: #{Mix.target()}")
12-
13-
# read cache before cleaning up mix state in reload_project
14-
cached_deps = read_cached_deps()
15-
mixfile = SourceFile.Path.absname(MixfileHelpers.mix_exs(), root_path)
16-
17-
case reload_project(mixfile, root_path) do
18-
{:ok, mixfile_diagnostics} ->
19-
{deps_result, deps_raw_diagnostics} =
20-
with_diagnostics([log: true], fn ->
21-
try do
22-
# this call can raise
23-
current_deps = Mix.Dep.load_on_environment([])
24-
25-
purge_changed_deps(current_deps, cached_deps)
26-
27-
if Keyword.get(opts, :fetch_deps?) and current_deps != cached_deps do
28-
fetch_deps(current_deps)
7+
build_pid_reference =
8+
spawn_monitor(fn ->
9+
with_build_lock(fn ->
10+
{us, result} =
11+
:timer.tc(fn ->
12+
Logger.info("Starting build with MIX_ENV: #{Mix.env()} MIX_TARGET: #{Mix.target()}")
13+
14+
# read cache before cleaning up mix state in reload_project
15+
cached_deps = read_cached_deps()
16+
mixfile = SourceFile.Path.absname(MixfileHelpers.mix_exs(), root_path)
17+
18+
case reload_project(mixfile, root_path) do
19+
{:ok, mixfile_diagnostics} ->
20+
{deps_result, deps_raw_diagnostics} =
21+
with_diagnostics([log: true], fn ->
22+
try do
23+
# this call can raise
24+
current_deps =
25+
if Version.match?(System.version(), "< 1.16.0-dev") do
26+
Mix.Dep.load_on_environment([])
27+
else
28+
Mix.Dep.Converger.converge([])
29+
end
30+
31+
purge_changed_deps(current_deps, cached_deps)
32+
33+
if Keyword.get(opts, :fetch_deps?) and current_deps != cached_deps do
34+
fetch_deps(current_deps)
35+
end
36+
37+
state = %{
38+
get: Mix.Project.get(),
39+
# project_file: Mix.Project.project_file(),
40+
config: Mix.Project.config(),
41+
# config_files: Mix.Project.config_files(),
42+
config_mtime: Mix.Project.config_mtime(),
43+
umbrella?: Mix.Project.umbrella?(),
44+
apps_paths: Mix.Project.apps_paths(),
45+
# deps_path: Mix.Project.deps_path(),
46+
# deps_apps: Mix.Project.deps_apps(),
47+
# deps_scms: Mix.Project.deps_scms(),
48+
deps_paths: Mix.Project.deps_paths(),
49+
# build_path: Mix.Project.build_path(),
50+
manifest_path: Mix.Project.manifest_path()
51+
}
52+
53+
ElixirLS.LanguageServer.MixProject.store(state)
54+
55+
:ok
56+
catch
57+
kind, err ->
58+
{payload, stacktrace} = Exception.blame(kind, err, __STACKTRACE__)
59+
{:error, kind, payload, stacktrace}
60+
end
61+
end)
62+
63+
deps_diagnostics =
64+
deps_raw_diagnostics
65+
|> Enum.map(&Diagnostics.code_diagnostic/1)
66+
67+
case deps_result do
68+
:ok ->
69+
if Keyword.get(opts, :compile?) do
70+
{status, compile_diagnostics} =
71+
run_mix_compile(Keyword.get(opts, :force?, false))
72+
73+
compile_diagnostics =
74+
Diagnostics.normalize(compile_diagnostics, root_path, mixfile)
75+
76+
Server.build_finished(
77+
parent,
78+
{status, mixfile_diagnostics ++ deps_diagnostics ++ compile_diagnostics}
79+
)
80+
81+
:"mix_compile_#{status}"
82+
else
83+
Server.build_finished(
84+
parent,
85+
{:ok, mixfile_diagnostics ++ deps_diagnostics}
86+
)
87+
88+
:mix_compile_disabled
2989
end
3090

31-
state = %{
32-
get: Mix.Project.get(),
33-
# project_file: Mix.Project.project_file(),
34-
config: Mix.Project.config(),
35-
# config_files: Mix.Project.config_files(),
36-
config_mtime: Mix.Project.config_mtime(),
37-
umbrella?: Mix.Project.umbrella?(),
38-
apps_paths: Mix.Project.apps_paths(),
39-
# deps_path: Mix.Project.deps_path(),
40-
# deps_apps: Mix.Project.deps_apps(),
41-
# deps_scms: Mix.Project.deps_scms(),
42-
deps_paths: Mix.Project.deps_paths(),
43-
# build_path: Mix.Project.build_path(),
44-
manifest_path: Mix.Project.manifest_path()
45-
}
46-
47-
ElixirLS.LanguageServer.MixProject.store(state)
48-
49-
:ok
50-
catch
51-
kind, err ->
52-
{payload, stacktrace} = Exception.blame(kind, err, __STACKTRACE__)
53-
{:error, kind, payload, stacktrace}
54-
end
55-
end)
56-
57-
deps_diagnostics =
58-
deps_raw_diagnostics
59-
|> Enum.map(&Diagnostics.code_diagnostic/1)
60-
61-
case deps_result do
62-
:ok ->
63-
if Keyword.get(opts, :compile?) do
64-
{status, compile_diagnostics} = run_mix_compile()
65-
66-
compile_diagnostics =
67-
Diagnostics.normalize(compile_diagnostics, root_path, mixfile)
68-
91+
{:error, kind, err, stacktrace} ->
92+
# TODO get path from exception message
6993
Server.build_finished(
7094
parent,
71-
{status, mixfile_diagnostics ++ deps_diagnostics ++ compile_diagnostics}
95+
{:error,
96+
mixfile_diagnostics ++
97+
deps_diagnostics ++
98+
[
99+
Diagnostics.error_to_diagnostic(
100+
kind,
101+
err,
102+
stacktrace,
103+
mixfile,
104+
root_path
105+
)
106+
]}
72107
)
73108

74-
:"mix_compile_#{status}"
75-
else
76-
Server.build_finished(
77-
parent,
78-
{:ok, mixfile_diagnostics ++ deps_diagnostics}
79-
)
109+
:deps_error
110+
end
80111

81-
:mix_compile_disabled
82-
end
83-
84-
{:error, kind, err, stacktrace} ->
85-
# TODO get path from exception message
86-
Server.build_finished(
87-
parent,
88-
{:error,
89-
mixfile_diagnostics ++
90-
deps_diagnostics ++
91-
[
92-
Diagnostics.error_to_diagnostic(
93-
kind,
94-
err,
95-
stacktrace,
96-
mixfile,
97-
root_path
98-
)
99-
]}
100-
)
112+
{:error, mixfile_diagnostics} ->
113+
Server.build_finished(parent, {:error, mixfile_diagnostics})
114+
:mixfile_error
101115

102-
:deps_error
103-
end
116+
:no_mixfile ->
117+
Server.build_finished(parent, {:no_mixfile, []})
118+
:no_mixfile
119+
end
120+
end)
104121

105-
{:error, mixfile_diagnostics} ->
106-
Server.build_finished(parent, {:error, mixfile_diagnostics})
107-
:mixfile_error
122+
if Keyword.get(opts, :compile?) do
123+
Tracer.save()
124+
Logger.info("Compile took #{div(us, 1000)} milliseconds")
125+
else
126+
Logger.info("Mix project load took #{div(us, 1000)} milliseconds")
127+
end
108128

109-
:no_mixfile ->
110-
Server.build_finished(parent, {:no_mixfile, []})
111-
:no_mixfile
112-
end
113-
end)
129+
JsonRpc.telemetry("build", %{"elixir_ls.build_result" => result}, %{
130+
"elixir_ls.build_time" => div(us, 1000)
131+
})
132+
end)
133+
end)
114134

115-
if Keyword.get(opts, :compile?) do
116-
Tracer.save()
117-
Logger.info("Compile took #{div(us, 1000)} milliseconds")
118-
else
119-
Logger.info("Mix project load took #{div(us, 1000)} milliseconds")
120-
end
135+
spawn(fn ->
136+
Process.monitor(parent)
137+
{build_process, _ref} = build_pid_reference
138+
Process.monitor(build_process)
121139

122-
JsonRpc.telemetry("build", %{"elixir_ls.build_result" => result}, %{
123-
"elixir_ls.build_time" => div(us, 1000)
124-
})
125-
end)
140+
receive do
141+
{:DOWN, _ref, _, ^build_process, _reason} ->
142+
:ok
143+
144+
{:DOWN, _ref, _, ^parent, _reason} ->
145+
Process.exit(build_process, :kill)
146+
end
126147
end)
148+
149+
build_pid_reference
127150
end
128151

129-
def clean(clean_deps? \\ false) do
152+
def clean(root_path, clean_deps? \\ false) when is_binary(root_path) do
130153
with_build_lock(fn ->
131-
Mix.Task.clear()
132-
run_mix_clean(clean_deps?)
154+
mixfile = SourceFile.Path.absname(MixfileHelpers.mix_exs(), root_path)
155+
156+
case reload_project(mixfile, root_path) do
157+
{:ok, _} ->
158+
Mix.Task.clear()
159+
run_mix_clean(clean_deps?)
160+
161+
other ->
162+
other
163+
end
133164
end)
134165
end
135166

136167
def with_build_lock(func) do
137168
:global.trans({__MODULE__, self()}, func)
138169
end
139170

140-
def reload_project(mixfile, root_path) do
171+
defp reload_project(mixfile, root_path) do
141172
if File.exists?(mixfile) do
142173
if module = Mix.Project.get() do
143174
build_path = Mix.Project.config()[:build_path]
@@ -321,7 +352,7 @@ defmodule ElixirLS.LanguageServer.Build do
321352
end
322353
end
323354

324-
defp run_mix_compile do
355+
defp run_mix_compile(force?) do
325356
opts = [
326357
"--return-errors",
327358
"--ignore-module-conflict",
@@ -335,6 +366,13 @@ defmodule ElixirLS.LanguageServer.Build do
335366
opts ++ ["--all-warnings"]
336367
end
337368

369+
opts =
370+
if force? do
371+
opts ++ ["--force"]
372+
else
373+
opts
374+
end
375+
338376
case Mix.Task.run("compile", opts) do
339377
{status, diagnostics} when status in [:ok, :error, :noop] and is_list(diagnostics) ->
340378
{status, diagnostics}

0 commit comments

Comments
 (0)