Skip to content

Commit 301711a

Browse files
committed
Add map test and shared type helper
1 parent cc9f4b8 commit 301711a

File tree

4 files changed

+323
-345
lines changed

4 files changed

+323
-345
lines changed

lib/elixir/test/elixir/module/types/expr_test.exs

Lines changed: 2 additions & 305 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,9 @@
1-
Code.require_file("../../test_helper.exs", __DIR__)
1+
Code.require_file("type_helper.exs", __DIR__)
22

33
defmodule Module.Types.ExprTest do
44
use ExUnit.Case, async: true
5-
import Module.Types.Expr
6-
alias Module.Types
7-
alias Module.Types.Infer
85

9-
defmacro quoted_expr(vars \\ [], body) do
10-
quote do
11-
{vars, body} = unquote(Macro.escape(expand_expr(vars, body)))
12-
13-
body
14-
|> of_expr(new_stack(), new_context(vars))
15-
|> lift_result()
16-
end
17-
end
18-
19-
defp expand_expr(vars, expr) do
20-
fun =
21-
quote do
22-
fn unquote(vars) -> unquote(expr) end
23-
end
24-
25-
{ast, _env} = :elixir_expand.expand(fun, __ENV__)
26-
{:fn, _, [{:->, _, [[vars], body]}]} = ast
27-
{vars, body}
28-
end
29-
30-
defp new_context(vars) do
31-
context =
32-
Types.context(
33-
"expr_test.ex",
34-
TypesTest,
35-
{:test, 0},
36-
[],
37-
Module.ParallelChecker.test_cache()
38-
)
39-
40-
Enum.reduce(vars, context, fn var, context ->
41-
{_type, context} = Infer.new_var(var, context)
42-
context
43-
end)
44-
end
45-
46-
defp new_stack() do
47-
%{
48-
Types.stack()
49-
| last_expr: {:foo, [], nil}
50-
}
51-
end
52-
53-
defp lift_result({:ok, type, context}) do
54-
{:ok, Types.lift_type(type, context)}
55-
end
56-
57-
defp lift_result({:error, {type, reason, _context}}) do
58-
{:error, {type, reason}}
59-
end
6+
import TypeHelper
607

618
defmodule :"Elixir.Module.Types.ExprTest.Struct" do
629
defstruct foo: :atom, bar: 123, baz: %{}
@@ -94,256 +41,6 @@ defmodule Module.Types.ExprTest do
9441
assert quoted_expr({:a, 123}) == {:ok, {:tuple, [{:atom, :a}, :integer]}}
9542
end
9643

97-
test "map" do
98-
assert quoted_expr(%{}) == {:ok, {:map, []}}
99-
assert quoted_expr(%{a: :b}) == {:ok, {:map, [{:required, {:atom, :a}, {:atom, :b}}]}}
100-
assert quoted_expr([a], %{123 => a}) == {:ok, {:map, [{:required, :integer, {:var, 0}}]}}
101-
102-
assert quoted_expr(%{123 => :foo, 456 => :bar}) ==
103-
{:ok, {:map, [{:required, :integer, {:union, [{:atom, :bar}, {:atom, :foo}]}}]}}
104-
end
105-
106-
test "struct" do
107-
assert quoted_expr(%:"Elixir.Module.Types.ExprTest.Struct"{}) ==
108-
{:ok,
109-
{:map,
110-
[
111-
{:required, {:atom, :__struct__}, {:atom, Module.Types.ExprTest.Struct}},
112-
{:required, {:atom, :bar}, :integer},
113-
{:required, {:atom, :baz}, {:map, []}},
114-
{:required, {:atom, :foo}, {:atom, :atom}}
115-
]}}
116-
117-
assert quoted_expr(%:"Elixir.Module.Types.ExprTest.Struct"{foo: 123, bar: :atom}) ==
118-
{:ok,
119-
{:map,
120-
[
121-
{:required, {:atom, :__struct__}, {:atom, Module.Types.ExprTest.Struct}},
122-
{:required, {:atom, :baz}, {:map, []}},
123-
{:required, {:atom, :foo}, :integer},
124-
{:required, {:atom, :bar}, {:atom, :atom}}
125-
]}}
126-
end
127-
128-
test "map field" do
129-
assert quoted_expr(%{foo: :bar}.foo) == {:ok, {:atom, :bar}}
130-
131-
assert quoted_expr(
132-
(
133-
map = %{foo: :bar}
134-
map.foo
135-
)
136-
) == {:ok, {:atom, :bar}}
137-
138-
assert quoted_expr(
139-
[map],
140-
(
141-
map.foo
142-
map.bar
143-
map
144-
)
145-
) ==
146-
{:ok,
147-
{:map,
148-
[
149-
{:required, {:atom, :bar}, {:var, 0}},
150-
{:required, {:atom, :foo}, {:var, 1}},
151-
{:optional, :dynamic, :dynamic}
152-
]}}
153-
154-
assert quoted_expr(
155-
[map],
156-
(
157-
:foo = map.foo
158-
:bar = map.bar
159-
map
160-
)
161-
) ==
162-
{:ok,
163-
{:map,
164-
[
165-
{:required, {:atom, :bar}, {:atom, :bar}},
166-
{:required, {:atom, :foo}, {:atom, :foo}},
167-
{:optional, :dynamic, :dynamic}
168-
]}}
169-
170-
assert {:error,
171-
{:unable_unify,
172-
{{:map, [{:required, {:atom, :bar}, {:var, 1}}, {:optional, :dynamic, :dynamic}]},
173-
{:map, [{:required, {:atom, :foo}, {:atom, :foo}}]},
174-
_}}} =
175-
quoted_expr(
176-
(
177-
map = %{foo: :foo}
178-
map.bar
179-
)
180-
)
181-
end
182-
183-
defmodule :"Elixir.Module.Types.ExprTest.Struct2" do
184-
defstruct [:field]
185-
end
186-
187-
test "map fields and structs" do
188-
assert quoted_expr(
189-
[map],
190-
(
191-
%Module.Types.ExprTest.Struct2{} = map
192-
map.field
193-
map
194-
)
195-
) ==
196-
{:ok,
197-
{:map,
198-
[
199-
{:required, {:atom, :field}, {:var, 0}},
200-
{:required, {:atom, :__struct__}, {:atom, Module.Types.ExprTest.Struct2}}
201-
]}}
202-
203-
assert quoted_expr(
204-
[map],
205-
(
206-
_ = map.field
207-
%Module.Types.ExprTest.Struct2{} = map
208-
)
209-
) ==
210-
{:ok,
211-
{:map,
212-
[
213-
{:required, {:atom, :field}, {:var, 0}},
214-
{:required, {:atom, :__struct__}, {:atom, Module.Types.ExprTest.Struct2}}
215-
]}}
216-
217-
assert {:error, {:unable_unify, {_, _, _}}} =
218-
quoted_expr(
219-
[map],
220-
(
221-
%Module.Types.ExprTest.Struct2{} = map
222-
map.no_field
223-
)
224-
)
225-
226-
assert {:error, {:unable_unify, {_, _, _}}} =
227-
quoted_expr(
228-
[map],
229-
(
230-
_ = map.no_field
231-
%Module.Types.ExprTest.Struct2{} = map
232-
)
233-
)
234-
end
235-
236-
test "map pattern" do
237-
assert quoted_expr(%{a: :b} = %{a: :b}) ==
238-
{:ok, {:map, [{:required, {:atom, :a}, {:atom, :b}}]}}
239-
240-
assert quoted_expr(
241-
(
242-
a = :a
243-
%{^a => :b} = %{:a => :b}
244-
)
245-
) == {:ok, {:map, [{:required, {:atom, :a}, {:atom, :b}}]}}
246-
247-
assert {:error,
248-
{:unable_unify,
249-
{{:map, [{:required, {:atom, :c}, {:atom, :d}}]},
250-
{:map, [{:required, {:atom, :a}, {:atom, :b}}, {:optional, :dynamic, :dynamic}]},
251-
_}}} = quoted_expr(%{a: :b} = %{c: :d})
252-
253-
assert {:error,
254-
{:unable_unify,
255-
{{:map, [{:required, {:atom, :b}, {:atom, :error}}]},
256-
{:map, [{:required, {:var, 0}, {:atom, :ok}}, {:optional, :dynamic, :dynamic}]},
257-
_}}} =
258-
quoted_expr(
259-
(
260-
a = :a
261-
%{^a => :ok} = %{:b => :error}
262-
)
263-
)
264-
end
265-
266-
test "map update" do
267-
assert quoted_expr(
268-
(
269-
map = %{foo: :a}
270-
%{map | foo: :b}
271-
)
272-
) ==
273-
{:ok, {:map, [{:required, {:atom, :foo}, {:atom, :b}}]}}
274-
275-
assert quoted_expr([map], %{map | foo: :b}) ==
276-
{:ok,
277-
{:map, [{:optional, :dynamic, :dynamic}, {:required, {:atom, :foo}, {:atom, :b}}]}}
278-
279-
assert {:error,
280-
{:unable_unify,
281-
{{:map, [{:optional, :dynamic, :dynamic}, {:required, {:atom, :bar}, :dynamic}]},
282-
{:map, [{:required, {:atom, :foo}, {:atom, :a}}]},
283-
_}}} =
284-
quoted_expr(
285-
(
286-
map = %{foo: :a}
287-
%{map | bar: :b}
288-
)
289-
)
290-
end
291-
292-
test "struct update" do
293-
assert quoted_expr(
294-
(
295-
map = %Module.Types.ExprTest.Struct2{field: :a}
296-
%Module.Types.ExprTest.Struct2{map | field: :b}
297-
)
298-
) ==
299-
{:ok,
300-
{:map,
301-
[
302-
{:required, {:atom, :__struct__}, {:atom, Module.Types.ExprTest.Struct2}},
303-
{:required, {:atom, :field}, {:atom, :b}}
304-
]}}
305-
306-
assert {:error,
307-
{:unable_unify,
308-
{{:map,
309-
[
310-
{:required, {:atom, :__struct__}, {:atom, Module.Types.ExprTest.Struct2}},
311-
{:required, {:atom, :field}, :dynamic}
312-
]}, {:map, [{:required, {:atom, :field}, {:atom, :a}}]},
313-
_}}} =
314-
quoted_expr(
315-
(
316-
map = %{field: :a}
317-
%Module.Types.ExprTest.Struct2{map | field: :b}
318-
)
319-
)
320-
321-
assert quoted_expr([map], %Module.Types.ExprTest.Struct2{map | field: :b}) ==
322-
{:ok,
323-
{:map,
324-
[
325-
{:required, {:atom, :__struct__}, {:atom, Module.Types.ExprTest.Struct2}},
326-
{:required, {:atom, :field}, {:atom, :b}}
327-
]}}
328-
329-
assert {:error,
330-
{:unable_unify,
331-
{{:map,
332-
[{:optional, :dynamic, :dynamic}, {:required, {:atom, :not_field}, :dynamic}]},
333-
{:map,
334-
[
335-
{:required, {:atom, :__struct__}, {:atom, Module.Types.ExprTest.Struct2}},
336-
{:required, {:atom, :field}, {:atom, nil}}
337-
]},
338-
_}}} =
339-
quoted_expr(
340-
(
341-
map = %Module.Types.ExprTest.Struct2{}
342-
%{map | not_field: :b}
343-
)
344-
)
345-
end
346-
34744
# Use module attribute to avoid formatter adding parentheses
34845
@mix_module Mix
34946

0 commit comments

Comments
 (0)