@@ -220,9 +220,9 @@ defmodule Module.Types.Descr do
220
220
defp union ( :bitmap , v1 , v2 ) , do: v1 ||| v2
221
221
defp union ( :dynamic , v1 , v2 ) , do: dynamic_union ( v1 , v2 )
222
222
defp union ( :list , v1 , v2 ) , do: list_union ( v1 , v2 )
223
- defp union ( :map , v1 , v2 ) , do: map_union ( v1 , v2 )
223
+ defp union ( :map , v1 , v2 ) , do: map_or_tuple_union ( v1 , v2 )
224
224
defp union ( :optional , 1 , 1 ) , do: 1
225
- defp union ( :tuple , v1 , v2 ) , do: tuple_union ( v1 , v2 )
225
+ defp union ( :tuple , v1 , v2 ) , do: map_or_tuple_union ( v1 , v2 )
226
226
227
227
@ doc """
228
228
Computes the intersection of two descrs.
@@ -1278,8 +1278,75 @@ defmodule Module.Types.Descr do
1278
1278
1279
1279
defp map_only? ( descr ) , do: empty? ( Map . delete ( descr , :map ) )
1280
1280
1281
- # Union is list concatenation
1282
- defp map_union ( dnf1 , dnf2 ) , do: dnf1 ++ ( dnf2 -- dnf1 )
1281
+ defp map_or_tuple_union ( dnf1 , dnf2 ) do
1282
+ if optimized = maybe_optimize_union ( dnf1 , dnf2 ) do
1283
+ optimized
1284
+ else
1285
+ # Union is list concatenation
1286
+ # Removes duplicates in union, which should trickle to other operations.
1287
+ # This is a cheap optimization that relies on structural equality.
1288
+ dnf1 ++ ( dnf2 -- dnf1 )
1289
+ end
1290
+ end
1291
+
1292
+ defp maybe_optimize_union ( [ { tag1 , pos1 , [ ] } ] = dnf1 , [ { tag2 , pos2 , [ ] } ] = dnf2 ) do
1293
+ # Avoid the explosion for unions of very similar maps or tuples,
1294
+ # by returning only one if it contains the other
1295
+ # e.g. %{a: integer()} or %{..., a: term()} -> %{..., a: term()}
1296
+ cond do
1297
+ map_or_tuple_simple_subtype? ( tag1 , pos1 , tag2 , pos2 ) -> dnf2
1298
+ map_or_tuple_simple_subtype? ( tag2 , pos2 , tag1 , pos1 ) -> dnf1
1299
+ true -> nil
1300
+ end
1301
+ end
1302
+
1303
+ defp maybe_optimize_union ( _ , _ ) , do: nil
1304
+
1305
+ defp map_or_tuple_simple_subtype? ( _ , _ , :open , pos2 ) when pos2 == % { } , do: true
1306
+
1307
+ defp map_or_tuple_simple_subtype? ( :closed , pos1 , :closed , pos2 )
1308
+ when map_size ( pos1 ) == map_size ( pos2 ) do
1309
+ all_key_simple_subtypes? ( pos1 , pos2 )
1310
+ end
1311
+
1312
+ defp map_or_tuple_simple_subtype? ( _ , pos1 , :open , pos2 ) when map_size ( pos1 ) >= map_size ( pos2 ) do
1313
+ all_key_simple_subtypes? ( pos1 , pos2 )
1314
+ end
1315
+
1316
+ defp map_or_tuple_simple_subtype? ( _ , _ , _ , _ ) , do: false
1317
+
1318
+ defp all_key_simple_subtypes? ( pos1 , pos2 ) do
1319
+ Enum . all? ( pos1 , fn { key , type1 } ->
1320
+ case pos2 do
1321
+ % { ^ key => type2 } -> simple_subtype? ( type1 , type2 )
1322
+ _ -> false
1323
+ end
1324
+ end )
1325
+ end
1326
+
1327
+ defp simple_subtype? ( _ , :term ) , do: true
1328
+ defp simple_subtype? ( same , same ) , do: true
1329
+
1330
+ defp simple_subtype? ( type1 , type2 ) when map_size ( type1 ) == 1 and map_size ( type2 ) == 1 do
1331
+ case { type1 , type2 } do
1332
+ { % { atom: _ } , % { atom: { :negation , neg } } } when neg == % { } ->
1333
+ true
1334
+
1335
+ { % { bitmap: bitmap1 } , % { bitmap: bitmap2 } } ->
1336
+ ( bitmap1 &&& bitmap2 ) === bitmap2
1337
+
1338
+ { % { map: [ { tag1 , pos1 , [ ] } ] } , % { map: [ { tag2 , pos2 , [ ] } ] } } ->
1339
+ map_or_tuple_simple_subtype? ( tag1 , pos1 , tag2 , pos2 )
1340
+
1341
+ { % { tuple: [ { tag1 , pos1 , [ ] } ] } , % { tuple: [ { tag2 , pos2 , [ ] } ] } } ->
1342
+ map_or_tuple_simple_subtype? ( tag1 , pos1 , tag2 , pos2 )
1343
+
1344
+ _ ->
1345
+ false
1346
+ end
1347
+ end
1348
+
1349
+ defp simple_subtype? ( _ , _ ) , do: false
1283
1350
1284
1351
# Given two unions of maps, intersects each pair of maps.
1285
1352
defp map_intersection ( dnf1 , dnf2 ) do
@@ -2049,10 +2116,6 @@ defmodule Module.Types.Descr do
2049
2116
end
2050
2117
end
2051
2118
2052
- # Removes duplicates in union, which should trickle to other operations.
2053
- # This is a cheap optimization that relies on structural equality.
2054
- defp tuple_union ( left , right ) , do: left ++ ( right -- left )
2055
-
2056
2119
defp tuple_to_quoted ( dnf , opts ) do
2057
2120
dnf
2058
2121
|> tuple_simplify ( )
0 commit comments