diff --git a/lib/elixir/lib/module/types/descr.ex b/lib/elixir/lib/module/types/descr.ex index 772e63e85db..7c765af9230 100644 --- a/lib/elixir/lib/module/types/descr.ex +++ b/lib/elixir/lib/module/types/descr.ex @@ -2260,6 +2260,11 @@ defmodule Module.Types.Descr do defp map_only?(descr), do: empty?(Map.delete(descr, :map)) + def map_normal_form([{tag, pos, negs}], acc), do: [{tag, acc ++ pos, negs}] + + def map_normal_form([{tag1, pos1, negs1}, {tag2, pos2, negs2} | rest]) do + end + defp map_union(dnf1, dnf2) do # Union is just concatenation, but we rely on some optimization strategies to # avoid the list to grow when possible @@ -2370,6 +2375,8 @@ defmodule Module.Types.Descr do if subtype?(v2, v1), do: :right_subtype_of_left end + defp map_intersection(dnf, dnf), do: dnf + # Given two unions of maps, intersects each pair of maps. defp map_intersection(dnf1, dnf2) do for {tag1, pos1, negs1} <- dnf1, @@ -2378,15 +2385,17 @@ defmodule Module.Types.Descr do acc -> try do {tag, fields} = map_literal_intersection(tag1, pos1, tag2, pos2) - entry = {tag, fields, negs1 ++ negs2} + negs = negs1 ++ (negs2 -- negs1) + entry = {tag, fields, negs} # Imagine a, b, c, where a is closed and b and c are open with # no keys in common. The result in both cases will be a and we # want to avoid adding duplicates, especially as intersection # is a cartesian product. - case :lists.member(entry, acc) do - true -> acc - false -> [entry | acc] + cond do + :lists.member({tag, fields}, negs) -> acc + :lists.member(entry, acc) -> acc + true -> [entry | acc] end catch :empty -> acc @@ -2454,33 +2463,42 @@ defmodule Module.Types.Descr do if empty?(type), do: throw(:empty), else: type end - defp map_difference(_, dnf) when dnf == @map_top do - 0 - end + defp map_difference(_, dnf) when dnf == @map_top, do: 0 + defp map_difference(dnf, dnf), do: 0 defp map_difference(dnf1, dnf2) do Enum.reduce(dnf2, dnf1, fn - # Optimization: we are removing an open map with one field. - {:open, fields2, []}, dnf1 when map_size(fields2) == 1 -> - Enum.reduce(dnf1, [], fn {tag1, fields1, negs1}, acc -> + {:open, fields2, []}, current_dnf when map_size(fields2) == 1 -> + # Optimization: we are removing an open map with one field. + Enum.reduce(current_dnf, [], fn {tag1, fields1, negs1}, acc -> {key, value, _rest} = :maps.next(:maps.iterator(fields2)) t_diff = difference(Map.get(fields1, key, tag_to_type(tag1)), value) if empty?(t_diff) do acc else - [{tag1, Map.put(fields1, key, t_diff), negs1} | acc] + {tag, pos} = {tag1, Map.put(fields1, key, t_diff)} + + cond do + :lists.member({tag, pos}, negs1) -> acc + true -> [{tag, pos, negs1} | acc] + end end end) - {tag2, fields2, negs2}, dnf1 -> - Enum.reduce(dnf1, [], fn {tag1, fields1, negs1}, acc -> - acc = [{tag1, fields1, [{tag2, fields2} | negs1]} | acc] + {tag2, fields2, negs2}, current_dnf -> + Enum.reduce(current_dnf, [], fn {tag1, fields1, negs1}, acc -> + negs = [{tag2, fields2} | negs1] + acc = [{tag1, fields1, negs} | acc] Enum.reduce(negs2, acc, fn {neg_tag2, neg_fields2}, acc -> try do {tag, fields} = map_literal_intersection(tag1, fields1, neg_tag2, neg_fields2) - [{tag, fields, negs1} | acc] + + cond do + :lists.member({tag, fields}, negs1) -> acc + true -> [{tag, fields, negs1} | acc] + end catch :empty -> acc end diff --git a/lib/elixir/lib/module/types/helpers.ex b/lib/elixir/lib/module/types/helpers.ex index aea8c0b6074..2795f9d1090 100644 --- a/lib/elixir/lib/module/types/helpers.ex +++ b/lib/elixir/lib/module/types/helpers.ex @@ -495,4 +495,101 @@ defmodule Module.Types.Helpers do defp zip_map_reduce([], [], list, acc, _fun) do {Enum.reverse(list), acc} end + + def descr_size(:term), do: 1 + + def descr_size(%{} = descr) do + Enum.reduce(descr, 0, fn {key, value}, acc -> + acc + descr_size(key, value) + end) + end + + def descr_size(list) when is_list(list) do + dnf_size(list) + end + + def dnf_size(dnf) do + Enum.reduce(dnf, 0, fn {_tag, pos, negs}, acc -> + negs_size = + Enum.reduce(negs, 0, fn {_neg_tag, fields}, neg_acc -> + neg_acc + 1 + map_size(fields) + end) + + acc + 1 + map_size(pos) + negs_size + end) + end + + def dnf_size_detailed(dnf) do + {total_pos, total_negs} = + Enum.reduce(dnf, {0, 0}, fn {_tag, pos, negs}, {pos_acc, neg_acc} -> + negs_size = + Enum.reduce(negs, 0, fn {_neg_tag, fields}, neg_sum -> + neg_sum + 1 + map_size(fields) + end) + + {pos_acc + map_size(pos), neg_acc + negs_size} + end) + + {total_pos, total_negs} + end + + def descr_size(:tuple, dnf), do: length(dnf) + def descr_size(:fun, bdd), do: bdd_size(bdd) + + def descr_size(:map, dnf) do + Enum.reduce(dnf, 0, fn {_tag, _pos, negs}, acc -> + acc + 1 + length(negs) + end) + end + + def descr_size(:list, dnf) do + Enum.reduce(dnf, 0, fn {_, _last, negs}, acc -> + acc + 1 + length(negs) + end) + end + + def descr_size(_, _), do: 1 + + def bdd_size({_fun, l, r}) do + bdd_size(l) + bdd_size(r) + 1 + end + + def bdd_size(_bdd), do: 0 + + def list_dnf_size(dnf) do + Enum.reduce(dnf, 0, fn {_, _, negs}, _acc -> + 1 + length(negs) + end) + end + + # defp map_difference_check_sizes(dnf1, dnf2) do + # {pos1, neg1} = Module.Types.Helpers.dnf_size_detailed(dnf1) + # {pos2, neg2} = Module.Types.Helpers.dnf_size_detailed(dnf2) + # IO.inspect({length(dnf1), pos1, neg1}, label: "dnf1 (clauses, pos, neg)") + # IO.inspect({length(dnf2), pos2, neg2}, label: "dnf2 (clauses, pos, neg)") + + # IO.puts("dnf1 breakdown:") + + # Enum.with_index(dnf1, fn {tag, pos, negs}, idx -> + # negs_size = + # Enum.reduce(negs, 0, fn {_neg_tag, fields}, acc -> + # acc + 1 + map_size(fields) + # end) + + # IO.inspect({idx, tag, map_size(pos), negs_size}, label: " clause") + # IO.puts(Module.Types.Descr.to_quoted_string(%{map: [{tag, pos, negs}]})) + # end) + + # IO.puts("dnf2 breakdown:") + + # Enum.with_index(dnf2, fn {tag, pos, negs}, idx -> + # negs_size = + # Enum.reduce(negs, 0, fn {_neg_tag, fields}, acc -> + # acc + 1 + map_size(fields) + # end) + + # IO.inspect({idx, tag, map_size(pos), negs_size}, label: " clause") + # IO.puts(Module.Types.Descr.to_quoted_string(%{map: [{tag, pos, negs}]})) + # end) + # end end