diff --git a/lib/mneme/assertion/pattern.ex b/lib/mneme/assertion/pattern.ex index 1a54dea..5408856 100644 --- a/lib/mneme/assertion/pattern.ex +++ b/lib/mneme/assertion/pattern.ex @@ -35,6 +35,21 @@ defmodule Mneme.Assertion.Pattern do Pattern.new(exprs, guard: guard, notes: notes) end + @doc """ + Adds an improper tail onto a list pattern. + """ + def with_improper_tail(%Pattern{expr: list} = list_pattern, %Pattern{} = tail) + when is_list(list) do + %Pattern{ + expr: improper_tail_expr(list, tail.expr), + guard: combine_guards(list_pattern.guard, tail.guard), + notes: list_pattern.notes ++ tail.notes + } + end + + defp improper_tail_expr([last], tail_expr), do: [{:|, [], [last, tail_expr]}] + defp improper_tail_expr([x | xs], tail_expr), do: [x | improper_tail_expr(xs, tail_expr)] + defp combine_guards(nil, guard), do: guard defp combine_guards(guard, nil), do: guard defp combine_guards(g1, {_, meta, _} = g2), do: {:and, meta, [g2, g1]} diff --git a/lib/mneme/assertion/pattern_builder.ex b/lib/mneme/assertion/pattern_builder.ex index e3bea4b..b27c241 100644 --- a/lib/mneme/assertion/pattern_builder.ex +++ b/lib/mneme/assertion/pattern_builder.ex @@ -75,7 +75,16 @@ defmodule Mneme.Assertion.PatternBuilder do defp do_to_patterns([], _, vars), do: {[Pattern.new([])], vars} defp do_to_patterns(list, context, vars) when is_list(list) do - {patterns, vars} = enum_to_patterns(list, context, vars) + {patterns, vars} = + case pop_tail(list) do + {list, []} -> + enum_to_patterns(list, context, vars) + + {list, improper_tail} -> + {patterns, vars} = enum_to_patterns(list, context, vars) + {[tail_pattern | _], vars} = do_to_patterns(improper_tail, context, vars) + {Enum.map(patterns, &Pattern.with_improper_tail(&1, tail_pattern)), vars} + end if List.ascii_printable?(list) do {patterns ++ [charlist_pattern(list, context)], vars} @@ -474,4 +483,13 @@ defmodule Mneme.Assertion.PatternBuilder do name_i = :"#{name}#{i}" if(name_i in var_names, do: get_unique_name(name, var_names, i + 1), else: name_i) end + + defp pop_tail([x | []]), do: {[x], []} + + defp pop_tail([x | xs]) when is_list(xs) do + {xs, tail} = pop_tail(xs) + {[x | xs], tail} + end + + defp pop_tail([x | improper_tail]), do: {[x], improper_tail} end diff --git a/test/mneme/assertion/pattern_builder_test.exs b/test/mneme/assertion/pattern_builder_test.exs index 3635adc..dd38b5d 100644 --- a/test/mneme/assertion/pattern_builder_test.exs +++ b/test/mneme/assertion/pattern_builder_test.exs @@ -65,6 +65,10 @@ defmodule Mneme.Assertion.PatternBuilderTest do auto_assert ["[1, [:nested], 3]"] <- to_pattern_strings([1, [:nested], 3]) end + test "improper lists" do + auto_assert ["[:x | :y]"] <- to_pattern_strings([:x | :y]) + end + test "pins and guards" do ref = make_ref() auto_assert ["ref when is_reference(ref)"] <- to_pattern_strings(ref) diff --git a/test_integration/basic_test.exs b/test_integration/basic_test.exs index 836ae78..10098b0 100644 --- a/test_integration/basic_test.exs +++ b/test_integration/basic_test.exs @@ -128,6 +128,11 @@ defmodule Mneme.Integration.BasicTest do auto_assert [ref] when is_reference(ref) <- l end + test "improper lists" do + # y + auto_assert [:x | :y] <- [:x | :y] + end + test "charlists" do # y auto_assert [102, 111, 111] <- String.to_charlist("foo")