Skip to content

Commit 8e6cc2c

Browse files
author
José Valim
committed
Make sure Macro.to_string/2 emits valid quoted expressions
Signed-off-by: José Valim <[email protected]>
1 parent db5c155 commit 8e6cc2c

File tree

2 files changed

+58
-14
lines changed

2 files changed

+58
-14
lines changed

lib/elixir/lib/macro.ex

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -719,7 +719,13 @@ defmodule Macro do
719719
end
720720

721721
# All other structures
722-
def to_string(other, fun), do: fun.(other, inspect(other, []))
722+
def to_string(other, fun) do
723+
fun.(other, inspect_no_limit(other))
724+
end
725+
726+
defp inspect_no_limit(value) do
727+
Kernel.inspect(value, limit: :infinity, printable_limit: :infinity)
728+
end
723729

724730
defp bitpart_to_string({:::, _, [left, right]} = ast, fun) do
725731
result =
@@ -773,15 +779,15 @@ defmodule Macro do
773779
"\#{" <> to_string(arg, fun) <> "}"
774780

775781
binary when is_binary(binary) ->
776-
binary = inspect(binary, [])
782+
binary = inspect_no_limit(binary)
777783
:binary.part(binary, 1, byte_size(binary) - 2)
778784
end)
779785

780786
<<?", parts::binary, ?">>
781787
end
782788

783789
defp module_to_string(atom, _fun) when is_atom(atom) do
784-
inspect(atom, [])
790+
inspect_no_limit(atom)
785791
end
786792

787793
defp module_to_string({:&, _, [val]} = expr, fun) when not is_integer(val) do
@@ -839,11 +845,25 @@ defmodule Macro do
839845
:error
840846
end
841847

842-
defp sigil_call({sigil, _, [{:<<>>, _, _} = bin, args]} = ast, fun)
848+
defp sigil_call({sigil, _, [{:<<>>, _, _} = parts, args]} = ast, fun)
843849
when is_atom(sigil) and is_list(args) do
844850
case Atom.to_string(sigil) do
845-
<<"sigil_", name>> ->
846-
{:ok, fun.(ast, "~" <> <<name>> <> interpolate(bin, fun) <> sigil_args(args, fun))}
851+
<<"sigil_", name>> when name >= ?A and name <= ?Z ->
852+
{:<<>>, _, [binary]} = parts
853+
854+
formatted =
855+
if :binary.last(binary) == ?\n do
856+
binary = String.replace(binary, ~s["""], ~s["\\""])
857+
<<?~, name, ~s["""\n], binary::binary, ~s["""], sigil_args(args, fun)::binary>>
858+
else
859+
{left, right} = select_sigil_container(binary)
860+
<<?~, name, left, binary::binary, right, sigil_args(args, fun)::binary>>
861+
end
862+
863+
{:ok, fun.(ast, formatted)}
864+
865+
<<"sigil_", name>> when name >= ?a and name <= ?z ->
866+
{:ok, fun.(ast, "~" <> <<name>> <> interpolate(parts, fun) <> sigil_args(args, fun))}
847867

848868
_ ->
849869
:error
@@ -854,6 +874,18 @@ defmodule Macro do
854874
:error
855875
end
856876

877+
defp select_sigil_container(binary) do
878+
cond do
879+
:binary.match(binary, ["\""]) == :nomatch -> {?", ?"}
880+
:binary.match(binary, ["\'"]) == :nomatch -> {?', ?'}
881+
:binary.match(binary, ["(", ")"]) == :nomatch -> {?(, ?)}
882+
:binary.match(binary, ["[", "]"]) == :nomatch -> {?[, ?]}
883+
:binary.match(binary, ["{", "}"]) == :nomatch -> {?{, ?}}
884+
:binary.match(binary, ["<", ">"]) == :nomatch -> {?<, ?>}
885+
true -> {?/, ?/}
886+
end
887+
end
888+
857889
defp sigil_args([], _fun), do: ""
858890
defp sigil_args(args, fun), do: fun.(args, List.to_string(args))
859891

@@ -952,8 +984,9 @@ defmodule Macro do
952984
end
953985

954986
defp map_list_to_string(list, fun) do
955-
Enum.map_join(list, ", ", fn {key, value} ->
956-
to_string(key, fun) <> " => " <> to_string(value, fun)
987+
Enum.map_join(list, ", ", fn
988+
{key, value} -> to_string(key, fun) <> " => " <> to_string(value, fun)
989+
other -> to_string(other, fun)
957990
end)
958991
end
959992

lib/elixir/test/elixir/macro_test.exs

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -318,16 +318,26 @@ defmodule MacroTest do
318318
end
319319

320320
test "sigil call" do
321-
assert Macro.to_string(quote(do: ~r"123")) == ~s/~r"123"/
322-
assert Macro.to_string(quote(do: ~r"123"u)) == ~s/~r"123"u/
323-
assert Macro.to_string(quote(do: ~r"\n123")) == ~s/~r"\\\\n123"/
321+
assert Macro.to_string(quote(do: ~r"123")) == ~S/~r"123"/
322+
assert Macro.to_string(quote(do: ~r"123"u)) == ~S/~r"123"u/
323+
assert Macro.to_string(quote(do: ~r"\n123")) == ~S/~r"\\n123"/
324324

325325
assert Macro.to_string(quote(do: ~r"1#{two}3")) == ~S/~r"1#{two}3"/
326326
assert Macro.to_string(quote(do: ~r"1#{two}3"u)) == ~S/~r"1#{two}3"u/
327327

328-
assert Macro.to_string(quote(do: ~R"123")) == ~s/~R"123"/
329-
assert Macro.to_string(quote(do: ~R"123"u)) == ~s/~R"123"u/
330-
assert Macro.to_string(quote(do: ~R"\n123")) == ~s/~R"\\\\n123"/
328+
assert Macro.to_string(quote(do: ~R"123")) == ~S/~R"123"/
329+
assert Macro.to_string(quote(do: ~R"123"u)) == ~S/~R"123"u/
330+
assert Macro.to_string(quote(do: ~R"\n123")) == ~S/~R"\n123"/
331+
332+
assert Macro.to_string(quote(do: ~S["'(123)'"])) == ~S/~S["'(123)'"]/
333+
334+
assert Macro.to_string(
335+
quote do
336+
~S"""
337+
"123"
338+
"""
339+
end
340+
) == ~s[~S"""\n"123"\n"""]
331341
end
332342

333343
test "tuple call" do
@@ -586,6 +596,7 @@ defmodule MacroTest do
586596
assert Macro.to_string(quote(do: %Test{foo: 1, bar: 1})) == "%Test{foo: 1, bar: 1}"
587597
assert Macro.to_string(quote(do: %Test{struct | foo: 2})) == "%Test{struct | foo: 2}"
588598
assert Macro.to_string(quote(do: %Test{} + 1)) == "%Test{} + 1"
599+
assert Macro.to_string(quote(do: %Test{foo(1)} + 2)) == "%Test{foo(1)} + 2"
589600
end
590601

591602
test "binary operators" do

0 commit comments

Comments
 (0)