Skip to content

Commit c6ed523

Browse files
committed
Fix bugs and add completeness to function pretty printing
1 parent 7307f3c commit c6ed523

File tree

2 files changed

+102
-70
lines changed

2 files changed

+102
-70
lines changed

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

Lines changed: 91 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -528,15 +528,19 @@ defmodule Module.Types.Descr do
528528
if term_type?(descr) do
529529
{:term, [], []}
530530
else
531-
# Dynamic always come first for visibility
532-
{dynamic, static} =
531+
{dynamic, static, extra} =
533532
case :maps.take(:dynamic, descr) do
534-
:error -> {%{}, descr}
535-
{:term, static} -> {:term, static}
536-
{dynamic, static} -> {difference(dynamic, static), static}
537-
end
533+
:error ->
534+
{%{}, descr, []}
535+
536+
{:term, static} ->
537+
{:term, static, []}
538538

539-
{static, dynamic, extra} = fun_denormalize(static, dynamic, opts)
539+
{dynamic, static} ->
540+
# Denormalize functions before we do the difference
541+
{static, dynamic, extra} = fun_denormalize(static, dynamic, opts)
542+
{difference(dynamic, static), static, extra}
543+
end
540544

541545
# Merge empty list and list together if they both exist
542546
{extra, static} =
@@ -553,6 +557,7 @@ defmodule Module.Types.Descr do
553557
{extra, static}
554558
end
555559

560+
# Dynamic always come first for visibility
556561
unions =
557562
to_quoted(:dynamic, dynamic, opts) ++
558563
Enum.sort(
@@ -1464,6 +1469,42 @@ defmodule Module.Types.Descr do
14641469
end
14651470

14661471
defp fun_intersection(bdd1, bdd2) do
1472+
# If intersecting with the top type for that arity, no-op
1473+
case {bdd1, bdd2} do
1474+
{bdd, {{args, return} = fun, :fun_top, :fun_bottom}} when is_tuple(bdd) ->
1475+
if return == :term and Enum.all?(args, &(&1 == %{})) and
1476+
matching_arity_left?(bdd, length(args)) do
1477+
bdd
1478+
else
1479+
{fun, bdd, :fun_bottom}
1480+
end
1481+
1482+
{{{args, return} = fun, :fun_top, :fun_bottom}, bdd} when is_tuple(bdd) ->
1483+
if return == :term and Enum.all?(args, &(&1 == %{})) and
1484+
matching_arity_left?(bdd, length(args)) do
1485+
bdd
1486+
else
1487+
{fun, bdd, :fun_bottom}
1488+
end
1489+
1490+
_ ->
1491+
fun_intersection_recur(bdd1, bdd2)
1492+
end
1493+
end
1494+
1495+
defp matching_arity_left?({{args, _return}, l, r}, arity) do
1496+
length(args) == arity and matching_arity_left?(l, arity) and matching_arity_right?(r, arity)
1497+
end
1498+
1499+
defp matching_arity_left?(_, _arity), do: true
1500+
1501+
defp matching_arity_right?({_, l, r}, arity) do
1502+
matching_arity_left?(l, arity) and matching_arity_right?(r, arity)
1503+
end
1504+
1505+
defp matching_arity_right?(_, _arity), do: true
1506+
1507+
defp fun_intersection_recur(bdd1, bdd2) do
14671508
case {bdd1, bdd2} do
14681509
# Base cases
14691510
{_, :fun_bottom} ->
@@ -1482,45 +1523,27 @@ defmodule Module.Types.Descr do
14821523
# If intersecting with a single positive or negative function, we insert
14831524
# it at the root instead of merging the trees (this avoids going down the
14841525
# whole bdd).
1485-
{bdd, {{args, return} = fun, :fun_top, :fun_bottom}} ->
1486-
# If intersecting with the top type for that arity, no-op
1487-
if return == :term and Enum.all?(args, &(&1 == %{})) and
1488-
matching_arity?(bdd, length(args)) do
1489-
bdd
1490-
else
1491-
{fun, bdd, :fun_bottom}
1492-
end
1526+
{bdd, {fun, :fun_top, :fun_bottom}} ->
1527+
{fun, bdd, :fun_bottom}
14931528

14941529
{bdd, {fun, :fun_bottom, :fun_top}} ->
14951530
{fun, :fun_bottom, bdd}
14961531

1497-
{{{args, return} = fun, :fun_top, :fun_bottom}, bdd} ->
1498-
# If intersecting with the top type for that arity, no-op
1499-
if return == :term and Enum.all?(args, &(&1 == %{})) and
1500-
matching_arity?(bdd, length(args)) do
1501-
bdd
1502-
else
1503-
{fun, bdd, :fun_bottom}
1504-
end
1532+
{{fun, :fun_top, :fun_bottom}, bdd} ->
1533+
{fun, bdd, :fun_bottom}
15051534

15061535
{{fun, :fun_bottom, :fun_top}, bdd} ->
15071536
{fun, :fun_bottom, bdd}
15081537

15091538
# General cases
15101539
{{fun, l1, r1}, {fun, l2, r2}} ->
1511-
{fun, fun_intersection(l1, l2), fun_intersection(r1, r2)}
1540+
{fun, fun_intersection_recur(l1, l2), fun_intersection_recur(r1, r2)}
15121541

15131542
{{fun, l, r}, bdd} ->
1514-
{fun, fun_intersection(l, bdd), fun_intersection(r, bdd)}
1543+
{fun, fun_intersection_recur(l, bdd), fun_intersection_recur(r, bdd)}
15151544
end
15161545
end
15171546

1518-
defp matching_arity?({{args, _return}, l, r}, arity) do
1519-
length(args) == arity and matching_arity?(l, arity) and matching_arity?(r, arity)
1520-
end
1521-
1522-
defp matching_arity?(_, _arity), do: true
1523-
15241547
defp fun_difference(bdd1, bdd2) do
15251548
case {bdd1, bdd2} do
15261549
{:fun_bottom, _} -> :fun_bottom
@@ -1541,7 +1564,7 @@ defmodule Module.Types.Descr do
15411564
dynamic_pos = fun_get_pos(dynamic_bdd)
15421565

15431566
if static_pos != [] and dynamic_pos != [] do
1544-
{dynamic_pos, static_pos} = fun_denormalize_pos(dynamic_pos, static_pos)
1567+
{static_pos, dynamic_pos} = fun_denormalize_pos(static_pos, dynamic_pos)
15451568

15461569
quoted =
15471570
if dynamic_pos == [] do
@@ -1564,50 +1587,51 @@ defmodule Module.Types.Descr do
15641587
{static, dynamic, []}
15651588
end
15661589

1567-
defp fun_denormalize_pos(dynamic_unions, static_unions) do
1568-
Enum.reduce(dynamic_unions, {[], static_unions}, fn
1590+
defp fun_denormalize_pos(static_unions, dynamic_unions) do
1591+
Enum.map_reduce(static_unions, dynamic_unions, fn
15691592
# Handle fun() types accordingly
1570-
[], {dynamic_unions, static_unions} ->
1571-
{[[] | dynamic_unions], static_unions}
1572-
1573-
dynamic_intersections, {dynamic_unions, static_unions} ->
1574-
{dynamic_intersections, static_unions} =
1575-
Enum.reduce(dynamic_intersections, {[], static_unions}, fn
1576-
{args, return}, {acc, static_unions} ->
1577-
case fun_denormalize_arrow(args, return, static_unions) do
1578-
{:ok, static_unions} -> {acc, static_unions}
1579-
:error -> {[{args, return} | acc], static_unions}
1580-
end
1581-
end)
1593+
[], dynamic_unions ->
1594+
{[], List.delete(dynamic_unions, [])}
15821595

1583-
if dynamic_intersections == [] do
1584-
{dynamic_unions, static_unions}
1585-
else
1586-
{[dynamic_intersections | dynamic_unions], static_unions}
1596+
static_intersections, dynamic_unions ->
1597+
case pivot(dynamic_unions, [], &fun_denormalize_intersections(static_intersections, &1)) do
1598+
{match, dynamic_unions} -> {match, dynamic_unions}
1599+
:error -> {static_intersections, dynamic_unions}
15871600
end
15881601
end)
15891602
end
15901603

1591-
defp fun_denormalize_arrow(dynamic_args, dynamic_return, static_unions) do
1592-
pivot(static_unions, [], fn static_intersections ->
1593-
pivot(static_intersections, [], fn {static_args, static_return} ->
1594-
if subtype?(static_return, dynamic_return) and args_subtype?(dynamic_args, static_args) do
1595-
args =
1596-
Enum.zip_with(static_args, dynamic_args, fn static_arg, dynamic_arg ->
1597-
union(dynamic(difference(static_arg, dynamic_arg)), dynamic_arg)
1598-
end)
1604+
defp fun_denormalize_intersections(statics, dynamics) do
1605+
if length(statics) == length(dynamics) do
1606+
fun_denormalize_intersections(statics, dynamics, [])
1607+
else
1608+
:error
1609+
end
1610+
end
15991611

1600-
return = union(dynamic(difference(dynamic_return, static_return)), static_return)
1601-
{:ok, {args, return}}
1602-
else
1603-
:error
1604-
end
1605-
end)
1606-
end)
1612+
# We assume those pairs are always formed in the same order
1613+
defp fun_denormalize_intersections(
1614+
[{static_args, static_return} | statics],
1615+
[{dynamic_args, dynamic_return} | dynamics],
1616+
acc
1617+
) do
1618+
if subtype?(static_return, dynamic_return) and args_subtype?(dynamic_args, static_args) do
1619+
args =
1620+
Enum.zip_with(static_args, dynamic_args, fn static_arg, dynamic_arg ->
1621+
union(dynamic(static_arg), dynamic_arg)
1622+
end)
1623+
1624+
return = union(dynamic(dynamic_return), static_return)
1625+
fun_denormalize_intersections(statics, dynamics, [{args, return} | acc])
1626+
else
1627+
:error
1628+
end
16071629
end
16081630

1631+
defp fun_denormalize_intersections([], [], acc), do: {:ok, acc}
1632+
16091633
defp arrow_subtype?(left_args, left_return, right_args, right_return) do
1610-
subtype?(right_return, left_return) and args_subtype?(left_args, right_args)
1634+
subtype?(left_return, right_return) and args_subtype?(right_args, left_args)
16111635
end
16121636

16131637
defp args_subtype?(left_args, right_args) do
@@ -1618,7 +1642,7 @@ defmodule Module.Types.Descr do
16181642

16191643
defp pivot([head | tail], acc, fun) do
16201644
case fun.(head) do
1621-
{:ok, value} -> {:ok, acc ++ [value | tail]}
1645+
{:ok, value} -> {value, acc ++ tail}
16221646
:error -> pivot(tail, [head | acc], fun)
16231647
end
16241648
end

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

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1821,9 +1821,6 @@ defmodule Module.Types.DescrTest do
18211821
|> intersection(none_fun(2))
18221822
|> to_quoted_string() == "none()"
18231823

1824-
assert fun([integer()], atom()) |> intersection(none_fun(1)) |> to_quoted_string() ==
1825-
"(integer() -> atom())"
1826-
18271824
assert fun([integer(), float()], boolean()) |> to_quoted_string() ==
18281825
"(integer(), float() -> boolean())"
18291826

@@ -1838,6 +1835,17 @@ defmodule Module.Types.DescrTest do
18381835
"(integer() -> boolean()) and (float() -> boolean())"
18391836
end
18401837

1838+
test "function with optimized intersections" do
1839+
assert fun([integer()], atom()) |> intersection(none_fun(1)) |> to_quoted_string() ==
1840+
"(integer() -> atom())"
1841+
1842+
assert fun([integer()], atom())
1843+
|> difference(none_fun(2))
1844+
|> intersection(none_fun(1))
1845+
|> to_quoted_string() ==
1846+
"(integer() -> atom())"
1847+
end
1848+
18411849
test "function with dynamic signatures" do
18421850
assert fun([dynamic(integer())], float()) |> to_quoted_string() ==
18431851
"(dynamic(integer()) -> float())"

0 commit comments

Comments
 (0)