Skip to content
Closed
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
97 changes: 97 additions & 0 deletions lib/elixir/lib/macro.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2674,6 +2674,75 @@ defmodule Macro do
end
end

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 =
Enum.flat_map(clauses, fn
{:<-, _meta, [left, right]} = clause ->
quote do
[
code = unquote(Macro.escape(clause)),
value = unquote(right),
unquote(:<-)({{code, value}, unquote(left)}, {{code, value}, value})
]
end

expr ->
[expr]
end)

modified_opts =
case opts do
[do: do_code] ->
else_code =
quote do
[
unquote(:->)(
[{unquote(unmatched_ast_var), unquote(result_var)}],
{:__else__, unquote(unmatched_ast_var), unquote(result_var)}
)
]
end

[do: {:__matched__, do_code}, else: else_code]

[do: do_code, else: else_code] ->
else_code =
Enum.map(else_code, fn
{:->, _meta, [[left], right]} ->
quote do
unquote(:->)(
[{unquote(unmatched_ast_var), unquote(left)}],
{:__else__, unquote(unmatched_ast_var), unquote(right)}
)
end
end)

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]
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)}
Expand Down Expand Up @@ -2828,6 +2897,34 @@ defmodule Macro do
{formatted, result}
end

defp dbg_format_ast_to_debug({:with, ast, [], {:__matched__, value}}, options) do
formatted = [
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__, {clause_ast, clause_value}, value}},
options
) do
formatted = [
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}
end

defp dbg_format_ast_to_debug({:value, code_ast, value}, options) do
{dbg_format_ast_with_value(code_ast, value, options), value}
end
Expand Down
101 changes: 101 additions & 0 deletions lib/elixir/test/elixir/macro_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -652,6 +652,107 @@ 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 =~ """
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

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:
{: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
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:
{: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
{_result, formatted} = dbg_format("hello")
refute formatted =~ "\e["
Expand Down