@@ -139,28 +139,20 @@ defmodule Module.Types.Expr do
139139 end
140140
141141 # %{map | ...}
142- def of_expr ( { :%{} , _ , [ { :| , _ , [ _map , args ] } ] } = expr , stack , context ) do
142+ def of_expr ( { :%{} , _ , [ { :| , _ , [ map , args ] } ] } = expr , stack , context ) do
143143 stack = push_expr_stack ( expr , stack )
144144
145- case of_pairs ( args , stack , context ) do
146- { :ok , _pairs , context } -> { :ok , { :map , [ { :optional , :dynamic , :dynamic } ] } , context }
147- { :error , reason } -> { :error , reason }
148- end
145+ additional_pairs = [ { :optional , :dynamic , :dynamic } ]
146+ map_update ( map , args , additional_pairs , stack , context )
149147 end
150148
151149 # %Struct{map | ...}
152- def of_expr ( { :% , meta , [ module , { :%{} , _ , [ { :| , _ , [ _map , args ] } ] } ] } = expr , stack , context ) do
150+ def of_expr ( { :% , meta , [ module , { :%{} , _ , [ { :| , _ , [ map , args ] } ] } ] } = expr , stack , context ) do
153151 context = Remote . check ( module , :__struct__ , 0 , meta , context )
154152 stack = push_expr_stack ( expr , stack )
155153
156- case of_pairs ( args , stack , context ) do
157- { :ok , _pairs , context } ->
158- pairs = [ { :required , { :atom , :__struct__ } , { :atom , module } } ]
159- { :ok , { :map , pairs } , context }
160-
161- { :error , reason } ->
162- { :error , reason }
163- end
154+ additional_pairs = [ { :required , { :atom , :__struct__ } , { :atom , module } } ]
155+ map_update ( map , args , additional_pairs , stack , context )
164156 end
165157
166158 # %{...}
@@ -395,6 +387,27 @@ defmodule Module.Types.Expr do
395387 end )
396388 end
397389
390+ defp map_update ( map , args , additional_pairs , stack , context ) do
391+ with { :ok , map_type , context } <- of_expr ( map , stack , context ) ,
392+ { :ok , arg_pairs , context } <- of_pairs ( args , stack , context ) ,
393+ arg_pairs = pairs_to_unions ( arg_pairs , context ) ,
394+ # Change value types to dynamic to reuse map unification for map updates
395+ dynamic_value_pairs =
396+ Enum . map ( arg_pairs , fn { :required , key , _value } -> { :required , key , :dynamic } end ) ,
397+ args_type = { :map , additional_pairs ++ dynamic_value_pairs } ,
398+ { :ok , type , context } <- unify ( args_type , map_type , stack , context ) do
399+ # Retrieve map type and overwrite with the new value types from the map update
400+ { :map , pairs } = resolve_var ( type , context )
401+
402+ updated_pairs =
403+ Enum . reduce ( arg_pairs , pairs , fn { :required , key , value } , pairs ->
404+ List . keyreplace ( pairs , key , 1 , { :required , key , value } )
405+ end )
406+
407+ { :ok , { :map , updated_pairs } , context }
408+ end
409+ end
410+
398411 defp for_clause ( { :<- , _ , [ left , expr ] } , stack , context ) do
399412 { pattern , guards } = extract_head ( [ left ] )
400413
0 commit comments