@@ -1264,8 +1264,115 @@ defmodule Module.Types.Descr do
12641264
12651265 defp map_only? ( descr ) , do: empty? ( Map . delete ( descr , :map ) )
12661266
1267- # Union is list concatenation
1268- defp map_union ( dnf1 , dnf2 ) , do: dnf1 ++ ( dnf2 -- dnf1 )
1267+ defp map_union ( dnf1 , dnf2 ) do
1268+ # Union is just concatenation, but we rely on some optimization strategies to
1269+ # avoid the list to grow when possible
1270+
1271+ # first pass trying to identify patterns where two maps can be fused as one
1272+ with [ map1 ] <- dnf1 ,
1273+ [ map2 ] <- dnf2 ,
1274+ optimized when optimized != nil <- maybe_optimize_map_union ( map1 , map2 ) do
1275+ [ optimized ]
1276+ else
1277+ # otherwise we just concatenate and remove structural duplicates
1278+ _ -> dnf1 ++ ( dnf2 -- dnf1 )
1279+ end
1280+ end
1281+
1282+ defp maybe_optimize_map_union ( { tag1 , pos1 , [ ] } = map1 , { tag2 , pos2 , [ ] } = map2 ) do
1283+ case map_union_optimization_strategy ( tag1 , pos1 , tag2 , pos2 ) do
1284+ :all_equal ->
1285+ map1
1286+
1287+ :any_map ->
1288+ { :open , % { } , [ ] }
1289+
1290+ { :one_key_difference , key , v1 , v2 } ->
1291+ new_pos = Map . put ( pos1 , key , union ( v1 , v2 ) )
1292+ { tag1 , new_pos , [ ] }
1293+
1294+ :left_subtype_of_right ->
1295+ map2
1296+
1297+ :right_subtype_of_left ->
1298+ map1
1299+
1300+ nil ->
1301+ nil
1302+ end
1303+ end
1304+
1305+ defp maybe_optimize_map_union ( _ , _ ) , do: nil
1306+
1307+ defp map_union_optimization_strategy ( tag1 , pos1 , tag2 , pos2 )
1308+ defp map_union_optimization_strategy ( tag , pos , tag , pos ) , do: :all_equal
1309+ defp map_union_optimization_strategy ( :open , empty , _ , _ ) when empty == % { } , do: :any_map
1310+ defp map_union_optimization_strategy ( _ , _ , :open , empty ) when empty == % { } , do: :any_map
1311+
1312+ defp map_union_optimization_strategy ( tag , pos1 , tag , pos2 )
1313+ when map_size ( pos1 ) == map_size ( pos2 ) do
1314+ :maps . iterator ( pos1 )
1315+ |> :maps . next ( )
1316+ |> do_map_union_optimization_strategy ( pos2 , :all_equal )
1317+ end
1318+
1319+ defp map_union_optimization_strategy ( :open , pos1 , _ , pos2 )
1320+ when map_size ( pos1 ) <= map_size ( pos2 ) do
1321+ :maps . iterator ( pos1 )
1322+ |> :maps . next ( )
1323+ |> do_map_union_optimization_strategy ( pos2 , :right_subtype_of_left )
1324+ end
1325+
1326+ defp map_union_optimization_strategy ( _ , pos1 , :open , pos2 )
1327+ when map_size ( pos1 ) >= map_size ( pos2 ) do
1328+ :maps . iterator ( pos2 )
1329+ |> :maps . next ( )
1330+ |> do_map_union_optimization_strategy ( pos1 , :right_subtype_of_left )
1331+ |> case do
1332+ :right_subtype_of_left -> :left_subtype_of_right
1333+ nil -> nil
1334+ end
1335+ end
1336+
1337+ defp map_union_optimization_strategy ( _ , _ , _ , _ ) , do: nil
1338+
1339+ defp do_map_union_optimization_strategy ( :none , _ , status ) , do: status
1340+
1341+ defp do_map_union_optimization_strategy ( { key , v1 , iterator } , pos2 , status ) do
1342+ with % { ^ key => v2 } <- pos2 ,
1343+ next_status when next_status != nil <- map_union_next_strategy ( key , v1 , v2 , status ) do
1344+ do_map_union_optimization_strategy ( :maps . next ( iterator ) , pos2 , next_status )
1345+ else
1346+ _ -> nil
1347+ end
1348+ end
1349+
1350+ defp map_union_next_strategy ( key , v1 , v2 , status )
1351+
1352+ # structurally equal values do not impact the ongoing strategy
1353+ defp map_union_next_strategy ( _key , same , same , status ) , do: status
1354+
1355+ defp map_union_next_strategy ( key , v1 , v2 , :all_equal ) do
1356+ if key != :__struct__ , do: { :one_key_difference , key , v1 , v2 }
1357+ end
1358+
1359+ defp map_union_next_strategy ( _key , v1 , v2 , { :one_key_difference , _ , d1 , d2 } ) do
1360+ # we have at least two key differences now, we switch strategy
1361+ # if both are subtypes in one direction, keep checking
1362+ cond do
1363+ subtype? ( d1 , d2 ) and subtype? ( v1 , v2 ) -> :left_subtype_of_right
1364+ subtype? ( d2 , d1 ) and subtype? ( v2 , v1 ) -> :right_subtype_of_left
1365+ true -> nil
1366+ end
1367+ end
1368+
1369+ defp map_union_next_strategy ( _key , v1 , v2 , :left_subtype_of_right ) do
1370+ if subtype? ( v1 , v2 ) , do: :left_subtype_of_right
1371+ end
1372+
1373+ defp map_union_next_strategy ( _key , v1 , v2 , :right_subtype_of_left ) do
1374+ if subtype? ( v2 , v1 ) , do: :right_subtype_of_left
1375+ end
12691376
12701377 # Given two unions of maps, intersects each pair of maps.
12711378 defp map_intersection ( dnf1 , dnf2 ) do
@@ -1747,29 +1854,19 @@ defmodule Module.Types.Descr do
17471854
17481855 defp map_non_negated_fuse ( maps ) do
17491856 Enum . reduce ( maps , [ ] , fn map , acc ->
1750- case Enum . split_while ( acc , & non_fusible_maps? ( map , & 1 ) ) do
1751- { _ , [ ] } ->
1752- [ map | acc ]
1753-
1754- { others , [ match | rest ] } ->
1755- fused = map_non_negated_fuse_pair ( map , match )
1756- others ++ [ fused | rest ]
1757- end
1857+ fuse_with_first_fusible ( map , acc )
17581858 end )
17591859 end
17601860
1761- # Two maps are fusible if they differ in at most one element.
1762- defp non_fusible_maps? ( { _ , fields1 , [ ] } , { _ , fields2 , [ ] } ) do
1763- Enum . count_until ( fields1 , fn { key , value } -> Map . fetch! ( fields2 , key ) != value end , 2 ) > 1
1764- end
1765-
1766- defp map_non_negated_fuse_pair ( { tag , fields1 , [ ] } , { _ , fields2 , [ ] } ) do
1767- fields =
1768- symmetrical_merge ( fields1 , fields2 , fn _k , v1 , v2 ->
1769- if v1 == v2 , do: v1 , else: union ( v1 , v2 )
1770- end )
1861+ defp fuse_with_first_fusible ( map , [ ] ) , do: [ map ]
17711862
1772- { tag , fields , [ ] }
1863+ defp fuse_with_first_fusible ( map , [ candidate | rest ] ) do
1864+ if fused = maybe_optimize_map_union ( map , candidate ) do
1865+ # we found a fusible candidate, we're done
1866+ [ fused | rest ]
1867+ else
1868+ [ candidate | fuse_with_first_fusible ( map , rest ) ]
1869+ end
17731870 end
17741871
17751872 # If all fields are the same except one, we can optimize map difference.
0 commit comments