@@ -191,20 +191,55 @@ defmodule Module.Types.Of do
191191
192192 { dynamic? , domain , single , multiple } =
193193 Enum . reduce ( pairs_types , { false , [ ] , [ ] , [ ] } , fn
194- { key_tagged_type , dynamic_pair? , value_type } , { dynamic? , domain , single , multiple } ->
194+ { pos_neg_domain , dynamic_pair? , value_type } , { dynamic? , domain , single , multiple } ->
195195 dynamic? = dynamic? or dynamic_pair?
196196
197- case key_tagged_type do
198- # Because a multiple key may override single keys, we can only
199- # collect single keys while there are no multiples.
200- { :keys , [ key ] } when multiple == [ ] ->
201- { dynamic? , domain , [ { key , value_type } | single ] , multiple }
202-
203- { :keys , keys } ->
204- { dynamic? , domain , single , [ { keys , value_type } | multiple ] }
205-
206- { :domain , keys } ->
207- { dynamic? , [ { keys , value_type } | domain ] , single , multiple }
197+ case pos_neg_domain do
198+ # If atom is included in domain keys, it unions all previous
199+ # single and multiple, except the ones negated:
200+ #
201+ # %{foo: :bar, term() => :baz}
202+ # #=> %{foo: :bar or :baz, term() => :baz}
203+ #
204+ # %{foo: :bar, not :foo => :baz}
205+ # #=> %{foo: :bar, term() => :baz}
206+ #
207+ # In case the negated term does not appear, we set it to none():
208+ #
209+ # %{foo: :bar, term() => :baz}
210+ # #=> %{term() => :baz, foo: :bar or :baz}
211+ #
212+ # %{not :foo => :baz}
213+ # #=> %{term() => :baz, foo: none()}
214+ #
215+ # In case we are dealing with multiple keys, we always merge the
216+ # domain. A more precise approach would be to postpone doing so
217+ # until the cartesian map is distributed but those should be very
218+ # uncommon.
219+ { [ ] , negs , domain_keys } ->
220+ if :atom in domain_keys do
221+ { single , multiple } = union_negated ( negs , value_type , single , multiple )
222+ { dynamic? , [ { domain_keys , value_type } | domain ] , single , multiple }
223+ else
224+ { dynamic? , [ { domain_keys , value_type } | domain ] , single , multiple }
225+ end
226+
227+ { pos , [ ] , domain_keys } ->
228+ domain =
229+ case domain_keys do
230+ [ ] -> domain
231+ _ -> [ { domain_keys , value_type } | domain ]
232+ end
233+
234+ case pos do
235+ # Because a multiple key may override single keys, we can only
236+ # collect single keys while there are no multiples.
237+ [ key ] when multiple == [ ] ->
238+ { dynamic? , domain , [ { key , value_type } | single ] , multiple }
239+
240+ _ ->
241+ { dynamic? , domain , single , [ { pos , value_type } | multiple ] }
242+ end
208243 end
209244 end )
210245
@@ -225,37 +260,63 @@ defmodule Module.Types.Of do
225260 { if ( dynamic? , do: dynamic ( map ) , else: map ) , context }
226261 end
227262
263+ defp union_negated ( [ ] , new_type , single , multiple ) do
264+ single = Enum . map ( single , fn { key , old_type } -> { key , union ( old_type , new_type ) } end )
265+ multiple = Enum . map ( multiple , fn { keys , old_type } -> { keys , union ( old_type , new_type ) } end )
266+ { single , multiple }
267+ end
268+
269+ defp union_negated ( negated , new_type , single , multiple ) do
270+ { single , matched } =
271+ Enum . map_reduce ( single , [ ] , fn { key , old_type } , matched ->
272+ if key in negated do
273+ { { key , old_type } , [ key | matched ] }
274+ else
275+ { { key , union ( old_type , new_type ) } , matched }
276+ end
277+ end )
278+
279+ multiple =
280+ Enum . map ( multiple , fn { keys , old_type } ->
281+ { keys , union ( old_type , new_type ) }
282+ end )
283+
284+ { Enum . map ( negated -- matched , fn key -> { key , none ( ) } end ) ++ single , multiple }
285+ end
286+
228287 defp pairs ( pairs , expected , stack , context , of_fun ) do
229288 Enum . map_reduce ( pairs , context , fn { key , value } , context ->
230- { key_tagged_type , dynamic_key? , context } = map_key_type ( key , stack , context , of_fun )
289+ { pos_neg_domain , dynamic_key? , context } = map_key_type ( key , stack , context , of_fun )
231290
232291 expected_value_type =
233- with { :keys , [ key ] } <- key_tagged_type ,
292+ with { [ key ] , [ ] , [ ] } <- pos_neg_domain ,
234293 { _ , expected_value_type } <- map_fetch_key ( expected , key ) do
235294 expected_value_type
236295 else
237296 _ -> term ( )
238297 end
239298
240299 { value_type , context } = of_fun . ( value , expected_value_type , stack , context )
241- { { key_tagged_type , dynamic_key? or gradual? ( value_type ) , value_type } , context }
300+ { { pos_neg_domain , dynamic_key? or gradual? ( value_type ) , value_type } , context }
242301 end )
243302 end
244303
245304 defp map_key_type ( key , _stack , context , _of_fun ) when is_atom ( key ) do
246- { { :keys , [ key ] } , false , context }
305+ { { [ key ] , [ ] , [ ] } , false , context }
247306 end
248307
249308 defp map_key_type ( key , stack , context , of_fun ) do
250309 { key_type , context } = of_fun . ( key , term ( ) , stack , context )
310+ domain_keys = to_domain_keys ( key_type )
251311
252- # TODO: Deal with negations such that
253- # `%{not :key => value}` => `%{atom() => value, key: none()}`
254- # `%{:key => value, not :key => value}` => `%{atom() => value, key: value}`
255- case atom_fetch ( key_type ) do
256- { :finite , list } -> { { :keys , list } , gradual? ( key_type ) , context }
257- _ -> { { :domain , to_domain_keys ( key_type ) } , gradual? ( key_type ) , context }
258- end
312+ pos_neg_domain =
313+ case atom_fetch ( key_type ) do
314+ { :finite , list } -> { list , [ ] , List . delete ( domain_keys , :atom ) }
315+ { :infinite , list } -> { [ ] , list , domain_keys }
316+ :error -> { [ ] , [ ] , domain_keys }
317+ end
318+
319+ { pos_neg_domain , gradual? ( key_type ) , context }
259320 end
260321
261322 defp cartesian_map ( lists ) do
@@ -457,9 +518,12 @@ defmodule Module.Types.Of do
457518 """
458519 def modules ( type , fun , arity , hints \\ [ ] , expr , meta , stack , context ) do
459520 case atom_fetch ( type ) do
460- { _ , mods } ->
521+ { :finite , mods } ->
461522 { mods , context }
462523
524+ { :infinite , _ } ->
525+ { [ ] , context }
526+
463527 :error ->
464528 warning = { :badmodule , expr , type , fun , arity , hints , context }
465529 { [ ] , error ( warning , meta , stack , context ) }
0 commit comments