Skip to content

Commit 7ab2145

Browse files
committed
Initial work on processing guards, or/orelse excluded
1 parent fff97fd commit 7ab2145

4 files changed

Lines changed: 182 additions & 58 deletions

File tree

lib/elixir/lib/module/types.ex

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -442,7 +442,9 @@ defmodule Module.Types do
442442
warnings: [],
443443
# All vars and their types
444444
vars: %{},
445-
# Variables and arguments from patterns
445+
# Variables that are specific to the current environment/conditional
446+
conditional_vars: nil,
447+
# Track metadata specific to matches and guards
446448
pattern_info: nil,
447449
# If type checking has found an error/failure
448450
failed: false,

lib/elixir/lib/module/types/of.ex

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -459,7 +459,7 @@ defmodule Module.Types.Of do
459459
Module.Types.Pattern.of_match_var(left, type, expr, stack, context)
460460

461461
:guard ->
462-
Module.Types.Pattern.of_guard(left, type, expr, stack, context)
462+
Module.Types.Pattern.of_guard(left, {false, type}, expr, stack, context)
463463

464464
:expr ->
465465
left = annotate_interpolation(left, right)
@@ -511,9 +511,9 @@ defmodule Module.Types.Of do
511511
compatible_size(actual, expr, stack, context)
512512
end
513513

514-
defp specifier_size(_pattern_or_guard, {:size, _, [arg]} = expr, stack, context)
514+
defp specifier_size(match_or_guard, {:size, _, [arg]} = expr, stack, context)
515515
when not is_integer(arg) do
516-
{actual, context} = Module.Types.Pattern.of_guard(arg, integer(), expr, stack, context)
516+
{actual, context} = Module.Types.Pattern.of_size(match_or_guard, arg, expr, stack, context)
517517
compatible_size(actual, expr, stack, context)
518518
end
519519

lib/elixir/lib/module/types/pattern.ex

Lines changed: 162 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)