Skip to content

Commit 90b5266

Browse files
committed
Do not expand negations when pretty printing tuples, see #15154
1 parent 382b5b0 commit 90b5266

2 files changed

Lines changed: 158 additions & 39 deletions

File tree

lib/elixir/lib/module/types/descr.ex

Lines changed: 77 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ defmodule Module.Types.Descr do
285285
end
286286

287287
defp unwrap_domain_tuple(%{tuple: bdd} = descr, transform) when map_size(descr) == 1 do
288-
tuple_bdd_to_dnf(bdd) |> Enum.map(transform)
288+
tuple_bdd_to_dnf_no_negations(bdd) |> Enum.map(transform)
289289
end
290290

291291
defp unwrap_domain_tuple(descr, _transform) when descr == %{}, do: []
@@ -679,15 +679,15 @@ defmodule Module.Types.Descr do
679679
defp each_singleton?(:atom, atoms), do: match?({:union, set} when map_size(set) == 1, atoms)
680680

681681
defp each_singleton?(:tuple, bdd) do
682-
case tuple_bdd_to_dnf(bdd) do
682+
case tuple_bdd_to_dnf_no_negations(bdd) do
683683
[] -> :empty
684684
[{:closed, entries}] -> Enum.all?(entries, &static_singleton?/1)
685685
_ -> false
686686
end
687687
end
688688

689689
defp each_singleton?(:map, bdd) do
690-
case map_bdd_to_dnf_no_negations(bdd) do
690+
case map_bdd_to_dnf_remove_empty(bdd) do
691691
[] ->
692692
:empty
693693

@@ -3172,7 +3172,7 @@ defmodule Module.Types.Descr do
31723172
if empty?(type), do: throw(:empty), else: type
31733173
end
31743174

3175-
defp map_bdd_to_dnf_no_negations(bdd) do
3175+
defp map_bdd_to_dnf_remove_empty(bdd) do
31763176
bdd_to_dnf(bdd)
31773177
|> Enum.reduce([], fn {pos, negs}, acc ->
31783178
case non_empty_map_literals_intersection(pos) do
@@ -3189,7 +3189,7 @@ defmodule Module.Types.Descr do
31893189
end)
31903190
end
31913191

3192-
defp map_bdd_to_dnf_with_negations(bdd) do
3192+
defp map_bdd_to_dnf_with_empty(bdd) do
31933193
bdd_to_dnf(bdd)
31943194
|> Enum.reduce([], fn {pos, negs}, acc ->
31953195
case non_empty_map_literals_intersection(pos) do
@@ -3257,7 +3257,7 @@ defmodule Module.Types.Descr do
32573257
end
32583258

32593259
defp map_fetch_key_static(%{map: bdd}, key) do
3260-
bdd |> map_bdd_to_dnf_with_negations() |> map_dnf_fetch_static(key)
3260+
bdd |> map_bdd_to_dnf_with_empty() |> map_dnf_fetch_static(key)
32613261
end
32623262

32633263
defp map_fetch_key_static(%{}, _key), do: {false, none()}
@@ -3395,7 +3395,7 @@ defmodule Module.Types.Descr do
33953395
end
33963396

33973397
defp map_to_list_static(%{map: bdd}, fun) do
3398-
case map_bdd_to_dnf_no_negations(bdd) do
3398+
case map_bdd_to_dnf_remove_empty(bdd) do
33993399
[] ->
34003400
:badmap
34013401

@@ -3590,7 +3590,7 @@ defmodule Module.Types.Descr do
35903590
{required_keys, optional_keys, maybe_negated_set, required_domains, optional_domains} =
35913591
split_keys
35923592

3593-
dnf = map_bdd_to_dnf_with_negations(bdd)
3593+
dnf = map_bdd_to_dnf_with_empty(bdd)
35943594
bdd = map_update_put_negated(bdd, maybe_negated_set, type_fun)
35953595

35963596
{found?, value, domains, errors} =
@@ -3658,7 +3658,7 @@ defmodule Module.Types.Descr do
36583658
{term(), open_map(), [], true}
36593659
else
36603660
acc = {none(), none(), [], false}
3661-
dnf = map_bdd_to_dnf_with_negations(@map_top)
3661+
dnf = map_bdd_to_dnf_with_empty(@map_top)
36623662
map_update_keys_static(dnf, required_keys, optional_keys, type_fun, force?, static?, acc)
36633663
end
36643664
end
@@ -3969,7 +3969,7 @@ defmodule Module.Types.Descr do
39693969
domains -> map_update_put_domains(bdd, domains, type_fun)
39703970
end
39713971

3972-
dnf = map_bdd_to_dnf_with_negations(bdd)
3972+
dnf = map_bdd_to_dnf_with_empty(bdd)
39733973
map_put_keys_static(dnf, required_keys ++ optional_keys, type, descr)
39743974
end
39753975

@@ -3987,7 +3987,7 @@ defmodule Module.Types.Descr do
39873987
if required_domains != [] or optional_domains != [] do
39883988
open_map()
39893989
else
3990-
dnf = map_bdd_to_dnf_with_negations(@map_top)
3990+
dnf = map_bdd_to_dnf_with_empty(@map_top)
39913991
map_put_keys_static(dnf, required_keys ++ optional_keys, type, none())
39923992
end
39933993
end
@@ -4047,7 +4047,7 @@ defmodule Module.Types.Descr do
40474047
{required_keys, optional_keys, maybe_negated_set, required_domains, optional_domains} =
40484048
split_keys
40494049

4050-
dnf = map_bdd_to_dnf_with_negations(bdd)
4050+
dnf = map_bdd_to_dnf_with_empty(bdd)
40514051

40524052
acc = none()
40534053
acc = map_get_keys(dnf, required_keys, acc)
@@ -4552,7 +4552,7 @@ defmodule Module.Types.Descr do
45524552

45534553
defp map_to_quoted(bdd, opts) do
45544554
bdd
4555-
|> map_bdd_to_dnf_with_negations()
4555+
|> map_bdd_to_dnf_with_empty()
45564556
|> Enum.flat_map(fn {tag, fields, negs} ->
45574557
map_eliminate_while_negs_decrease(tag, fields, negs)
45584558
end)
@@ -5192,25 +5192,43 @@ defmodule Module.Types.Descr do
51925192
end
51935193

51945194
defp tuple_to_quoted(bdd, opts) do
5195-
tuple_bdd_to_dnf(bdd)
5195+
tuple_bdd_to_dnf_with_negations(bdd)
51965196
|> tuple_fusion()
51975197
|> Enum.map(&tuple_literal_to_quoted(&1, opts))
51985198
end
51995199

52005200
# Transforms a bdd into a union of tuples with no negations.
5201-
# Note: it is important to compose the results with tuple_dnf_union/2 to avoid duplicates
5202-
defp tuple_bdd_to_dnf(bdd) do
5201+
# Note: it is important to compose the results with
5202+
# tuple_dnf_union/2 to avoid duplicates
5203+
defp tuple_bdd_to_dnf_no_negations(bdd) do
52035204
bdd_to_dnf(bdd)
5204-
|> Enum.reduce([], fn {positive_tuples, negative_tuples}, acc ->
5205-
case non_empty_tuple_literals_intersection(positive_tuples) do
5205+
|> Enum.reduce([], fn {pos, negs}, acc ->
5206+
case non_empty_tuple_literals_intersection(pos) do
5207+
:empty ->
5208+
acc
5209+
5210+
{tag, elements} ->
5211+
if tuple_line_empty?(tag, elements, negs) do
5212+
acc
5213+
else
5214+
tuple_eliminate_negations(tag, elements, negs) |> tuple_dnf_union(acc)
5215+
end
5216+
end
5217+
end)
5218+
end
5219+
5220+
defp tuple_bdd_to_dnf_with_negations(bdd) do
5221+
bdd_to_dnf(bdd)
5222+
|> Enum.reduce([], fn {pos, negs}, acc ->
5223+
case non_empty_tuple_literals_intersection(pos) do
52065224
:empty ->
52075225
acc
52085226

52095227
{tag, elements} ->
5210-
if tuple_line_empty?(tag, elements, negative_tuples) do
5228+
if tuple_line_empty?(tag, elements, negs) do
52115229
acc
52125230
else
5213-
tuple_eliminate_negations(tag, elements, negative_tuples) |> tuple_dnf_union(acc)
5231+
[{tag, elements, negs} | acc]
52145232
end
52155233
end
52165234
end)
@@ -5219,20 +5237,32 @@ defmodule Module.Types.Descr do
52195237
# Given a union of tuples, fuses the tuple unions when possible,
52205238
# e.g. {integer(), atom()} or {float(), atom()} into {number(), atom()}
52215239
# The negations of two fused tuples are just concatenated.
5222-
defp tuple_fusion(dnf_no_negations) do
5223-
# Steps:
5224-
# 1. Consider tuples without negations apart from those with
5225-
# 2. Group tuples by size and tag
5226-
# 3. Try fusions for each group until no fusion is found
5227-
# 4. Merge the groups back into a dnf
5228-
dnf_no_negations
5229-
|> Enum.group_by(fn {tag, elems} -> {tag, length(elems)} end)
5230-
|> Enum.flat_map(fn {_, tuples} -> tuple_non_negated_fuse(tuples) end)
5231-
end
5232-
5233-
defp tuple_non_negated_fuse(tuples) do
5234-
Enum.reduce(tuples, [], fn tuple, acc ->
5235-
tuple_fuse_with_first_fusible(tuple, acc)
5240+
#
5241+
# Steps:
5242+
# 1. Consider tuples without negations apart from those with
5243+
# 2. Group tuples by size and tag
5244+
# 3. Try fusions for each group until no fusion is found
5245+
# 4. Merge the groups back into a dnf
5246+
defp tuple_fusion(dnf) do
5247+
{with_negs, without_negs} =
5248+
Enum.reduce(dnf, {[], %{}}, fn
5249+
{tag, elements, []}, {with, without} ->
5250+
key = {tag, length(elements)}
5251+
value = {tag, elements}
5252+
{with, Map.update(without, key, [value], &[value | &1])}
5253+
5254+
triplet, {with, without} ->
5255+
{[triplet | with], without}
5256+
end)
5257+
5258+
Enum.reduce(without_negs, with_negs, fn {_, tuples}, with_negs ->
5259+
tuples
5260+
|> Enum.reduce([], fn tuple, acc ->
5261+
tuple_fuse_with_first_fusible(tuple, acc)
5262+
end)
5263+
|> Enum.reduce(with_negs, fn {tag, elements}, with_negs ->
5264+
[{tag, elements, []} | with_negs]
5265+
end)
52365266
end)
52375267
end
52385268

@@ -5247,9 +5277,17 @@ defmodule Module.Types.Descr do
52475277
end
52485278
end
52495279

5250-
defp tuple_literal_to_quoted({:closed, []}, _opts), do: {:{}, [], []}
5280+
defp tuple_literal_to_quoted({:closed, [], []}, _opts), do: {:{}, [], []}
5281+
5282+
defp tuple_literal_to_quoted({tag, elements, negs}, opts) do
5283+
pos = tuple_fields_to_quoted(tag, elements, opts)
5284+
5285+
Enum.reduce(negs, pos, fn {tag, elements}, acc ->
5286+
{:and, [], [acc, {:not, [], [tuple_fields_to_quoted(tag, elements, opts)]}]}
5287+
end)
5288+
end
52515289

5252-
defp tuple_literal_to_quoted({tag, elements}, opts) do
5290+
defp tuple_fields_to_quoted(tag, elements, opts) do
52535291
case tag do
52545292
:closed -> {:{}, [], Enum.map(elements, &to_quoted(&1, opts))}
52555293
:open -> {:{}, [], Enum.map(elements, &to_quoted(&1, opts)) ++ [{:..., [], nil}]}
@@ -5359,7 +5397,7 @@ defmodule Module.Types.Descr do
53595397
end
53605398

53615399
defp tuple_get(bdd, index) do
5362-
tuple_bdd_to_dnf(bdd)
5400+
tuple_bdd_to_dnf_no_negations(bdd)
53635401
|> Enum.reduce(none(), fn
53645402
{tag, elements}, acc -> Enum.at(elements, index, tuple_tag_to_type(tag)) |> union(acc)
53655403
end)
@@ -5390,7 +5428,7 @@ defmodule Module.Types.Descr do
53905428
end
53915429

53925430
defp process_tuples_values(bdd) do
5393-
tuple_bdd_to_dnf(bdd)
5431+
tuple_bdd_to_dnf_no_negations(bdd)
53945432
|> Enum.reduce(none(), fn {tag, elements}, acc ->
53955433
cond do
53965434
Enum.any?(elements, &empty?/1) -> none()
@@ -5528,7 +5566,7 @@ defmodule Module.Types.Descr do
55285566
defp tuple_of_size_at_least_static?(descr, index) do
55295567
case descr do
55305568
%{tuple: bdd} ->
5531-
tuple_bdd_to_dnf(bdd)
5569+
tuple_bdd_to_dnf_no_negations(bdd)
55325570
|> Enum.all?(fn {_, elements} -> length(elements) >= index end)
55335571

55345572
%{} ->

lib/elixir/test/elixir/module/types/integration_test.exs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1015,6 +1015,87 @@ defmodule Module.Types.IntegrationTest do
10151015

10161016
assert_no_warnings(files)
10171017
end
1018+
1019+
test "pretty printing large tuples with negations" do
1020+
files = %{
1021+
"large_tuples.ex" => """
1022+
defmodule LargeTuplesWithNegations do
1023+
@abc [:a, :b]
1024+
defguard g1(t) when tuple_size(t) < 10
1025+
defguard g2(t, i) when elem(t, i) in @abc
1026+
1027+
def main() do
1028+
foo(Bar)
1029+
end
1030+
1031+
def foo(t) when g1(t) and g2(t, 0) and g2(t, 1) and g2(t, 2),
1032+
do: {bar(elem(t, 3)), bar(elem(t, 4))}
1033+
1034+
def foo(t) when g1(t) and g2(t, 1) and g2(t, 2) and g2(t, 3),
1035+
do: {bar(elem(t, 4)), bar(elem(t, 5))}
1036+
1037+
def foo(t) when g1(t) and g2(t, 2) and g2(t, 3) and g2(t, 4),
1038+
do: {bar(elem(t, 5)), bar(elem(t, 6))}
1039+
1040+
def foo(t) when g1(t) and g2(t, 3) and g2(t, 4) and g2(t, 5),
1041+
do: {bar(elem(t, 6)), bar(elem(t, 0))}
1042+
1043+
def foo(t) when g1(t) and g2(t, 4) and g2(t, 5) and g2(t, 6),
1044+
do: {bar(elem(t, 0)), bar(elem(t, 1))}
1045+
1046+
def foo(t) when g1(t) and g2(t, 5) and g2(t, 6) and g2(t, 0),
1047+
do: {bar(elem(t, 1)), bar(elem(t, 2))}
1048+
1049+
def foo(t) when g1(t) and g2(t, 6) and g2(t, 0) and g2(t, 1),
1050+
do: {bar(elem(t, 2)), bar(elem(t, 3))}
1051+
1052+
def bar(t) when g1(t) and g2(t, 0) and g2(t, 1) and g2(t, 2),
1053+
do: {baz(elem(t, 3)), baz(elem(t, 4))}
1054+
1055+
def bar(t) when g1(t) and g2(t, 1) and g2(t, 2) and g2(t, 3),
1056+
do: {baz(elem(t, 4)), baz(elem(t, 5))}
1057+
1058+
def bar(t) when g1(t) and g2(t, 2) and g2(t, 3) and g2(t, 4),
1059+
do: {baz(elem(t, 5)), baz(elem(t, 6))}
1060+
1061+
def bar(t) when g1(t) and g2(t, 3) and g2(t, 4) and g2(t, 5),
1062+
do: {baz(elem(t, 6)), baz(elem(t, 0))}
1063+
1064+
def bar(t) when g1(t) and g2(t, 4) and g2(t, 5) and g2(t, 6),
1065+
do: {baz(elem(t, 0)), baz(elem(t, 1))}
1066+
1067+
def bar(t) when g1(t) and g2(t, 5) and g2(t, 6) and g2(t, 0),
1068+
do: {baz(elem(t, 1)), baz(elem(t, 2))}
1069+
1070+
def bar(t) when g1(t) and g2(t, 6) and g2(t, 0) and g2(t, 1),
1071+
do: {baz(elem(t, 2)), baz(elem(t, 3))}
1072+
1073+
def baz(t) when g1(t) and elem(t, 0) in @abc and elem(t, 1) in @abc and elem(t, 2) in @abc,
1074+
do: 0
1075+
1076+
def baz(t) when g1(t) and elem(t, 1) in @abc and elem(t, 2) in @abc and elem(t, 3) in @abc,
1077+
do: 1
1078+
1079+
def baz(t) when g1(t) and elem(t, 2) in @abc and elem(t, 3) in @abc and elem(t, 4) in @abc,
1080+
do: 2
1081+
1082+
def baz(t) when g1(t) and elem(t, 3) in @abc and elem(t, 4) in @abc and elem(t, 5) in @abc,
1083+
do: 3
1084+
1085+
def baz(t) when g1(t) and elem(t, 4) in @abc and elem(t, 5) in @abc and elem(t, 6) in @abc,
1086+
do: 4
1087+
1088+
def baz(t) when g1(t) and elem(t, 5) in @abc and elem(t, 6) in @abc and elem(t, 0) in @abc,
1089+
do: 5
1090+
1091+
def baz(t) when g1(t) and elem(t, 6) in @abc and elem(t, 0) in @abc and elem(t, 1) in @abc,
1092+
do: 6
1093+
end
1094+
"""
1095+
}
1096+
1097+
assert_warnings(files, ["incompatible types given to foo/1"])
1098+
end
10181099
end
10191100

10201101
describe "undefined warnings" do

0 commit comments

Comments
 (0)