@@ -1268,6 +1268,91 @@ defmodule Module.Types.DescrTest do
12681268      # assert difference(tuple([number(), term()]), tuple([integer(), atom()])) 
12691269      #        |> to_quoted_string() == 
12701270      #          "{float(), term()} or {number(), term() and not atom()}" 
1271+ 
1272+       assert  union ( tuple ( [ integer ( ) ,  atom ( ) ] ) ,  tuple ( [ integer ( ) ,  atom ( ) ] ) )  |>  to_quoted_string ( )  == 
1273+                "{integer(), atom()}" 
1274+ 
1275+       assert  union ( tuple ( [ integer ( ) ,  atom ( ) ] ) ,  tuple ( [ float ( ) ,  atom ( ) ] ) )  |>  to_quoted_string ( )  == 
1276+                "{float() or integer(), atom()}" 
1277+ 
1278+       assert  union ( tuple ( [ integer ( ) ,  atom ( ) ] ) ,  tuple ( [ float ( ) ,  atom ( ) ] ) ) 
1279+              |>  union ( tuple ( [ pid ( ) ,  pid ( ) ,  port ( ) ] ) ) 
1280+              |>  union ( tuple ( [ pid ( ) ,  pid ( ) ,  atom ( ) ] ) ) 
1281+              |>  to_quoted_string ( )  == 
1282+                "{float() or integer(), atom()} or {pid(), pid(), atom() or port()}" 
1283+ 
1284+       assert  union ( open_tuple ( [ integer ( ) ] ) ,  open_tuple ( [ float ( ) ] ) )  |>  to_quoted_string ( )  == 
1285+                "{float() or integer(), ...}" 
1286+ 
1287+       # {:ok, {term(), integer()}} or {:ok, {term(), float()}} or {:exit, :kill} or {:exit, :timeout} 
1288+       assert  tuple ( [ atom ( [ :ok ] ) ,  tuple ( [ term ( ) ,  empty_list ( ) ] ) ] ) 
1289+              |>  union ( tuple ( [ atom ( [ :ok ] ) ,  tuple ( [ term ( ) ,  open_map ( ) ] ) ] ) ) 
1290+              |>  union ( tuple ( [ atom ( [ :exit ] ) ,  atom ( [ :kill ] ) ] ) ) 
1291+              |>  union ( tuple ( [ atom ( [ :exit ] ) ,  atom ( [ :timeout ] ) ] ) ) 
1292+              |>  to_quoted_string ( )  == 
1293+                "{:exit, :kill or :timeout} or {:ok, {term(), %{...} or empty_list()}}" 
1294+ 
1295+       # Detection of duplicates 
1296+       assert  tuple ( [ atom ( [ :ok ] ) ,  term ( ) ] ) 
1297+              |>  union ( tuple ( [ atom ( [ :ok ] ) ,  term ( ) ] ) ) 
1298+              |>  to_quoted_string ( )  ==  "{:ok, term()}" 
1299+ 
1300+       assert  tuple ( [ closed_map ( a:  integer ( ) ,  b:  atom ( ) ) ,  open_map ( ) ] ) 
1301+              |>  union ( tuple ( [ closed_map ( a:  integer ( ) ,  b:  atom ( ) ) ,  open_map ( ) ] ) ) 
1302+              |>  to_quoted_string ( )  == 
1303+                "{%{a: integer(), b: atom()}, %{...}}" 
1304+ 
1305+       # Nested fusion 
1306+       assert  tuple ( [ closed_map ( a:  integer ( ) ,  b:  atom ( ) ) ,  open_map ( ) ] ) 
1307+              |>  union ( tuple ( [ closed_map ( a:  float ( ) ,  b:  atom ( ) ) ,  open_map ( ) ] ) ) 
1308+              |>  to_quoted_string ( )  == 
1309+                "{%{a: float() or integer(), b: atom()}, %{...}}" 
1310+ 
1311+       # Complex simplification of map/tuple combinations. Initial type is: 
1312+       # ``` 
1313+       #   dynamic( 
1314+       #     :error or 
1315+       #     ({%Decimal{coef: :inf, exp: integer(), sign: integer()}, binary()} or 
1316+       #       {%Decimal{coef: :NaN, exp: integer(), sign: integer()}, binary()} or 
1317+       #       {%Decimal{coef: integer(), exp: integer(), sign: integer()}, term()} or 
1318+       #       {%Decimal{coef: :inf, exp: integer(), sign: integer()} or 
1319+       #           %Decimal{coef: :NaN, exp: integer(), sign: integer()} or 
1320+       #           %Decimal{coef: integer(), exp: integer(), sign: integer()}, term()}) 
1321+       #   ) 
1322+       # ``` 
1323+       decimal_inf  = 
1324+         closed_map ( 
1325+           __struct__:  atom ( [ Decimal ] ) , 
1326+           coef:  atom ( [ :inf ] ) , 
1327+           exp:  integer ( ) , 
1328+           sign:  integer ( ) 
1329+         ) 
1330+ 
1331+       decimal_nan  = 
1332+         closed_map ( 
1333+           __struct__:  atom ( [ Decimal ] ) , 
1334+           coef:  atom ( [ :NaN ] ) , 
1335+           exp:  integer ( ) , 
1336+           sign:  integer ( ) 
1337+         ) 
1338+ 
1339+       decimal_int  = 
1340+         closed_map ( __struct__:  atom ( [ Decimal ] ) ,  coef:  integer ( ) ,  exp:  integer ( ) ,  sign:  integer ( ) ) 
1341+ 
1342+       assert  atom ( [ :error ] ) 
1343+              |>  union ( 
1344+                tuple ( [ decimal_inf ,  binary ( ) ] ) 
1345+                |>  union ( 
1346+                  tuple ( [ decimal_nan ,  binary ( ) ] ) 
1347+                  |>  union ( 
1348+                    tuple ( [ decimal_int ,  term ( ) ] ) 
1349+                    |>  union ( tuple ( [ union ( decimal_inf ,  union ( decimal_nan ,  decimal_int ) ) ,  term ( ) ] ) ) 
1350+                  ) 
1351+                ) 
1352+              ) 
1353+              |>  dynamic ( ) 
1354+              |>  to_quoted_string ( )  == 
1355+                "dynamic(\n   :error or\n     ({%Decimal{coef: integer() or (:NaN or :inf), exp: integer(), sign: integer()}, term()} or\n        {%Decimal{coef: :NaN or :inf, exp: integer(), sign: integer()}, binary()})\n )" 
12711356    end 
12721357
12731358    test  "map"  do 
@@ -1311,6 +1396,50 @@ defmodule Module.Types.DescrTest do
13111396      assert  difference ( open_map ( a:  number ( ) ,  b:  atom ( ) ) ,  open_map ( a:  integer ( ) ) ) 
13121397             |>  to_quoted_string ( )  ==  "%{..., a: float(), b: atom()}" 
13131398
1399+       # Basic map fusion 
1400+       assert  union ( closed_map ( a:  integer ( ) ) ,  closed_map ( a:  integer ( ) ) )  |>  to_quoted_string ( )  == 
1401+                "%{a: integer()}" 
1402+ 
1403+       assert  union ( closed_map ( a:  integer ( ) ) ,  closed_map ( a:  float ( ) ) )  |>  to_quoted_string ( )  == 
1404+                "%{a: float() or integer()}" 
1405+ 
1406+       # Nested fusion 
1407+       assert  union ( closed_map ( a:  integer ( ) ,  b:  atom ( ) ) ,  closed_map ( a:  float ( ) ,  b:  atom ( ) ) ) 
1408+              |>  union ( closed_map ( x:  pid ( ) ,  y:  pid ( ) ,  z:  port ( ) ) ) 
1409+              |>  union ( closed_map ( x:  pid ( ) ,  y:  pid ( ) ,  z:  atom ( ) ) ) 
1410+              |>  to_quoted_string ( )  == 
1411+                "%{a: float() or integer(), b: atom()} or %{x: pid(), y: pid(), z: atom() or port()}" 
1412+ 
1413+       # Open map fusion 
1414+       assert  union ( open_map ( a:  integer ( ) ) ,  open_map ( a:  float ( ) ) )  |>  to_quoted_string ( )  == 
1415+                "%{..., a: float() or integer()}" 
1416+ 
1417+       # Fusing complex nested maps with unions 
1418+       assert  closed_map ( status:  atom ( [ :ok ] ) ,  data:  closed_map ( value:  term ( ) ,  count:  empty_list ( ) ) ) 
1419+              |>  union ( 
1420+                closed_map ( status:  atom ( [ :ok ] ) ,  data:  closed_map ( value:  term ( ) ,  count:  open_map ( ) ) ) 
1421+              ) 
1422+              |>  union ( closed_map ( status:  atom ( [ :error ] ) ,  reason:  atom ( [ :timeout ] ) ) ) 
1423+              |>  union ( closed_map ( status:  atom ( [ :error ] ) ,  reason:  atom ( [ :crash ] ) ) ) 
1424+              |>  to_quoted_string ( )  == 
1425+                "%{data: %{count: %{...} or empty_list(), value: term()}, status: :ok} or\n   %{reason: :crash or :timeout, status: :error}" 
1426+ 
1427+       # Difference and union tests 
1428+       assert  closed_map ( status:  atom ( [ :ok ] ) ,  value:  term ( ) ) 
1429+              |>  difference ( closed_map ( status:  atom ( [ :ok ] ) ,  value:  float ( ) ) ) 
1430+              |>  union ( 
1431+                closed_map ( status:  atom ( [ :ok ] ) ,  value:  term ( ) ) 
1432+                |>  difference ( closed_map ( status:  atom ( [ :ok ] ) ,  value:  integer ( ) ) ) 
1433+              ) 
1434+              |>  to_quoted_string ( )  == 
1435+                "%{status: :ok, value: term()}" 
1436+ 
1437+       # Nested map fusion 
1438+       assert  closed_map ( data:  closed_map ( x:  integer ( ) ,  y:  atom ( ) ) ,  meta:  open_map ( ) ) 
1439+              |>  union ( closed_map ( data:  closed_map ( x:  float ( ) ,  y:  atom ( ) ) ,  meta:  open_map ( ) ) ) 
1440+              |>  to_quoted_string ( )  == 
1441+                "%{data: %{x: float() or integer(), y: atom()}, meta: %{...}}" 
1442+ 
13141443      # Test complex combinations 
13151444      assert  intersection ( open_map ( a:  number ( ) ,  b:  atom ( ) ) ,  open_map ( a:  integer ( ) ,  c:  boolean ( ) ) ) 
13161445             |>  union ( difference ( open_map ( x:  atom ( ) ) ,  open_map ( x:  boolean ( ) ) ) ) 
0 commit comments