@@ -287,34 +287,36 @@ defmodule Module.Types.Expr do
287287 of_expr ( post , expected , post , stack , context )
288288 end
289289
290- def of_expr ( { :cond , _meta , [ [ { :do , clauses } ] ] } , expected , expr , stack , original ) do
291- clauses
292- |> reduce_non_empty ( { none ( ) , original } , fn
293- { :-> , meta , [ [ head ] , body ] } , { acc , context } , last? ->
294- { head_type , context } = of_expr ( head , @ pending , head , stack , context )
295-
296- context =
297- if is_warning ( stack ) do
298- case truthiness ( head_type ) do
299- :always_true when not last? ->
300- warning = { :badcond , "always match" , head_type , head , context }
301- warn ( __MODULE__ , warning , meta , stack , context )
302-
303- :always_false ->
304- warning = { :badcond , "never match" , head_type , head , context }
305- warn ( __MODULE__ , warning , meta , stack , context )
306-
307- _ ->
308- context
290+ def of_expr ( { :cond , meta , [ [ { :do , clauses } ] ] } , expected , expr , stack , original ) do
291+ cache_result ( meta , stack , original , fn ->
292+ clauses
293+ |> reduce_non_empty ( { none ( ) , original } , fn
294+ { :-> , meta , [ [ head ] , body ] } , { acc , context } , last? ->
295+ { head_type , context } = of_expr ( head , @ pending , head , stack , context )
296+
297+ context =
298+ if is_warning ( stack ) do
299+ case truthiness ( head_type ) do
300+ :always_true when not last? ->
301+ warning = { :badcond , "always match" , head_type , head , context }
302+ warn ( __MODULE__ , warning , meta , stack , context )
303+
304+ :always_false ->
305+ warning = { :badcond , "never match" , head_type , head , context }
306+ warn ( __MODULE__ , warning , meta , stack , context )
307+
308+ _ ->
309+ context
310+ end
311+ else
312+ context
309313 end
310- else
311- context
312- end
313314
314- { body_type , context } = of_expr ( body , expected , expr , stack , context )
315- { union ( body_type , acc ) , Of . reset_vars ( context , original ) }
315+ { body_type , context } = of_expr ( body , expected , expr , stack , context )
316+ { union ( body_type , acc ) , Of . reset_vars ( context , original ) }
317+ end )
318+ |> dynamic_unless_static ( stack )
316319 end )
317- |> dynamic_unless_static ( stack )
318320 end
319321
320322 def of_expr ( { :case , meta , [ case_expr , [ { :do , _clauses } ] ] } , expected , _expr , stack , context )
@@ -383,139 +385,149 @@ defmodule Module.Types.Expr do
383385 end
384386
385387 # fn pat -> expr end
386- def of_expr ( { :fn , _meta , clauses } , _expected , _expr , stack , context ) do
387- [ { :-> , _ , [ head , _ ] } | _ ] = clauses
388- { patterns , _guards } = extract_head ( head )
389- domain = Enum . map ( patterns , fn _ -> dynamic ( ) end )
390-
391- of_body = fn _args_types , body , context -> of_expr ( body , term ( ) , body , stack , context ) end
392-
393- { acc , context } =
394- of_clauses_fun ( clauses , domain , :fn , stack , context , of_body , [ ] , fn
395- trees , body_type , context , acc ->
396- args_types = Pattern . of_domain ( trees , stack , context )
397- add_inferred ( acc , args_types , body_type )
398- end )
388+ def of_expr ( { :fn , meta , clauses } , _expected , _expr , stack , context ) do
389+ cache_result ( meta , stack , context , fn ->
390+ [ { :-> , _ , [ head , _ ] } | _ ] = clauses
391+ { patterns , _guards } = extract_head ( head )
392+ domain = Enum . map ( patterns , fn _ -> dynamic ( ) end )
399393
400- { fun_from_inferred_clauses ( acc ) , context }
394+ of_body = fn _args_types , body , context -> of_expr ( body , term ( ) , body , stack , context ) end
395+
396+ { acc , context } =
397+ of_clauses_fun ( clauses , domain , :fn , stack , context , of_body , [ ] , fn
398+ trees , body_type , context , acc ->
399+ args_types = Pattern . of_domain ( trees , stack , context )
400+ add_inferred ( acc , args_types , body_type )
401+ end )
402+
403+ { fun_from_inferred_clauses ( acc ) , context }
404+ end )
401405 end
402406
403407 def of_expr ( { :try , meta , [ [ do: body ] ++ blocks ] } , expected , expr , stack , original ) do
404- { after_block , blocks } = Keyword . pop ( blocks , :after )
405- { else_block , blocks } = Keyword . pop ( blocks , :else )
406-
407- { type , context } =
408- if else_block do
409- { type , context } = of_expr ( body , @ pending , body , stack , original )
410- info = { :try_else , meta , body , type }
411- of_clauses ( else_block , [ type ] , expected , info , stack , context , none ( ) )
412- else
413- of_expr ( body , expected , expr , stack , original )
414- end
408+ cache_result ( meta , stack , original , fn ->
409+ { after_block , blocks } = Keyword . pop ( blocks , :after )
410+ { else_block , blocks } = Keyword . pop ( blocks , :else )
411+
412+ { type , context } =
413+ if else_block do
414+ { type , context } = of_expr ( body , @ pending , body , stack , original )
415+ info = { :try_else , meta , body , type }
416+ of_clauses ( else_block , [ type ] , expected , info , stack , context , none ( ) )
417+ else
418+ of_expr ( body , expected , expr , stack , original )
419+ end
415420
416- { type , context } =
417- blocks
418- |> Enum . reduce ( { type , Of . reset_vars ( context , original ) } , fn
419- { :rescue , clauses } , acc_context ->
420- Enum . reduce ( clauses , acc_context , fn
421- { :-> , _ , [ [ { :in , meta , [ var , exceptions ] } = expr ] , body ] } , { acc , context } ->
422- { type , context } =
423- of_rescue ( var , exceptions , body , expr , :rescue , meta , stack , context )
421+ { type , context } =
422+ blocks
423+ |> Enum . reduce ( { type , Of . reset_vars ( context , original ) } , fn
424+ { :rescue , clauses } , acc_context ->
425+ Enum . reduce ( clauses , acc_context , fn
426+ { :-> , _ , [ [ { :in , meta , [ var , exceptions ] } = expr ] , body ] } , { acc , context } ->
427+ { type , context } =
428+ of_rescue ( var , exceptions , body , expr , :rescue , meta , stack , context )
424429
425- { union ( type , acc ) , context }
430+ { union ( type , acc ) , context }
426431
427- { :-> , meta , [ [ var ] , body ] } , { acc , context } ->
428- { type , context } =
429- of_rescue ( var , [ ] , body , var , :anonymous_rescue , meta , stack , context )
432+ { :-> , meta , [ [ var ] , body ] } , { acc , context } ->
433+ { type , context } =
434+ of_rescue ( var , [ ] , body , var , :anonymous_rescue , meta , stack , context )
430435
431- { union ( type , acc ) , context }
432- end )
436+ { union ( type , acc ) , context }
437+ end )
433438
434- { :catch , clauses } , { acc , context } ->
435- args = [ @ try_catch , dynamic ( ) ]
436- of_clauses ( clauses , args , expected , :try_catch , stack , context , acc )
437- end )
438- |> dynamic_unless_static ( stack )
439+ { :catch , clauses } , { acc , context } ->
440+ args = [ @ try_catch , dynamic ( ) ]
441+ of_clauses ( clauses , args , expected , :try_catch , stack , context , acc )
442+ end )
443+ |> dynamic_unless_static ( stack )
439444
440- if after_block do
441- { _type , context } = of_expr ( after_block , term ( ) , after_block , stack , context )
442- { type , context }
443- else
444- { type , context }
445- end
445+ if after_block do
446+ { _type , context } = of_expr ( after_block , term ( ) , after_block , stack , context )
447+ { type , context }
448+ else
449+ { type , context }
450+ end
451+ end )
446452 end
447453
448454 @ timeout_type union ( integer ( ) , atom ( [ :infinity ] ) )
449455
450- def of_expr ( { :receive , _meta , [ blocks ] } , expected , expr , stack , original ) do
451- blocks
452- |> Enum . reduce ( { none ( ) , original } , fn
453- { :do , { :__block__ , _ , [ ] } } , acc_context ->
454- acc_context
456+ def of_expr ( { :receive , meta , [ blocks ] } , expected , expr , stack , original ) do
457+ cache_result ( meta , stack , original , fn ->
458+ blocks
459+ |> Enum . reduce ( { none ( ) , original } , fn
460+ { :do , { :__block__ , _ , [ ] } } , acc_context ->
461+ acc_context
455462
456- { :do , clauses } , { acc , context } ->
457- of_clauses ( clauses , [ dynamic ( ) ] , expected , :receive , stack , context , acc )
463+ { :do , clauses } , { acc , context } ->
464+ of_clauses ( clauses , [ dynamic ( ) ] , expected , :receive , stack , context , acc )
458465
459- { :after , [ { :-> , meta , [ [ timeout ] , body ] } ] = after_expr } , { acc , context } ->
460- { timeout_type , context } = of_expr ( timeout , @ timeout_type , after_expr , stack , context )
461- { body_type , context } = of_expr ( body , expected , expr , stack , context )
466+ { :after , [ { :-> , meta , [ [ timeout ] , body ] } ] = after_expr } , { acc , context } ->
467+ { timeout_type , context } = of_expr ( timeout , @ timeout_type , after_expr , stack , context )
468+ { body_type , context } = of_expr ( body , expected , expr , stack , context )
462469
463- if compatible? ( timeout_type , @ timeout_type ) do
464- { union ( body_type , acc ) , Of . reset_vars ( context , original ) }
465- else
466- error = { :badtimeout , timeout_type , timeout , context }
467- { union ( body_type , acc ) , error ( __MODULE__ , error , meta , stack , context ) }
468- end
470+ if compatible? ( timeout_type , @ timeout_type ) do
471+ { union ( body_type , acc ) , Of . reset_vars ( context , original ) }
472+ else
473+ error = { :badtimeout , timeout_type , timeout , context }
474+ { union ( body_type , acc ) , error ( __MODULE__ , error , meta , stack , context ) }
475+ end
476+ end )
477+ |> dynamic_unless_static ( stack )
469478 end )
470- |> dynamic_unless_static ( stack )
471479 end
472480
473481 def of_expr ( { :for , meta , [ _ | _ ] = args } , expected , expr , stack , context ) do
474- { clauses , [ [ { :do , block } | opts ] ] } = Enum . split ( args , - 1 )
475- context = Enum . reduce ( clauses , context , & for_clause ( & 1 , stack , & 2 ) )
476-
477- # We don't need to type check uniq, as it is a compile-time boolean.
478- # We handle reduce and into accordingly instead.
479- if Keyword . has_key? ( opts , :reduce ) do
480- reduce = Keyword . fetch! ( opts , :reduce )
481- { reduce_type , context } = of_expr ( reduce , expected , expr , stack , context )
482- # TODO: We need to type check against dynamic() instead of using reduce_type
483- # because this is recursive. We need to infer the block type first.
484- args = [ dynamic ( ) ]
485- of_clauses ( block , args , expected , :for_reduce , stack , context , reduce_type )
486- else
487- # TODO: Use the collectable protocol for the output
488- # TODO: Use the expected type for the block output
489- into = Keyword . get ( opts , :into , [ ] )
490- { into_type , into_kind , context } = for_into ( into , meta , stack , context )
491- { block_type , context } = of_expr ( block , @ pending , block , stack , context )
492-
493- case into_kind do
494- :bitstring ->
495- case compatible_intersection ( block_type , bitstring ( ) ) do
496- { :ok , intersection } ->
497- { return_union ( into_type , intersection , stack ) , context }
498-
499- { :error , _ } ->
500- error = { :badbitbody , block_type , block , context }
501- { error_type ( ) , error ( __MODULE__ , error , meta , stack , context ) }
502- end
482+ cache_result ( meta , stack , context , fn ->
483+ { clauses , [ [ { :do , block } | opts ] ] } = Enum . split ( args , - 1 )
484+ context = Enum . reduce ( clauses , context , & for_clause ( & 1 , stack , & 2 ) )
485+
486+ # We don't need to type check uniq, as it is a compile-time boolean.
487+ # We handle reduce and into accordingly instead.
488+ if Keyword . has_key? ( opts , :reduce ) do
489+ reduce = Keyword . fetch! ( opts , :reduce )
490+ { reduce_type , context } = of_expr ( reduce , expected , expr , stack , context )
491+ # TODO: We need to type check against dynamic() instead of using reduce_type
492+ # because this is recursive. We need to infer the block type first.
493+ args = [ dynamic ( ) ]
494+ of_clauses ( block , args , expected , :for_reduce , stack , context , reduce_type )
495+ else
496+ # TODO: Use the collectable protocol for the output
497+ # TODO: Use the expected type for the block output
498+ into = Keyword . get ( opts , :into , [ ] )
499+ { into_type , into_kind , context } = for_into ( into , meta , stack , context )
500+ { block_type , context } = of_expr ( block , @ pending , block , stack , context )
501+
502+ case into_kind do
503+ :bitstring ->
504+ case compatible_intersection ( block_type , bitstring ( ) ) do
505+ { :ok , intersection } ->
506+ { return_union ( into_type , intersection , stack ) , context }
507+
508+ { :error , _ } ->
509+ error = { :badbitbody , block_type , block , context }
510+ { error_type ( ) , error ( __MODULE__ , error , meta , stack , context ) }
511+ end
503512
504- :non_empty_list ->
505- { return_union ( into_type , non_empty_list ( block_type ) , stack ) , context }
513+ :non_empty_list ->
514+ { return_union ( into_type , non_empty_list ( block_type ) , stack ) , context }
506515
507- :none ->
508- { into_type , context }
516+ :none ->
517+ { into_type , context }
518+ end
509519 end
510- end
520+ end )
511521 end
512522
513523 # TODO: with pat <- expr do expr end
514- def of_expr ( { :with , _meta , [ _ | _ ] = clauses } , _expected , _expr , stack , original ) do
515- { clauses , [ options ] } = Enum . split ( clauses , - 1 )
516- context = Enum . reduce ( clauses , original , & with_clause ( & 1 , stack , & 2 ) )
517- context = Enum . reduce ( options , context , & with_option ( & 1 , stack , & 2 , original ) )
518- { dynamic ( ) , context }
524+ def of_expr ( { :with , meta , [ _ | _ ] = clauses } , _expected , _expr , stack , original ) do
525+ cache_result ( meta , stack , original , fn ->
526+ { clauses , [ options ] } = Enum . split ( clauses , - 1 )
527+ context = Enum . reduce ( clauses , original , & with_clause ( & 1 , stack , & 2 ) )
528+ context = Enum . reduce ( options , context , & with_option ( & 1 , stack , & 2 , original ) )
529+ { dynamic ( ) , context }
530+ end )
519531 end
520532
521533 def of_expr ( { { :. , _ , [ fun ] } , _ , args } = call , _expected , _expr , stack , context ) do
@@ -775,6 +787,23 @@ defmodule Module.Types.Expr do
775787 defp dynamic_unless_static ( { _ , _ } = output , % { mode: :static } ) , do: output
776788 defp dynamic_unless_static ( { type , context } , % { mode: _ } ) , do: { dynamic ( type ) , context }
777789
790+ defp cache_result ( meta , % { reverse_arrow: reverse_arrow } , context , fun ) do
791+ case reverse_arrow do
792+ nil ->
793+ fun . ( )
794+
795+ :cache ->
796+ { result , context } = fun . ( )
797+ version = Keyword . fetch! ( meta , :version )
798+ context = put_in ( context . reverse_arrows [ version ] , result )
799+ { result , context }
800+
801+ :use ->
802+ version = Keyword . fetch! ( meta , :version )
803+ { Map . fetch! ( context . reverse_arrows , version ) , context }
804+ end
805+ end
806+
778807 defp cache_arrows ( _meta , % { reverse_arrow: nil } , _fun ) , do: nil
779808
780809 defp cache_arrows ( meta , % { reverse_arrow: :cache } , fun ) do
0 commit comments