@@ -343,7 +343,7 @@ defmodule Module.Types.Descr do
343343 def empty? ( :term ) , do: false
344344
345345 def empty? ( % { } = descr ) do
346- case Map . get ( descr , :dynamic , descr ) do
346+ case :maps . get ( :dynamic , descr , _default = descr ) do
347347 :term ->
348348 false
349349
@@ -1897,18 +1897,18 @@ defmodule Module.Types.Descr do
18971897
18981898 defp map_non_negated_fuse ( maps ) do
18991899 Enum . reduce ( maps , [ ] , fn map , acc ->
1900- fuse_with_first_fusible ( map , acc )
1900+ map_fuse_with_first_fusible ( map , acc )
19011901 end )
19021902 end
19031903
1904- defp fuse_with_first_fusible ( map , [ ] ) , do: [ map ]
1904+ defp map_fuse_with_first_fusible ( map , [ ] ) , do: [ map ]
19051905
1906- defp fuse_with_first_fusible ( map , [ candidate | rest ] ) do
1906+ defp map_fuse_with_first_fusible ( map , [ candidate | rest ] ) do
19071907 if fused = maybe_optimize_map_union ( map , candidate ) do
19081908 # we found a fusible candidate, we're done
19091909 [ fused | rest ]
19101910 else
1911- [ candidate | fuse_with_first_fusible ( map , rest ) ]
1911+ [ candidate | map_fuse_with_first_fusible ( map , rest ) ]
19121912 end
19131913 end
19141914
@@ -2157,9 +2157,106 @@ defmodule Module.Types.Descr do
21572157 end
21582158 end
21592159
2160- # Removes duplicates in union, which should trickle to other operations.
2161- # This is a cheap optimization that relies on structural equality.
2162- defp tuple_union ( left , right ) , do: left ++ ( right -- left )
2160+ defp tuple_union ( dnf1 , dnf2 ) do
2161+ # Union is just concatenation, but we rely on some optimization strategies to
2162+ # avoid the list to grow when possible
2163+
2164+ # first pass trying to identify patterns where two maps can be fused as one
2165+ with [ tuple1 ] <- dnf1 ,
2166+ [ tuple2 ] <- dnf2 ,
2167+ optimized when optimized != nil <- maybe_optimize_tuple_union ( tuple1 , tuple2 ) do
2168+ [ optimized ]
2169+ else
2170+ # otherwise we just concatenate and remove structural duplicates
2171+ _ -> dnf1 ++ ( dnf2 -- dnf1 )
2172+ end
2173+ end
2174+
2175+ defp maybe_optimize_tuple_union ( { tag1 , pos1 , [ ] } = tuple1 , { tag2 , pos2 , [ ] } = tuple2 ) do
2176+ case tuple_union_optimization_strategy ( tag1 , pos1 , tag2 , pos2 ) do
2177+ :all_equal ->
2178+ tuple1
2179+
2180+ { :one_index_difference , index , v1 , v2 } ->
2181+ new_pos = List . replace_at ( pos1 , index , union ( v1 , v2 ) )
2182+ { tag1 , new_pos , [ ] }
2183+
2184+ :left_subtype_of_right ->
2185+ tuple2
2186+
2187+ :right_subtype_of_left ->
2188+ tuple1
2189+
2190+ nil ->
2191+ nil
2192+ end
2193+ end
2194+
2195+ defp maybe_optimize_tuple_union ( _ , _ ) , do: nil
2196+
2197+ defp tuple_union_optimization_strategy ( tag1 , pos1 , tag2 , pos2 )
2198+ defp tuple_union_optimization_strategy ( tag , pos , tag , pos ) , do: :all_equal
2199+
2200+ # might be one extra loop but cheap and avoids doing deep subtype comparisons
2201+ defp tuple_union_optimization_strategy ( :closed , pos1 , :closed , pos2 )
2202+ when length ( pos1 ) != length ( pos2 ) ,
2203+ do: nil
2204+
2205+ defp tuple_union_optimization_strategy ( tag1 , pos1 , tag2 , pos2 ) do
2206+ status =
2207+ case { tag1 , tag2 } do
2208+ { :open , :closed } -> :right_subtype_of_left
2209+ { :closed , :open } -> :left_subtype_of_right
2210+ { same , same } -> :all_equal
2211+ end
2212+
2213+ do_tuple_union_optimization_strategy ( tag1 , pos1 , tag2 , pos2 , 0 , status )
2214+ end
2215+
2216+ defp do_tuple_union_optimization_strategy ( _tag1 , [ ] , _tag2 , [ ] , _i , status ) , do: status
2217+
2218+ defp do_tuple_union_optimization_strategy ( :open , [ ] , _tag2 , _pos2 , _i , status )
2219+ when status in [ :all_equal , :right_subtype_of_left ] ,
2220+ do: :right_subtype_of_left
2221+
2222+ defp do_tuple_union_optimization_strategy ( _tag1 , _pos1 , :open , [ ] , _i , status )
2223+ when status in [ :all_equal , :left_subtype_of_right ] ,
2224+ do: :left_subtype_of_right
2225+
2226+ defp do_tuple_union_optimization_strategy ( tag1 , [ v1 | pos1 ] , tag2 , [ v2 | pos2 ] , i , status ) do
2227+ if next_status = tuple_union_next_strategy ( i , v1 , v2 , status ) do
2228+ do_tuple_union_optimization_strategy ( tag1 , pos1 , tag2 , pos2 , i + 1 , next_status )
2229+ end
2230+ end
2231+
2232+ defp do_tuple_union_optimization_strategy ( _tag1 , _pos1 , _tag2 , _pos2 , _i , _status ) , do: nil
2233+
2234+ defp tuple_union_next_strategy ( index , v1 , v2 , status )
2235+
2236+ # structurally equal values do not impact the ongoing strategy
2237+ defp tuple_union_next_strategy ( _index , same , same , status ) , do: status
2238+
2239+ defp tuple_union_next_strategy ( index , v1 , v2 , :all_equal ) do
2240+ { :one_index_difference , index , v1 , v2 }
2241+ end
2242+
2243+ defp tuple_union_next_strategy ( _index , v1 , v2 , { :one_index_difference , _ , d1 , d2 } ) do
2244+ # we have at least two differences now, we switch strategy
2245+ # if both are subtypes in one direction, keep checking
2246+ cond do
2247+ subtype? ( d1 , d2 ) and subtype? ( v1 , v2 ) -> :left_subtype_of_right
2248+ subtype? ( d2 , d1 ) and subtype? ( v2 , v1 ) -> :right_subtype_of_left
2249+ true -> nil
2250+ end
2251+ end
2252+
2253+ defp tuple_union_next_strategy ( _index , v1 , v2 , :left_subtype_of_right ) do
2254+ if subtype? ( v1 , v2 ) , do: :left_subtype_of_right
2255+ end
2256+
2257+ defp tuple_union_next_strategy ( _index , v1 , v2 , :right_subtype_of_left ) do
2258+ if subtype? ( v2 , v1 ) , do: :right_subtype_of_left
2259+ end
21632260
21642261 defp tuple_to_quoted ( dnf , opts ) do
21652262 dnf
@@ -2189,27 +2286,19 @@ defmodule Module.Types.Descr do
21892286
21902287 defp tuple_non_negated_fuse ( tuples ) do
21912288 Enum . reduce ( tuples , [ ] , fn tuple , acc ->
2192- case Enum . split_while ( acc , & non_fusible_tuples? ( tuple , & 1 ) ) do
2193- { _ , [ ] } ->
2194- [ tuple | acc ]
2195-
2196- { others , [ match | rest ] } ->
2197- fused = tuple_non_negated_fuse_pair ( tuple , match )
2198- others ++ [ fused | rest ]
2199- end
2289+ tuple_fuse_with_first_fusible ( tuple , acc )
22002290 end )
22012291 end
22022292
2203- # Two tuples are fusible if they have no negations and differ in at most one element.
2204- defp non_fusible_tuples? ( { _ , elems1 , [ ] } , { _ , elems2 , [ ] } ) do
2205- Enum . zip ( elems1 , elems2 ) |> Enum . count_until ( fn { a , b } -> a != b end , 2 ) > 1
2206- end
2207-
2208- defp tuple_non_negated_fuse_pair ( { tag , elems1 , [ ] } , { _ , elems2 , [ ] } ) do
2209- fused_elements =
2210- Enum . zip_with ( elems1 , elems2 , fn a , b -> if a == b , do: a , else: union ( a , b ) end )
2293+ defp tuple_fuse_with_first_fusible ( tuple , [ ] ) , do: [ tuple ]
22112294
2212- { tag , fused_elements , [ ] }
2295+ defp tuple_fuse_with_first_fusible ( tuple , [ candidate | rest ] ) do
2296+ if fused = maybe_optimize_tuple_union ( tuple , candidate ) do
2297+ # we found a fusible candidate, we're done
2298+ [ fused | rest ]
2299+ else
2300+ [ candidate | tuple_fuse_with_first_fusible ( tuple , rest ) ]
2301+ end
22132302 end
22142303
22152304 defp tuple_each_to_quoted ( { tag , positive_tuple , negative_tuples } , opts ) do
0 commit comments