@@ -2258,15 +2258,20 @@ defmodule Module.Types.Descr do
22582258 do: :bdd_bot ,
22592259 else: bdd_leaf ( list1 , difference ( last1 , last2 ) )
22602260 else
2261- bdd_difference ( bdd1 , bdd2 , & list_leaf_disjoint? / 2 )
2261+ bdd_difference ( bdd1 , bdd2 , & list_leaf_compare / 2 )
22622262 end
22632263 end
22642264
22652265 defp list_difference ( bdd1 , bdd2 ) ,
2266- do: bdd_difference ( bdd1 , bdd2 , & list_leaf_disjoint? / 2 )
2266+ do: bdd_difference ( bdd1 , bdd2 , & list_leaf_compare / 2 )
22672267
2268- defp list_leaf_disjoint? ( bdd_leaf ( list1 , last1 ) , bdd_leaf ( list2 , last2 ) ) ,
2269- do: disjoint? ( list1 , list2 ) or disjoint? ( last1 , last2 )
2268+ defp list_leaf_compare ( bdd_leaf ( list1 , last1 ) , bdd_leaf ( list2 , last2 ) ) do
2269+ if disjoint? ( list1 , list2 ) or disjoint? ( last1 , last2 ) do
2270+ :disjoint
2271+ else
2272+ :none
2273+ end
2274+ end
22702275
22712276 defp list_empty? ( @ non_empty_list_top ) , do: false
22722277
@@ -2969,13 +2974,13 @@ defmodule Module.Types.Descr do
29692974 end
29702975
29712976 _ when is_atom ( tag ) and is_atom ( neg_tag ) ->
2972- case map_difference_strategy ( fields , neg_fields , tag , neg_tag , :all_equal ) do
2973- :all_equal when tag == neg_tag or neg_tag == :open ->
2974- :bdd_bot
2975-
2977+ case map_difference_strategy ( fields , neg_fields , tag , neg_tag ) do
29762978 :disjoint ->
29772979 bdd_leaf ( tag , fields )
29782980
2981+ :left_subtype_of_right ->
2982+ :bdd_bot
2983+
29792984 { :one_key_difference , key , v1 , v2 } ->
29802985 t_diff = difference ( fields_get ( fields , key , v1 ) , v2 )
29812986
@@ -2985,38 +2990,28 @@ defmodule Module.Types.Descr do
29852990 bdd_leaf ( tag , fields_store ( key , t_diff , fields ) )
29862991 end
29872992
2988- :left_subtype_of_right ->
2989- :bdd_bot
2990-
29912993 _ ->
29922994 bdd_difference ( map1 , map2 )
29932995 end
29942996
29952997 _ ->
2996- bdd_difference ( map1 , map2 , & map_leaf_disjoint? / 2 )
2998+ bdd_difference ( map1 , map2 , & map_leaf_compare / 2 )
29972999 end
29983000 end
29993001
30003002 defp map_difference ( bdd_leaf ( :open , [ ] ) , bdd2 ) ,
30013003 do: bdd_negation ( bdd2 )
30023004
3003- defp map_difference ( bdd1 , bdd2 ) ,
3004- do: bdd_difference ( bdd1 , bdd2 , & map_leaf_disjoint? / 2 )
3005-
3006- defp map_leaf_disjoint? ( bdd_leaf ( _tag1 , fields1 ) , bdd_leaf ( _tag2 , fields2 ) ) do
3007- disjoint_structs? ( fields1 , fields2 )
3005+ defp map_difference ( bdd1 , bdd2 ) do
3006+ bdd_difference ( bdd1 , bdd2 , & map_leaf_compare / 2 )
30083007 end
30093008
3010- defp disjoint_structs? ( fields1 , fields2 ) do
3011- case { fields_find ( :__struct__ , fields1 ) , fields_find ( :__struct__ , fields2 ) } do
3012- { { :ok , % { atom: atom } = d1 } , { :ok , d2 } } when map_size ( d1 ) == 1 ->
3013- disjoint_atom_descr? ( atom , d2 )
3014-
3015- { { :ok , d1 } , { :ok , % { atom: atom } = d2 } } when map_size ( d2 ) == 1 ->
3016- disjoint_atom_descr? ( atom , d1 )
3017-
3018- _ ->
3019- false
3009+ defp map_leaf_compare ( bdd_leaf ( tag , fields ) , bdd_leaf ( neg_tag , neg_fields ) ) do
3010+ case map_difference_strategy ( fields , neg_fields , tag , neg_tag ) do
3011+ :disjoint -> :disjoint
3012+ :left_subtype_of_right -> :subtype
3013+ { :one_key_difference , _ , v1 , v2 } -> if subtype? ( v1 , v2 ) , do: :subtype , else: :none
3014+ _ -> :none
30203015 end
30213016 end
30223017
@@ -4361,10 +4356,7 @@ defmodule Module.Types.Descr do
43614356 if empty_intersection? do
43624357 { acc_fields , acc_negs }
43634358 else
4364- case map_difference_strategy ( acc_fields , neg_fields , tag , neg_tag , :all_equal ) do
4365- :all_equal when tag == neg_tag or neg_tag == :open ->
4366- { acc_fields , acc_negs }
4367-
4359+ case map_difference_strategy ( acc_fields , neg_fields , tag , neg_tag ) do
43684360 { :one_key_difference , key , v1 , v2 } ->
43694361 { fields_store ( key , difference ( v1 , v2 ) , acc_fields ) , acc_negs }
43704362
@@ -4378,13 +4370,27 @@ defmodule Module.Types.Descr do
43784370 end )
43794371 end
43804372
4381- defp map_difference_strategy ( [ { k1 , _ } | t1 ] , [ { k2 , _ } | _ ] = l2 , tag1 , tag2 , status )
4373+ defp map_difference_strategy ( fields1 , fields2 , tag1 , tag2 ) do
4374+ if is_atom ( tag1 ) and is_atom ( tag2 ) do
4375+ status = if tag1 == tag2 or tag2 == :open , do: :all_equal , else: :none
4376+ map_difference_strategy ( fields1 , fields2 , tag1 , tag2 , status )
4377+ else
4378+ :none
4379+ end
4380+ end
4381+
4382+ defp map_difference_strategy ( [ { k1 , value } | t1 ] , [ { k2 , _ } | _ ] = l2 , tag1 , tag2 , status )
43824383 when k1 < k2 do
43834384 # Left side has a key the right side does not have,
43844385 # left can only be a subtype if the right side is open.
4386+ # If the right side is closed and the key is not optional, they are disjoint.
43854387 case status do
4386- _ when tag2 != :open ->
4387- :none
4388+ _ when tag2 == :closed ->
4389+ if not is_optional_static ( value ) do
4390+ :disjoint
4391+ else
4392+ map_difference_strategy ( t1 , l2 , tag1 , tag2 , :none )
4393+ end
43884394
43894395 :all_equal ->
43904396 map_difference_strategy ( t1 , l2 , tag1 , tag2 , :left_subtype_of_right )
@@ -4402,11 +4408,15 @@ defmodule Module.Types.Descr do
44024408 end
44034409 end
44044410
4405- defp map_difference_strategy ( [ { k1 , _ } | _ ] , [ { k2 , value } | _ ] , tag1 , _tag2 , _status )
4411+ defp map_difference_strategy ( [ { k1 , _ } | _ ] = l1 , [ { k2 , value } | t2 ] , tag1 , tag2 , _status )
44064412 when k1 > k2 do
44074413 # Right side has a key the left side does not have,
44084414 # if left-side is closed, they are disjoint.
4409- if tag1 == :closed and not is_optional_static ( value ) , do: :disjoint , else: :none
4415+ if tag1 == :closed and not is_optional_static ( value ) do
4416+ :disjoint
4417+ else
4418+ map_difference_strategy ( l1 , t2 , tag1 , tag2 , :none )
4419+ end
44104420 end
44114421
44124422 defp map_difference_strategy ( [ { _ , v } | t1 ] , [ { _ , v } | t2 ] , tag1 , tag2 , status ) do
@@ -4430,20 +4440,19 @@ defmodule Module.Types.Descr do
44304440 :none
44314441 end
44324442
4433- # all_equal or left_subtype_of_right
44344443 _ ->
4435- if subtype? ( v1 , v2 ) ,
4444+ if status in [ :all_equal , :left_subtype_of_right ] and subtype? ( v1 , v2 ) ,
44364445 do: map_difference_strategy ( t1 , t2 , tag1 , tag2 , :left_subtype_of_right ) ,
4437- else: :none
4446+ else: map_difference_strategy ( t1 , t2 , tag1 , tag2 , :none )
44384447 end
44394448 end
44404449 end
44414450
44424451 defp map_difference_strategy ( [ ] , [ ] , _tag1 , _tag2 , status ) do
4443- status
4452+ if status == :all_equal , do: :left_subtype_of_right , else: status
44444453 end
44454454
4446- defp map_difference_strategy ( _l1 , l2 , tag1 , tag2 , status ) do
4455+ defp map_difference_strategy ( l1 , l2 , tag1 , tag2 , status ) do
44474456 cond do
44484457 tag2 == :open and l2 == [ ] ->
44494458 case status do
@@ -4455,11 +4464,17 @@ defmodule Module.Types.Descr do
44554464
44564465 :left_subtype_of_right ->
44574466 :left_subtype_of_right
4467+
4468+ :none ->
4469+ :none
44584470 end
44594471
44604472 tag1 == :closed and l2 != [ ] and Enum . all? ( l2 , fn { _ , v } -> not is_optional_static ( v ) end ) ->
44614473 :disjoint
44624474
4475+ tag2 == :closed and l1 != [ ] and Enum . all? ( l1 , fn { _ , v } -> not is_optional_static ( v ) end ) ->
4476+ :disjoint
4477+
44634478 true ->
44644479 :none
44654480 end
@@ -4816,11 +4831,15 @@ defmodule Module.Types.Descr do
48164831 do: bdd_negation ( bdd2 )
48174832
48184833 defp tuple_difference ( bdd1 , bdd2 ) ,
4819- do: bdd_difference ( bdd1 , bdd2 , & tuple_leaf_disjoint ?/ 2 )
4834+ do: bdd_difference ( bdd1 , bdd2 , & tuple_leaf_compare ?/ 2 )
48204835
4821- defp tuple_leaf_disjoint? ( bdd_leaf ( tag1 , elements1 ) , bdd_leaf ( tag2 , elements2 ) ) do
4822- mismatched_tuple_sizes? ( tag1 , elements1 , tag2 , elements2 ) or
4823- disjoint_tagged_tuples? ( elements1 , elements2 )
4836+ defp tuple_leaf_compare? ( bdd_leaf ( tag1 , elements1 ) , bdd_leaf ( tag2 , elements2 ) ) do
4837+ if mismatched_tuple_sizes? ( tag1 , elements1 , tag2 , elements2 ) or
4838+ disjoint_tagged_tuples? ( elements1 , elements2 ) do
4839+ :disjoint
4840+ else
4841+ :none
4842+ end
48244843 end
48254844
48264845 # A very cheap check for tagged tuples
@@ -5593,65 +5612,87 @@ defmodule Module.Types.Descr do
55935612 defp bdd_difference_union ( i , u1 , u2 ) ,
55945613 do: bdd_difference ( i , bdd_union ( u1 , u2 ) )
55955614
5596- # Optimize differences
5597- #
5598- # ## When D2 == :bottom
5599- #
5600- # We can rewrite the BDD format:
5615+ ## Optimize differences
5616+
5617+ # For the right-side being a leaf, we have:
56015618 #
5602- # B1 and not B2
5603- # ((a1 and C1) or B1_no_C1) and not B2
5604- # ((a1 and C1) and not B2) or (B1_no_C1 and not B2)
5605- # (a1 and C1 and not ((a2 and C2) or U2)) or (B1_no_C1 and not B2)
5606- # (a1 and C1 and not (a2 and C2) and not U2) or (B1_no_C1 and not B2)
5607- # (a1 and C1 and not U2) or (B1_no_C1 and not B2)
5608- # (a1 and C1 and not U2) or (U1 and not B2) or (not a1 and D1 and not B2)
5619+ # ((a1 and C1) or U1 or (not a1 and D1)) and not a2
56095620 #
5610- # The last line is equivalent to the BDD:
5611- # {a1, C1 and not U2, U1 and not B2, D1 and not B2}
5621+ # If disjoint?(a1, a2), we end up with:
56125622 #
5613- # ## When D2 != :bottom
5623+ # (a1 and C1) or (U1 and not d2) or (not a1 and D1 and not a2)
56145624 #
5615- # We could rewrite it to use the same optimization as bdd_leaf_intersection.
5616- # However, given differences of negations are not common and because
5617- # bdd_leaf_intersection can be expensive for open maps, we skip this step for now.
5625+ # If subtype?(a1, a2), we end up with:
56185626 #
5619- # (B1) and not (B2)
5620- # (B1) and not (B2_no_D2 or (not a2 and D2))
5621- # (B1) and (not B2_no_D2 and (a2 or not D2))
5622- # (B1) and (a2 or not D2) and not B2_no_D2
5623- # ((B1 and a2) or (B1 and not D2)) and not B2_no_D2
5627+ # (U1 and not d2) or (D1 and not a2)
5628+ defp bdd_difference ( { a1 , c1 , u1 , d1 } = bdd1 , bdd_leaf ( _ , _ ) = bdd2 , leaf_compare )
5629+ when is_tuple ( bdd2 ) do
5630+ case leaf_compare . ( a1 , bdd2 ) do
5631+ :disjoint ->
5632+ bdd_union (
5633+ bdd_difference ( u1 , bdd2 , leaf_compare ) ,
5634+ bdd_difference ( { a1 , :bdd_bot , :bdd_bot , d1 } , bdd2 )
5635+ )
5636+ |> bdd_union ( { a1 , c1 , :bdd_bot , :bdd_bot } )
56245637
5625- defp bdd_difference ( bdd1 , { _ , _ , _ , d } = bdd2 , _leaf_disjoint ) when d != :bdd_bot do
5626- bdd_difference ( bdd1 , bdd2 )
5627- end
5638+ :subtype ->
5639+ bdd_union ( bdd_difference ( u1 , bdd2 , leaf_compare ) , bdd_difference ( d1 , bdd2 , leaf_compare ) )
56285640
5629- defp bdd_difference ( bdd_leaf ( _ , _ ) = bdd1 , bdd2 , leaf_disjoint )
5630- when is_tuple ( bdd2 ) do
5631- { leaf , _ , u , :bdd_bot } = bdd_expand ( bdd2 )
5641+ :none when a1 < bdd2 ->
5642+ { a1 , bdd_difference ( c1 , bdd2 , leaf_compare ) , bdd_difference ( u1 , bdd2 , leaf_compare ) ,
5643+ bdd_difference ( d1 , bdd2 , leaf_compare ) }
5644+ |> case do
5645+ { _ , :bdd_bot , u , :bdd_bot } -> u
5646+ other -> other
5647+ end
56325648
5633- case leaf_disjoint . ( bdd1 , leaf ) do
5634- true when u == :bdd_bot -> bdd1
5635- true -> bdd_difference ( bdd1 , u , leaf_disjoint )
5636- false -> bdd_difference ( bdd1 , bdd2 )
5649+ :none ->
5650+ bdd_difference ( bdd1 , bdd2 )
56375651 end
56385652 end
56395653
5640- defp bdd_difference ( { a1 , c1 , u1 , d1 } = bdd1 , bdd2 , leaf_disjoint )
5641- when is_tuple ( bdd2 ) do
5642- { a2 , _c2 , u2 , :bdd_bot } = bdd_expand ( bdd2 )
5654+ # For the left-side being a leaf, we have:
5655+ #
5656+ # a1 and not ((a2 and C2) or U2 or (not a2 and D2))
5657+ # a1 and not (a2 and C2) and not U2 and (a2 or not D2)
5658+ #
5659+ # If disjoint?(a1, a2):
5660+ #
5661+ # a1 and not (a2 and C2) = a1 (a2 and C2 is subset of a2, disjoint from a1)
5662+ # a1 and (a2 or not D2) = a1 and not D2 (a1 and a2 = bottom)
5663+ #
5664+ # Result: a1 and not D2 and not U2
5665+ #
5666+ # If subtype?(a1, a2):
5667+ #
5668+ # a1 and not (a2 and C2) = a1 and not C2 (a1 and not a2 = bottom)
5669+ # a1 and (a2 or not D2) = a1 (a1 and a2 = a1)
5670+ #
5671+ # Result: a1 and not C2 and not U2
5672+ defp bdd_difference ( bdd_leaf ( _ , _ ) = bdd1 , bdd2 , leaf_compare ) when is_tuple ( bdd2 ) do
5673+ { a2 , c2 , u2 , d2 } = bdd_expand ( bdd2 )
56435674
5644- case leaf_disjoint . ( a1 , a2 ) do
5645- true ->
5646- { a1 , bdd_difference ( c1 , u2 , leaf_disjoint ) , bdd_difference ( u1 , bdd2 , leaf_disjoint ) ,
5647- bdd_difference ( d1 , bdd2 , leaf_disjoint ) }
5675+ case leaf_compare . ( bdd1 , a2 ) do
5676+ :disjoint ->
5677+ bdd1 |> bdd_difference ( u2 , leaf_compare ) |> bdd_difference ( d2 , leaf_compare )
5678+
5679+ :subtype ->
5680+ bdd1 |> bdd_difference ( u2 , leaf_compare ) |> bdd_difference ( c2 , leaf_compare )
5681+
5682+ :none when a2 < bdd1 ->
5683+ { a2 , bdd_difference ( bdd1 , bdd_union ( c2 , u2 ) , leaf_compare ) , :bdd_bot ,
5684+ bdd_difference ( bdd1 , bdd_union ( d2 , u2 ) , leaf_compare ) }
5685+ |> case do
5686+ { _ , :bdd_bot , u , :bdd_bot } -> u
5687+ other -> other
5688+ end
56485689
5649- false ->
5690+ :none ->
56505691 bdd_difference ( bdd1 , bdd2 )
56515692 end
56525693 end
56535694
5654- defp bdd_difference ( bdd1 , bdd2 , _leaf_disjoint ) do
5695+ defp bdd_difference ( bdd1 , bdd2 , _leaf_compare ) do
56555696 bdd_difference ( bdd1 , bdd2 )
56565697 end
56575698
0 commit comments