@@ -2284,10 +2284,46 @@ defmodule Module.Types.Descr do
22842284 { acc , dynamic? }
22852285 end
22862286
2287+ # TODO: Rename this to tuple_tag_to_type
22872288 defp tag_to_type ( :open ) , do: term_or_optional ( )
22882289 defp tag_to_type ( :closed ) , do: not_set ( )
2289- defp tag_to_type ( { :closed , domain } ) , do: Map . get ( domain , domain_key ( :atom ) , not_set ( ) )
2290- defp tag_to_type ( { :open , domain } ) , do: Map . get ( domain , domain_key ( :atom ) , term_or_optional ( ) )
2290+
2291+ # Rename this to map_key_tag_to_type
2292+ defp map_key_tag_to_type ( :open ) , do: term_or_optional ( )
2293+ defp map_key_tag_to_type ( :closed ) , do: not_set ( )
2294+ defp map_key_tag_to_type ( { :closed , domain } ) , do: Map . get ( domain , domain_key ( :atom ) , not_set ( ) )
2295+
2296+ defp map_key_tag_to_type ( { :open , domain } ) ,
2297+ do: Map . get ( domain , domain_key ( :atom ) , term_or_optional ( ) )
2298+
2299+ # Helpers for domain key validation
2300+ # TODO: Merge this and the next clause into one
2301+ defp split_domain_key_pairs ( pairs ) do
2302+ Enum . split_with ( pairs , fn
2303+ { domain_key ( _ ) , _ } -> false
2304+ _ -> true
2305+ end )
2306+ end
2307+
2308+ defp validate_domain_keys ( pairs ) do
2309+ # Check if domain keys are valid and don't overlap
2310+ domains = Enum . map ( pairs , fn { domain_key ( domain ) , _ } -> domain end )
2311+
2312+ if length ( domains ) != length ( Enum . uniq ( domains ) ) do
2313+ raise ArgumentError , "Domain key types should not overlap"
2314+ end
2315+
2316+ # Check that all domain keys are valid
2317+ invalid_domains = Enum . reject ( domains , & ( & 1 in @ domain_key_types ) )
2318+
2319+ if invalid_domains != [ ] do
2320+ raise ArgumentError ,
2321+ "Invalid domain key types: #{ inspect ( invalid_domains ) } . " <>
2322+ "Valid types are: #{ inspect ( @ domain_key_types ) } "
2323+ end
2324+
2325+ Enum . map ( pairs , fn { key , type } -> { key , if_set ( type ) } end )
2326+ end
22912327
22922328 defguardp is_optional_static ( map )
22932329 when is_map ( map ) and is_map_key ( map , :optional )
@@ -2474,8 +2510,8 @@ defmodule Module.Types.Descr do
24742510 # For a closed map with domains intersected with an open map with domains:
24752511 # 1. The result is closed (more restrictive)
24762512 # 2. We need to check each domain in the open map against the closed map
2477- default1 = tag_to_type ( tag1 )
2478- default2 = tag_to_type ( tag2 )
2513+ default1 = map_key_tag_to_type ( tag1 )
2514+ default2 = map_key_tag_to_type ( tag2 )
24792515
24802516 # Compute the new domain
24812517 tag = map_domain_intersection ( tag1 , tag2 )
@@ -2486,27 +2522,10 @@ defmodule Module.Types.Descr do
24862522 # 3. If key is only in map2, compute non empty intersection with atom1
24872523 # We do that by computing intersection on all key labels in both map1 and map2,
24882524 # using default values when a key is not present.
2489- keys1_set = :sets . from_list ( Map . keys ( map1 ) , version: 2 )
2490- keys2_set = :sets . from_list ( Map . keys ( map2 ) , version: 2 )
2491-
2492- # Combine all unique keys using :sets.union
2493- all_keys_set = :sets . union ( keys1_set , keys2_set )
2494- all_keys = :sets . to_list ( all_keys_set )
2495-
2496- new_fields =
2497- for key <- all_keys do
2498- in_map1? = Map . has_key? ( map1 , key )
2499- in_map2? = Map . has_key? ( map2 , key )
2500-
2501- cond do
2502- in_map1? and in_map2? -> { key , non_empty_intersection! ( map1 [ key ] , map2 [ key ] ) }
2503- in_map1? -> { key , non_empty_intersection! ( map1 [ key ] , default2 ) }
2504- in_map2? -> { key , non_empty_intersection! ( default1 , map2 [ key ] ) }
2505- end
2506- end
2507- |> :maps . from_list ( )
2508-
2509- { tag , new_fields }
2525+ { tag ,
2526+ symmetrical_merge ( map1 , default1 , map2 , default2 , fn _key , v1 , v2 ->
2527+ non_empty_intersection! ( v1 , v2 )
2528+ end ) }
25102529 end
25112530
25122531 # Compute the intersection of two tags or tag-domain pairs.
@@ -2516,8 +2535,8 @@ defmodule Module.Types.Descr do
25162535 defp map_domain_intersection ( tag , :open ) , do: tag
25172536
25182537 defp map_domain_intersection ( { tag1 , domains1 } , { tag2 , domains2 } ) do
2519- default1 = tag_to_type ( tag1 )
2520- default2 = tag_to_type ( tag2 )
2538+ default1 = map_key_tag_to_type ( tag1 )
2539+ default2 = map_key_tag_to_type ( tag2 )
25212540
25222541 new_domains =
25232542 for domain_key <- @ domain_key_types , reduce: % { } do
@@ -2568,7 +2587,7 @@ defmodule Module.Types.Descr do
25682587 { :open , fields2 , [ ] } , dnf1 when map_size ( fields2 ) == 1 ->
25692588 Enum . reduce ( dnf1 , [ ] , fn { tag1 , fields1 , negs1 } , acc ->
25702589 { key , value , _rest } = :maps . next ( :maps . iterator ( fields2 ) )
2571- t_diff = difference ( Map . get ( fields1 , key , tag_to_type ( tag1 ) ) , value )
2590+ t_diff = difference ( Map . get ( fields1 , key , map_key_tag_to_type ( tag1 ) ) , value )
25722591
25732592 if empty? ( t_diff ) do
25742593 acc
@@ -2641,7 +2660,11 @@ defmodule Module.Types.Descr do
26412660 # Optimization: if the key does not exist in the map, avoid building
26422661 # if_set/not_set pairs and return the popped value directly.
26432662 defp map_fetch_static ( % { map: [ { tag , fields , [ ] } ] } , key ) when not is_map_key ( fields , key ) do
2644- tag_to_type ( tag ) |> pop_optional_static ( )
2663+ case tag do
2664+ :open -> { true , term ( ) }
2665+ :closed -> { true , none ( ) }
2666+ other -> map_key_tag_to_type ( other ) |> pop_optional_static ( )
2667+ end
26452668 end
26462669
26472670 # Takes a map dnf and returns the union of types it can take for a given key.
@@ -2655,7 +2678,7 @@ defmodule Module.Types.Descr do
26552678
26562679 # Optimization: if there are no negatives and the key does not exist, return the default one.
26572680 { tag , % { } , [ ] } , acc ->
2658- tag_to_type ( tag ) |> union ( acc )
2681+ map_key_tag_to_type ( tag ) |> union ( acc )
26592682
26602683 { tag , fields , negs } , acc ->
26612684 { fst , snd } = map_pop_key ( tag , fields , key )
@@ -3026,7 +3049,7 @@ defmodule Module.Types.Descr do
30263049
30273050 key_type , acc ->
30283051 # Note: we could stop if we reach term()_or_optional()
3029- Map . get ( domains , domain_key ( key_type ) , tag_to_type ( tag ) ) |> union ( acc )
3052+ Map . get ( domains , domain_key ( key_type ) , map_key_tag_to_type ( tag ) ) |> union ( acc )
30303053 end )
30313054 end
30323055
@@ -3115,15 +3138,15 @@ defmodule Module.Types.Descr do
31153138 dnf
31163139 |> Enum . reduce ( none ( ) , fn
31173140 { tag , _fields , [ ] } , acc when is_atom ( tag ) ->
3118- tag_to_type ( tag ) |> union ( acc )
3141+ map_key_tag_to_type ( tag ) |> union ( acc )
31193142
31203143 # Optimization: if there are no negatives and domains exists, return its value
31213144 { { _tag , % { domain_key ( ^ key_domain ) => value } } , _fields , [ ] } , acc ->
31223145 value |> union ( acc )
31233146
31243147 # Optimization: if there are no negatives and the key does not exist, return the default type.
31253148 { { tag , % { } } , _fields , [ ] } , acc ->
3126- tag_to_type ( tag ) |> union ( acc )
3149+ map_key_tag_to_type ( tag ) |> union ( acc )
31273150
31283151 { tag , fields , negs } , acc ->
31293152 { fst , snd } = map_pop_domain ( tag , fields , key_domain )
@@ -3252,8 +3275,8 @@ defmodule Module.Types.Descr do
32523275
32533276 defp map_empty? ( tag , fields , [ { neg_tag , neg_fields } | negs ] ) do
32543277 if map_check_domain_keys ( tag , neg_tag ) do
3255- atom_default = tag_to_type ( tag )
3256- neg_atom_default = tag_to_type ( neg_tag )
3278+ atom_default = map_key_tag_to_type ( tag )
3279+ neg_atom_default = map_key_tag_to_type ( neg_tag )
32573280
32583281 ( Enum . all? ( neg_fields , fn { neg_key , neg_type } ->
32593282 cond do
@@ -3418,7 +3441,7 @@ defmodule Module.Types.Descr do
34183441 defp map_pop_key ( tag , fields , key ) do
34193442 case :maps . take ( key , fields ) do
34203443 { value , fields } -> { value , % { map: map_new ( tag , fields ) } }
3421- :error -> { tag_to_type ( tag ) , % { map: map_new ( tag , fields ) } }
3444+ :error -> { map_key_tag_to_type ( tag ) , % { map: map_new ( tag , fields ) } }
34223445 end
34233446 end
34243447
@@ -3428,12 +3451,12 @@ defmodule Module.Types.Descr do
34283451 defp map_pop_domain ( { tag , domains } , fields , domain_key ) do
34293452 case :maps . take ( domain_key ( domain_key ) , domains ) do
34303453 { value , domains } -> { value , % { map: map_new ( tag , fields , domains ) } }
3431- :error -> { tag_to_type ( tag ) , % { map: map_new ( tag , fields , domains ) } }
3454+ :error -> { map_key_tag_to_type ( tag ) , % { map: map_new ( tag , fields , domains ) } }
34323455 end
34333456 end
34343457
34353458 defp map_pop_domain ( tag , fields , _domain_key ) ,
3436- do: { tag_to_type ( tag ) , % { map: map_new ( tag , fields ) } }
3459+ do: { map_key_tag_to_type ( tag ) , % { map: map_new ( tag , fields ) } }
34373460
34383461 defp map_split_negative ( negs , key ) do
34393462 Enum . reduce_while ( negs , [ ] , fn
@@ -4532,9 +4555,9 @@ defmodule Module.Types.Descr do
45324555
45334556 ## Map helpers
45344557
4558+ # Erlang maps:merge_with/3 has to preserve the order in combiner.
4559+ # We don't care about the order, so we have a faster implementation.
45354560 defp symmetrical_merge ( left , right , fun ) do
4536- # Erlang maps:merge_with/3 has to preserve the order in combiner.
4537- # We don't care about the order, so we have a faster implementation.
45384561 if map_size ( left ) > map_size ( right ) do
45394562 iterator_merge ( :maps . next ( :maps . iterator ( right ) ) , left , fun )
45404563 else
@@ -4554,9 +4577,44 @@ defmodule Module.Types.Descr do
45544577
45554578 defp iterator_merge ( :none , map , _fun ) , do: map
45564579
4580+ # Perform a symmetrical merge with default values
4581+ defp symmetrical_merge ( left , left_default , right , right_default , fun ) do
4582+ iterator = :maps . next ( :maps . iterator ( left ) )
4583+ iterator_merge_left ( iterator , left_default , right , right_default , % { } , fun )
4584+ end
4585+
4586+ defp iterator_merge_left ( { key , v1 , iterator } , v1_default , map , v2_default , acc , fun ) do
4587+ value =
4588+ case map do
4589+ % { ^ key => v2 } -> fun . ( key , v1 , v2 )
4590+ % { } -> fun . ( key , v1 , v2_default )
4591+ end
4592+
4593+ acc = Map . put ( acc , key , value )
4594+ iterator_merge_left ( :maps . next ( iterator ) , v1_default , map , v2_default , acc , fun )
4595+ end
4596+
4597+ defp iterator_merge_left ( :none , v1_default , map , _v2_default , acc , fun ) do
4598+ iterator_merge_right ( :maps . next ( :maps . iterator ( map ) ) , v1_default , acc , fun )
4599+ end
4600+
4601+ defp iterator_merge_right ( { key , v2 , iterator } , v1_default , acc , fun ) do
4602+ acc =
4603+ case acc do
4604+ % { ^ key => _ } -> acc
4605+ % { } -> Map . put ( acc , key , fun . ( key , v1_default , v2 ) )
4606+ end
4607+
4608+ iterator_merge_right ( :maps . next ( iterator ) , v1_default , acc , fun )
4609+ end
4610+
4611+ defp iterator_merge_right ( :none , _v1_default , acc , _fun ) do
4612+ acc
4613+ end
4614+
4615+ # Erlang maps:intersect_with/3 has to preserve the order in combiner.
4616+ # We don't care about the order, so we have a faster implementation.
45574617 defp symmetrical_intersection ( left , right , fun ) do
4558- # Erlang maps:intersect_with/3 has to preserve the order in combiner.
4559- # We don't care about the order, so we have a faster implementation.
45604618 if map_size ( left ) > map_size ( right ) do
45614619 iterator_intersection ( :maps . next ( :maps . iterator ( right ) ) , left , [ ] , fun )
45624620 else
@@ -4610,32 +4668,4 @@ defmodule Module.Types.Descr do
46104668 defp non_empty_map_or ( [ head | tail ] , fun ) do
46114669 Enum . reduce ( tail , fun . ( head ) , & { :or , [ ] , [ & 2 , fun . ( & 1 ) ] } )
46124670 end
4613-
4614- # Helpers for domain key validation
4615- defp split_domain_key_pairs ( pairs ) do
4616- Enum . split_with ( pairs , fn
4617- { domain_key ( _ ) , _ } -> false
4618- _ -> true
4619- end )
4620- end
4621-
4622- defp validate_domain_keys ( pairs ) do
4623- # Check if domain keys are valid and don't overlap
4624- domains = Enum . map ( pairs , fn { domain_key ( domain ) , _ } -> domain end )
4625-
4626- if length ( domains ) != length ( Enum . uniq ( domains ) ) do
4627- raise ArgumentError , "Domain key types should not overlap"
4628- end
4629-
4630- # Check that all domain keys are valid
4631- invalid_domains = Enum . reject ( domains , & ( & 1 in @ domain_key_types ) )
4632-
4633- if invalid_domains != [ ] do
4634- raise ArgumentError ,
4635- "Invalid domain key types: #{ inspect ( invalid_domains ) } . " <>
4636- "Valid types are: #{ inspect ( @ domain_key_types ) } "
4637- end
4638-
4639- Enum . map ( pairs , fn { key , type } -> { key , if_set ( type ) } end )
4640- end
46414671end
0 commit comments