From 68176b7f6d531205f6d151aa33c631ec88fa0c2e Mon Sep 17 00:00:00 2001 From: Daniel Kukula Date: Mon, 7 Oct 2024 19:34:02 +0200 Subject: [PATCH 1/4] implement_dbg_for_with --- lib/elixir/lib/macro.ex | 104 ++++++++++++++++++++++++++ lib/elixir/test/elixir/macro_test.exs | 63 ++++++++++++++++ 2 files changed, 167 insertions(+) diff --git a/lib/elixir/lib/macro.ex b/lib/elixir/lib/macro.ex index c3803470ff1..88f714c2009 100644 --- a/lib/elixir/lib/macro.ex +++ b/lib/elixir/lib/macro.ex @@ -2674,6 +2674,69 @@ defmodule Macro do end end + defp dbg_ast_to_debuggable({:with, meta, args} = ast, _env) do + {opts, clauses} = + List.pop_at(args, -1) + + modified_clauses = + Enum.flat_map(clauses, fn + {:<-, _meta, [left, right]} = clause -> + quote do + [ + line = Keyword.get(unquote(meta), :line), + code = unquote(Macro.escape(clause)), + value = unquote(right), + unquote(:<-)({{line, code, value}, unquote(left)}, {{line, code, value}, value}) + ] + end + + # Other expressions are omitted. + expr -> + [expr] + end) + + modified_opts = + case opts do + [do: do_code] -> + else_code = + quote do + [ + unquote(:->)( + [{unquote({:unmatched_ast, [], Elixir}), unquote({:result, [], Elixir})}], + {:__else__, unquote({:unmatched_ast, [], Elixir}), + unquote({:result, [], Elixir})} + ) + ] + end + + [do: {:__matched__, do_code}, else: else_code] + + [do: do_code, else: else_code] -> + else_code = + Enum.map(else_code, fn + {:->, meta, [[left], right]} = else_clause -> + quote do + unquote(:->)( + [{unquote({:unmatched_ast, [], Elixir}), unquote(left)}], + {:__else__, unquote({:unmatched_ast, [], Elixir}), + {Keyword.get(unquote(meta), :line), unquote(Macro.escape(else_clause))}, + unquote(right)} + ) + end + end) + + [do: {:__matched__, do_code}, else: else_code] + end + + modified = modified_clauses ++ [modified_opts] + modified_with_ast = {:with, meta, modified} + + quote do + value = unquote(modified_with_ast) + {:with, unquote(Macro.escape(ast)), [], value} + end + end + # Any other AST. defp dbg_ast_to_debuggable(ast, _env) do quote do: {:value, unquote(escape(ast)), unquote(ast)} @@ -2828,6 +2891,47 @@ defmodule Macro do {formatted, result} end + defp dbg_format_ast_to_debug({:with, _ast, [], {:__matched__, value}}, options) do + formatted = [ + dbg_maybe_underline("All with clauses matched, result:", options), + ?\n, + inspect(value, options), + ?\n + ] + + {formatted, value} + end + + defp dbg_format_ast_to_debug( + {:with, _ast, [], {:__else__, {line, clause_ast, _}, value}}, + options + ) do + formatted = [ + dbg_maybe_underline("With clause unmatched on line #{line}", options), + ?\n, + dbg_format_ast_with_value(clause_ast, value, options) + ] + + {formatted, value} + end + + defp dbg_format_ast_to_debug( + {:with, _ast, [], + {:__else__, {line, clause_ast, clause_result}, {else_line, else_ast}, value}}, + options + ) do + formatted = [ + dbg_maybe_underline("With clause unmatched on line #{line}", options), + ?\n, + dbg_format_ast_with_value(clause_ast, clause_result, options), + dbg_maybe_underline("Then else clause matched on line #{else_line}", options), + ?\n, + dbg_format_ast_with_value(else_ast, value, options) + ] + + {formatted, value} + end + defp dbg_format_ast_to_debug({:value, code_ast, value}, options) do {dbg_format_ast_with_value(code_ast, value, options), value} end diff --git a/lib/elixir/test/elixir/macro_test.exs b/lib/elixir/test/elixir/macro_test.exs index a131caf029c..3dc1f7ec7aa 100644 --- a/lib/elixir/test/elixir/macro_test.exs +++ b/lib/elixir/test/elixir/macro_test.exs @@ -652,6 +652,69 @@ defmodule MacroTest do """ end + test "with with/1 (all clauses match)" do + opts = %{width: 10, height: 15} + + {result, formatted} = + dbg_format( + with {:ok, width} <- Map.fetch(opts, :width), + double_width = width * 2, + IO.puts("just a side effect"), + {:ok, height} <- Map.fetch(opts, :height) do + {:ok, double_width * height} + end + ) + + assert result == {:ok, 300} + + assert formatted =~ "macro_test.exs" + + assert formatted =~ """ + All with clauses matched, result: + {:ok, 300} + """ + end + + test "with with/1 (no else)" do + opts = %{width: 10} + + {result, formatted} = + dbg_format( + with {:ok, width} <- Map.fetch(opts, :width), + {:ok, height} <- Map.fetch(opts, :height) do + {:ok, width * height} + end + ) + + assert result == :error + + assert formatted =~ "macro_test.exs" + assert formatted =~ "With clause unmatched on line" + assert formatted =~ "{:ok, height} <- Map.fetch(opts, :height) #=> :error" + end + + test "with with/1 (else clause)" do + opts = %{width: 10} + + {result, formatted} = + dbg_format( + with {:ok, width} <- Map.fetch(opts, :width), + {:ok, height} <- Map.fetch(opts, :height) do + width * height + else + :error -> 0 + end + ) + + assert result == 0 + + assert formatted =~ "macro_test.exs" + assert formatted =~ "With clause unmatched on line" + assert formatted =~ "{:ok, height} <- Map.fetch(opts, :height) #=> :error" + assert formatted =~ "Then else clause matched on line" + assert formatted =~ "->(:error, 0) #=> 0" + end + test "with \"syntax_colors: []\" it doesn't print any color sequences" do {_result, formatted} = dbg_format("hello") refute formatted =~ "\e[" From 0c9915e55913e65eabf2f4bcc31b7c20af9aa15e Mon Sep 17 00:00:00 2001 From: Daniel Kukula Date: Tue, 8 Oct 2024 20:07:39 +0200 Subject: [PATCH 2/4] use unique var --- lib/elixir/lib/macro.ex | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/lib/elixir/lib/macro.ex b/lib/elixir/lib/macro.ex index 88f714c2009..586e77198b0 100644 --- a/lib/elixir/lib/macro.ex +++ b/lib/elixir/lib/macro.ex @@ -2675,8 +2675,8 @@ defmodule Macro do end defp dbg_ast_to_debuggable({:with, meta, args} = ast, _env) do - {opts, clauses} = - List.pop_at(args, -1) + {opts, clauses} = List.pop_at(args, -1) + unmatched_ast_var = unique_var(:unmatched_ast_var, __MODULE__) modified_clauses = Enum.flat_map(clauses, fn @@ -2702,9 +2702,8 @@ defmodule Macro do quote do [ unquote(:->)( - [{unquote({:unmatched_ast, [], Elixir}), unquote({:result, [], Elixir})}], - {:__else__, unquote({:unmatched_ast, [], Elixir}), - unquote({:result, [], Elixir})} + [{unquote(unmatched_ast_var), unquote({:result, [], Elixir})}], + {:__else__, unquote(unmatched_ast_var), unquote({:result, [], Elixir})} ) ] end @@ -2717,8 +2716,8 @@ defmodule Macro do {:->, meta, [[left], right]} = else_clause -> quote do unquote(:->)( - [{unquote({:unmatched_ast, [], Elixir}), unquote(left)}], - {:__else__, unquote({:unmatched_ast, [], Elixir}), + [{unquote(unmatched_ast_var), unquote(left)}], + {:__else__, unquote(unmatched_ast_var), {Keyword.get(unquote(meta), :line), unquote(Macro.escape(else_clause))}, unquote(right)} ) From c8f49b8a6a46bfe44ca1239e12623a777d9221ea Mon Sep 17 00:00:00 2001 From: Daniel Kukula Date: Tue, 8 Oct 2024 20:55:47 +0200 Subject: [PATCH 3/4] result var --- lib/elixir/lib/macro.ex | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/elixir/lib/macro.ex b/lib/elixir/lib/macro.ex index 586e77198b0..534ddddd2bc 100644 --- a/lib/elixir/lib/macro.ex +++ b/lib/elixir/lib/macro.ex @@ -2676,7 +2676,8 @@ defmodule Macro do defp dbg_ast_to_debuggable({:with, meta, args} = ast, _env) do {opts, clauses} = List.pop_at(args, -1) - unmatched_ast_var = unique_var(:unmatched_ast_var, __MODULE__) + unmatched_ast_var = unique_var(:unmatched_ast, __MODULE__) + result_var = unique_var(:result, __MODULE__) modified_clauses = Enum.flat_map(clauses, fn @@ -2702,8 +2703,8 @@ defmodule Macro do quote do [ unquote(:->)( - [{unquote(unmatched_ast_var), unquote({:result, [], Elixir})}], - {:__else__, unquote(unmatched_ast_var), unquote({:result, [], Elixir})} + [{unquote(unmatched_ast_var), unquote(result_var)}], + {:__else__, unquote(unmatched_ast_var), unquote(result_var)} ) ] end From 04e4b7edf0721c734e6939d51f786723486461a1 Mon Sep 17 00:00:00 2001 From: Daniel Kukula Date: Wed, 9 Oct 2024 20:01:10 +0200 Subject: [PATCH 4/4] update debug log --- lib/elixir/lib/macro.ex | 61 ++++++++++++--------------- lib/elixir/test/elixir/macro_test.exs | 54 ++++++++++++++++++++---- 2 files changed, 73 insertions(+), 42 deletions(-) diff --git a/lib/elixir/lib/macro.ex b/lib/elixir/lib/macro.ex index 534ddddd2bc..d012a499527 100644 --- a/lib/elixir/lib/macro.ex +++ b/lib/elixir/lib/macro.ex @@ -2677,6 +2677,7 @@ defmodule Macro do defp dbg_ast_to_debuggable({:with, meta, args} = ast, _env) do {opts, clauses} = List.pop_at(args, -1) unmatched_ast_var = unique_var(:unmatched_ast, __MODULE__) + unmatched_else_var = unique_var(:unmatched_else, __MODULE__) result_var = unique_var(:result, __MODULE__) modified_clauses = @@ -2684,14 +2685,12 @@ defmodule Macro do {:<-, _meta, [left, right]} = clause -> quote do [ - line = Keyword.get(unquote(meta), :line), code = unquote(Macro.escape(clause)), value = unquote(right), - unquote(:<-)({{line, code, value}, unquote(left)}, {{line, code, value}, value}) + unquote(:<-)({{code, value}, unquote(left)}, {{code, value}, value}) ] end - # Other expressions are omitted. expr -> [expr] end) @@ -2714,18 +2713,25 @@ defmodule Macro do [do: do_code, else: else_code] -> else_code = Enum.map(else_code, fn - {:->, meta, [[left], right]} = else_clause -> + {:->, _meta, [[left], right]} -> quote do unquote(:->)( [{unquote(unmatched_ast_var), unquote(left)}], - {:__else__, unquote(unmatched_ast_var), - {Keyword.get(unquote(meta), :line), unquote(Macro.escape(else_clause))}, - unquote(right)} + {:__else__, unquote(unmatched_ast_var), unquote(right)} ) end end) - [do: {:__matched__, do_code}, else: else_code] + final_else_catch_all = + quote do + unquote(:->)( + [{unquote(unmatched_ast_var), unquote(unmatched_else_var)}], + {:__else__, unquote(unmatched_ast_var), + raise(WithClauseError, term: unquote(unmatched_else_var))} + ) + end + + [do: {:__matched__, do_code}, else: else_code ++ [final_else_catch_all]] end modified = modified_clauses ++ [modified_opts] @@ -2891,42 +2897,29 @@ defmodule Macro do {formatted, result} end - defp dbg_format_ast_to_debug({:with, _ast, [], {:__matched__, value}}, options) do + defp dbg_format_ast_to_debug({:with, ast, [], {:__matched__, value}}, options) do formatted = [ - dbg_maybe_underline("All with clauses matched, result:", options), - ?\n, - inspect(value, options), - ?\n - ] - - {formatted, value} - end - - defp dbg_format_ast_to_debug( - {:with, _ast, [], {:__else__, {line, clause_ast, _}, value}}, - options - ) do - formatted = [ - dbg_maybe_underline("With clause unmatched on line #{line}", options), - ?\n, - dbg_format_ast_with_value(clause_ast, value, options) + dbg_maybe_underline("With clause", options), + " (all clauses matched, do block evaluated):\n", + dbg_maybe_underline("With expression", options), + ":\n", + dbg_format_ast_with_value(ast, value, options) ] {formatted, value} end defp dbg_format_ast_to_debug( - {:with, _ast, [], - {:__else__, {line, clause_ast, clause_result}, {else_line, else_ast}, value}}, + {:with, ast, [], {:__else__, {clause_ast, clause_value}, value}}, options ) do formatted = [ - dbg_maybe_underline("With clause unmatched on line #{line}", options), - ?\n, - dbg_format_ast_with_value(clause_ast, clause_result, options), - dbg_maybe_underline("Then else clause matched on line #{else_line}", options), - ?\n, - dbg_format_ast_with_value(else_ast, value, options) + dbg_maybe_underline("With clause unmatched on", options), + ":\n", + dbg_format_ast_with_value(clause_ast, clause_value, options), + dbg_maybe_underline("With expression", options), + ":\n", + dbg_format_ast_with_value(ast, value, options) ] {formatted, value} diff --git a/lib/elixir/test/elixir/macro_test.exs b/lib/elixir/test/elixir/macro_test.exs index 3dc1f7ec7aa..125580ebe9d 100644 --- a/lib/elixir/test/elixir/macro_test.exs +++ b/lib/elixir/test/elixir/macro_test.exs @@ -670,8 +670,14 @@ defmodule MacroTest do assert formatted =~ "macro_test.exs" assert formatted =~ """ - All with clauses matched, result: - {:ok, 300} + With clause (all clauses matched, do block evaluated): + With expression: + with {:ok, width} <- Map.fetch(opts, :width), + double_width = width * 2, + IO.puts(\"just a side effect\"), + {:ok, height} <- Map.fetch(opts, :height) do + {:ok, double_width * height} + end #=> {:ok, 300} """ end @@ -689,8 +695,16 @@ defmodule MacroTest do assert result == :error assert formatted =~ "macro_test.exs" - assert formatted =~ "With clause unmatched on line" - assert formatted =~ "{:ok, height} <- Map.fetch(opts, :height) #=> :error" + + assert formatted =~ """ + With clause unmatched on: + {:ok, height} <- Map.fetch(opts, :height) #=> :error + With expression: + with {:ok, width} <- Map.fetch(opts, :width), + {:ok, height} <- Map.fetch(opts, :height) do + {:ok, width * height} + end #=> :error + """ end test "with with/1 (else clause)" do @@ -709,10 +723,34 @@ defmodule MacroTest do assert result == 0 assert formatted =~ "macro_test.exs" - assert formatted =~ "With clause unmatched on line" - assert formatted =~ "{:ok, height} <- Map.fetch(opts, :height) #=> :error" - assert formatted =~ "Then else clause matched on line" - assert formatted =~ "->(:error, 0) #=> 0" + + assert formatted =~ """ + With clause unmatched on: + {:ok, height} <- Map.fetch(opts, :height) #=> :error + With expression: + with {:ok, width} <- Map.fetch(opts, :width), + {:ok, height} <- Map.fetch(opts, :height) do + width * height + else + :error -> 0 + end #=> 0 + """ + end + + test "with with/1 (else clause unmatched)" do + assert_raise( + WithClauseError, + "no with clause matching: :not_found", + fn -> + dbg( + with :ok <- :not_found do + :ok + else + :error -> :error + end + ) + end + ) end test "with \"syntax_colors: []\" it doesn't print any color sequences" do