@@ -172,57 +172,38 @@ defmodule Module.Types.Expr do
172172 # allow variables defined on the left side of | to be available
173173 # on the right side, this is safe.
174174 { pairs_types , context } =
175- Of . pairs ( args , expected , stack , context , & of_expr ( & 1 , & 2 , expr , & 3 , & 4 ) )
175+ Enum . map_reduce ( args , context , fn { key , value } , context ->
176+ { key_type , context } = of_expr ( key , term ( ) , expr , stack , context )
177+ { value_type , context } = of_expr ( value , term ( ) , expr , stack , context )
178+ { { key_type , value_type } , context }
179+ end )
176180
177181 expected =
178182 if stack . mode == :traversal do
179183 expected
180184 else
181- # TODO: Once we introduce domain keys, if we ever find a domain
182- # that overlaps atoms, we can only assume optional(atom()) => term(),
183- # which is what the `open_map()` below falls back into anyway.
184- Enum . reduce_while ( pairs_types , expected , fn
185- { _ , [ key ] , _ } , acc ->
186- case map_fetch_and_put ( acc , key , term ( ) ) do
187- { _value , acc } -> { :cont , acc }
188- _ -> { :halt , open_map ( ) }
185+ # The only information we can attach to the expected types is that
186+ # certain keys are expected.
187+ expected_pairs =
188+ Enum . flat_map ( pairs_types , fn { key_type , _value_type } ->
189+ case atom_fetch ( key_type ) do
190+ { :finite , [ key ] } -> [ { key , term ( ) } ]
191+ _ -> [ ]
189192 end
193+ end )
190194
191- _ , _ ->
192- { :halt , open_map ( ) }
193- end )
195+ intersection ( expected , open_map ( expected_pairs ) )
194196 end
195197
196198 { map_type , context } = of_expr ( map , expected , expr , stack , context )
197199
198200 try do
199- Of . permutate_map ( pairs_types , stack , fn fallback , keys_to_assert , pairs ->
200- # Ensure all keys to assert and all type pairs exist in map
201- keys_to_assert = Enum . map ( pairs , & elem ( & 1 , 0 ) ) ++ keys_to_assert
202-
203- Enum . each ( Enum . map ( pairs , & elem ( & 1 , 0 ) ) ++ keys_to_assert , fn key ->
204- case map_fetch ( map_type , key ) do
205- { _ , _ } -> :ok
206- :badkey -> throw ( { :badkey , map_type , key , update , context } )
207- :badmap -> throw ( { :badmap , map_type , update , context } )
208- end
209- end )
210-
211- # If all keys are known is no fallback (i.e. we know all keys being updated),
212- # we can update the existing map.
213- if fallback == none ( ) do
214- Enum . reduce ( pairs , map_type , fn { key , type } , acc ->
215- case map_fetch_and_put ( acc , key , type ) do
216- { _value , descr } -> descr
217- :badkey -> throw ( { :badkey , map_type , key , update , context } )
218- :badmap -> throw ( { :badmap , map_type , update , context } )
219- end
220- end )
221- else
222- # TODO: Use the fallback type to actually indicate if open or closed.
223- # The fallback must be unioned with the result of map_values with all
224- # `keys` deleted.
225- dynamic ( open_map ( pairs ) )
201+ Enum . reduce ( pairs_types , map_type , fn { key_type , value_type } , acc ->
202+ case map_update ( acc , key_type , value_type ) do
203+ { :ok , descr } -> descr
204+ { :badkey , key } -> throw ( { :badkey , map_type , key , update , context } )
205+ { :baddomain , domain } -> throw ( { :baddomain , map_type , domain , update , context } )
206+ :badmap -> throw ( { :badmap , map_type , update , context } )
226207 end
227208 end )
228209 catch
@@ -240,13 +221,15 @@ defmodule Module.Types.Expr do
240221 stack ,
241222 context
242223 ) do
224+ # We pass the expected type as `term()` because the struct update
225+ # operator already expects it to be a map at this point.
243226 { map_type , context } = of_expr ( map , term ( ) , struct , stack , context )
244227
245228 context =
246229 if stack . mode == :traversal do
247230 context
248231 else
249- with { false , struct_key_type } <- map_fetch ( map_type , :__struct__ ) ,
232+ with { false , struct_key_type } <- map_fetch_key ( map_type , :__struct__ ) ,
250233 { :finite , [ ^ module ] } <- atom_fetch ( struct_key_type ) do
251234 context
252235 else
@@ -259,8 +242,8 @@ defmodule Module.Types.Expr do
259242 # TODO: Once we support typed structs, we need to type check them here
260243 { type , context } = of_expr ( value , term ( ) , expr , stack , context )
261244
262- case map_fetch_and_put ( acc , key , type ) do
263- { _value , acc } -> { acc , context }
245+ case map_put_key ( acc , key , type ) do
246+ { :ok , acc } -> { acc , context }
264247 _ -> { acc , context }
265248 end
266249 end )
@@ -906,6 +889,27 @@ defmodule Module.Types.Expr do
906889 }
907890 end
908891
892+ def format_diagnostic ( { :baddomain , type , key_type , expr , context } ) do
893+ traces = collect_traces ( expr , context )
894+
895+ % {
896+ details: % { typing_traces: traces } ,
897+ message:
898+ IO . iodata_to_binary ( [
899+ """
900+ expected a map with key of type #{ to_quoted_string ( key_type ) } in map update syntax:
901+
902+ #{ expr_to_string ( expr , collapse_structs: false ) |> indent ( 4 ) }
903+
904+ but got type:
905+
906+ #{ to_quoted_string ( type , collapse_structs: false ) |> indent ( 4 ) }
907+ """ ,
908+ format_traces ( traces )
909+ ] )
910+ }
911+ end
912+
909913 def format_diagnostic ( { :badbinary , type , expr , context } ) do
910914 traces = collect_traces ( expr , context )
911915
0 commit comments