@@ -243,24 +243,41 @@ defmodule Kernel.Typespec do
243243 Converts a spec clause back to Elixir AST.
244244 """
245245 def spec_to_ast ( name , { :type , line , :fun , [ { :type , _ , :product , args } , result ] } ) do
246- args = lc arg inlist args , do: typespec_to_ast ( arg )
247- { ::: , [ line: line ] , [ { name , [ line: line ] , args } , typespec_to_ast ( result ) ] }
246+ ast_args = lc arg inlist args , do: typespec_to_ast ( arg )
247+ ast = { ::: , [ line: line ] , [ { name , [ line: line ] , ast_args } , typespec_to_ast ( result ) ] }
248+
249+ vars = args ++ [ result ]
250+ |> Enum . flat_map ( & collect_vars / 1 )
251+ |> Enum . uniq
252+ |> Enum . map ( & { & 1 , { :var , [ line: line ] , nil } } )
253+
254+ unless vars == [ ] do
255+ ast = { :when , [ line: line ] , [ ast , vars ] }
256+ end
257+
258+ ast
248259 end
249260
250261 def spec_to_ast ( name , { :type , line , :fun , [ ] } ) do
251262 { ::: , [ line: line ] , [ { name , [ line: line ] , [ ] } , quote ( do: term ) ] }
252263 end
253264
254265 def spec_to_ast ( name , { :type , line , :bounded_fun , [ { :type , _ , :fun , [ { :type , _ , :product , args } , result ] } , constraints ] } ) do
255- [ h | t ] =
256- lc { :type , line , :constraint , [ { :atom , _ , :is_subtype } , [ var , type ] ] } in list constraints do
257- { :is_subtype , [ line: line ] , [ typespec_to_ast ( var ) , typespec_to_ast ( type ) ] }
266+ guards =
267+ lc { :type , _ , :constraint , [ { :atom , _ , :is_subtype } , [ { : var, _ , var } , type ] ] } in list constraints do
268+ { var , typespec_to_ast ( type ) }
258269 end
259270
260- args = lc arg inlist args , do: typespec_to_ast ( arg )
261- guards = Enum . reduce t , h , fn ( x , acc ) -> { :and , line , [ acc , x ] } end
271+ ast_args = lc arg inlist args , do: typespec_to_ast ( arg )
272+
273+ vars = args ++ [ result ]
274+ |> Enum . flat_map ( & collect_vars / 1 )
275+ |> Enum . uniq
276+
277+ vars = vars -- Keyword . keys ( guards )
278+ |> Enum . map ( & { & 1 , { :var , [ line: line ] , nil } } )
262279
263- { :when , [ line: line ] , [ { ::: , [ line: line ] , [ { name , [ line: line ] , args } , typespec_to_ast ( result ) ] } , guards ] }
280+ { :when , [ line: line ] , [ { ::: , [ line: line ] , [ { name , [ line: line ] , ast_args } , typespec_to_ast ( result ) ] } , guards ++ vars ] }
264281 end
265282
266283 @ doc """
@@ -418,28 +435,27 @@ defmodule Kernel.Typespec do
418435 end
419436
420437 @ doc false
421- def defspec ( type , { :when , _ , [ { ::: , _ , [ { name , meta , args } , return ] } , constraints_guard ] } , caller ) do
438+ def defspec ( type , { :when , meta2 , [ { ::: , _ , [ { name , meta , args } , return ] } , guard ] } , caller ) do
422439 if is_atom ( args ) , do: args = [ ]
423- vars = guard_to_vars ( constraints_guard )
424- constraints = guard_to_constraints ( constraints_guard , vars , caller )
440+
441+ unless Keyword . keyword? ( guard ) do
442+ guard = Macro . to_string ( guard )
443+ compile_error caller , "invalid guard in function type specification `#{ guard } `"
444+ end
445+
446+ vars = Keyword . keys ( guard )
447+ constraints = guard_to_constraints ( guard , vars , meta2 , caller )
448+
425449 spec = { :type , line ( meta ) , :fun , fn_args ( meta , args , return , vars , caller ) }
426450 if constraints != [ ] do
427451 spec = { :type , line ( meta ) , :bounded_fun , [ spec , constraints ] }
428452 end
453+
429454 code = { { name , Kernel . length ( args ) } , spec }
430455 Module . compile_typespec ( caller . module , type , code )
431456 code
432457 end
433458
434- def defspec ( type , { :when , _ , [ fun , { ::: , _ , [ guards , return ] } ] } = spec , caller ) do
435- new_spec = { :when , [ ] , [ { ::: , [ ] , [ fun , return ] } , guards ] }
436- IO . write "typespec format is deprecated `#{ Macro . to_string ( spec ) } `\n " <>
437- "new format is: `#{ Macro . to_string ( new_spec ) } `\n " <>
438- Exception . format_stacktrace
439-
440- defspec ( type , new_spec , caller )
441- end
442-
443459 def defspec ( type , { ::: , _ , [ { name , meta , args } , return ] } , caller ) do
444460 if is_atom ( args ) , do: args = [ ]
445461 spec = { :type , line ( meta ) , :fun , fn_args ( meta , args , return , [ ] , caller ) }
@@ -453,40 +469,48 @@ defmodule Kernel.Typespec do
453469 compile_error caller , "invalid function type specification `#{ spec } `"
454470 end
455471
456- defp guard_to_vars ( { :is_subtype , _ , [ { name , _ , _ } , _ ] } ) do
457- [ name ]
472+ defp guard_to_constraints ( guard , vars , meta , caller ) do
473+ line = line ( meta )
474+
475+ Enum . reduce ( guard , [ ] , fn
476+ { _name , { :var , _ , context } } , acc when is_atom ( context ) ->
477+ acc
478+ { name , type } , acc ->
479+ constraint = [ { :atom , line , :is_subtype } , [ { :var , line , name } , typespec ( type , vars , caller ) ] ]
480+ type = { :type , line , :constraint , constraint }
481+ [ type | acc ]
482+ end ) |> Enum . reverse
458483 end
459484
460- defp guard_to_vars ( { :is_var , _ , [ { name , _ , _ } ] } ) do
461- [ name ]
485+ ## To AST conversion
486+
487+ defp collect_vars ( { :ann_type , _line , args } ) do
488+ Enum . flat_map ( args , & collect_vars / 1 )
462489 end
463490
464- defp guard_to_vars ( { :and , _ , [ left , right ] } ) do
465- guard_to_vars ( left ) ++ guard_to_vars ( right )
491+ defp collect_vars ( { :type , _line , _kind , args } ) do
492+ Enum . flat_map ( args , & collect_vars / 1 )
466493 end
467494
468- defp guard_to_constraints ( { :is_subtype , meta , [ { name , _ , context } , type ] } , vars , caller )
469- when is_atom ( name ) and is_atom ( context ) do
470- line = line ( meta )
471- contraints = [ { :atom , line , :is_subtype } , [ { :var , line , name } , typespec ( type , vars , caller ) ] ]
472- [ { :type , line , :constraint , contraints } ]
495+ defp collect_vars ( { :remote_type , _line , args } ) do
496+ Enum . flat_map ( args , & collect_vars / 1 )
473497 end
474498
475- defp guard_to_constraints ( { :is_var , _ , [ { name , _ , context } ] } , _ , _ )
476- when is_atom ( name ) and is_atom ( context ) do
477- [ ]
499+ defp collect_vars ( { :typed_record_field , _line , type } ) do
500+ collect_vars ( type )
478501 end
479502
480- defp guard_to_constraints ( { :and , _ , [ left , right ] } , vars , caller ) do
481- guard_to_constraints ( left , vars , caller ) ++ guard_to_constraints ( right , vars , caller )
503+ defp collect_vars ( { :paren_type , _line , [ type ] } ) do
504+ collect_vars ( type )
482505 end
483506
484- defp guard_to_constraints ( other , _vars , caller ) do
485- guard = Macro . to_string ( other )
486- compile_error caller , "invalid guard in function type specification `#{ guard } `"
507+ defp collect_vars ( { :var , _line , var } ) do
508+ [ erl_to_ex_var ( var ) ]
487509 end
488510
489- ## To AST conversion
511+ defp collect_vars ( _ ) do
512+ [ ]
513+ end
490514
491515 defp typespec_to_ast ( { :type , line , :tuple , :any } ) do
492516 typespec_to_ast ( { :type , line , :tuple , [ ] } )
@@ -549,14 +573,7 @@ defmodule Kernel.Typespec do
549573 end
550574
551575 defp typespec_to_ast ( { :var , line , var } ) do
552- var =
553- case atom_to_binary ( var ) do
554- << "_" , c :: [ binary , size ( 1 ) ] , rest :: binary >> ->
555- binary_to_atom ( "_#{ String . downcase ( c ) } #{ rest } " )
556- << c :: [ binary , size ( 1 ) ] , rest :: binary >> ->
557- binary_to_atom ( "#{ String . downcase ( c ) } #{ rest } " )
558- end
559- { var , line , nil }
576+ { erl_to_ex_var ( var ) , line , nil }
560577 end
561578
562579 # Special shortcut(s)
@@ -598,6 +615,15 @@ defmodule Kernel.Typespec do
598615
599616 defp typespec_to_ast ( other ) , do: other
600617
618+ defp erl_to_ex_var ( var ) do
619+ case atom_to_binary ( var ) do
620+ << "_" , c :: [ binary , size ( 1 ) ] , rest :: binary >> ->
621+ binary_to_atom ( "_#{ String . downcase ( c ) } #{ rest } " )
622+ << c :: [ binary , size ( 1 ) ] , rest :: binary >> ->
623+ binary_to_atom ( "#{ String . downcase ( c ) } #{ rest } " )
624+ end
625+ end
626+
601627 ## From AST conversion
602628
603629 defp line ( meta ) do
0 commit comments