@@ -3650,6 +3650,7 @@ defmodule Module.Types.Descr do
36503650 However, the third argument is an anonymous function that receives the current
36513651 value and returns `type_fun`. Note the value returned by `type_fun` cannot hold
36523652 dynamic. Any dynamic conversion must happen before invoking this function.
3653+
36533654 """
36543655 def map_update_fun ( descr , key_descr , type_fun , return_type? \\ true , force? \\ false ) do
36553656 gradual? = gradual? ( descr )
@@ -3766,7 +3767,7 @@ defmodule Module.Types.Descr do
37663767 # If any of required or optional domains are satisfied, then we compute the
37673768 # initial return type. `map_update_keys_static` will then union into the
37683769 # computed type below, using the original bdd/dnf, not the one with updated domains.
3769- descr = map_update_put_domains ( bdd , domains , type_fun )
3770+ descr = map_update_put_domains ( bdd , domains , type_fun , force? )
37703771 { remove_optional ( value ) , descr , errors , true }
37713772 else
37723773 { remove_optional ( value ) , none ( ) , errors , false }
@@ -3919,31 +3920,42 @@ defmodule Module.Types.Descr do
39193920 :found_key -> true
39203921 end
39213922
3923+ # For each domain key, check if it exists in the map DNF and classify it
3924+ # as valid (matched) or invalid (missing). Accumulates the value type if require_type? is set.
3925+ # Returns {found?, valid_domains, invalid_domains, accumulated_value_type},
3926+ # where found? tracks whether at least one domain key was matched in the map.
39223927 defp map_update_get_domains ( dnf , domain_keys , acc , require_type? , any_atom_key ) do
39233928 Enum . reduce ( domain_keys , { false , [ ] , [ ] , acc } , fn domain_key , { found? , valid , invalid , acc } ->
3929+ # Get the value type for this domain key, excluding optional entries
39243930 value = map_get_domain_no_optional ( dnf , domain_key , none ( ) )
39253931
39263932 cond do
3933+ # Atom domains are special: we also check for individually named atom keys
39273934 domain_key == :atom ->
39283935 atom_acc = any_atom_key . ( )
39293936
39303937 cond do
3938+ # Domain has a direct match: valid, union both atom keys and domain value
39313939 not empty? ( value ) ->
39323940 acc = if require_type? , do: union ( union ( atom_acc , acc ) , value ) , else: acc
39333941 { true , [ :atom | valid ] , invalid , acc }
39343942
3943+ # No direct match, but individual atom keys exist: found but domain is invalid
39353944 not empty? ( atom_acc ) ->
39363945 acc = if require_type? , do: union ( atom_acc , acc ) , else: acc
39373946 { true , valid , [ :atom | invalid ] , acc }
39383947
3948+ # No match at all
39393949 true ->
39403950 { found? , valid , [ :atom | invalid ] , acc }
39413951 end
39423952
3953+ # Non-atom domain key has a match: mark as valid
39433954 not empty? ( value ) ->
39443955 acc = if require_type? , do: union ( acc , value ) , else: acc
39453956 { true , [ domain_key | valid ] , invalid , acc }
39463957
3958+ # Non-atom domain key not found: mark as invalid
39473959 true ->
39483960 { found? , valid , [ domain_key | invalid ] , acc }
39493961 end
@@ -3973,24 +3985,27 @@ defmodule Module.Types.Descr do
39733985 # But that would not be helpful, as we can't distinguish between these two
39743986 # in Elixir code. It only makes sense to build the union for domain keys
39753987 # that do not exist.
3976- defp map_update_put_domains ( bdd , [ ] , _type_fun ) , do: % { map: bdd }
3988+ defp map_update_put_domains ( bdd , [ ] , _type_fun , _force? ) , do: % { map: bdd }
39773989
3978- defp map_update_put_domains ( bdd , domain_keys , type_fun ) do
3990+ defp map_update_put_domains ( bdd , domain_keys , type_fun , force? ) do
39793991 bdd =
39803992 bdd_map ( bdd , fn { tag , fields } ->
3981- { map_update_put_domain ( tag , domain_keys , type_fun ) , fields }
3993+ { map_update_put_domain ( tag , domain_keys , type_fun , force? ) , fields }
39823994 end )
39833995
39843996 % { map: bdd }
39853997 end
39863998
3987- defp map_update_put_domain ( tag_or_domains , domain_keys , type_fun ) do
3999+ defp map_update_put_domain ( tag_or_domains , domain_keys , type_fun , force? ) do
39884000 case tag_or_domains do
39894001 :open ->
39904002 :open
39914003
39924004 :closed ->
3993- fields_from_keys ( domain_keys , if_set ( type_fun . ( true , none ( ) ) ) )
4005+ # Non-forced updates must not invoke the callback on absent branches:
4006+ # the callback may itself typecheck a function application, and
4007+ # applying it to `none()` will raise undue warnings.
4008+ if force? , do: fields_from_keys ( domain_keys , if_set ( type_fun . ( true , none ( ) ) ) ) , else: :closed
39944009
39954010 # Note: domain_keys may contain duplicates, so we cannot
39964011 # do a side-by-side traversal here.
@@ -4001,7 +4016,8 @@ defmodule Module.Types.Descr do
40014016 fields_store ( domain_key , union ( value , type_fun . ( true , remove_optional ( value ) ) ) , acc )
40024017
40034018 :error ->
4004- fields_store ( domain_key , if_set ( type_fun . ( true , none ( ) ) ) , acc )
4019+ # Likewise, only forced updates may synthesize missing domain keys.
4020+ if force? , do: fields_store ( domain_key , if_set ( type_fun . ( true , none ( ) ) ) , acc ) , else: acc
40054021 end
40064022 end )
40074023 end
@@ -4078,7 +4094,7 @@ defmodule Module.Types.Descr do
40784094 descr =
40794095 case required_domains ++ optional_domains do
40804096 [ ] -> none ( )
4081- domains -> map_update_put_domains ( bdd , domains , type_fun )
4097+ domains -> map_update_put_domains ( bdd , domains , type_fun , true )
40824098 end
40834099
40844100 dnf = map_bdd_to_dnf_with_empty ( bdd )
0 commit comments