@@ -784,18 +784,40 @@ defmodule Module.Types.Pattern do
784784 of_pattern ( suffix , path , stack , context )
785785 end
786786
787+ # [prefix | suffix]
788+ defp of_list ( [ prefix ] , suffix , path , stack , context ) when is_var ( prefix ) and is_var ( suffix ) do
789+ { suffix_type , suffix_precise? , context } =
790+ of_pattern ( suffix , [ :tail | path ] , stack , context )
791+
792+ context = annotate_list_subpattern ( suffix , context )
793+
794+ { prefix_type , prefix_precise? , context } =
795+ of_subpattern ( prefix , [ :head | path ] , stack , context )
796+
797+ context = annotate_list_subpattern ( prefix , context )
798+
799+ type =
800+ if is_descr ( prefix_type ) and is_descr ( suffix_type ) do
801+ non_empty_list ( prefix_type , suffix_type )
802+ else
803+ { :non_empty_list , [ prefix_type ] , suffix_type }
804+ end
805+
806+ { type , prefix_precise? and suffix_precise? , context }
807+ end
808+
787809 # [prefix1, prefix2, prefix3], [prefix1, prefix2 | suffix]
788810 defp of_list ( prefix , suffix , path , stack , context ) do
789- { suffix_type , tail_precise ?, context } = of_pattern ( suffix , [ :tail | path ] , stack , context )
811+ { suffix_type , _precise ?, context } = of_pattern ( suffix , [ :tail | path ] , stack , context )
790812
791- { head_precise? , static , dynamic , context } =
792- Enum . reduce ( prefix , { true , [ ] , [ ] , context } , fn arg , { _ , static , dynamic , context } ->
793- { type , precise ?, context } = of_subpattern ( arg , [ :head | path ] , stack , context )
813+ { static , dynamic , context } =
814+ Enum . reduce ( prefix , { [ ] , [ ] , context } , fn arg , { static , dynamic , context } ->
815+ { type , _precise ?, context } = of_subpattern ( arg , [ :head | path ] , stack , context )
794816
795817 if is_descr ( type ) do
796- { precise? , [ type | static ] , dynamic , context }
818+ { [ type | static ] , dynamic , context }
797819 else
798- { precise? , static , [ type | dynamic ] , context }
820+ { static , [ type | dynamic ] , context }
799821 end
800822 end )
801823
@@ -811,11 +833,19 @@ defmodule Module.Types.Pattern do
811833 { :non_empty_list , [ Enum . reduce ( static , & union / 2 ) | dynamic ] , suffix_type }
812834 end
813835
814- precise? =
815- head_precise? and tail_precise? and is_var ( suffix ) and
816- match? ( [ head ] when is_var ( head ) , prefix )
836+ { type , false , context }
837+ end
817838
818- { type , precise? , context }
839+ defp list_subpattern? ( version , context ) do
840+ is_map_key ( context . subpatterns , { :list , version } )
841+ end
842+
843+ defp annotate_list_subpattern ( { name , meta , _ } , context ) do
844+ if name != :_ do
845+ put_in ( context . subpatterns [ { :list , Keyword . fetch! ( meta , :version ) } ] , true )
846+ else
847+ context
848+ end
819849 end
820850
821851 # These cases don't need to store information because they have no intersection
@@ -861,45 +891,43 @@ defmodule Module.Types.Pattern do
861891 { true , of_changed ( changed , stack , context ) }
862892 end
863893
864- defp of_guards ( [ guard ] , changed , vars , stack , context ) do
865- context = init_pattern_info ( context , { false , nil , true , vars , Map . from_keys ( changed , [ ] ) } )
894+ defp of_guards ( guards , changed , vars , stack , context ) do
895+ context =
896+ init_pattern_info ( context , % {
897+ allow_empty?: false ,
898+ parent_version: nil ,
899+ precise?: true ,
900+ vars: vars ,
901+ changed: Map . from_keys ( changed , [ ] )
902+ } )
903+
904+ { guard_precise? , context } = of_guards ( guards , stack , context )
905+ { % { precise?: precise? , changed: changed } , context } = pop_pattern_info ( context )
906+ { precise? and guard_precise? , of_changed ( Map . keys ( changed ) , stack , context ) }
907+ end
908+
909+ defp of_guards ( [ guard ] , stack , context ) do
866910 { type , context } = of_guard ( guard , stack , context )
867- { precise? , context } = maybe_badguard ( type , guard , stack , context )
868- { { _ , _ , pattern_vars? , _ , changed_map } , context } = pop_pattern_info ( context )
869- { precise? and pattern_vars? , of_changed ( Map . keys ( changed_map ) , stack , context ) }
911+ maybe_badguard ( type , guard , stack , context )
870912 end
871913
872- defp of_guards ( guards , changed , vars , stack , context ) do
873- context = init_pattern_info ( context , { false , nil , true , vars , Map . from_keys ( changed , [ ] ) } )
914+ defp of_guards ( guards , stack , context ) do
874915 expr = Enum . reduce ( guards , { :_ , [ ] , [ ] } , & { :when , [ ] , [ & 2 , & 1 ] } )
875916
876- { precise? , context } =
877- Of . with_conditional_vars ( guards , true , expr , stack , context , fn guard , precise? , context ->
878- { type , context } = of_guard ( guard , stack , context )
879- { guard_precise? , context } = maybe_badguard ( type , guard , stack , context )
880- { guard_precise? and precise? , context }
881- end )
882-
883- { { _ , _ , pattern_vars? , _ , changed_map } , context } = pop_pattern_info ( context )
884- { precise? and pattern_vars? , of_changed ( Map . keys ( changed_map ) , stack , context ) }
917+ Of . with_conditional_vars ( guards , true , expr , stack , context , fn guard , precise? , context ->
918+ { type , context } = of_guard ( guard , stack , context )
919+ { guard_precise? , context } = maybe_badguard ( type , guard , stack , context )
920+ { guard_precise? and precise? , context }
921+ end )
885922 end
886923
887- defp update_parent_version (
888- parent_version ,
889- % { pattern_info: { fail_mode? , old_version , pattern_vars? , vars , changed_map } } = context
890- ) do
891- { old_version ,
892- % { context | pattern_info: { fail_mode? , parent_version , pattern_vars? , vars , changed_map } } }
924+ defp update_parent_version ( parent_version , % { pattern_info: pattern_info } = context ) do
925+ { pattern_info . parent_version ,
926+ % { context | pattern_info: % { pattern_info | parent_version: parent_version } } }
893927 end
894928
895- defp enable_conditional_mode (
896- % { pattern_info: { _fail_mode? , version , pattern_vars? , vars , changed_map } } = context
897- ) do
898- % {
899- context
900- | pattern_info: { true , version , pattern_vars? , vars , changed_map } ,
901- conditional_vars: % { }
902- }
929+ defp enable_conditional_mode ( % { pattern_info: pattern_info } = context ) do
930+ % { context | pattern_info: % { pattern_info | allow_empty?: true } , conditional_vars: % { } }
903931 end
904932
905933 defp maybe_badguard ( type , guard , stack , context ) do
@@ -1011,18 +1039,26 @@ defmodule Module.Types.Pattern do
10111039 # and also when vars change, so we need to deal with all possibilities
10121040 # for pattern_info.
10131041 case context . pattern_info do
1014- { fail_mode? , dep_version , pattern_vars? , vars , changed } when is_map ( changed ) ->
1015- pattern_vars? = pattern_vars? and not is_map_key ( vars , version )
1042+ % {
1043+ allow_empty?: allow_empty? ,
1044+ precise?: precise? ,
1045+ vars: vars ,
1046+ parent_version: parent_version ,
1047+ changed: changed
1048+ } = pattern_info ->
1049+ precise? =
1050+ precise? and not is_map_key ( vars , version ) and not list_subpattern? ( version , context )
1051+
10161052 changed = Map . put ( changed , version , [ ] )
1017- pattern_info = { fail_mode? , dep_version , pattern_vars? , vars , changed }
1053+ pattern_info = % { pattern_info | precise?: precise? , changed: changed }
10181054 context = % { context | pattern_info: pattern_info }
10191055
10201056 context =
1021- if dep_version != nil ,
1022- do: Of . track_var ( version , [ dep_version ] , [ ] , context ) ,
1057+ if parent_version != nil ,
1058+ do: Of . track_var ( version , [ parent_version ] , [ ] , context ) ,
10231059 else: context
10241060
1025- Of . refine_body_var ( version , expected , expr , stack , context , fail_mode ?)
1061+ Of . refine_body_var ( version , expected , expr , stack , context , allow_empty ?)
10261062
10271063 list when is_list ( list ) ->
10281064 node = path_node ( expected , var , expr , [ ] )
@@ -1054,12 +1090,14 @@ defmodule Module.Types.Pattern do
10541090 of_guard ( other_side , term ( ) , call , stack , context )
10551091 end
10561092
1093+ @ comp_op [ :== , :"/=" , :"=:=" , :"=/=" ]
1094+
10571095 defp of_remote ( fun , [ left , right ] = args , call , expected , stack , context )
1058- when fun in [ :== , :"/=" , :"=:=" , :"=/=" ] do
1096+ when fun in @ comp_op do
10591097 with false <- Macro . quoted_literal? ( left ) or Macro . quoted_literal? ( right ) ,
10601098 true <- is_var ( left ) or is_var ( right ) ,
10611099 { boolean , _maybe_or_always } <- booleaness ( expected ) ,
1062- % { pattern_info: { _ , _ , _ , _ , _ } } <- context do
1100+ % { pattern_info: % { } } <- context do
10631101 polarity =
10641102 case boolean do
10651103 true -> fun in [ :== , :"=:=" ]
@@ -1089,6 +1127,27 @@ defmodule Module.Types.Pattern do
10891127 # building nested conditional environments.
10901128 [ left | right ] = unpack_op ( call , fun , [ ] )
10911129
1130+ # In case the right side is a sequence of comparisons with imprecise literals, collapse them.
1131+ right =
1132+ with { { :. , _ , [ :erlang , comp_op ] } , _ , [ { var_name , _ , var_ctx } , literal ] }
1133+ when comp_op in @ comp_op and is_atom ( var_name ) and is_atom ( var_ctx ) and
1134+ ( is_number ( literal ) or is_binary ( literal ) ) <- left ,
1135+ true <-
1136+ Enum . all? ( right , fn
1137+ { { :. , _ , [ :erlang , ^ comp_op ] } , _ , [ { ^ var_name , _ , ^ var_ctx } , other_literal ] }
1138+ when is_integer ( literal ) and is_integer ( other_literal )
1139+ when is_float ( literal ) and is_float ( other_literal )
1140+ when is_binary ( literal ) and is_binary ( other_literal ) ->
1141+ true
1142+
1143+ _ ->
1144+ false
1145+ end ) do
1146+ [ ]
1147+ else
1148+ _ -> right
1149+ end
1150+
10921151 # For example, if the expected type is true for andalso, then it can
10931152 # only be true if both clauses are executed, so we know the first
10941153 # argument has to be true and the second has to be expected.
@@ -1116,6 +1175,17 @@ defmodule Module.Types.Pattern do
11161175 { type , vars_conds } =
11171176 of_logical_cond ( [ left | right ] , true , expected , abort_domain , stack , cond_context , [ ] )
11181177
1178+ # We will be precise if all branches changed the same variable
1179+ context =
1180+ update_in ( context . pattern_info . precise? , fn
1181+ false ->
1182+ false
1183+
1184+ true ->
1185+ [ { _ , cond } | tail ] = vars_conds
1186+ Enum . all? ( tail , fn { _ , tail_cond } -> cond == tail_cond end )
1187+ end )
1188+
11191189 { type , Of . reduce_conditional_vars ( vars_conds , call , stack , context ) }
11201190 end
11211191 end
0 commit comments