diff --git a/lib/gradient/elixir_expr.ex b/lib/gradient/elixir_expr.ex index a28c30ca..d7429458 100644 --- a/lib/gradient/elixir_expr.ex +++ b/lib/gradient/elixir_expr.ex @@ -55,6 +55,10 @@ defmodule Gradient.ElixirExpr do "\'" <> List.to_string(charlist) <> "\'" end + def pp_expr({:remote, _, _, _} = remote_name) do + pp_name(remote_name) + end + def pp_expr({:cons, _, _, _} = cons) do case cons_to_int_list(cons) do {:ok, l} -> @@ -291,6 +295,9 @@ defmodule Gradient.ElixirExpr do {:throw, :not_found} -> # throw pp_expr(type) <> ", " <> pp_expr(var) <> " -> " <> pp_expr(body) + + {_variable, :not_found} -> + pp_expr(type) <> ", " <> pp_expr(var) <> " -> " <> pp_expr(body) end end @@ -482,6 +489,9 @@ defmodule Gradient.ElixirExpr do defp pp_cons({:cons, _, h, {:var, _, _} = v}), do: pp_expr(h) <> " | " <> pp_expr(v) defp pp_cons({:cons, _, h, t}), do: pp_expr(h) <> ", " <> pp_cons(t) + defp pp_name({:remote, _, {:var, _, _} = var, {:atom, _, n}}), + do: pp_expr(var) <> "." <> to_string(n) + defp pp_name({:remote, _, {:atom, _, m}, {:atom, _, n}}), do: ElixirFmt.parse_module(m) <> to_string(n) diff --git a/test/examples/Elixir.CallRemoteException.beam b/test/examples/Elixir.CallRemoteException.beam new file mode 100644 index 00000000..8f6a7d17 Binary files /dev/null and b/test/examples/Elixir.CallRemoteException.beam differ diff --git a/test/examples/call_remote_exception.ex b/test/examples/call_remote_exception.ex new file mode 100644 index 00000000..feaa1bf5 --- /dev/null +++ b/test/examples/call_remote_exception.ex @@ -0,0 +1,22 @@ +defmodule CallRemoteException do + def call(conn, opts) do + try do + :ok + rescue + e in Plug.Conn.WrapperError -> + exception = Exception.normalize(:error, e.reason, e.stack) + _ = Sentry.capture_exception(exception, stacktrace: e.stack, event_source: :plug) + Plug.Conn.WrapperError.reraise(e) + + e -> + _ = Sentry.capture_exception(e, stacktrace: __STACKTRACE__, event_source: :plug) + :erlang.raise(:error, e, __STACKTRACE__) + catch + kind, reason -> + message = "Uncaught #{kind} - #{inspect(reason)}" + stack = __STACKTRACE__ + _ = Sentry.capture_message(message, stacktrace: stack, event_source: :plug) + :erlang.raise(kind, reason, stack) + end + end +end diff --git a/test/gradient/elixir_expr_test.exs b/test/gradient/elixir_expr_test.exs index 619acf7b..4fb27c27 100644 --- a/test/gradient/elixir_expr_test.exs +++ b/test/gradient/elixir_expr_test.exs @@ -326,4 +326,69 @@ defmodule Gradient.ElixirExprTest do actual end end + + test "pp and format complex try expression" do + {_tokens, ast} = + Gradient.TestHelpers.load("Elixir.CallRemoteException.beam", "call_remote_exception.ex") + + {:function, _, :call, 2, [{:clause, _ann, _args, _guards, [try_expr]}]} = + Enum.reverse(ast) |> List.first() + + res = ElixirExpr.pp_expr_format(try_expr) + + # FIXME `raise {:badkey, :stack, _gen}` is not correct + + expected = ~s""" + try do + :ok + catch + :error, %Plug.Conn.WrapperError{} = e -> + exception = + Exception.normalize( + :error, + case e do + %{reason: _gen} -> _gen + _gen when :erlang.is_map(_gen) -> raise {:badkey, :reason, _gen} + _gen -> _gen.reason() + end, + case e do + %{stack: _gen} -> _gen + _gen when :erlang.is_map(_gen) -> raise {:badkey, :stack, _gen} + _gen -> _gen.stack() + end + ) + + _ = + Sentry.capture_exception(exception, [ + {:stacktrace, + case e do + %{stack: _gen} -> _gen + _gen when :erlang.is_map(_gen) -> raise {:badkey, :stack, _gen} + _gen -> _gen.stack() + end}, + {:event_source, :plug} + ]) + + Plug.Conn.WrapperError.reraise(e) + + :error, e -> + _ = Sentry.capture_exception(e, [{:stacktrace, __STACKTRACE__}, {:event_source, :plug}]) + :erlang.raise(:error, e, __STACKTRACE__) + + kind, reason -> + message = + <<"Uncaught ", + case kind do + _gen when :erlang.is_binary(_gen) -> _gen + _gen -> String.Chars.to_string(_gen) + end::binary, " - ", Kernel.inspect(reason)::binary>> + + stack = __STACKTRACE__ + _ = Sentry.capture_message(message, [{:stacktrace, stack}, {:event_source, :plug}]) + :erlang.raise(kind, reason, stack) + end + """ + + assert expected == :erlang.iolist_to_binary(res) <> "\n" + end end diff --git a/test/gradient/elixir_fmt_test.exs b/test/gradient/elixir_fmt_test.exs index 78eafb14..ebd374fb 100644 --- a/test/gradient/elixir_fmt_test.exs +++ b/test/gradient/elixir_fmt_test.exs @@ -239,6 +239,53 @@ defmodule Gradient.ElixirFmtTest do end end + @tag :skip + test "format call_intersect error" do + error = + {:type_error, :call_intersect, 7, + [ + {:type, 0, :bounded_fun, + [ + {:type, 0, :fun, + [ + {:type, 0, :product, + [ + {:atom, 0, :error}, + {:type, 0, :any, []}, + {:user_type, [file: 'Elixir.Exception.erl', location: 0], :stacktrace, []} + ]}, + {:user_type, [file: 'Elixir.Exception.erl', location: 0], :t, []} + ]}, + [] + ]}, + {:type, 0, :bounded_fun, + [ + {:type, 0, :fun, + [ + {:type, 0, :product, + [ + {:user_type, [file: 'Elixir.Exception.erl', location: 0], :non_error_kind, []}, + {:var, 0, :payload}, + {:user_type, [file: 'Elixir.Exception.erl', location: 0], :stacktrace, []} + ]}, + {:var, 0, :payload} + ]}, + [] + ]} + ], {:remote, 7, {:atom, 7, Exception}, {:atom, 7, :normalize}}} + + res = ElixirFmt.format_error(error, []) + + expected = ~s""" + The type of the function Exception.normalize, called on line 7 doesn't match the surrounding calling context. + It has the following type + \e[35m(:error, any(), stacktrace() -> t())\e[0m + \e[35m(non_error_kind(), payload, stacktrace() -> payload)\e[0m\n + """ + + assert expected == :erlang.iolist_to_binary(res) + end + @tag :skip test "format_expr_type_error/4" do opts = [forms: basic_erlang_forms()] diff --git a/test/support/expr_data.ex b/test/support/expr_data.ex index 7be58f73..c263e96c 100644 --- a/test/support/expr_data.ex +++ b/test/support/expr_data.ex @@ -28,7 +28,9 @@ defmodule Gradient.ExprData do {"char", {:char, 0, ?c}, "?c"}, {"float", {:float, 0, 12.0}, "12.0"}, {"integer", {:integer, 0, 1}, "1"}, - {"erlang string", {:string, 0, 'ala ma kota'}, ~s('ala ma kota')} + {"erlang string", {:string, 0, 'ala ma kota'}, ~s('ala ma kota')}, + {"remote name", {:remote, 7, {:atom, 7, Exception}, {:atom, 7, :normalize}}, + "Exception.normalize"} ] end