Collapse expressions to improve trace debugging

This commit is contained in:
José Valim
2026-01-26 14:16:21 +01:00
parent 663019d048
commit 47d2b6d305
3 changed files with 146 additions and 18 deletions
+74 -4
View File
@@ -261,7 +261,8 @@ defmodule Module.Types.Helpers do
Converts the given expression to a string,
translating inlined Erlang calls back to Elixir.
We also undo some macro expressions done by the Kernel module.
We also undo some macro expressions done by the Kernel module
and collapse complex expressions.
## Options
@@ -352,9 +353,69 @@ defmodule Module.Types.Helpers do
{:&, amp_meta, [{:/, slash_meta, [{{:., dot_meta, [mod, fun]}, call_meta, []}, arity]}]}
{:case, meta, [expr, [do: clauses]]} = case ->
{:case, meta, [expr, [do: clauses]]} ->
if meta[:type_check] == :expr do
case clauses do
[
{:->, _,
[
[
{:when, _,
[
{var, _, Kernel},
{{:., _, [:erlang, :orelse]}, _,
[
{{:., _, [:erlang, :"=:="]}, _, [{var, _, Kernel}, false]},
{{:., _, [:erlang, :"=:="]}, _, [{var, _, Kernel}, nil]}
]}
]}
],
true
]},
{:->, _, [[{:_, _, Kernel}], false]}
] ->
{:!, meta, [expr]}
[
{:->, _,
[
[
{:when, _,
[
{var, _, Kernel},
{{:., _, [:erlang, :orelse]}, _,
[
{{:., _, [:erlang, :"=:="]}, _, [{var, _, Kernel}, false]},
{{:., _, [:erlang, :"=:="]}, _, [{var, _, Kernel}, nil]}
]}
]}
],
right_side
]},
{:->, _, [[{var, _, Kernel}], {var, _, Kernel}]}
] ->
{:||, meta, [expr, right_side]}
[
{:->, _,
[
[
{:when, _,
[
{var, _, Kernel},
{{:., _, [:erlang, :orelse]}, _,
[
{{:., _, [:erlang, :"=:="]}, _, [{var, _, Kernel}, false]},
{{:., _, [:erlang, :"=:="]}, _, [{var, _, Kernel}, nil]}
]}
]}
],
{var, _, Kernel}
]},
{:->, _, [[{:_, _, Kernel}], right_side]}
] ->
{:&&, meta, [expr, right_side]}
[
{:->, _,
[
@@ -382,12 +443,21 @@ defmodule Module.Types.Helpers do
{:if, meta, [expr, [do: do_block, else: else_block]]}
_ ->
case
{:case, meta, [expr, [do: {:..., [], []}]]}
end
else
case
{:case, meta, [expr, [do: {:..., [], []}]]}
end
{:try, meta, [[do: _] ++ _]} ->
{:try, meta, [[do: {:..., [], []}]]}
{:cond, meta, [[do: _]]} ->
{:cond, meta, [[do: {:..., [], []}]]}
{:receive, meta, [[do: _] ++ _]} ->
{:receive, meta, [[do: {:..., [], []}]]}
{var, meta, context} = expr when is_atom(var) and is_atom(context) ->
if is_integer(meta[:capture]) do
{:&, meta, [meta[:capture]]}
@@ -495,8 +495,7 @@ defmodule Module.Types.ExprTest do
# from: types_test.ex:LINE-9
mod =
cond do
z -> x
true -> y
...
end
"""
end
@@ -1081,8 +1080,7 @@ defmodule Module.Types.ExprTest do
# from: types_test.ex:LINE-9
foo_or_bar =
cond do
:rand.uniform() > 0.5 -> :foo
true -> :bar
...
end
where "x" was given the type:
@@ -7,15 +7,75 @@ defmodule Module.Types.HelpersTest do
use ExUnit.Case, async: true
import Module.Types.Helpers
test "expr_to_string/1" do
assert expr_to_string({1, 2}) == "{1, 2}"
assert expr_to_string(quote(do: Foo.bar(arg))) == "Foo.bar(arg)"
assert expr_to_string(quote(do: :erlang.band(a, b))) == "Bitwise.band(a, b)"
assert expr_to_string(quote(do: :erlang.orelse(a, b))) == "a or b"
assert expr_to_string(quote(do: :erlang."=:="(a, b))) == "a === b"
assert expr_to_string(quote(do: :erlang.list_to_atom(a))) == "List.to_atom(a)"
assert expr_to_string(quote(do: :maps.remove(a, b))) == "Map.delete(b, a)"
assert expr_to_string(quote(do: :erlang.element(1, a))) == "elem(a, 0)"
assert expr_to_string(quote(do: :erlang.element(:erlang.+(a, 1), b))) == "elem(b, a)"
describe "expr_to_string/1" do
test "common expressions" do
assert expr_to_string({1, 2}) == "{1, 2}"
assert expr_to_string(quote(do: Foo.bar(arg))) == "Foo.bar(arg)"
end
test "rewrites" do
assert expr_to_string(quote(do: :erlang.band(a, b))) == "Bitwise.band(a, b)"
assert expr_to_string(quote(do: :erlang.orelse(a, b))) == "a or b"
assert expr_to_string(quote(do: :erlang."=:="(a, b))) == "a === b"
assert expr_to_string(quote(do: :erlang.list_to_atom(a))) == "List.to_atom(a)"
assert expr_to_string(quote(do: :maps.remove(a, b))) == "Map.delete(b, a)"
assert expr_to_string(quote(do: :erlang.element(1, a))) == "elem(a, 0)"
assert expr_to_string(quote(do: :erlang.element(:erlang.+(a, 1), b))) == "elem(b, a)"
end
test "Kernel macros" do
case = Macro.expand(quote(do: if(condition, do: :this, else: :that)), __ENV__)
assert expr_to_string(case) == "if condition do\n :this\nelse\n :that\nend"
case = Macro.expand(quote(do: :this || :that), __ENV__)
assert expr_to_string(case) == ":this || :that"
case = Macro.expand(quote(do: :this && :that), __ENV__)
assert expr_to_string(case) == ":this && :that"
case = Macro.expand(quote(do: !expr), __ENV__)
assert expr_to_string(case) == "!expr"
end
test "case/try/receive/cond" do
assert expr_to_string(
quote do
case expr do
:this -> :this!
:that -> :that!
end
end
) == "case expr do\n ...\nend"
assert expr_to_string(
quote do
try do
:this -> :this!
:that -> :that!
rescue
_ ->
nil
end
end
) == "try do\n ...\nend"
assert expr_to_string(
quote do
cond do
:this -> :this!
:that -> :that!
end
end
) == "cond do\n ...\nend"
assert expr_to_string(
quote do
receive do
:this -> :this!
:that -> :that!
end
end
) == "receive do\n ...\nend"
end
end
end