@@ -3767,7 +3767,7 @@ defmodule Module.Types.Descr do
37673767 # If any of required or optional domains are satisfied, then we compute the
37683768 # initial return type. `map_update_keys_static` will then union into the
37693769 # computed type below, using the original bdd/dnf, not the one with updated domains.
3770- descr = map_update_put_domains ( bdd , domains , type_fun )
3770+ descr = map_update_put_domains ( bdd , domains , type_fun , force? )
37713771 { remove_optional ( value ) , descr , errors , true }
37723772 else
37733773 { remove_optional ( value ) , none ( ) , errors , false }
@@ -3920,31 +3920,42 @@ defmodule Module.Types.Descr do
39203920 :found_key -> true
39213921 end
39223922
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.
39233927 defp map_update_get_domains ( dnf , domain_keys , acc , require_type? , any_atom_key ) do
39243928 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
39253930 value = map_get_domain_no_optional ( dnf , domain_key , none ( ) )
39263931
39273932 cond do
3933+ # Atom domains are special: we also check for individually named atom keys
39283934 domain_key == :atom ->
39293935 atom_acc = any_atom_key . ( )
39303936
39313937 cond do
3938+ # Domain has a direct match: valid, union both atom keys and domain value
39323939 not empty? ( value ) ->
39333940 acc = if require_type? , do: union ( union ( atom_acc , acc ) , value ) , else: acc
39343941 { true , [ :atom | valid ] , invalid , acc }
39353942
3943+ # No direct match, but individual atom keys exist: found but domain is invalid
39363944 not empty? ( atom_acc ) ->
39373945 acc = if require_type? , do: union ( atom_acc , acc ) , else: acc
39383946 { true , valid , [ :atom | invalid ] , acc }
39393947
3948+ # No match at all
39403949 true ->
39413950 { found? , valid , [ :atom | invalid ] , acc }
39423951 end
39433952
3953+ # Non-atom domain key has a match: mark as valid
39443954 not empty? ( value ) ->
39453955 acc = if require_type? , do: union ( acc , value ) , else: acc
39463956 { true , [ domain_key | valid ] , invalid , acc }
39473957
3958+ # Non-atom domain key not found: mark as invalid
39483959 true ->
39493960 { found? , valid , [ domain_key | invalid ] , acc }
39503961 end
@@ -3974,24 +3985,29 @@ defmodule Module.Types.Descr do
39743985 # But that would not be helpful, as we can't distinguish between these two
39753986 # in Elixir code. It only makes sense to build the union for domain keys
39763987 # that do not exist.
3977- defp map_update_put_domains ( bdd , [ ] , _type_fun ) , do: % { map: bdd }
3988+ defp map_update_put_domains ( bdd , [ ] , _type_fun , _force? ) , do: % { map: bdd }
39783989
3979- defp map_update_put_domains ( bdd , domain_keys , type_fun ) do
3990+ defp map_update_put_domains ( bdd , domain_keys , type_fun , force? ) do
39803991 bdd =
39813992 bdd_map ( bdd , fn { tag , fields } ->
3982- { map_update_put_domain ( tag , domain_keys , type_fun ) , fields }
3993+ { map_update_put_domain ( tag , domain_keys , type_fun , force? ) , fields }
39833994 end )
39843995
39853996 % { map: bdd }
39863997 end
39873998
3988- 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
39894000 case tag_or_domains do
39904001 :open ->
39914002 :open
39924003
39934004 :closed ->
3994- 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? ,
4009+ do: fields_from_keys ( domain_keys , if_set ( type_fun . ( true , none ( ) ) ) ) ,
4010+ else: :closed
39954011
39964012 # Note: domain_keys may contain duplicates, so we cannot
39974013 # do a side-by-side traversal here.
@@ -4002,7 +4018,10 @@ defmodule Module.Types.Descr do
40024018 fields_store ( domain_key , union ( value , type_fun . ( true , remove_optional ( value ) ) ) , acc )
40034019
40044020 :error ->
4005- fields_store ( domain_key , if_set ( type_fun . ( true , none ( ) ) ) , acc )
4021+ # Likewise, only forced updates may synthesize missing domain keys.
4022+ if force? ,
4023+ do: fields_store ( domain_key , if_set ( type_fun . ( true , none ( ) ) ) , acc ) ,
4024+ else: acc
40064025 end
40074026 end )
40084027 end
@@ -4079,7 +4098,7 @@ defmodule Module.Types.Descr do
40794098 descr =
40804099 case required_domains ++ optional_domains do
40814100 [ ] -> none ( )
4082- domains -> map_update_put_domains ( bdd , domains , type_fun )
4101+ domains -> map_update_put_domains ( bdd , domains , type_fun , true )
40834102 end
40844103
40854104 dnf = map_bdd_to_dnf_with_empty ( bdd )
0 commit comments