@@ -1278,8 +1278,130 @@ 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_union ( dnf1 , dnf2 ) do
1282
+ # Union is just concatenation, but we rely on some optimization strategies to
1283
+ # avoid the list to grow when possible
1284
+
1285
+ # first pass trying to identify patterns where two maps can be fused as one
1286
+ with [ { tag1 , pos1 , [ ] } ] <- dnf1 ,
1287
+ [ { tag2 , pos2 , [ ] } ] <- dnf2 ,
1288
+ strategy when strategy != nil <- map_union_optimization_strategy ( tag1 , pos1 , tag2 , pos2 ) do
1289
+ case strategy do
1290
+ :all_equal ->
1291
+ dnf1
1292
+
1293
+ :any_map ->
1294
+ [ { :open , % { } , [ ] } ]
1295
+
1296
+ { :one_key_difference , key , v1 , v2 } ->
1297
+ new_pos = Map . put ( pos1 , key , union ( v1 , v2 ) )
1298
+ [ { tag1 , new_pos , [ ] } ]
1299
+
1300
+ :left_subtype_of_right ->
1301
+ dnf2
1302
+
1303
+ :right_subtype_of_left ->
1304
+ dnf1
1305
+ end
1306
+ else
1307
+ # otherwise we just concatenate and remove structural duplicates
1308
+ _ -> dnf1 ++ ( dnf2 -- dnf1 )
1309
+ end
1310
+ end
1311
+
1312
+ defp map_union_optimization_strategy ( tag1 , pos1 , tag2 , pos2 )
1313
+ defp map_union_optimization_strategy ( tag , pos , tag , pos ) , do: :all_equal
1314
+ defp map_union_optimization_strategy ( :open , empty , _ , _ ) when empty == % { } , do: :any_map
1315
+ defp map_union_optimization_strategy ( _ , _ , :open , empty ) when empty == % { } , do: :any_map
1316
+
1317
+ defp map_union_optimization_strategy ( tag , pos1 , tag , pos2 )
1318
+ when map_size ( pos1 ) == map_size ( pos2 ) do
1319
+ :maps . iterator ( pos1 )
1320
+ |> :maps . next ( )
1321
+ |> do_map_union_optimization_strategy ( pos2 , :all_equal )
1322
+ end
1323
+
1324
+ defp map_union_optimization_strategy ( :open , pos1 , _ , pos2 )
1325
+ when map_size ( pos1 ) <= map_size ( pos2 ) do
1326
+ :maps . iterator ( pos1 )
1327
+ |> :maps . next ( )
1328
+ |> do_map_union_optimization_strategy ( pos2 , :right_subtype_of_left )
1329
+ end
1330
+
1331
+ defp map_union_optimization_strategy ( _ , pos1 , :open , pos2 )
1332
+ when map_size ( pos1 ) >= map_size ( pos2 ) do
1333
+ :maps . iterator ( pos2 )
1334
+ |> :maps . next ( )
1335
+ |> do_map_union_optimization_strategy ( pos1 , :right_subtype_of_left )
1336
+ |> case do
1337
+ :right_subtype_of_left -> :left_subtype_of_right
1338
+ nil -> nil
1339
+ end
1340
+ end
1341
+
1342
+ defp map_union_optimization_strategy ( _ , _ , _ , _ ) , do: nil
1343
+
1344
+ defp do_map_union_optimization_strategy ( :none , _ , status ) , do: status
1345
+
1346
+ defp do_map_union_optimization_strategy ( { key , v1 , iterator } , pos2 , status ) do
1347
+ with % { ^ key => v2 } <- pos2 ,
1348
+ next_status when next_status != nil <- map_union_next_strategy ( key , v1 , v2 , status ) do
1349
+ do_map_union_optimization_strategy ( :maps . next ( iterator ) , pos2 , next_status )
1350
+ else
1351
+ _ -> nil
1352
+ end
1353
+ end
1354
+
1355
+ defp map_union_next_strategy ( key , v1 , v2 , status )
1356
+
1357
+ # structurally equal values do not impact the ongoing strategy
1358
+ defp map_union_next_strategy ( _key , same , same , status ) , do: status
1359
+
1360
+ defp map_union_next_strategy ( key , v1 , v2 , :all_equal ) do
1361
+ if key != :__struct__ , do: { :one_key_difference , key , v1 , v2 }
1362
+ end
1363
+
1364
+ defp map_union_next_strategy ( _key , v1 , v2 , { :one_key_difference , _ , d1 , d2 } ) do
1365
+ # we have at least two key differences now, we switch strategy
1366
+ # if both are subtypes in one direction, keep checking
1367
+ cond do
1368
+ trivial_subtype? ( d1 , d2 ) and trivial_subtype? ( v1 , v2 ) -> :left_subtype_of_right
1369
+ trivial_subtype? ( d2 , d1 ) and trivial_subtype? ( v2 , v1 ) -> :right_subtype_of_left
1370
+ true -> nil
1371
+ end
1372
+ end
1373
+
1374
+ defp map_union_next_strategy ( _key , v1 , v2 , :left_subtype_of_right ) do
1375
+ if trivial_subtype? ( v1 , v2 ) , do: :left_subtype_of_right
1376
+ end
1377
+
1378
+ defp map_union_next_strategy ( _key , v1 , v2 , :right_subtype_of_left ) do
1379
+ if trivial_subtype? ( v2 , v1 ) , do: :right_subtype_of_left
1380
+ end
1381
+
1382
+ # cheap to compute sub-typing
1383
+ # a trivial subtype is always a subtype, but not all subtypes are subtypes
1384
+ defp trivial_subtype? ( _ , :term ) , do: true
1385
+ defp trivial_subtype? ( same , same ) , do: true
1386
+
1387
+ defp trivial_subtype? ( % { } = left , % { } = right )
1388
+ when map_size ( left ) == 1 and map_size ( right ) == 1 do
1389
+ case { left , right } do
1390
+ { % { atom: _ } , % { atom: { :negation , neg } } } when neg == % { } ->
1391
+ true
1392
+
1393
+ { % { map: _ } , % { map: [ { :open , pos , [ ] } ] } } when pos == % { } ->
1394
+ true
1395
+
1396
+ { % { bitmap: bitmap1 } , % { bitmap: bitmap2 } } ->
1397
+ ( bitmap1 &&& bitmap2 ) === bitmap2
1398
+
1399
+ _ ->
1400
+ false
1401
+ end
1402
+ end
1403
+
1404
+ defp trivial_subtype? ( _ , _ ) , do: false
1283
1405
1284
1406
# Given two unions of maps, intersects each pair of maps.
1285
1407
defp map_intersection ( dnf1 , dnf2 ) do
0 commit comments