@@ -19,7 +19,7 @@ defmodule Kernel.Typespec do
1919
2020 Most of the built-in types provided in Erlang (for example, `pid()`) are
2121 expressed the same way: `pid()` or simply `pid`. Parametrized types are also
22- supported (`list(integer`) and so are remote types (`Enum.t`).
22+ supported (`list(integer) `) and so are remote types (`Enum.t`).
2323
2424 Integers and atom literals are allowed as types (ex. `1`, `:atom` or
2525 `false`). All other types are built of unions of predefined types. Certain
@@ -353,19 +353,21 @@ defmodule Kernel.Typespec do
353353 Converts a spec clause back to Elixir AST.
354354 """
355355 def spec_to_ast ( name , { :type , line , :fun , [ { :type , _ , :product , args } , result ] } ) do
356- ast_args = lc arg inlist args , do: typespec_to_ast ( arg )
357- ast = { ::: , [ line: line ] , [ { name , [ line: line ] , ast_args } , typespec_to_ast ( result ) ] }
356+ meta = [ line: line ]
357+ body = { name , meta , Enum . map ( args , & typespec_to_ast / 1 ) }
358358
359359 vars = args ++ [ result ]
360360 |> Enum . flat_map ( & collect_vars / 1 )
361361 |> Enum . uniq
362- |> Enum . map ( & { & 1 , { :var , [ line: line ] , nil } } )
362+ |> Enum . map ( & { & 1 , { :var , meta , nil } } )
363363
364- unless vars == [ ] do
365- ast = { :when , [ line: line ] , [ ast , vars ] }
364+ result = if vars == [ ] do
365+ typespec_to_ast ( result )
366+ else
367+ { :when , meta , [ typespec_to_ast ( result ) , vars ] }
366368 end
367369
368- ast
370+ { ::: , meta , [ body , result ] }
369371 end
370372
371373 def spec_to_ast ( name , { :type , line , :fun , [ ] } ) do
@@ -378,16 +380,20 @@ defmodule Kernel.Typespec do
378380 { var , typespec_to_ast ( type ) }
379381 end
380382
381- ast_args = lc arg inlist args , do: typespec_to_ast ( arg )
383+ meta = [ line: line ]
382384
383385 vars = args ++ [ result ]
384386 |> Enum . flat_map ( & collect_vars / 1 )
385387 |> Enum . uniq
388+ |> Kernel . -- ( Keyword . keys ( guards ) )
389+ |> Enum . map ( & { & 1 , { :var , meta , nil } } )
386390
387- vars = vars -- Keyword . keys ( guards )
388- |> Enum . map ( & { & 1 , { :var , [ line: line ] , nil } } )
391+ args = lc arg inlist args , do: typespec_to_ast ( arg )
389392
390- { :when , [ line: line ] , [ { ::: , [ line: line ] , [ { name , [ line: line ] , ast_args } , typespec_to_ast ( result ) ] } , guards ++ vars ] }
393+ { ::: , meta , [
394+ { name , [ line: line ] , args } ,
395+ { :when , meta , [ typespec_to_ast ( result ) , guards ++ vars ] }
396+ ] }
391397 end
392398
393399 @ doc """
@@ -473,7 +479,7 @@ defmodule Kernel.Typespec do
473479 The result is returned as a list of tuples where the first
474480 element is spec name and arity and the second is the spec.
475481
476- The module must have a corresponding beam file
482+ The module must have a corresponding beam file
477483 which can be located by the runtime system.
478484 """
479485 @ spec beam_callbacks ( module | binary ) :: [ tuple ] | nil
@@ -524,7 +530,7 @@ defmodule Kernel.Typespec do
524530
525531 def deftype ( _kind , other , caller ) do
526532 type_spec = Macro . to_string ( other )
527- compile_error caller , "invalid type specification ` #{ type_spec } ` "
533+ compile_error caller , "invalid type specification: #{ type_spec } "
528534 end
529535
530536 defp do_deftype ( kind , { name , _ , args } , definition , caller ) do
@@ -545,16 +551,17 @@ defmodule Kernel.Typespec do
545551 end
546552
547553 @ doc false
548- def defspec ( type , { :when , meta2 , [ { ::: , _ , [ { name , meta , args } , return ] } , guard ] } , caller ) do
554+ def defspec ( type , { ::: , meta , [ { name , _ , args } , return_and_guard ] } , caller ) do
549555 if is_atom ( args ) , do: args = [ ]
556+ { return , guard } = split_return_and_guard ( return_and_guard )
550557
551558 unless Keyword . keyword? ( guard ) do
552559 guard = Macro . to_string ( guard )
553- compile_error caller , "invalid guard in function type specification ` #{ guard } ` "
560+ compile_error caller , "expected keywords as guard in function type specification, got: #{ guard } "
554561 end
555562
556563 vars = Keyword . keys ( guard )
557- constraints = guard_to_constraints ( guard , vars , meta2 , caller )
564+ constraints = guard_to_constraints ( guard , vars , meta , caller )
558565
559566 spec = { :type , line ( meta ) , :fun , fn_args ( meta , args , return , vars , caller ) }
560567 if constraints != [ ] do
@@ -566,17 +573,22 @@ defmodule Kernel.Typespec do
566573 code
567574 end
568575
569- def defspec ( type , { ::: , _ , [ { name , meta , args } , return ] } , caller ) do
570- if is_atom ( args ) , do: args = [ ]
571- spec = { :type , line ( meta ) , :fun , fn_args ( meta , args , return , [ ] , caller ) }
572- code = { { name , Kernel . length ( args ) } , spec }
573- Module . compile_typespec ( caller . module , type , code )
574- code
575- end
576-
577576 def defspec ( _type , other , caller ) do
578577 spec = Macro . to_string ( other )
579- compile_error caller , "invalid function type specification `#{ spec } `"
578+ compile_error caller , "invalid function type specification: #{ spec } "
579+ end
580+
581+ defp split_return_and_guard ( { :when , _ , [ return , guard ] } ) do
582+ { return , guard }
583+ end
584+
585+ defp split_return_and_guard ( { :| , meta , [ left , right ] } ) do
586+ { return , guard } = split_return_and_guard ( right )
587+ { { :| , meta , [ left , return ] } , guard }
588+ end
589+
590+ defp split_return_and_guard ( other ) do
591+ { other , [ ] }
580592 end
581593
582594 defp guard_to_constraints ( guard , vars , meta , caller ) do
@@ -656,8 +668,7 @@ defmodule Kernel.Typespec do
656668
657669 defp typespec_to_ast ( { :type , line , :union , args } ) do
658670 args = lc arg inlist args , do: typespec_to_ast ( arg )
659- Enum . reduce tl ( args ) , hd ( args ) ,
660- fn ( arg , expr ) -> { :| , [ line: line ] , [ expr , arg ] } end
671+ Enum . reduce Enum . reverse ( args ) , fn ( arg , expr ) -> { :| , [ line: line ] , [ arg , expr ] } end
661672 end
662673
663674 defp typespec_to_ast ( { :type , line , :fun , [ { :type , _ , :product , args } , result ] } ) do
@@ -749,7 +760,7 @@ defmodule Kernel.Typespec do
749760
750761 # Handle unions
751762 defp typespec ( { :| , meta , [ _ , _ ] } = exprs , vars , caller ) do
752- exprs = Enum . reverse ( collect_union ( exprs ) )
763+ exprs = collect_union ( exprs )
753764 union = lc e inlist exprs , do: typespec ( e , vars , caller )
754765 { :type , line ( meta ) , :union , union }
755766 end
@@ -886,9 +897,10 @@ defmodule Kernel.Typespec do
886897 typespec ( { :nonempty_list , [ ] , [ spec ] } , vars , caller )
887898 end
888899
889- defp typespec ( [ h | t ] = l , vars , caller ) do
890- union = Enum . reduce ( t , validate_kw ( h , l , caller ) , fn ( x , acc ) ->
891- { :| , [ ] , [ acc , validate_kw ( x , l , caller ) ] }
900+ defp typespec ( list , vars , caller ) do
901+ [ h | t ] = Enum . reverse ( list )
902+ union = Enum . reduce ( t , validate_kw ( h , list , caller ) , fn ( x , acc ) ->
903+ { :| , [ ] , [ validate_kw ( x , list , caller ) , acc ] }
892904 end )
893905 typespec ( { :list , [ ] , [ union ] } , vars , caller )
894906 end
@@ -904,12 +916,12 @@ defmodule Kernel.Typespec do
904916 { :remote_type , line ( meta ) , [ remote , name , arguments ] }
905917 end
906918
907- defp collect_union ( { :| , _ , [ a , b ] } ) , do: [ b | collect_union ( a ) ]
919+ defp collect_union ( { :| , _ , [ a , b ] } ) , do: [ a | collect_union ( b ) ]
908920 defp collect_union ( v ) , do: [ v ]
909921
910922 defp validate_kw ( { key , _ } = t , _ , _caller ) when is_atom ( key ) , do: t
911923 defp validate_kw ( _ , original , caller ) do
912- compile_error ( caller , "unexpected list ` #{ Macro . to_string original } ` in typespec " )
924+ compile_error ( caller , "unexpected list in typespec: #{ Macro . to_string original } " )
913925 end
914926
915927 defp fn_args ( meta , args , return , vars , caller ) do
0 commit comments