@@ -37,13 +37,15 @@ defmodule Module.Types.Descr do
3737
3838 defmacro bdd_leaf ( arg1 , arg2 ) , do: { arg1 , arg2 }
3939
40- # Map fields are stored as orddicts (sorted key-value lists).
40+ # Map fields and domains are stored as orddicts (sorted key-value lists).
4141 @ fields_new [ ]
4242 defguardp is_fields_empty ( fields ) when fields == [ ]
4343 defguardp fields_size ( fields ) when length ( fields )
4444
45- @ domain_key_types [ :binary , :integer , :float , :pid , :port , :reference ] ++
46- [ :fun , :atom , :tuple , :map , :list ]
45+ @ domain_key_types :lists . sort (
46+ [ :binary , :integer , :float , :pid , :port , :reference ] ++
47+ [ :fun , :atom , :tuple , :map , :list ]
48+ )
4749
4850 # Remark: those are explicit BDD constructors. The functional constructors are `bdd_new/1` and `bdd_new/3`.
4951 @ fun_top { :negation , % { } }
@@ -2649,14 +2651,14 @@ defmodule Module.Types.Descr do
26492651 defp domain_key_to_descr ( :list ) , do: @ list_top
26502652
26512653 defp map_descr ( tag , pairs ) do
2652- { fields , domains , dynamic? } = map_descr_pairs ( pairs , [ ] , % { } , false )
2654+ { fields , domains , dynamic? } = map_descr_pairs ( pairs , [ ] , @ fields_new , false )
26532655
26542656 map_new =
2655- if domains != % { } do
2657+ if not is_fields_empty ( domains ) do
26562658 domains =
26572659 if tag == :open do
26582660 value = term_or_optional ( )
2659- Enum . reduce ( @ domain_key_types , domains , & Map . put_new ( & 2 , & 1 , value ) )
2661+ fields_put_all_new ( domains , @ domain_key_types , value )
26602662 else
26612663 domains
26622664 end
@@ -2673,11 +2675,23 @@ defmodule Module.Types.Descr do
26732675 end
26742676
26752677 defp map_put_domain ( domain , domain_keys , value ) when is_list ( domain_keys ) do
2676- Enum . reduce ( domain_keys , domain , fn key , acc when is_atom ( key ) ->
2677- Map . update ( acc , key , if_set ( value ) , & union ( & 1 , value ) )
2678- end )
2678+ map_put_domain ( domain , :lists . usort ( domain_keys ) , if_set ( value ) , value )
2679+ end
2680+
2681+ defp map_put_domain ( [ { k1 , v1 } | t1 ] , [ k2 | _ ] = keys , initial , value ) when k1 < k2 do
2682+ [ { k1 , v1 } | map_put_domain ( t1 , keys , initial , value ) ]
2683+ end
2684+
2685+ defp map_put_domain ( [ { k1 , v1 } | t1 ] , [ k1 | keys ] , _initial , value ) do
2686+ [ { k1 , union ( v1 , value ) } | map_put_domain ( t1 , keys , if_set ( value ) , value ) ]
26792687 end
26802688
2689+ defp map_put_domain ( domain , [ k2 | keys ] , initial , value ) do
2690+ [ { k2 , initial } | map_put_domain ( domain , keys , initial , value ) ]
2691+ end
2692+
2693+ defp map_put_domain ( domain , [ ] , _initial , _value ) , do: domain
2694+
26812695 defp map_descr_pairs ( [ { key , :term } | rest ] , fields , domain , dynamic? ) do
26822696 case is_atom ( key ) do
26832697 true -> map_descr_pairs ( rest , [ { key , :term } | fields ] , domain , dynamic? )
@@ -2708,16 +2722,16 @@ defmodule Module.Types.Descr do
27082722 # Gets the default type associated to atom keys in a map.
27092723 defp map_key_tag_to_type ( :open ) , do: term_or_optional ( )
27102724 defp map_key_tag_to_type ( :closed ) , do: not_set ( )
2711- defp map_key_tag_to_type ( map = % { } ) , do: Map . get ( map , :atom , not_set ( ) )
2725+ defp map_key_tag_to_type ( domains ) , do: fields_get ( domains , :atom , not_set ( ) )
27122726
27132727 # Gets the domain type association to a map.
27142728 # In this case, we already remove the optional to simplify upstream.
27152729 @ compile { :inline , map_domain_tag_to_type: 1 }
27162730 defp map_domain_tag_to_type ( :open ) , do: term ( )
27172731 defp map_domain_tag_to_type ( :closed ) , do: none ( )
27182732
2719- defp map_domain_tag_to_type ( domain = % { } , key ) do
2720- remove_optional ( Map . get ( domain , key , none ( ) ) )
2733+ defp map_domain_tag_to_type ( domain , key ) when is_list ( domain ) do
2734+ remove_optional ( fields_get ( domain , key , none ( ) ) )
27212735 end
27222736
27232737 defp map_domain_tag_to_type ( domain , _key ) do
@@ -3071,29 +3085,34 @@ defmodule Module.Types.Descr do
30713085 defp map_domain_intersection ( :open , tag_or_domains ) , do: tag_or_domains
30723086 defp map_domain_intersection ( tag_or_domains , :open ) , do: tag_or_domains
30733087
3074- defp map_domain_intersection ( domains1 = % { } , domains2 = % { } ) do
3075- new_domains =
3076- for { domain_key , type1 } <- domains1 , reduce: % { } do
3077- acc_domains ->
3078- case domains2 do
3079- % { ^ domain_key => type2 } ->
3080- inter = intersection ( type1 , type2 )
3088+ defp map_domain_intersection ( domains1 , domains2 ) do
3089+ # If the explicit domains are empty, use simple atom tags
3090+ case map_domain_intersection_fields ( domains1 , domains2 ) do
3091+ [ ] -> :closed
3092+ new_domains -> new_domains
3093+ end
3094+ end
3095+
3096+ defp map_domain_intersection_fields ( [ { k1 , _ } | t1 ] , [ { k2 , _ } | _ ] = l2 ) when k1 < k2 do
3097+ map_domain_intersection_fields ( t1 , l2 )
3098+ end
30813099
3082- if empty_or_optional? ( inter ) do
3083- acc_domains
3084- else
3085- Map . put ( acc_domains , domain_key , inter )
3086- end
3100+ defp map_domain_intersection_fields ( [ { k1 , _ } | _ ] = l1 , [ { k2 , _ } | t2 ] ) when k1 > k2 do
3101+ map_domain_intersection_fields ( l1 , t2 )
3102+ end
30873103
3088- _ ->
3089- acc_domains
3090- end
3091- end
3104+ defp map_domain_intersection_fields ( [ { k , type1 } | t1 ] , [ { _ , type2 } | t2 ] ) do
3105+ inter = intersection ( type1 , type2 )
30923106
3093- # If the explicit domains are empty, use simple atom tags
3094- if map_size ( new_domains ) == 0 , do: :closed , else: new_domains
3107+ if empty_or_optional? ( inter ) do
3108+ map_domain_intersection_fields ( t1 , t2 )
3109+ else
3110+ [ { k , inter } | map_domain_intersection_fields ( t1 , t2 ) ]
3111+ end
30953112 end
30963113
3114+ defp map_domain_intersection_fields ( _ , _ ) , do: [ ]
3115+
30973116 defp map_literal_intersection_open_closed ( [ { k1 , v1 } | t1 ] , [ { k2 , _ } | _ ] = l2 ) when k1 < k2 do
30983117 # If the type in the open map is optional, we continue
30993118 case v1 do
@@ -3423,8 +3442,8 @@ defmodule Module.Types.Descr do
34233442 # which is filtered by `map_bdd_to_dnf_*/1`.
34243443 throw ( :open )
34253444
3426- domains = % { } ->
3427- Enum . reduce ( domains , acc , fn { domain_key , value } , acc ->
3445+ domains when is_list ( domains ) ->
3446+ fields_fold ( domains , acc , fn domain_key , value , acc ->
34283447 value = remove_optional ( value )
34293448
34303449 if empty? ( value ) do
@@ -3862,16 +3881,18 @@ defmodule Module.Types.Descr do
38623881 :open
38633882
38643883 :closed ->
3865- Map . from_keys ( domain_keys , if_set ( type_fun . ( true , none ( ) ) ) )
3884+ fields_from_keys ( domain_keys , if_set ( type_fun . ( true , none ( ) ) ) )
38663885
3867- domains = % { } ->
3886+ # Note: domain_keys may contain duplicates, so we cannot
3887+ # do a side-by-side traversal here.
3888+ domains when is_list ( domains ) ->
38683889 Enum . reduce ( domain_keys , domains , fn domain_key , acc ->
3869- case acc do
3870- % { ^ domain_key => value } ->
3871- % { acc | domain_key => union ( value , type_fun . ( true , remove_optional ( value ) ) ) }
3890+ case fields_find ( domain_key , acc ) do
3891+ { :ok , value } ->
3892+ fields_store ( domain_key , union ( value , type_fun . ( true , remove_optional ( value ) ) ) , acc )
38723893
3873- % { } ->
3874- Map . put ( acc , domain_key , if_set ( type_fun . ( true , none ( ) ) ) )
3894+ :error ->
3895+ fields_store ( domain_key , if_set ( type_fun . ( true , none ( ) ) ) , acc )
38753896 end
38763897 end )
38773898 end
@@ -4303,25 +4324,27 @@ defmodule Module.Types.Descr do
43034324
43044325 # An open map is a subtype iff the negative domains are all present as term_or_optional()
43054326 defp map_check_domain_keys? ( :open , neg_domains ) do
4306- map_size ( neg_domains ) == length ( @ domain_key_types ) and
4307- Enum . all? ( neg_domains , fn { _domain_key , type } -> subtype? ( term_or_optional ( ) , type ) end )
4327+ fields_size ( neg_domains ) == length ( @ domain_key_types ) and
4328+ Enum . all? ( fields_to_list ( neg_domains ) , fn { _domain_key , type } ->
4329+ subtype? ( term_or_optional ( ) , type )
4330+ end )
43084331 end
43094332
43104333 # A positive domains is smaller than a closed map iff all its keys are empty or optional
43114334 defp map_check_domain_keys? ( pos_domains , :closed ) do
4312- Enum . all? ( pos_domains , fn { _domain_key , type } -> empty_or_optional? ( type ) end )
4335+ Enum . all? ( fields_to_list ( pos_domains ) , fn { _domain_key , type } -> empty_or_optional? ( type ) end )
43134336 end
43144337
43154338 # Component-wise comparison of domains
43164339 defp map_check_domain_keys? ( pos_domains , neg_domains ) do
4317- Enum . all? ( pos_domains , fn { domain_key , type } ->
4318- subtype? ( type , Map . get ( neg_domains , domain_key , not_set ( ) ) )
4340+ Enum . all? ( fields_to_list ( pos_domains ) , fn { domain_key , type } ->
4341+ subtype? ( type , fields_get ( neg_domains , domain_key , not_set ( ) ) )
43194342 end )
43204343 end
43214344
43224345 # Pop a domain type, already removing non optional.
4323- defp map_pop_domain_bdd ( domains = % { } , fields , domain_key ) do
4324- case :maps . take ( domain_key , domains ) do
4346+ defp map_pop_domain_bdd ( domains , fields , domain_key ) when is_list ( domains ) do
4347+ case fields_take ( domain_key , domains ) do
43254348 { value , domains } -> { true , value , map_new ( domains , fields ) }
43264349 :error -> { false , none ( ) , map_new ( domains , fields ) }
43274350 end
@@ -4551,26 +4574,26 @@ defmodule Module.Types.Descr do
45514574 end
45524575 end
45534576
4554- def map_literal_to_quoted ( { :closed , empty } , _opts ) when is_fields_empty ( empty ) do
4577+ defp map_literal_to_quoted ( { :closed , empty } , _opts ) when is_fields_empty ( empty ) do
45554578 { :empty_map , [ ] , [ ] }
45564579 end
45574580
4558- def map_literal_to_quoted ( { :open , empty } , _opts ) when is_fields_empty ( empty ) do
4581+ defp map_literal_to_quoted ( { :open , empty } , _opts ) when is_fields_empty ( empty ) do
45594582 { :map , [ ] , [ ] }
45604583 end
45614584
4562- def map_literal_to_quoted ( { domains = % { } , empty } , _opts )
4563- when map_size ( domains ) == 0 and is_fields_empty ( empty ) do
4585+ defp map_literal_to_quoted ( { domains , empty } , _opts )
4586+ when is_fields_empty ( domains ) and is_fields_empty ( empty ) do
45644587 { :empty_map , [ ] , [ ] }
45654588 end
45664589
4567- def map_literal_to_quoted ( { :open , [ { :__struct__ , @ not_atom_or_optional } ] } , _opts ) do
4590+ defp map_literal_to_quoted ( { :open , [ { :__struct__ , @ not_atom_or_optional } ] } , _opts ) do
45684591 { :non_struct_map , [ ] , [ ] }
45694592 end
45704593
4571- def map_literal_to_quoted ( { domains = % { } , fields } , opts ) do
4594+ defp map_literal_to_quoted ( { domains , fields } , opts ) when is_list ( domains ) do
45724595 domain_fields =
4573- for { domain_key , value_type } <- domains do
4596+ for { domain_key , value_type } <- fields_to_list ( domains ) do
45744597 non_optional = remove_optional_static ( value_type )
45754598
45764599 value_quoted =
@@ -4587,7 +4610,7 @@ defmodule Module.Types.Descr do
45874610 { :%{} , [ ] , domain_fields ++ regular_fields_quoted }
45884611 end
45894612
4590- def map_literal_to_quoted ( { tag , fields } , opts ) do
4613+ defp map_literal_to_quoted ( { tag , fields } , opts ) do
45914614 case tag do
45924615 :closed ->
45934616 with { :ok , struct_descr } <- fields_find ( :__struct__ , fields ) ,
@@ -4655,12 +4678,13 @@ defmodule Module.Types.Descr do
46554678
46564679 ## Map fields helpers
46574680 #
4658- # Map fields are stored as orddicts (sorted key-value lists).
4681+ # Map fields and domains are stored as orddicts (sorted key-value lists).
46594682 # These helpers wrap :orddict operations so the representation
46604683 # can be changed without modifying every call site.
46614684
46624685 @ compile { :inline ,
46634686 fields_from_reverse_list: 1 ,
4687+ fields_from_keys: 2 ,
46644688 fields_to_list: 1 ,
46654689 fields_fold: 3 ,
46664690 fields_keys: 1 ,
@@ -4674,13 +4698,18 @@ defmodule Module.Types.Descr do
46744698 fields_map: 2 }
46754699
46764700 defp fields_from_reverse_list ( list ) , do: :lists . ukeysort ( 1 , list )
4701+ defp fields_from_keys ( keys , value ) , do: Enum . map ( :lists . usort ( keys ) , & { & 1 , value } )
46774702 defp fields_to_list ( fields ) , do: fields
46784703 defp fields_fold ( fields , acc , fun ) , do: :orddict . fold ( fun , acc , fields )
4679-
46804704 defp fields_keys ( fields ) , do: :orddict . fetch_keys ( fields )
46814705 defp fields_store ( key , value , fields ) , do: :orddict . store ( key , value , fields )
46824706 defp fields_find ( key , fields ) , do: :orddict . find ( key , fields )
46834707 defp fields_take ( key , fields ) , do: :orddict . take ( key , fields )
4708+ defp fields_fetch! ( key , fields ) , do: :orddict . fetch ( key , fields )
4709+ defp fields_is_key ( key , fields ) , do: :orddict . is_key ( key , fields )
4710+
4711+ defp fields_merge ( fun , fields1 , fields2 ) , do: :orddict . merge ( fun , fields1 , fields2 )
4712+ defp fields_map ( fun , fields ) , do: :orddict . map ( fun , fields )
46844713
46854714 defp fields_get ( fields , key , default ) do
46864715 case :orddict . find ( key , fields ) do
@@ -4689,10 +4718,20 @@ defmodule Module.Types.Descr do
46894718 end
46904719 end
46914720
4692- defp fields_fetch! ( key , fields ) , do: :orddict . fetch ( key , fields )
4693- defp fields_is_key ( key , fields ) , do: :orddict . is_key ( key , fields )
4694- defp fields_merge ( fun , fields1 , fields2 ) , do: :orddict . merge ( fun , fields1 , fields2 )
4695- defp fields_map ( fun , fields ) , do: :orddict . map ( fun , fields )
4721+ defp fields_put_all_new ( fields , [ ] , _value ) , do: fields
4722+ defp fields_put_all_new ( [ ] , keys , value ) , do: Enum . map ( keys , & { & 1 , value } )
4723+
4724+ defp fields_put_all_new ( [ { k1 , _ } = h | t1 ] , [ k2 | _ ] = keys , value ) when k1 < k2 do
4725+ [ h | fields_put_all_new ( t1 , keys , value ) ]
4726+ end
4727+
4728+ defp fields_put_all_new ( [ { k1 , _ } | _ ] = fields , [ k2 | keys ] , value ) when k1 > k2 do
4729+ [ { k2 , value } | fields_put_all_new ( fields , keys , value ) ]
4730+ end
4731+
4732+ defp fields_put_all_new ( [ h | t1 ] , [ _ | keys ] , value ) do
4733+ [ h | fields_put_all_new ( t1 , keys , value ) ]
4734+ end
46964735
46974736 defp fields_merge_with_defaults ( [ { k1 , v1 } | rest1 ] = f1 , d1 , [ { k2 , v2 } | rest2 ] = f2 , d2 , fun ) do
46984737 cond do
0 commit comments