@@ -3254,25 +3254,88 @@ defmodule Module.Types.Descr do
32543254 end
32553255
32563256 { tag , fields , negs } , acc ->
3257- { fst , snd } = map_pop_key ( tag , fields , key )
3257+ { value , bdd } = map_pop_key_bdd ( tag , fields , key )
32583258
3259- case map_split_negative_key ( negs , key ) do
3260- :empty ->
3261- acc
3262-
3263- negative ->
3264- negative
3265- |> pair_make_disjoint ( )
3266- |> pair_eliminate_negations_fst ( fst , snd )
3267- |> union ( acc )
3268- end
3259+ negs
3260+ |> map_split_negative_key ( key , value , bdd )
3261+ |> Enum . reduce ( acc , fn { value , _ } , acc -> union ( value , acc ) end )
32693262 end )
32703263 catch
32713264 :open -> { true , term ( ) }
32723265 else
32733266 value -> pop_optional_static ( value )
32743267 end
32753268
3269+ defp map_split_negative_key ( negs , key , value , bdd ) do
3270+ map_split_negative ( negs , value , bdd , fn neg_tag , neg_fields ->
3271+ case fields_take ( key , neg_fields ) do
3272+ { neg_value , neg_fields } -> { true , neg_value , map_new ( neg_tag , neg_fields ) }
3273+ :error -> { false , map_key_tag_to_type ( neg_tag ) , map_new ( neg_tag , neg_fields ) }
3274+ end
3275+ end )
3276+ end
3277+
3278+ # Remove negatives:
3279+ # {t, s} \ {t₁, s₁} = {t \ t₁, s} ∪ {t ∩ t₁, s \ s₁}
3280+ defp map_split_negative ( negs , value , bdd , take_fun ) do
3281+ Enum . reduce ( negs , [ { value , bdd } ] , fn
3282+ { :open , empty } , _acc when is_fields_empty ( empty ) ->
3283+ throw ( :empty )
3284+
3285+ { neg_tag , neg_fields } , acc ->
3286+ { found? , neg_value , neg_bdd } = take_fun . ( neg_tag , neg_fields )
3287+
3288+ if not found? and neg_tag == :open do
3289+ # In case the map is open, t \ t₁ is always empty,
3290+ # so we just need to deal with the bdd.
3291+ Enum . reduce ( acc , [ ] , fn { value , bdd } , acc ->
3292+ diff_bdd = map_difference ( bdd , neg_bdd )
3293+
3294+ if map_empty? ( diff_bdd ) do
3295+ acc
3296+ else
3297+ [ { value , diff_bdd } | acc ]
3298+ end
3299+ end )
3300+ else
3301+ Enum . reduce ( acc , [ ] , fn { value , bdd } , acc ->
3302+ # If the negative tag is closed, then they are likely disjoint,
3303+ # so we can drastically cut down the amount of operations.
3304+ if neg_tag == :closed and map_empty? ( map_intersection ( bdd , neg_bdd ) ) do
3305+ [ { value , bdd } | acc ]
3306+ else
3307+ diff_bdd = map_difference ( bdd , neg_bdd )
3308+
3309+ cond do
3310+ value == neg_value or subtype? ( value , neg_value ) ->
3311+ if map_empty? ( diff_bdd ) , do: acc , else: [ { value , diff_bdd } | acc ]
3312+
3313+ map_empty? ( diff_bdd ) ->
3314+ prepend_pair_unless_empty_diff ( value , neg_value , bdd , acc )
3315+
3316+ true ->
3317+ prepend_pair_unless_empty_diff ( value , neg_value , bdd , [ { value , diff_bdd } | acc ] )
3318+ end
3319+ end
3320+ end )
3321+ end
3322+ end )
3323+ catch
3324+ :empty -> [ ]
3325+ end
3326+
3327+ defp map_pop_key_bdd ( tag , fields , key ) do
3328+ case fields_take ( key , fields ) do
3329+ { value , fields } -> { value , map_new ( tag , fields ) }
3330+ :error -> { map_key_tag_to_type ( tag ) , map_new ( tag , fields ) }
3331+ end
3332+ end
3333+
3334+ defp prepend_pair_unless_empty_diff ( value , neg_value , bdd , acc ) do
3335+ diff_value = difference ( value , neg_value )
3336+ if empty? ( diff_value ) , do: acc , else: [ { diff_value , bdd } | acc ]
3337+ end
3338+
32763339 @ doc """
32773340 Returns the map converted into a list.
32783341 """
@@ -3645,29 +3708,26 @@ defmodule Module.Types.Descr do
36453708 # Note: if initial is nil, it means the value is not required.
36463709 # So we don't compute it for performance.
36473710 defp map_dnf_pop_key_static ( dnf , key , initial ) do
3648- { value , descr } =
3649- Enum . reduce ( dnf , { initial , none ( ) } , fn
3711+ { value , bdd } =
3712+ Enum . reduce ( dnf , { initial , :bdd_bot } , fn
36503713 # Optimization: if there are no negatives, we can directly remove the key.
3651- { tag , fields , [ ] } , { value , map } ->
3652- { fst , snd } = map_pop_key ( tag , fields , key )
3653- { maybe_union ( value , fn -> fst end ) , union ( map , snd ) }
3654-
3655- { tag , fields , negs } , { value , map } ->
3656- { fst , snd } = map_pop_key ( tag , fields , key )
3714+ { tag , fields , [ ] } , { value , bdd } ->
3715+ { fst , snd } = map_pop_key_bdd ( tag , fields , key )
3716+ { maybe_union ( value , fn -> fst end ) , map_union ( bdd , snd ) }
36573717
3658- case map_split_negative_key ( negs , key ) do
3659- :empty ->
3660- { value , map }
3718+ { tag , fields , negs } , { value , bdd } ->
3719+ { fst , snd } = map_pop_key_bdd ( tag , fields , key )
3720+ pairs = map_split_negative_key ( negs , key , fst , snd )
36613721
3662- negative ->
3663- disjoint = pair_make_disjoint ( negative )
3664-
3665- { maybe_union ( value , fn -> pair_eliminate_negations_fst ( disjoint , fst , snd ) end ) ,
3666- disjoint |> pair_eliminate_negations_snd ( fst , snd ) |> union ( map ) }
3667- end
3722+ { maybe_union ( value , fn -> Enum . reduce ( pairs , none ( ) , & union ( elem ( & 1 , 0 ) , & 2 ) ) end ) ,
3723+ Enum . reduce ( pairs , bdd , & map_union ( elem ( & 1 , 1 ) , & 2 ) ) }
36683724 end )
36693725
3670- { value , descr }
3726+ if bdd == :bdd_bot do
3727+ { value , % { } }
3728+ else
3729+ { value , % { map: bdd } }
3730+ end
36713731 end
36723732
36733733 # For keys with `not :foo`, we generate an approximation
@@ -3990,18 +4050,13 @@ defmodule Module.Types.Descr do
39904050 map_domain_tag_to_type ( tag , domain_key ) |> union ( acc )
39914051
39924052 { tag_or_domains , fields , negs } , acc ->
3993- { fst , snd } = map_pop_domain_no_optional ( tag_or_domains , fields , domain_key )
3994-
3995- case map_split_negative_domain ( negs , domain_key ) do
3996- :empty ->
3997- acc
4053+ { _found , value , bdd } = map_pop_domain_bdd ( tag_or_domains , fields , domain_key )
39984054
3999- negative ->
4000- negative
4001- |> pair_make_disjoint ( )
4002- |> pair_eliminate_negations_fst ( fst , snd )
4003- |> union ( acc )
4004- end
4055+ negs
4056+ |> map_split_negative ( value , bdd , fn neg_tag , neg_fields ->
4057+ map_pop_domain_bdd ( neg_tag , neg_fields , domain_key )
4058+ end )
4059+ |> Enum . reduce ( acc , fn { value , _ } , acc -> union ( value , acc ) end )
40054060 end )
40064061 end
40074062
@@ -4258,42 +4313,17 @@ defmodule Module.Types.Descr do
42584313 end )
42594314 end
42604315
4261- defp map_pop_key ( tag , fields , key ) do
4262- case fields_take ( key , fields ) do
4263- { value , fields } -> { value , % { map: map_new ( tag , fields ) } }
4264- :error -> { map_key_tag_to_type ( tag ) , % { map: map_new ( tag , fields ) } }
4265- end
4266- end
4267-
42684316 # Pop a domain type, already removing non optional.
4269- defp map_pop_domain_no_optional ( domains = % { } , fields , domain_key ) do
4317+ defp map_pop_domain_bdd ( domains = % { } , fields , domain_key ) do
42704318 case :maps . take ( domain_key , domains ) do
4271- { value , domains } -> { value , % { map: map_new ( domains , fields ) } }
4272- :error -> { none ( ) , % { map: map_new ( domains , fields ) } }
4319+ { value , domains } -> { true , value , map_new ( domains , fields ) }
4320+ :error -> { false , none ( ) , map_new ( domains , fields ) }
42734321 end
42744322 end
42754323
42764324 # Open/close key
4277- defp map_pop_domain_no_optional ( tag , fields , _domain_key ) ,
4278- do: { map_domain_tag_to_type ( tag ) , % { map: map_new ( tag , fields ) } }
4279-
4280- defp map_split_negative_key ( negs , key ) do
4281- Enum . reduce_while ( negs , [ ] , fn
4282- # A negation with an open map means the whole thing is empty.
4283- { :open , empty } , _acc when is_fields_empty ( empty ) -> { :halt , :empty }
4284- { tag , fields } , neg_acc -> { :cont , [ map_pop_key ( tag , fields , key ) | neg_acc ] }
4285- end )
4286- end
4287-
4288- defp map_split_negative_domain ( negs , domain_key ) do
4289- Enum . reduce_while ( negs , [ ] , fn
4290- { :open , empty } , _acc when is_fields_empty ( empty ) ->
4291- { :halt , :empty }
4292-
4293- { tag , fields } , neg_acc ->
4294- { :cont , [ map_pop_domain_no_optional ( tag , fields , domain_key ) | neg_acc ] }
4295- end )
4296- end
4325+ defp map_pop_domain_bdd ( tag , fields , _domain_key ) ,
4326+ do: { false , map_domain_tag_to_type ( tag ) , map_new ( tag , fields ) }
42974327
42984328 # Continue to eliminate negations while length of list of negs decreases
42994329 defp map_eliminate_while_negs_decrease ( tag , fields , [ ] ) , do: { tag , fields , [ ] }
@@ -5767,127 +5797,6 @@ defmodule Module.Types.Descr do
57675797 defp bdd_head ( { lit , _ , _ , _ } ) , do: lit
57685798 defp bdd_head ( pair ) , do: pair
57695799
5770- ## Pairs
5771-
5772- # To simplify disjunctive normal forms of e.g., map types, it is useful to
5773- # convert them into disjunctive normal forms of pairs of types, and define
5774- # normalization algorithms on pairs.
5775- #
5776- # The algorithms take a line, a list of pairs `{positive, negative}` where
5777- # `positive` is a list of literals and `negative` is a list of negated literals.
5778- # Positive pairs can all be intersected component-wise. Negative ones are
5779- # eliminated iteratively.
5780-
5781- # Eliminates negations from `{t, s} and not negative` where `negative` is a
5782- # union of pairs disjoint on their first component.
5783- #
5784- # Formula:
5785- # {t, s} and not (union<i=1..n> {t_i, s_i})
5786- # = union<i=1..n> {t and t_i, s and not s_i}
5787- # or {t and not (union{i=1..n} t_i), s}
5788- #
5789- # This eliminates all top-level negations and produces a union of pairs that
5790- # are disjoint on their first component. The function `pair_eliminate_negations_fst`
5791- # is optimized to only keep the first component out of those pairs.
5792- defp pair_eliminate_negations_fst ( negative , t , s ) do
5793- { pair_union , diff_of_t_i } =
5794- Enum . reduce ( negative , { none ( ) , t } , fn { t_i , s_i } , { accu , diff_of_t_i } ->
5795- i = intersection ( t , t_i )
5796-
5797- if empty? ( i ) do
5798- { accu , diff_of_t_i }
5799- else
5800- diff_of_t_i = difference ( diff_of_t_i , t_i )
5801- s_diff = difference ( s , s_i )
5802-
5803- if empty? ( s_diff ) ,
5804- do: { accu , diff_of_t_i } ,
5805- else: { union ( i , accu ) , diff_of_t_i }
5806- end
5807- end )
5808-
5809- union ( pair_union , diff_of_t_i )
5810- end
5811-
5812- # The formula above is symmetric with respect to the first and second components.
5813- # Hence the following also holds true:
5814- #
5815- # {t, s} and not (union<i=1..n> {t_i, s_i})
5816- # = union<i=1..n> {t and not t_i, s and s_i}
5817- # or {t, s and not (union{i=1..n} s_i)}
5818- #
5819- # which is used to in the following function, optimized to keep the second component.
5820- defp pair_eliminate_negations_snd ( negative , t , s ) do
5821- { pair_union , diff_of_s_i } =
5822- Enum . reduce ( negative , { none ( ) , s } , fn { t_i , s_i } , { accu , diff_of_s_i } ->
5823- i = intersection ( s , s_i )
5824-
5825- if empty? ( i ) do
5826- { accu , diff_of_s_i }
5827- else
5828- diff_of_s_i = difference ( diff_of_s_i , s_i )
5829- t_diff = difference ( t , t_i )
5830-
5831- if empty? ( t_diff ) ,
5832- do: { accu , diff_of_s_i } ,
5833- else: { union ( i , accu ) , diff_of_s_i }
5834- end
5835- end )
5836-
5837- union ( diff_of_s_i , pair_union )
5838- end
5839-
5840- # Makes a union of pairs into an equivalent union of disjoint pairs.
5841- #
5842- # Inserts a pair of types {fst, snd} into a list of pairs that are disjoint
5843- # on their first component. The invariant on `acc` is that its elements are
5844- # two-to-two disjoint with the first argument's `pairs`.
5845- #
5846- # To insert {fst, snd} into a disjoint pairs list, we go through the list to find
5847- # each pair whose first element has a non-empty intersection with `fst`. Then
5848- # we decompose {fst, snd} over each such pair to produce disjoint ones, and add
5849- # the decompositions into the accumulator.
5850- defp pair_make_disjoint ( pairs ) do
5851- Enum . reduce ( pairs , [ ] , fn { t1 , t2 } , acc -> add_pair_to_disjoint_list ( acc , t1 , t2 , [ ] ) end )
5852- end
5853-
5854- defp add_pair_to_disjoint_list ( [ ] , fst , snd , acc ) , do: [ { fst , snd } | acc ]
5855-
5856- defp add_pair_to_disjoint_list ( [ { s1 , s2 } | pairs ] , fst , snd , acc ) do
5857- x = intersection ( fst , s1 )
5858-
5859- if empty? ( x ) do
5860- add_pair_to_disjoint_list ( pairs , fst , snd , [ { s1 , s2 } | acc ] )
5861- else
5862- fst_diff = difference ( fst , s1 )
5863- s1_diff = difference ( s1 , fst )
5864- empty_fst_diff = empty? ( fst_diff )
5865- empty_s1_diff = empty? ( s1_diff )
5866-
5867- cond do
5868- # if fst is a subtype of s1, the disjointedness invariant ensures we can
5869- # add those two pairs and end the recursion
5870- empty_fst_diff and empty_s1_diff ->
5871- [ { x , union ( snd , s2 ) } | pairs ++ acc ]
5872-
5873- empty_fst_diff ->
5874- [ { s1_diff , s2 } , { x , union ( snd , s2 ) } | pairs ++ acc ]
5875-
5876- empty_s1_diff ->
5877- add_pair_to_disjoint_list ( pairs , fst_diff , snd , [ { x , union ( snd , s2 ) } | acc ] )
5878-
5879- true ->
5880- # case where, when comparing {fst, snd} and {s1, s2}, both (fst and not s1)
5881- # and (s1 and not fst) are non empty. that is, there is something in fst
5882- # that is not in s1, and something in s1 that is not in fst
5883- add_pair_to_disjoint_list ( pairs , fst_diff , snd , [
5884- { s1_diff , s2 } ,
5885- { x , union ( snd , s2 ) } | acc
5886- ] )
5887- end
5888- end
5889- end
5890-
58915800 ## Map helpers
58925801
58935802 # Erlang maps:merge_with/3 has to preserve the order in combiner.
0 commit comments