/
map.ex
120 lines (100 loc) · 3.9 KB
/
map.ex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
defmodule TypeCheck.Builtin.Map do
defstruct [:key_type, :value_type]
use TypeCheck
@opaque! t :: %__MODULE__{key_type: TypeCheck.Type.t(), value_type: TypeCheck.Type.t()}
@type! problem_tuple ::
{t(), :not_a_map, %{}, any()}
| {t(), :key_error,
%{problem: lazy(TypeCheck.TypeError.Formatter.problem_tuple()), key: any()}, any()}
| {t(), :value_error,
%{problem: lazy(TypeCheck.TypeError.Formatter.problem_tuple()), key: any()}, any()}
defimpl TypeCheck.Protocols.Escape do
def escape(s) do
%{
s
| key_type: TypeCheck.Protocols.Escape.escape(s.key_type),
value_type: TypeCheck.Protocols.Escape.escape(s.value_type)
}
end
end
defimpl TypeCheck.Protocols.ToCheck do
def to_check(s, param) do
quote generated: true, location: :keep do
case unquote(param) do
x when not is_map(x) ->
{:error,
{unquote(TypeCheck.Internals.Escaper.escape(s)), :not_a_map, %{}, unquote(param)}}
_ ->
unquote(build_keypairs_check(s.key_type, s.value_type, param, s))
end
end
end
defp build_keypairs_check(%TypeCheck.Builtin.Any{}, %TypeCheck.Builtin.Any{}, param, _s) do
quote generated: true, location: :keep do
{:ok, [], unquote(param)}
end
end
defp build_keypairs_check(key_type, value_type, param, s) do
key_check =
TypeCheck.Protocols.ToCheck.to_check(key_type, Macro.var(:single_field_key, __MODULE__))
value_check =
TypeCheck.Protocols.ToCheck.to_check(
value_type,
Macro.var(:single_field_value, __MODULE__)
)
quote generated: true, location: :keep do
orig_param = unquote(param)
res =
orig_param
# :maps.to_list is needed so a struct without an Enumerable impl does not crash this code:
|> :maps.to_list()
|> Enum.reduce_while({:ok, [], []}, fn {key, value}, {:ok, bindings, altered_param} ->
var!(single_field_key, unquote(__MODULE__)) = key
var!(single_field_value, unquote(__MODULE__)) = value
case {unquote(key_check), unquote(value_check)} do
{{:ok, key_bindings, altered_key}, {:ok, value_bindings, altered_value}} ->
altered_param = [{altered_key, altered_value} | altered_param]
res = {:ok, value_bindings ++ key_bindings ++ bindings, altered_param}
{:cont, res}
{{:error, problem}, _} ->
res =
{:error,
{unquote(TypeCheck.Internals.Escaper.escape(s)), :key_error,
%{problem: problem, key: key}, orig_param}}
{:halt, res}
{_, {:error, problem}} ->
res =
{:error,
{unquote(TypeCheck.Internals.Escaper.escape(s)), :value_error,
%{problem: problem, key: key}, orig_param}}
{:halt, res}
end
end)
case res do
{:ok, bindings, altered_param} ->
{:ok, bindings, :maps.from_list(altered_param)}
other ->
other
end
end
end
end
defimpl TypeCheck.Protocols.Inspect do
def inspect(list, opts) do
key_str = TypeCheck.Protocols.Inspect.inspect(list.key_type, opts)
val_str = TypeCheck.Protocols.Inspect.inspect(list.value_type, opts)
["%{optional(", key_str, ") => ", val_str, "}"]
|> Inspect.Algebra.concat()
|> Inspect.Algebra.color(:builtin_type, opts)
end
end
if Code.ensure_loaded?(StreamData) do
defimpl TypeCheck.Protocols.ToStreamData do
def to_gen(s) do
key_gen = TypeCheck.Protocols.ToStreamData.to_gen(s.key_type)
value_gen = TypeCheck.Protocols.ToStreamData.to_gen(s.value_type)
StreamData.map_of(key_gen, value_gen)
end
end
end
end