|
1 | | -Code.require_file("../../test_helper.exs", __DIR__) |
| 1 | +Code.require_file("type_helper.exs", __DIR__) |
2 | 2 |
|
3 | 3 | defmodule Module.Types.ExprTest do |
4 | 4 | use ExUnit.Case, async: true |
5 | | - import Module.Types.Expr |
6 | | - alias Module.Types |
7 | | - alias Module.Types.Infer |
8 | 5 |
|
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 |
60 | 7 |
|
61 | 8 | defmodule :"Elixir.Module.Types.ExprTest.Struct" do |
62 | 9 | defstruct foo: :atom, bar: 123, baz: %{} |
@@ -94,256 +41,6 @@ defmodule Module.Types.ExprTest do |
94 | 41 | assert quoted_expr({:a, 123}) == {:ok, {:tuple, [{:atom, :a}, :integer]}} |
95 | 42 | end |
96 | 43 |
|
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 | | - |
347 | 44 | # Use module attribute to avoid formatter adding parentheses |
348 | 45 | @mix_module Mix |
349 | 46 |
|
|
0 commit comments