@@ -343,7 +343,7 @@ defmodule Module.Types.Descr do
343
343
def empty? ( :term ) , do: false
344
344
345
345
def empty? ( % { } = descr ) do
346
- case Map . get ( descr , :dynamic , descr ) do
346
+ case :maps . get ( :dynamic , descr , _default = descr ) do
347
347
:term ->
348
348
false
349
349
@@ -1897,18 +1897,18 @@ defmodule Module.Types.Descr do
1897
1897
1898
1898
defp map_non_negated_fuse ( maps ) do
1899
1899
Enum . reduce ( maps , [ ] , fn map , acc ->
1900
- fuse_with_first_fusible ( map , acc )
1900
+ map_fuse_with_first_fusible ( map , acc )
1901
1901
end )
1902
1902
end
1903
1903
1904
- defp fuse_with_first_fusible ( map , [ ] ) , do: [ map ]
1904
+ defp map_fuse_with_first_fusible ( map , [ ] ) , do: [ map ]
1905
1905
1906
- defp fuse_with_first_fusible ( map , [ candidate | rest ] ) do
1906
+ defp map_fuse_with_first_fusible ( map , [ candidate | rest ] ) do
1907
1907
if fused = maybe_optimize_map_union ( map , candidate ) do
1908
1908
# we found a fusible candidate, we're done
1909
1909
[ fused | rest ]
1910
1910
else
1911
- [ candidate | fuse_with_first_fusible ( map , rest ) ]
1911
+ [ candidate | map_fuse_with_first_fusible ( map , rest ) ]
1912
1912
end
1913
1913
end
1914
1914
@@ -2157,9 +2157,106 @@ defmodule Module.Types.Descr do
2157
2157
end
2158
2158
end
2159
2159
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
2163
2260
2164
2261
defp tuple_to_quoted ( dnf , opts ) do
2165
2262
dnf
@@ -2189,27 +2286,19 @@ defmodule Module.Types.Descr do
2189
2286
2190
2287
defp tuple_non_negated_fuse ( tuples ) do
2191
2288
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 )
2200
2290
end )
2201
2291
end
2202
2292
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 ]
2211
2294
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
2213
2302
end
2214
2303
2215
2304
defp tuple_each_to_quoted ( { tag , positive_tuple , negative_tuples } , opts ) do
0 commit comments