@@ -190,7 +190,7 @@ defmodule Module.Types.Of do
190190 Returns `__info__(:struct)` information about a struct.
191191 """
192192 def struct_info ( struct , meta , stack , context ) do
193- context = remote ( struct , :__struct__ , 0 , meta , stack , context )
193+ { _ , context } = remote ( struct , :__struct__ , 0 , meta , stack , context )
194194
195195 info =
196196 struct . __info__ ( :struct ) ||
@@ -293,8 +293,73 @@ defmodule Module.Types.Of do
293293 context
294294 end
295295
296+ ## Modules
297+
298+ @ doc """
299+ Returns the modules.
300+
301+ The call information is used on report reporting.
302+ """
303+ def modules ( type , fun , arity , hints \\ [ ] , expr , meta , stack , context ) do
304+ case atom_fetch ( type ) do
305+ { _ , mods } ->
306+ { mods , context }
307+
308+ :error ->
309+ warning = { :badmodule , expr , type , fun , arity , hints , context }
310+ { [ ] , error ( warning , meta , stack , context ) }
311+ end
312+ end
313+
296314 ## Remotes
297315
316+ # Define strong arrows found in the standard library.
317+ # A strong arrow means that, if a type outside of its
318+ # domain is given, an error is raised. We are also
319+ # ensuring that domains for the same function have
320+ # no overlaps.
321+
322+ for { mod , fun , clauses } <- [
323+ { :erlang , :binary_to_integer , [ { [ binary ( ) ] , integer ( ) } ] } ,
324+ { :erlang , :integer_to_binary , [ { [ integer ( ) ] , binary ( ) } ] }
325+ ] do
326+ [ { args , _return } | _others ] = clauses
327+
328+ defp strong_remote ( unquote ( mod ) , unquote ( fun ) , unquote ( length ( args ) ) ) ,
329+ do: unquote ( Macro . escape ( clauses ) )
330+ end
331+
332+ defp strong_remote ( _mod , _fun , _arity ) , do: [ ]
333+
334+ @ doc """
335+ Checks a module is a valid remote.
336+
337+ It returns either a tuple with the remote information and the context.
338+ The remote information may be one of:
339+
340+ * `:none` - no typing information found.
341+
342+ * `{:infer, clauses}` - clauses from inferences. You must check all
343+ all clauses and return the union between them. They are dynamic
344+ and they can only be converted into arrows by computing the union
345+ of all arguments.
346+
347+ * `{:strong, clauses}` - clauses from signatures. So far these are
348+ strong arrows with non-overlapping domains. If you find one matching
349+ clause, you can stop looking for others.
350+
351+ """
352+ def remote ( module , fun , arity , meta , stack , context ) when is_atom ( module ) do
353+ if Keyword . get ( meta , :runtime_module , false ) do
354+ { :none , context }
355+ else
356+ case strong_remote ( module , fun , arity ) do
357+ [ ] -> { :none , check_export ( module , fun , arity , meta , stack , context ) }
358+ clauses -> { { :strong , clauses } , context }
359+ end
360+ end
361+ end
362+
298363 # TODO: Implement element without a literal index
299364
300365 def apply ( :erlang , :element , [ _ , tuple ] , { _ , meta , [ index , _ ] } = expr , stack , context )
@@ -407,36 +472,37 @@ defmodule Module.Types.Of do
407472 apply ( mod , name , args_types , expr , stack , context )
408473
409474 false ->
410- context = remote ( mod , name , arity , elem ( expr , 1 ) , stack , context )
411- { dynamic ( ) , context }
475+ { info , context } = remote ( mod , name , arity , elem ( expr , 1 ) , stack , context )
476+
477+ case apply_remote ( info , args_types ) do
478+ { :ok , type } ->
479+ { type , context }
480+
481+ { :error , clauses } ->
482+ error = { :badapply , expr , args_types , clauses , context }
483+ { error_type ( ) , error ( error , elem ( expr , 1 ) , stack , context ) }
484+ end
412485 end
413486 end
414487
415- @ doc """
416- Returns the modules for a given call.
417- """
418- def modules ( type , fun , arity , hints \\ [ ] , expr , meta , stack , context ) do
419- case atom_fetch ( type ) do
420- { _ , mods } ->
421- { mods , context }
488+ defp apply_remote ( :none , _args_types ) do
489+ { :ok , dynamic ( ) }
490+ end
422491
423- :error ->
424- warning = { :badmodule , expr , type , fun , arity , hints , context }
425- { [ ] , error ( warning , meta , stack , context ) }
426- end
492+ defp apply_remote ( { :strong , clauses } , args_types ) do
493+ Enum . find_value ( clauses , { :error , clauses } , fn { expected , return } ->
494+ if zip_compatible? ( args_types , expected ) do
495+ { :ok , return }
496+ end
497+ end )
427498 end
428499
429- @ doc """
430- Checks a module is a valid remote.
431- """
432- def remote ( module , fun , arity , meta , stack , context ) when is_atom ( module ) do
433- if Keyword . get ( meta , :runtime_module , false ) do
434- context
435- else
436- check_export ( module , fun , arity , meta , stack , context )
437- end
500+ defp zip_compatible? ( [ actual | actuals ] , [ expected | expecteds ] ) do
501+ compatible? ( actual , expected ) and zip_compatible? ( actuals , expecteds )
438502 end
439503
504+ defp zip_compatible? ( [ ] , [ ] ) , do: true
505+
440506 defp check_export ( module , fun , arity , meta , stack , context ) do
441507 case ParallelChecker . fetch_export ( stack . cache , module , fun , arity ) do
442508 { :ok , mode , :def , reason } ->
@@ -726,6 +792,31 @@ defmodule Module.Types.Of do
726792 }
727793 end
728794
795+ def format_diagnostic ( { :badapply , expr , args_types , clauses , context } ) do
796+ traces = collect_traces ( expr , context )
797+
798+ % {
799+ details: % { typing_traces: traces } ,
800+ message:
801+ IO . iodata_to_binary ( [
802+ """
803+ incompatible types given to #{ format_mfa ( expr ) } :
804+
805+ #{ expr_to_string ( expr ) |> indent ( 4 ) }
806+
807+ expected types:
808+
809+ #{ clauses_args_to_quoted_string ( clauses ) |> indent ( 4 ) }
810+
811+ but got types:
812+
813+ #{ args_to_quoted_string ( args_types ) |> indent ( 4 ) }
814+ """ ,
815+ format_traces ( traces )
816+ ] )
817+ }
818+ end
819+
729820 def format_diagnostic ( { :mismatched_comparison , expr , context } ) do
730821 traces = collect_traces ( expr , context )
731822
@@ -845,6 +936,25 @@ defmodule Module.Types.Of do
845936 match? ( { { :. , _ , [ var , _fun ] } , _ , _args } when is_var ( var ) , expr )
846937 end
847938
939+ defp clauses_args_to_quoted_string ( [ { args , _return } ] ) do
940+ args_to_quoted_string ( args )
941+ end
942+
943+ defp args_to_quoted_string ( [ arg ] ) do
944+ to_quoted_string ( arg )
945+ end
946+
947+ defp args_to_quoted_string ( args ) do
948+ { :_ , [ ] , Enum . map ( args , & to_quoted / 1 ) }
949+ |> Code.Formatter . to_algebra ( )
950+ |> Inspect.Algebra . format ( 98 )
951+ |> IO . iodata_to_binary ( )
952+ |> case do
953+ "_(\n " <> _ = multiple_lines -> binary_slice ( multiple_lines , 1 .. - 1 // 1 )
954+ single_line -> binary_slice ( single_line , 2 .. - 2 // 1 )
955+ end
956+ end
957+
848958 defp empty_if ( condition , content ) do
849959 if condition , do: "" , else: content
850960 end
0 commit comments