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 support #923

Merged
merged 9 commits into from
Jun 25, 2023
Merged
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
1 change: 1 addition & 0 deletions apps/elixir_ls_debugger/lib/debugger/variables.ex
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ defmodule ElixirLS.Debugger.Variables do
children =
var
|> Map.to_list()
|> Enum.sort()
|> Enum.slice(start || 0, count || map_size(var))

for {key, value} <- children do
Expand Down
13 changes: 10 additions & 3 deletions apps/elixir_ls_utils/lib/output_device.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ defmodule ElixirLS.Utils.OutputDevice do
## Client API

def start_link(device, output_fn) do
Task.start_link(fn -> loop({device, output_fn}) end)
Task.start_link(fn ->
# Trap exit to make sure the process completes :io_request handling before exiting
Process.flag(:trap_exit, true)
loop({device, output_fn})
end)
end

def child_spec(arguments) do
Expand All @@ -22,12 +26,13 @@ defmodule ElixirLS.Utils.OutputDevice do
}
end

def get_opts, do: @opts

## Implementation

defp loop(state) do
receive do
{:EXIT, _from, reason} ->
exit(reason)

{:io_request, from, reply_as, request} ->
result = io_request(request, state, reply_as)
send(from, {:io_reply, reply_as, result})
Expand Down Expand Up @@ -82,6 +87,8 @@ defmodule ElixirLS.Utils.OutputDevice do
end

defp io_request({:setopts, new_opts}, _state, _reply_as) do
# we do not support changing opts
# only validate that the passed ones match defaults
validate_otps(new_opts, {:ok, 0})
end

Expand Down
41 changes: 36 additions & 5 deletions apps/elixir_ls_utils/lib/packet_stream.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,17 @@ defmodule ElixirLS.Utils.PacketStream do
Reads from an IO device and provides a stream of incoming packets
"""

def stream(pid \\ Process.group_leader()) do
if is_pid(pid) do
:ok = :io.setopts(pid, binary: true, encoding: :latin1)
end
def stream(pid, halt_on_error? \\ false) when is_pid(pid) do
stream_pid = self()

Task.start_link(fn ->
ref = Process.monitor(pid)

receive do
{:DOWN, ^ref, :process, _pid, reason} ->
send(stream_pid, {:exit_reason, reason})
end
end)

Stream.resource(
fn -> :ok end,
Expand All @@ -31,7 +38,31 @@ defmodule ElixirLS.Utils.PacketStream do
:ok

{:error, reason} ->
raise "Unable to read from device: #{inspect(reason)}"
"Unable to read from input device: #{inspect(reason)}"

error_message =
unless Process.alive?(pid) do
receive do
{:exit_reason, exit_reason} ->
"Input device terminated: #{inspect(exit_reason)}"
after
500 -> "Input device terminated"
end
else
"Unable to read from device: #{inspect(reason)}"
end

if halt_on_error? do
if ElixirLS.Utils.WireProtocol.io_intercepted?() do
ElixirLS.Utils.WireProtocol.undo_intercept_output()
end

IO.puts(:stderr, error_message)

System.halt(1)
else
raise error_message
end
end
)
end
Expand Down
78 changes: 69 additions & 9 deletions apps/elixir_ls_utils/lib/wire_protocol.ex
Original file line number Diff line number Diff line change
Expand Up @@ -23,33 +23,93 @@ defmodule ElixirLS.Utils.WireProtocol do
end

def io_intercepted? do
!!Process.whereis(:raw_user)
!!Process.whereis(:raw_standard_error)
end

def intercept_output(print_fn, print_err_fn) do
raw_user = Process.whereis(:user)
raw_standard_error = Process.whereis(:standard_error)

:ok = :io.setopts(raw_user, OutputDevice.get_opts())
:ok = :io.setopts(raw_user, binary: true, encoding: :latin1)

{:ok, user} = OutputDevice.start_link(raw_user, print_fn)
{:ok, standard_error} = OutputDevice.start_link(raw_user, print_err_fn)
{:ok, intercepted_user} = OutputDevice.start_link(raw_user, print_fn)
{:ok, intercepted_standard_error} = OutputDevice.start_link(raw_user, print_err_fn)

Process.unregister(:user)
Process.register(raw_user, :raw_user)
Process.register(user, :user)
Process.register(intercepted_user, :user)

Process.unregister(:standard_error)
Process.register(raw_standard_error, :raw_standard_error)
Process.register(standard_error, :standard_error)
Process.register(intercepted_standard_error, :standard_error)

for process <- :erlang.processes(), process not in [raw_user, raw_standard_error] do
Process.group_leader(process, user)
for process <- :erlang.processes(),
process not in [
raw_user,
raw_standard_error,
intercepted_user,
intercepted_standard_error
] do
Process.group_leader(process, intercepted_user)
end
end

def undo_intercept_output() do
intercepted_user = Process.whereis(:user)
intercepted_standard_error = Process.whereis(:standard_error)

Process.unregister(:user)

raw_user =
try do
raw_user = Process.whereis(:raw_user)
Process.unregister(:raw_user)
Process.register(raw_user, :user)
raw_user
rescue
ArgumentError -> nil
end

Process.unregister(:standard_error)

raw_standard_error =
try do
raw_standard_error = Process.whereis(:raw_standard_error)
Process.unregister(:raw_standard_error)
Process.register(raw_standard_error, :standard_error)
raw_user
rescue
ArgumentError -> nil
end

if raw_user do
for process <- :erlang.processes(),
process not in [
raw_user,
raw_standard_error,
intercepted_user,
intercepted_standard_error
] do
Process.group_leader(process, raw_user)
end
else
init = :erlang.processes() |> hd

for process <- :erlang.processes(),
process not in [raw_standard_error, intercepted_user, intercepted_standard_error] do
Process.group_leader(process, init)
end
end

Process.unlink(intercepted_user)
Process.unlink(intercepted_standard_error)

Process.exit(intercepted_user, :kill)
Process.exit(intercepted_standard_error, :kill)
end

def stream_packets(receive_packets_fn) do
PacketStream.stream(Process.whereis(:raw_user))
PacketStream.stream(Process.whereis(:raw_user), true)
|> Stream.each(fn packet -> receive_packets_fn.(packet) end)
|> Stream.run()
end
Expand Down
2 changes: 1 addition & 1 deletion apps/elixir_ls_utils/priv/debugger.bat
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ IF EXIST "%APPDATA%\elixir_ls\setup.bat" (
)

SET ERL_LIBS=%~dp0;%ERL_LIBS%
elixir %ELS_ELIXIR_OPTS% --erl "+sbwt none +sbwtdcpu none +sbwtdio none %ELS_ERL_OPTS%" -e "ElixirLS.Debugger.CLI.main()"
elixir %ELS_ELIXIR_OPTS% --erl "-kernel standard_io_encoding latin1 +sbwt none +sbwtdcpu none +sbwtdio none %ELS_ERL_OPTS%" -e "ElixirLS.Debugger.CLI.main()"
2 changes: 1 addition & 1 deletion apps/elixir_ls_utils/priv/language_server.bat
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ IF EXIST "%APPDATA%\elixir_ls\setup.bat" (
)

SET ERL_LIBS=%~dp0;%ERL_LIBS%
elixir %ELS_ELIXIR_OPTS% --erl "+sbwt none +sbwtdcpu none +sbwtdio none %ELS_ERL_OPTS%" -e "ElixirLS.LanguageServer.CLI.main()"
elixir %ELS_ELIXIR_OPTS% --erl "-kernel standard_io_encoding latin1 +sbwt none +sbwtdcpu none +sbwtdio none %ELS_ERL_OPTS%" -e "ElixirLS.LanguageServer.CLI.main()"
2 changes: 1 addition & 1 deletion apps/elixir_ls_utils/priv/launch.sh
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,4 @@ fi

export ERL_LIBS="$SCRIPTPATH:$ERL_LIBS"

exec elixir $ELS_ELIXIR_OPTS --erl "+sbwt none +sbwtdcpu none +sbwtdio none $ELS_ERL_OPTS" -e "$ELS_SCRIPT"
exec elixir $ELS_ELIXIR_OPTS --erl "-kernel standard_io_encoding latin1 +sbwt none +sbwtdcpu none +sbwtdio none $ELS_ERL_OPTS" -e "$ELS_SCRIPT"
29 changes: 28 additions & 1 deletion apps/language_server/lib/language_server/dialyzer/analyzer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,26 @@ defmodule ElixirLS.LanguageServer.Dialyzer.Analyzer do
solvers: :undefined
)

Record.defrecordp(
:analysis_26,
:analysis,
analysis_pid: :undefined,
type: :succ_typings,
defines: [],
doc_plt: :undefined,
files: [],
include_dirs: [],
start_from: :byte_code,
plt: :undefined,
use_contracts: true,
behaviours_chk: false,
timing: false,
timing_server: :none,
callgraph_file: [],
mod_deps_file: [],
solvers: :undefined
)

def analyze(active_plt, []) do
{active_plt, %{}, []}
end
Expand All @@ -110,12 +130,19 @@ defmodule ElixirLS.LanguageServer.Dialyzer.Analyzer do
solvers: []
)

_ ->
25 ->
analysis_25(
plt: active_plt,
files: files,
solvers: []
)

_ ->
analysis_26(
plt: active_plt,
files: files,
solvers: []
)
end

parent = self()
Expand Down
18 changes: 14 additions & 4 deletions apps/language_server/lib/language_server/dialyzer/manifest.ex
Original file line number Diff line number Diff line change
Expand Up @@ -106,14 +106,15 @@ defmodule ElixirLS.LanguageServer.Dialyzer.Manifest do
exported_types_list
} = File.read!(manifest_path) |> :erlang.binary_to_term()

# FIXME: matching against opaque type
active_plt = :dialyzer_plt.new()

plt(
info: info,
types: types,
contracts: contracts,
callbacks: callbacks,
exported_types: exported_types
) = active_plt = apply(:dialyzer_plt, :new, [])
) = active_plt

for item <- info_list, do: :ets.insert(info, item)
for item <- types_list, do: :ets.insert(types, item)
Expand All @@ -127,7 +128,11 @@ defmodule ElixirLS.LanguageServer.Dialyzer.Manifest do
end

def load_elixir_plt() do
apply(:dialyzer_plt, :from_file, [to_charlist(elixir_plt_path())])
if String.to_integer(System.otp_release()) < 26 do
:dialyzer_plt.from_file(to_charlist(elixir_plt_path()))
else
:dialyzer_cplt.from_file(to_charlist(elixir_plt_path()))
end
rescue
_ -> build_elixir_plt()
catch
Expand Down Expand Up @@ -175,7 +180,12 @@ defmodule ElixirLS.LanguageServer.Dialyzer.Manifest do
)

JsonRpc.show_message(:info, "Saved Elixir PLT to #{elixir_plt_path()}")
:dialyzer_plt.from_file(to_charlist(elixir_plt_path()))

if String.to_integer(System.otp_release()) < 26 do
:dialyzer_plt.from_file(to_charlist(elixir_plt_path()))
else
:dialyzer_cplt.from_file(to_charlist(elixir_plt_path()))
end
end

defp otp_vsn() do
Expand Down
2 changes: 1 addition & 1 deletion scripts/debugger.bat
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@ SET MIX_ENV=prod
@REM elixir is a batch script and needs to be called
ECHO "" | CALL elixir "%~dp0quiet_install.exs" > nul
IF %ERRORLEVEL% NEQ 0 EXIT 1
elixir %ELS_ELIXIR_OPTS% --erl "+sbwt none +sbwtdcpu none +sbwtdio none %ELS_ERL_OPTS%" "%~dp0launch.exs"
elixir %ELS_ELIXIR_OPTS% --erl "-kernel standard_io_encoding latin1 +sbwt none +sbwtdcpu none +sbwtdio none %ELS_ERL_OPTS%" "%~dp0launch.exs"
2 changes: 1 addition & 1 deletion scripts/language_server.bat
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@ SET MIX_ENV=prod
@REM elixir is a batch script and needs to be called
ECHO "" | CALL elixir "%~dp0quiet_install.exs" >nul
IF %ERRORLEVEL% NEQ 0 EXIT 1
elixir %ELS_ELIXIR_OPTS% --erl "+sbwt none +sbwtdcpu none +sbwtdio none %ELS_ERL_OPTS%" "%~dp0launch.exs"
elixir %ELS_ELIXIR_OPTS% --erl "-kernel standard_io_encoding latin1 +sbwt none +sbwtdcpu none +sbwtdio none %ELS_ERL_OPTS%" "%~dp0launch.exs"
2 changes: 1 addition & 1 deletion scripts/launch.sh
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,4 @@ export MIX_ENV=prod
# we need to make sure it doesn't interfere with LSP/DAP
echo "" | elixir "$SCRIPTPATH/quiet_install.exs" >/dev/null || exit 1

exec elixir $ELS_ELIXIR_OPTS --erl "+sbwt none +sbwtdcpu none +sbwtdio none $ELS_ERL_OPTS" "$SCRIPTPATH/launch.exs"
exec elixir $ELS_ELIXIR_OPTS --erl "-kernel standard_io_encoding latin1 +sbwt none +sbwtdcpu none +sbwtdio none $ELS_ERL_OPTS" "$SCRIPTPATH/launch.exs"