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