@@ -190,7 +190,7 @@ defmodule Module.Types.Of do
190
190
Returns `__info__(:struct)` information about a struct.
191
191
"""
192
192
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 )
194
194
195
195
info =
196
196
struct . __info__ ( :struct ) ||
@@ -293,8 +293,73 @@ defmodule Module.Types.Of do
293
293
context
294
294
end
295
295
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
+
296
314
## Remotes
297
315
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
+
298
363
# TODO: Implement element without a literal index
299
364
300
365
def apply ( :erlang , :element , [ _ , tuple ] , { _ , meta , [ index , _ ] } = expr , stack , context )
@@ -407,36 +472,37 @@ defmodule Module.Types.Of do
407
472
apply ( mod , name , args_types , expr , stack , context )
408
473
409
474
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
412
485
end
413
486
end
414
487
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
422
491
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 )
427
498
end
428
499
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 )
438
502
end
439
503
504
+ defp zip_compatible? ( [ ] , [ ] ) , do: true
505
+
440
506
defp check_export ( module , fun , arity , meta , stack , context ) do
441
507
case ParallelChecker . fetch_export ( stack . cache , module , fun , arity ) do
442
508
{ :ok , mode , :def , reason } ->
@@ -726,6 +792,31 @@ defmodule Module.Types.Of do
726
792
}
727
793
end
728
794
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
+
729
820
def format_diagnostic ( { :mismatched_comparison , expr , context } ) do
730
821
traces = collect_traces ( expr , context )
731
822
@@ -845,6 +936,25 @@ defmodule Module.Types.Of do
845
936
match? ( { { :. , _ , [ var , _fun ] } , _ , _args } when is_var ( var ) , expr )
846
937
end
847
938
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
+
848
958
defp empty_if ( condition , content ) do
849
959
if condition , do: "" , else: content
850
960
end
0 commit comments