diff options
author | José Valim <jose.valim@plataformatec.com.br> | 2019-08-19 13:55:08 +0200 |
---|---|---|
committer | José Valim <jose.valim@plataformatec.com.br> | 2019-08-19 13:55:14 +0200 |
commit | 9504103f30dabfe48adcdb2c72286b66fc0985ac (patch) | |
tree | 19038b5327193ff60fe1f57c34f296f3c81607a1 | |
parent | 4d740be5efd9b155c354ac87a6eb738536b05134 (diff) | |
download | elixir-9504103f30dabfe48adcdb2c72286b66fc0985ac.tar.gz |
Do not expand structs when comparing AST on ExUnit.Diff
-rw-r--r-- | lib/elixir/lib/macro.ex | 33 | ||||
-rw-r--r-- | lib/elixir/test/elixir/macro_test.exs | 2 | ||||
-rw-r--r-- | lib/ex_unit/lib/ex_unit/diff.ex | 67 | ||||
-rw-r--r-- | lib/ex_unit/test/ex_unit/diff_test.exs | 27 |
4 files changed, 70 insertions, 59 deletions
diff --git a/lib/elixir/lib/macro.ex b/lib/elixir/lib/macro.ex index 45ce9d228..0e2069582 100644 --- a/lib/elixir/lib/macro.ex +++ b/lib/elixir/lib/macro.ex @@ -1478,18 +1478,43 @@ defmodule Macro do def operator?(name, arity) when is_atom(name) and is_integer(arity), do: false @doc """ - Returns `true` if the given quoted expression is an AST literal. + Returns `true` if the given quoted expression represents a quoted literal. + + Atoms, numbers, and functions are always literals. Binaries, lists, tuples, + maps, and structs are only literals if all of their terms are also literals. + + ## Examples + + iex> Macro.quoted_literal?(quote(do: "foo")) + true + iex> Macro.quoted_literal?(quote(do: {"foo", 1})) + true + iex> Macro.quoted_literal?(quote(do: %{foo: "bar"})) + true + iex> Macro.quoted_literal?(quote(do: %URI{path: "/"})) + true + iex> Macro.quoted_literal?(quote(do: URI.parse("/"))) + false + iex> Macro.quoted_literal?(quote(do: {foo, var})) + false + """ @doc since: "1.7.0" @spec quoted_literal?(t) :: boolean def quoted_literal?(term) + def quoted_literal?({:__aliases__, _, args}), + do: quoted_literal?(args) + + def quoted_literal?({:%, _, [left, right]}), + do: quoted_literal?(left) and quoted_literal?(right) + + def quoted_literal?({:%{}, _, args}), do: quoted_literal?(args) def quoted_literal?({left, right}), do: quoted_literal?(left) and quoted_literal?(right) def quoted_literal?(list) when is_list(list), do: Enum.all?(list, "ed_literal?/1) - def quoted_literal?(term) do - is_atom(term) or is_number(term) or is_binary(term) or is_function(term) - end + def quoted_literal?(term), + do: is_atom(term) or is_number(term) or is_binary(term) or is_function(term) @doc """ Receives an AST node and expands it until it can no longer diff --git a/lib/elixir/test/elixir/macro_test.exs b/lib/elixir/test/elixir/macro_test.exs index a50414fb9..8829bf4ca 100644 --- a/lib/elixir/test/elixir/macro_test.exs +++ b/lib/elixir/test/elixir/macro_test.exs @@ -939,6 +939,8 @@ defmodule MacroTest do test "quoted_literal?/1" do assert Macro.quoted_literal?(quote(do: "foo")) assert Macro.quoted_literal?(quote(do: {"foo", 1})) + assert Macro.quoted_literal?(quote(do: %{foo: "bar"})) + assert Macro.quoted_literal?(quote(do: %URI{path: "/"})) refute Macro.quoted_literal?(quote(do: {"foo", var})) end diff --git a/lib/ex_unit/lib/ex_unit/diff.ex b/lib/ex_unit/lib/ex_unit/diff.ex index 03cfdb6bb..dd6077a22 100644 --- a/lib/ex_unit/lib/ex_unit/diff.ex +++ b/lib/ex_unit/lib/ex_unit/diff.ex @@ -671,66 +671,51 @@ defmodule ExUnit.Diff do # Structs - defp compare_struct({:%, _, [struct1, left_map]}, %struct2{} = right, env) do - compare_struct(left_map, Map.from_struct(right), struct1, struct2, env) - end - defp compare_struct({:%, _, [struct1, left_map]}, right, env) do - compare_struct(left_map, right, struct1, nil, env) + compare_quoted_struct(left_map, struct1, right, env) end - defp compare_struct({:%{}, _, [{:__struct__, struct1} | left_items]}, %struct2{} = right, env) do - compare_struct({:%{}, [], left_items}, right, struct1, struct2, env) - end - - defp compare_struct(%struct1{} = left, %struct2{} = right, env) do - compare_struct(left, right, struct1, struct2, env) - end - - defp compare_struct(%struct1{} = left, right, env) do - compare_struct(left, right, struct1, nil, env) - end - - defp compare_struct(left, %struct2{} = right, env) do - compare_map(left, right, nil, struct2, env) + defp compare_struct({:%{}, meta, [{:__struct__, struct1} | left_items]}, right, env) do + compare_quoted_struct({:%{}, meta, left_items}, struct1, right, env) end defp compare_struct(left, right, env) do - compare_map(left, right, nil, nil, env) - end - - defp compare_struct(%{} = left, right, struct1, struct2, env) do - if Inspect.impl_for(left) not in [Inspect.Any, Inspect.Map] do - inspect_left = inspect(left) + compare_struct( + left, + Map.from_struct(left), + right, + maybe_struct(left), + maybe_struct(right), + env + ) + end + + defp compare_struct(%{} = value, left, right, struct1, struct2, env) do + if Inspect.impl_for(value) not in [Inspect.Any, Inspect.Map] do + inspect_left = inspect(value) inspect_right = inspect(right) if inspect_left != inspect_right do compare_string(inspect_left, inspect_right, ?\", env) else - compare_map(Map.from_struct(left), right, struct1, struct2, env) + compare_map(left, right, struct1, struct2, env) end else - compare_map(Map.from_struct(left), right, struct1, struct2, env) + compare_map(left, right, struct1, struct2, env) end end - defp compare_struct(left_map, right, struct1, struct2, env) do - try do - pid = self() - - ExUnit.CaptureIO.capture_io(:stderr, fn -> - {items, _} = Code.eval_quoted(left_map) - send(pid, {:struct, struct(struct1, items)}) - end) - - receive do - {:struct, left} -> compare_struct(left, right, struct1, struct2, env) - end - rescue - _ -> compare_map(left_map, right, struct1, struct2, env) + defp compare_quoted_struct({:%{}, _, kw} = map, struct, right, env) do + if Macro.quoted_literal?(kw) do + compare_struct(struct(struct, kw), map, right, struct, maybe_struct(right), env) + else + compare_map(map, right, struct, maybe_struct(right), env) end end + defp maybe_struct(%name{}), do: name + defp maybe_struct(_), do: nil + defp build_struct_result(nil, nil) do %__MODULE__{equivalent?: true} end diff --git a/lib/ex_unit/test/ex_unit/diff_test.exs b/lib/ex_unit/test/ex_unit/diff_test.exs index 17cede47a..2b35771b9 100644 --- a/lib/ex_unit/test/ex_unit/diff_test.exs +++ b/lib/ex_unit/test/ex_unit/diff_test.exs @@ -7,7 +7,7 @@ defmodule ExUnit.DiffTest do alias ExUnit.{Assertions, Diff} defmodule User do - defstruct [:age] + defstruct [:name, :age] end defmodule Person do @@ -451,7 +451,7 @@ defmodule ExUnit.DiffTest do refute_diff( %User{age: 16} = %User{age: 21}, "%ExUnit.DiffTest.User{age: 1-6-}", - "%ExUnit.DiffTest.User{age: +2+1}" + "%ExUnit.DiffTest.User{age: +2+1, name: nil}" ) refute_diff( @@ -475,18 +475,17 @@ defmodule ExUnit.DiffTest do refute_diff( %{age: 16, __struct__: Person} = %User{age: 16}, "%-ExUnit.DiffTest.Person-{age: 16}", - "%+ExUnit.DiffTest.User+{age: 16}" + "%+ExUnit.DiffTest.User+{age: 16, name: nil}" ) - pins = [tweety_one: 21] - - assert_diff(%User{age: ^tweety_one} = %User{age: 21}, [], pins) + pins = [twenty_one: 21] + assert_diff(%User{age: ^twenty_one} = %User{age: 21}, [], pins) assert_diff(%User{age: age} = %User{age: 21}, [age: 21], pins) refute_diff( - %User{^tweety_one => 21} = %User{age: 21}, - "%ExUnit.DiffTest.User{-^tweety_one => 21-}", - "%ExUnit.DiffTest.User{age: 21}", + %User{^twenty_one => 21} = %User{age: 21}, + "%ExUnit.DiffTest.User{-^twenty_one => 21-}", + "%ExUnit.DiffTest.User{age: 21, name: nil}", pins ) @@ -495,23 +494,23 @@ defmodule ExUnit.DiffTest do test "structs outside of match context" do assert_diff(%User{age: 16} == %User{age: 16}, []) - assert_diff(%{age: 16, __struct__: User} == %User{age: 16}, []) + assert_diff(%{age: 16, __struct__: User, name: nil} == %User{age: 16}, []) refute_diff( %User{age: 16} == %{age: 16}, - "%-ExUnit.DiffTest.User-{age: 16}", + "%-ExUnit.DiffTest.User-{age: 16, -name: nil-}", "%{age: 16}" ) refute_diff( %User{age: 16} == %User{age: 21}, - "%ExUnit.DiffTest.User{age: 1-6-}", - "%ExUnit.DiffTest.User{age: +2+1}" + "%ExUnit.DiffTest.User{age: 1-6-, name: nil}", + "%ExUnit.DiffTest.User{age: +2+1, name: nil}" ) refute_diff( %User{age: 16} == %Person{age: 21}, - "%-ExUnit.DiffTest.User-{age: 1-6-}", + "%-ExUnit.DiffTest.User-{age: 1-6-, -name: nil-}", "%+ExUnit.DiffTest.Person+{age: +2+1}" ) end |