@@ -8,8 +8,6 @@ defmodule Module.Types.Pattern do
88 alias Module.Types . { Apply , Of }
99 import Module.Types . { Helpers , Descr }
1010
11- @ guard atom ( [ true , false , :fail ] )
12-
1311 @ doc """
1412 Handles patterns and guards at once.
1513
@@ -38,7 +36,7 @@ defmodule Module.Types.Pattern do
3836 stack = % { stack | meta: meta }
3937
4038 { trees , context } = of_pattern_args ( patterns , expected , tag , stack , context )
41- { _ , context } = Enum . map_reduce ( guards , context , & of_guard ( & 1 , @ guard , & 1 , stack , & 2 ) )
39+ { _ , context } = of_guards ( guards , stack , context )
4240 { trees , context }
4341 end
4442
@@ -58,9 +56,9 @@ defmodule Module.Types.Pattern do
5856 end
5957
6058 defp of_pattern_args ( patterns , expected , tag , stack , context ) do
61- context = init_pattern_info ( context )
59+ context = init_match_info ( context )
6260 { trees , context } = of_pattern_args_zip ( patterns , expected , 0 , [ ] , stack , context )
63- { pattern_info , context } = pop_pattern_info ( context )
61+ { pattern_info , context } = pop_match_info ( context )
6462
6563 context =
6664 case of_pattern_intersect ( trees , 0 , [ ] , pattern_info , tag , stack , context ) do
@@ -90,9 +88,9 @@ defmodule Module.Types.Pattern do
9088 end
9189
9290 def of_match ( pattern , expected_fun , expr , stack , context ) do
93- context = init_pattern_info ( context )
91+ context = init_match_info ( context )
9492 { tree , context } = of_pattern ( pattern , [ % { root: { :arg , 0 } , expr: expr } ] , stack , context )
95- { pattern_info , context } = pop_pattern_info ( context )
93+ { pattern_info , context } = pop_match_info ( context )
9694 { expected , context } = expected_fun . ( of_pattern_tree ( tree , context ) , context )
9795
9896 args = [ { tree , expected , expr } ]
@@ -114,9 +112,9 @@ defmodule Module.Types.Pattern do
114112 end
115113
116114 def of_generator ( pattern , guards , expected , tag , expr , stack , context ) do
117- context = init_pattern_info ( context )
115+ context = init_match_info ( context )
118116 { tree , context } = of_pattern ( pattern , [ % { root: { :arg , 0 } , expr: expr } ] , stack , context )
119- { pattern_info , context } = pop_pattern_info ( context )
117+ { pattern_info , context } = pop_match_info ( context )
120118 args = [ { tree , expected , pattern } ]
121119
122120 context =
@@ -125,7 +123,7 @@ defmodule Module.Types.Pattern do
125123 { :error , context } -> context
126124 end
127125
128- { _ , context } = Enum . map_reduce ( guards , context , & of_guard ( & 1 , @ guard , & 1 , stack , & 2 ) )
126+ { _ , context } = of_guards ( guards , stack , context )
129127 context
130128 end
131129
@@ -290,6 +288,19 @@ defmodule Module.Types.Pattern do
290288 end
291289 end
292290
291+ # pattern_info stores the variables defined in patterns,
292+ # additional information about the number of variables in
293+ # arguments and list heads, and a counter used to compute
294+ # the number of list heads.
295+ # TODO: Move vars_deps and vars_paths into context.vars.
296+ defp init_match_info ( context ) do
297+ % { context | pattern_info: { [ ] , % { } , % { } } }
298+ end
299+
300+ defp pop_match_info ( % { pattern_info: pattern_info } = context ) do
301+ { pattern_info , % { context | pattern_info: nil } }
302+ end
303+
293304 defp of_pattern_var ( [ ] , type , _context ) do
294305 { :ok , type }
295306 end
@@ -397,8 +408,39 @@ defmodule Module.Types.Pattern do
397408 { binary ( ) , Of . binary ( args , :match , stack , context ) }
398409 end
399410
400- def of_match_var ( ast , expected , expr , stack , context ) do
401- of_guard ( ast , expected , expr , stack , context )
411+ def of_match_var ( { :^ , _meta , [ var ] } , expected , expr , stack , context ) do
412+ Of . refine_body_var ( var , expected , expr , stack , context )
413+ end
414+
415+ def of_match_var ( atom , _expected , _expr , _stack , context ) when is_atom ( atom ) do
416+ { atom ( ) , context }
417+ end
418+
419+ def of_match_var ( binary , _expected , _expr , _stack , context ) when is_binary ( binary ) do
420+ { binary ( ) , context }
421+ end
422+
423+ def of_match_var ( integer , _expected , _expr , _stack , context ) when is_integer ( integer ) do
424+ { integer ( ) , context }
425+ end
426+
427+ def of_match_var ( float , _expected , _expr , _stack , context ) when is_float ( float ) do
428+ { float ( ) , context }
429+ end
430+
431+ @ doc """
432+ Handle `size` in binary modifiers.
433+
434+ They behave like guards, so we need to take into account their scope.
435+ """
436+ def of_size ( :match , arg , expr , stack , % { pattern_info: pattern_info } = context ) do
437+ context = init_guard_info ( context )
438+ { type , context } = of_guard ( arg , { false , integer ( ) } , expr , stack , context )
439+ { type , % { context | pattern_info: pattern_info } }
440+ end
441+
442+ def of_size ( :guard , arg , expr , stack , context ) do
443+ of_guard ( arg , { false , integer ( ) } , expr , stack , context )
402444 end
403445
404446 ## Patterns
@@ -704,118 +746,185 @@ defmodule Module.Types.Pattern do
704746 end
705747
706748 ## Guards
707- # This function is public as it is invoked from Of.binary/4.
749+ #
750+ # Whenever we have a or/orelse, we need to build multiple environments
751+ # and we only preserve intersections of those environments. However,
752+ # when building those environments, domain checks are always passed
753+ # upstream, except when they are on the right-side of `orelse`.
754+ #
755+ # Therefore, in addition to `conditional_vars`, we have to track:
756+ #
757+ # 1. Should we process type checks? We always do so at the root of guards.
758+ # Inside or/orelse, we also need to check the environments.
759+ #
760+ # 2. Should we process domain checks? We always process it, except that, if
761+ # on the right-side of orelse, it is only kept if it is shared across
762+ # the environment vars.
763+
764+ @ guard atom ( [ true , false , :fail ] )
765+
766+ defp of_guards ( [ ] , _stack , context ) do
767+ { [ ] , context }
768+ end
769+
770+ defp of_guards ( guards , stack , context ) do
771+ # TODO: This match? is temporary until we support multiple guards
772+ context = init_guard_info ( context , match? ( [ _ ] , guards ) )
773+
774+ { types , context } =
775+ Enum . map_reduce ( guards , context , & of_guard ( & 1 , { true , @ guard } , & 1 , stack , & 2 ) )
776+
777+ { _ , context } = pop_guard_info ( context )
778+ { types , context }
779+ end
780+
781+ defp init_guard_info ( context , check_domain? \\ true ) do
782+ % { context | pattern_info: { check_domain? } }
783+ end
784+
785+ defp pop_guard_info ( % { pattern_info: pattern_info } = context ) do
786+ { pattern_info , % { context | pattern_info: nil } }
787+ end
708788
709789 # :atom
710- def of_guard ( atom , _expected , _expr , _stack , context ) when is_atom ( atom ) do
790+ def of_guard ( atom , _root_expected , _expr , _stack , context ) when is_atom ( atom ) do
711791 { atom ( [ atom ] ) , context }
712792 end
713793
714794 # 12
715- def of_guard ( literal , _expected , _expr , _stack , context ) when is_integer ( literal ) do
795+ def of_guard ( literal , _root_expected , _expr , _stack , context ) when is_integer ( literal ) do
716796 { integer ( ) , context }
717797 end
718798
719799 # 1.2
720- def of_guard ( literal , _expected , _expr , _stack , context ) when is_float ( literal ) do
800+ def of_guard ( literal , _root_expected , _expr , _stack , context ) when is_float ( literal ) do
721801 { float ( ) , context }
722802 end
723803
724804 # "..."
725- def of_guard ( literal , _expected , _expr , _stack , context ) when is_binary ( literal ) do
805+ def of_guard ( literal , _root_expected , _expr , _stack , context ) when is_binary ( literal ) do
726806 { binary ( ) , context }
727807 end
728808
729809 # []
730- def of_guard ( [ ] , _expected , _expr , _stack , context ) do
810+ def of_guard ( [ ] , _root_expected , _expr , _stack , context ) do
731811 { empty_list ( ) , context }
732812 end
733813
734814 # [expr, ...]
735- def of_guard ( list , _expected , expr , stack , context ) when is_list ( list ) do
815+ def of_guard ( list , _root_expected , expr , stack , context ) when is_list ( list ) do
736816 { prefix , suffix } = unpack_list ( list , [ ] )
737817
738818 { prefix , context } =
739- Enum . map_reduce ( prefix , context , & of_guard ( & 1 , term ( ) , expr , stack , & 2 ) )
819+ Enum . map_reduce ( prefix , context , & of_guard ( & 1 , { false , term ( ) } , expr , stack , & 2 ) )
740820
741- { suffix , context } = of_guard ( suffix , term ( ) , expr , stack , context )
821+ { suffix , context } = of_guard ( suffix , { false , term ( ) } , expr , stack , context )
742822 { non_empty_list ( Enum . reduce ( prefix , & union / 2 ) , suffix ) , context }
743823 end
744824
745825 # {left, right}
746- def of_guard ( { left , right } , expected , expr , stack , context ) do
747- of_guard ( { :{} , [ ] , [ left , right ] } , expected , expr , stack , context )
826+ def of_guard ( { left , right } , root_expected , expr , stack , context ) do
827+ of_guard ( { :{} , [ ] , [ left , right ] } , root_expected , expr , stack , context )
748828 end
749829
750830 # %Struct{...}
751- def of_guard ( { :% , meta , [ module , { :%{} , _ , args } ] } = struct , expected , _expr , stack , context )
831+ def of_guard (
832+ { :% , meta , [ module , { :%{} , _ , args } ] } = struct ,
833+ { _root , expected } ,
834+ _expr ,
835+ stack ,
836+ context
837+ )
752838 when is_atom ( module ) do
753- fun = & of_guard ( & 1 , & 2 , struct , & 3 , & 4 )
839+ fun = & of_guard ( & 1 , { false , & 2 } , struct , & 3 , & 4 )
754840 Of . struct_instance ( module , args , expected , meta , stack , context , fun )
755841 end
756842
757843 # %{...}
758- def of_guard ( { :%{} , _meta , args } , expected , expr , stack , context ) do
759- Of . closed_map ( args , expected , stack , context , & of_guard ( & 1 , & 2 , expr , & 3 , & 4 ) )
844+ def of_guard ( { :%{} , _meta , args } , { _root , expected } , expr , stack , context ) do
845+ Of . closed_map ( args , expected , stack , context , & of_guard ( & 1 , { false , & 2 } , expr , & 3 , & 4 ) )
760846 end
761847
762848 # <<>>
763- def of_guard ( { :<<>> , _meta , args } , _expected , _expr , stack , context ) do
849+ def of_guard ( { :<<>> , _meta , args } , _root_expected , _expr , stack , context ) do
764850 context = Of . binary ( args , :guard , stack , context )
765851 { binary ( ) , context }
766852 end
767853
768854 # ^var
769- def of_guard ( { :^ , _meta , [ var ] } , expected , expr , stack , context ) do
855+ def of_guard ( { :^ , _meta , [ var ] } , { _root , expected } , expr , stack , context ) do
770856 # This is used by binary size, which behaves as a mixture of match and guard
771857 Of . refine_body_var ( var , expected , expr , stack , context )
772858 end
773859
774860 # {...}
775- def of_guard ( { :{} , _meta , args } , _expected , expr , stack , context ) do
776- { types , context } = Enum . map_reduce ( args , context , & of_guard ( & 1 , term ( ) , expr , stack , & 2 ) )
861+ def of_guard ( { :{} , _meta , args } , _root_expected , expr , stack , context ) do
862+ { types , context } =
863+ Enum . map_reduce ( args , context , & of_guard ( & 1 , { false , term ( ) } , expr , stack , & 2 ) )
864+
777865 { tuple ( types ) , context }
778866 end
779867
780868 # var.field
781- def of_guard ( { { :. , _ , [ callee , key ] } , _ , [ ] } = map_fetch , _expected , expr , stack , context )
869+ def of_guard (
870+ { { :. , _ , [ callee , key ] } , _ , [ ] } = map_fetch ,
871+ { _root , expected } ,
872+ expr ,
873+ stack ,
874+ context
875+ )
782876 when not is_atom ( callee ) do
783- { type , context } = of_guard ( callee , term ( ) , expr , stack , context )
877+ { type , context } = of_guard ( callee , { false , open_map ( [ { key , expected } ] ) } , expr , stack , context )
784878 Of . map_fetch ( map_fetch , type , key , stack , context )
785879 end
786880
787881 # Remote
788- def of_guard ( { { :. , _ , [ :erlang , fun ] } , meta , args } = call , expected , _expr , stack , context )
882+ def of_guard ( { { :. , _ , [ :erlang , fun ] } , meta , args } = call , root_expected , _ , stack , context )
789883 when is_atom ( fun ) do
790- { info , domain , context } =
791- Apply . remote_domain ( :erlang , fun , args , expected , meta , stack , context )
792-
793- { args_types , context } =
794- zip_map_reduce ( args , domain , context , & of_guard ( & 1 , & 2 , call , stack , & 3 ) )
795-
796- Apply . remote_apply ( info , :erlang , fun , args_types , call , stack , context )
884+ of_remote ( fun , meta , args , call , root_expected , stack , context )
797885 end
798886
799887 # var
800- def of_guard ( var , _expected , _expr , _stack , context ) when is_var ( var ) do
801- { Of . var ( var , context ) , context }
888+ def of_guard ( var , { _root , expected } , expr , stack , context ) when is_var ( var ) do
889+ case context . pattern_info do
890+ { true } -> Of . refine_body_var ( var , expected , expr , stack , context )
891+ { false } -> { Of . var ( var , context ) , context }
892+ end
802893 end
803894
804- ## Helpers
895+ defp of_remote ( fun , meta , [ left , right ] , call , { _root , expected } , stack , context )
896+ when fun in [ :or , :orelse ] do
897+ { info , [ left_domain , right_domain ] , context } =
898+ Apply . remote_domain ( :erlang , fun , [ left , right ] , expected , meta , stack , context )
805899
806- # pattern_info stores the variables defined in patterns,
807- # additional information about the number of variables in
808- # arguments and list heads, and a counter used to compute
809- # the number of list heads.
810- # TODO: Consider moving pattern_info into context.vars.
811- defp init_pattern_info ( context ) do
812- % { context | pattern_info: { [ ] , % { } , % { } } }
900+ { left_type , context } = of_guard ( left , { false , left_domain } , call , stack , context )
901+
902+ { right_type , context } =
903+ if fun == :or do
904+ of_guard ( right , { false , right_domain } , call , stack , context )
905+ else
906+ % { pattern_info: pattern_info } = context
907+ context = % { context | pattern_info: { false } }
908+ { type , context } = of_guard ( right , { false , right_domain } , call , stack , context )
909+ { type , % { context | pattern_info: pattern_info } }
910+ end
911+
912+ args_types = [ left_type , right_type ]
913+ Apply . remote_apply ( info , :erlang , fun , args_types , call , stack , context )
813914 end
814915
815- defp pop_pattern_info ( % { pattern_info: pattern_info } = context ) do
816- { pattern_info , % { context | pattern_info: nil } }
916+ defp of_remote ( fun , meta , args , call , { _root , expected } , stack , context ) do
917+ { info , domain , context } =
918+ Apply . remote_domain ( :erlang , fun , args , expected , meta , stack , context )
919+
920+ { args_types , context } =
921+ zip_map_reduce ( args , domain , context , & of_guard ( & 1 , { false , & 2 } , call , stack , & 3 ) )
922+
923+ Apply . remote_apply ( info , :erlang , fun , args_types , call , stack , context )
817924 end
818925
926+ ## Helpers
927+
819928 def format_diagnostic ( { :badmatch , expr , context } ) do
820929 traces = collect_traces ( expr , context )
821930
0 commit comments