Skip to content

Commit ed75e60

Browse files
authored
Add more info for :with expressions in dbg/1 (#13891)
1 parent 08c72e3 commit ed75e60

File tree

2 files changed

+253
-0
lines changed

2 files changed

+253
-0
lines changed

lib/elixir/lib/macro.ex

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2674,6 +2674,84 @@ defmodule Macro do
26742674
end
26752675
end
26762676

2677+
defp dbg_ast_to_debuggable({:with, meta, args} = ast, _env) do
2678+
{opts, clauses} = List.pop_at(args, -1)
2679+
2680+
acc_var = unique_var(:acc, __MODULE__)
2681+
2682+
modified_clauses =
2683+
Enum.flat_map(clauses, fn
2684+
# We only detail assignments and pattern-matching clauses that
2685+
# can be helpful to understand how the result is constructed.
2686+
{:<-, _meta, [left, right]} ->
2687+
modified_left =
2688+
case left do
2689+
{:when, meta, [pattern, guard]} -> {:when, meta, [{pattern, quote(do: _)}, guard]}
2690+
pattern -> {pattern, quote(do: _)}
2691+
end
2692+
2693+
quote do
2694+
[
2695+
value = unquote(right),
2696+
unquote(acc_var) = [{unquote(escape(right)), value} | unquote(acc_var)],
2697+
unquote(modified_left) <- {value, unquote(acc_var)}
2698+
]
2699+
end
2700+
2701+
{:=, _meta, [left, right]} ->
2702+
quote do
2703+
[
2704+
value = unquote(right),
2705+
unquote(acc_var) = [{unquote(escape(right)), value} | unquote(acc_var)],
2706+
unquote(left) = value
2707+
]
2708+
end
2709+
2710+
# Other expressions like side effects are omitted.
2711+
expr ->
2712+
[expr]
2713+
end)
2714+
2715+
modified_opts =
2716+
Enum.map(opts, fn
2717+
{:do, do_block} ->
2718+
{:do, {do_block, acc_var}}
2719+
2720+
{:else, else_block} ->
2721+
clauses =
2722+
Enum.map(else_block, fn
2723+
{:->, meta, [[{:when, meta2, [pattern, guard]}], right]} ->
2724+
{:->, meta, [[{:when, meta2, [{pattern, acc_var}, guard]}], {right, acc_var}]}
2725+
2726+
{:->, meta, [[left], right]} ->
2727+
{:->, meta, [[{left, acc_var}], {right, acc_var}]}
2728+
2729+
invalid ->
2730+
invalid
2731+
end)
2732+
2733+
error_clause =
2734+
quote do
2735+
{other, _acc} -> raise WithClauseError, term: other
2736+
end
2737+
2738+
{:else, clauses ++ error_clause}
2739+
2740+
invalid ->
2741+
invalid
2742+
end)
2743+
2744+
modified_with_ast = {:with, meta, modified_clauses ++ [modified_opts]}
2745+
2746+
quote do
2747+
unquote(acc_var) = []
2748+
2749+
{value, acc} = unquote(modified_with_ast)
2750+
2751+
{:with, unquote(escape(ast)), Enum.reverse(acc), value}
2752+
end
2753+
end
2754+
26772755
# Any other AST.
26782756
defp dbg_ast_to_debuggable(ast, _env) do
26792757
quote do: {:value, unquote(escape(ast)), unquote(ast)}
@@ -2828,6 +2906,25 @@ defmodule Macro do
28282906
{formatted, result}
28292907
end
28302908

2909+
defp dbg_format_ast_to_debug({:with, ast, clauses, result}, options) do
2910+
formatted_clauses =
2911+
Enum.map(clauses, fn {clause_ast, clause_result} ->
2912+
dbg_format_ast_with_value(clause_ast, clause_result, options)
2913+
end)
2914+
2915+
formatted = [
2916+
dbg_maybe_underline("With clauses", options),
2917+
":\n",
2918+
formatted_clauses,
2919+
?\n,
2920+
dbg_maybe_underline("With expression", options),
2921+
":\n",
2922+
dbg_format_ast_with_value(ast, result, options)
2923+
]
2924+
2925+
{formatted, result}
2926+
end
2927+
28312928
defp dbg_format_ast_to_debug({:value, code_ast, value}, options) do
28322929
{dbg_format_ast_with_value(code_ast, value, options), value}
28332930
end

lib/elixir/test/elixir/macro_test.exs

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -652,6 +652,162 @@ defmodule MacroTest do
652652
"""
653653
end
654654

655+
test "with with/1 (all clauses match)" do
656+
opts = %{width: 10, height: 15}
657+
658+
{result, formatted} =
659+
dbg_format(
660+
with {:ok, width} <- Map.fetch(opts, :width),
661+
double_width = width * 2,
662+
IO.puts("just a side effect"),
663+
{:ok, height} <- Map.fetch(opts, :height) do
664+
{:ok, double_width * height}
665+
end
666+
)
667+
668+
assert result == {:ok, 300}
669+
670+
assert formatted =~ "macro_test.exs"
671+
672+
assert formatted =~ """
673+
With clauses:
674+
Map.fetch(opts, :width) #=> {:ok, 10}
675+
width * 2 #=> 20
676+
Map.fetch(opts, :height) #=> {:ok, 15}
677+
678+
With expression:
679+
with {:ok, width} <- Map.fetch(opts, :width),
680+
double_width = width * 2,
681+
IO.puts("just a side effect"),
682+
{:ok, height} <- Map.fetch(opts, :height) do
683+
{:ok, double_width * height}
684+
end #=> {:ok, 300}
685+
"""
686+
end
687+
688+
test "with with/1 (no else)" do
689+
opts = %{width: 10}
690+
691+
{result, formatted} =
692+
dbg_format(
693+
with {:ok, width} <- Map.fetch(opts, :width),
694+
{:ok, height} <- Map.fetch(opts, :height) do
695+
{:ok, width * height}
696+
end
697+
)
698+
699+
assert result == :error
700+
701+
assert formatted =~ "macro_test.exs"
702+
703+
assert formatted =~ """
704+
With clauses:
705+
Map.fetch(opts, :width) #=> {:ok, 10}
706+
Map.fetch(opts, :height) #=> :error
707+
708+
With expression:
709+
with {:ok, width} <- Map.fetch(opts, :width),
710+
{:ok, height} <- Map.fetch(opts, :height) do
711+
{:ok, width * height}
712+
end #=> :error
713+
"""
714+
end
715+
716+
test "with with/1 (else clause)" do
717+
opts = %{width: 10}
718+
719+
{result, formatted} =
720+
dbg_format(
721+
with {:ok, width} <- Map.fetch(opts, :width),
722+
{:ok, height} <- Map.fetch(opts, :height) do
723+
width * height
724+
else
725+
:error -> 0
726+
end
727+
)
728+
729+
assert result == 0
730+
assert formatted =~ "macro_test.exs"
731+
732+
assert formatted =~ """
733+
With clauses:
734+
Map.fetch(opts, :width) #=> {:ok, 10}
735+
Map.fetch(opts, :height) #=> :error
736+
737+
With expression:
738+
with {:ok, width} <- Map.fetch(opts, :width),
739+
{:ok, height} <- Map.fetch(opts, :height) do
740+
width * height
741+
else
742+
:error -> 0
743+
end #=> 0
744+
"""
745+
end
746+
747+
test "with with/1 (guard)" do
748+
opts = %{width: 10, height: 0.0}
749+
750+
{result, formatted} =
751+
dbg_format(
752+
with {:ok, width} when is_integer(width) <- Map.fetch(opts, :width),
753+
{:ok, height} when is_integer(height) <- Map.fetch(opts, :height) do
754+
width * height
755+
else
756+
_ -> nil
757+
end
758+
)
759+
760+
assert result == nil
761+
assert formatted =~ "macro_test.exs"
762+
763+
assert formatted =~ """
764+
With clauses:
765+
Map.fetch(opts, :width) #=> {:ok, 10}
766+
Map.fetch(opts, :height) #=> {:ok, 0.0}
767+
768+
With expression:
769+
with {:ok, width} when is_integer(width) <- Map.fetch(opts, :width),
770+
{:ok, height} when is_integer(height) <- Map.fetch(opts, :height) do
771+
width * height
772+
else
773+
_ -> nil
774+
end #=> nil
775+
"""
776+
end
777+
778+
test "with with/1 (guard in else)" do
779+
opts = %{}
780+
781+
{result, _formatted} =
782+
dbg_format(
783+
with {:ok, width} <- Map.fetch(opts, :width) do
784+
width
785+
else
786+
other when is_integer(other) -> :int
787+
other when is_atom(other) -> :atom
788+
end
789+
)
790+
791+
assert result == :atom
792+
end
793+
794+
test "with with/1 respects the WithClauseError" do
795+
value = Enum.random([:unexpected])
796+
797+
error =
798+
assert_raise WithClauseError, fn ->
799+
dbg(
800+
with :ok <- value do
801+
true
802+
else
803+
:error -> false
804+
end
805+
)
806+
end
807+
808+
assert error.term == :unexpected
809+
end
810+
655811
test "with \"syntax_colors: []\" it doesn't print any color sequences" do
656812
{_result, formatted} = dbg_format("hello")
657813
refute formatted =~ "\e["

0 commit comments

Comments
 (0)