Skip to content

Commit d8906c3

Browse files
committed
Track simple variable assignment in type system
1 parent 80621b6 commit d8906c3

File tree

20 files changed

+103
-145
lines changed

20 files changed

+103
-145
lines changed

lib/elixir/lib/enum.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1432,7 +1432,7 @@ defmodule Enum do
14321432
)
14331433

14341434
# Avoid warnings about Dict
1435-
dict_module = Dict
1435+
dict_module = String.to_atom("Dict")
14361436

14371437
reduce(reverse(enumerable), dict, fn entry, categories ->
14381438
dict_module.update(categories, fun.(entry), [entry], &[entry | &1])

lib/elixir/lib/hash_dict.ex

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -258,13 +258,13 @@ end
258258
defimpl Enumerable, for: HashDict do
259259
def reduce(dict, acc, fun) do
260260
# Avoid warnings about HashDict being deprecated.
261-
module = HashDict
261+
module = String.to_atom("HashDict")
262262
module.reduce(dict, acc, fun)
263263
end
264264

265265
def member?(dict, {key, value}) do
266266
# Avoid warnings about HashDict being deprecated.
267-
module = HashDict
267+
module = String.to_atom("HashDict")
268268
{:ok, match?({:ok, ^value}, module.fetch(dict, key))}
269269
end
270270

@@ -274,7 +274,7 @@ defimpl Enumerable, for: HashDict do
274274

275275
def count(dict) do
276276
# Avoid warnings about HashDict being deprecated.
277-
module = HashDict
277+
module = String.to_atom("HashDict")
278278
{:ok, module.size(dict)}
279279
end
280280

@@ -286,7 +286,7 @@ end
286286
defimpl Collectable, for: HashDict do
287287
def into(original) do
288288
# Avoid warnings about HashDict being deprecated.
289-
module = HashDict
289+
module = String.to_atom("HashDict")
290290

291291
collector_fun = fn
292292
dict, {:cont, {key, value}} -> module.put(dict, key, value)
@@ -303,7 +303,7 @@ defimpl Inspect, for: HashDict do
303303

304304
def inspect(dict, opts) do
305305
# Avoid warnings about HashDict being deprecated.
306-
module = HashDict
306+
module = String.to_atom("HashDict")
307307
concat(["#HashDict<", Inspect.List.inspect(module.to_list(dict), opts), ">"])
308308
end
309309
end

lib/elixir/lib/hash_set.ex

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -272,19 +272,19 @@ end
272272
defimpl Enumerable, for: HashSet do
273273
def reduce(set, acc, fun) do
274274
# Avoid warnings about HashSet being deprecated.
275-
module = HashSet
275+
module = String.to_atom("HashSet")
276276
module.reduce(set, acc, fun)
277277
end
278278

279279
def member?(set, term) do
280280
# Avoid warnings about HashSet being deprecated.
281-
module = HashSet
281+
module = String.to_atom("HashSet")
282282
{:ok, module.member?(set, term)}
283283
end
284284

285285
def count(set) do
286286
# Avoid warnings about HashSet being deprecated.
287-
module = HashSet
287+
module = String.to_atom("HashSet")
288288
{:ok, module.size(set)}
289289
end
290290

@@ -296,7 +296,7 @@ end
296296
defimpl Collectable, for: HashSet do
297297
def into(original) do
298298
# Avoid warnings about HashSet being deprecated.
299-
module = HashSet
299+
module = String.to_atom("HashSet")
300300

301301
collector_fun = fn
302302
set, {:cont, term} -> module.put(set, term)
@@ -313,7 +313,7 @@ defimpl Inspect, for: HashSet do
313313

314314
def inspect(set, opts) do
315315
# Avoid warnings about HashSet being deprecated.
316-
module = HashSet
316+
module = String.to_atom("HashSet")
317317
concat(["#HashSet<", Inspect.List.inspect(module.to_list(set), opts), ">"])
318318
end
319319
end

lib/elixir/lib/module/types/descr.ex

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ defmodule Module.Types.Descr do
5252
def integer(), do: %{bitmap: @bit_integer}
5353
def float(), do: %{bitmap: @bit_float}
5454
def fun(), do: %{bitmap: @bit_fun}
55+
# TODO: We need to propagate any dynamic up
5556
def open_map(pairs), do: %{map: map_new(:open, Map.new(pairs))}
5657
def closed_map(pairs), do: %{map: map_new(:closed, Map.new(pairs))}
5758
def open_map(), do: %{map: @map_top}
@@ -89,6 +90,18 @@ defmodule Module.Types.Descr do
8990
def term?(descr), do: subtype_static(@term, Map.delete(descr, :dynamic))
9091
def gradual?(descr), do: is_map_key(descr, :dynamic)
9192

93+
@doc """
94+
Make a whole type dynamic.
95+
96+
It is an optimized version of `intersection(dynamic(), type)`.
97+
"""
98+
def dynamic(descr) do
99+
case descr do
100+
%{dynamic: dynamic} -> %{dynamic: dynamic}
101+
%{} -> %{dynamic: descr}
102+
end
103+
end
104+
92105
@doc """
93106
Computes the union of two descrs.
94107
"""
@@ -675,8 +688,7 @@ defmodule Module.Types.Descr do
675688
{dynamic_optional?, dynamic_type} = map_fetch_static(dynamic, key)
676689
{static_optional?, static_type} = map_fetch_static(static, key)
677690

678-
{dynamic_optional? or static_optional?,
679-
union(intersection(dynamic(), dynamic_type), static_type)}
691+
{dynamic_optional? or static_optional?, union(dynamic(dynamic_type), static_type)}
680692
else
681693
:error
682694
end

lib/elixir/lib/module/types/expr.ex

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,11 +97,10 @@ defmodule Module.Types.Expr do
9797
end
9898

9999
# TODO: left = right
100+
# TODO: make sure the types are compatible
100101
def of_expr({:=, _meta, [left_expr, right_expr]}, stack, context) do
101-
with {:ok, _left_type, context} <-
102-
Pattern.of_pattern(left_expr, stack, context),
103-
{:ok, right_type, context} <- of_expr(right_expr, stack, context) do
104-
{:ok, right_type, context}
102+
with {:ok, right_type, context} <- of_expr(right_expr, stack, context) do
103+
Pattern.of_pattern(left_expr, {right_type, right_expr}, stack, context)
105104
end
106105
end
107106

lib/elixir/lib/module/types/helpers.ex

Lines changed: 0 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -100,42 +100,6 @@ defmodule Module.Types.Helpers do
100100

101101
defp do_reduce_ok([], acc, _fun), do: {:ok, acc}
102102

103-
@doc """
104-
Like `Enum.unzip/1` but only continues while `fun` returns `{:ok, elem1, elem2}`
105-
and stops on `{:error, reason}`.
106-
"""
107-
def unzip_ok(list) do
108-
do_unzip_ok(list, [], [])
109-
end
110-
111-
defp do_unzip_ok([{:ok, head1, head2} | tail], acc1, acc2) do
112-
do_unzip_ok(tail, [head1 | acc1], [head2 | acc2])
113-
end
114-
115-
defp do_unzip_ok([{:error, reason} | _tail], _acc1, _acc2), do: {:error, reason}
116-
117-
defp do_unzip_ok([], acc1, acc2), do: {:ok, Enum.reverse(acc1), Enum.reverse(acc2)}
118-
119-
@doc """
120-
Like `Enum.map/2` but only continues while `fun` returns `{:ok, elem}`
121-
and stops on `{:error, reason}`.
122-
"""
123-
def map_ok(list, fun) do
124-
do_map_ok(list, [], fun)
125-
end
126-
127-
defp do_map_ok([head | tail], acc, fun) do
128-
case fun.(head) do
129-
{:ok, elem} ->
130-
do_map_ok(tail, [elem | acc], fun)
131-
132-
{:error, reason} ->
133-
{:error, reason}
134-
end
135-
end
136-
137-
defp do_map_ok([], acc, _fun), do: {:ok, Enum.reverse(acc)}
138-
139103
@doc """
140104
Like `Enum.map_reduce/3` but only continues while `fun` returns `{:ok, elem, acc}`
141105
and stops on `{:error, reason}`.
@@ -155,71 +119,4 @@ defmodule Module.Types.Helpers do
155119
end
156120

157121
defp do_map_reduce_ok([], {list, acc}, _fun), do: {:ok, Enum.reverse(list), acc}
158-
159-
@doc """
160-
Like `Enum.flat_map/2` but only continues while `fun` returns `{:ok, list}`
161-
and stops on `{:error, reason}`.
162-
"""
163-
def flat_map_ok(list, fun) do
164-
do_flat_map_ok(list, [], fun)
165-
end
166-
167-
defp do_flat_map_ok([head | tail], acc, fun) do
168-
case fun.(head) do
169-
{:ok, elem} ->
170-
do_flat_map_ok(tail, [elem | acc], fun)
171-
172-
{:error, reason} ->
173-
{:error, reason}
174-
end
175-
end
176-
177-
defp do_flat_map_ok([], acc, _fun), do: {:ok, Enum.reverse(Enum.concat(acc))}
178-
179-
@doc """
180-
Like `Enum.flat_map_reduce/3` but only continues while `fun` returns `{:ok, list, acc}`
181-
and stops on `{:error, reason}`.
182-
"""
183-
def flat_map_reduce_ok(list, acc, fun) do
184-
do_flat_map_reduce_ok(list, {[], acc}, fun)
185-
end
186-
187-
defp do_flat_map_reduce_ok([head | tail], {list, acc}, fun) do
188-
case fun.(head, acc) do
189-
{:ok, elems, acc} ->
190-
do_flat_map_reduce_ok(tail, {[elems | list], acc}, fun)
191-
192-
{:error, reason} ->
193-
{:error, reason}
194-
end
195-
end
196-
197-
defp do_flat_map_reduce_ok([], {list, acc}, _fun),
198-
do: {:ok, Enum.reverse(Enum.concat(list)), acc}
199-
200-
@doc """
201-
Like `Enum.zip/1` but will zip multiple lists together instead of only two.
202-
"""
203-
def zip_many(lists) do
204-
zip_many(lists, [], [[]])
205-
end
206-
207-
defp zip_many([], [], [[] | acc]) do
208-
map_reverse(acc, [], &Enum.reverse/1)
209-
end
210-
211-
defp zip_many([], remain, [last | acc]) do
212-
zip_many(Enum.reverse(remain), [], [[] | [last | acc]])
213-
end
214-
215-
defp zip_many([[] | _], remain, [last | acc]) do
216-
zip_many(Enum.reverse(remain), [], [last | acc])
217-
end
218-
219-
defp zip_many([[elem | list1] | list2], remain, [last | acc]) do
220-
zip_many(list2, [list1 | remain], [[elem | last] | acc])
221-
end
222-
223-
defp map_reverse([], acc, _fun), do: acc
224-
defp map_reverse([head | tail], acc, fun), do: map_reverse(tail, [fun.(head) | acc], fun)
225122
end

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ defmodule Module.Types.Of do
4747

4848
%{} ->
4949
data = %{
50-
type: type,
50+
# TODO: We should not default to dynamic on static mode
51+
type: dynamic(type),
5152
name: var_name,
5253
context: var_context,
5354
off_traces: new_trace(expr, type, stack, [])

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

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ defmodule Module.Types.Pattern do
3030

3131
@doc """
3232
Return the type and typing context of a pattern expression with
33-
the given {expected, expr} pair or an error in case of a typing conflict.
33+
the given {expected, expr} pair or an error in case of a typing conflict.
3434
"""
3535

3636
# ^var
@@ -40,9 +40,23 @@ defmodule Module.Types.Pattern do
4040

4141
# left = right
4242
def of_pattern({:=, _meta, [left_expr, right_expr]}, expected_expr, stack, context) do
43-
with {:ok, _, context} <- of_pattern(left_expr, expected_expr, stack, context),
44-
{:ok, _, context} <- of_pattern(right_expr, expected_expr, stack, context),
45-
do: {:ok, dynamic(), context}
43+
# TODO: We need to properly track and annotate variables across (potentially nested) sides.
44+
case {is_var(left_expr), is_var(right_expr)} do
45+
{true, false} ->
46+
with {:ok, type, context} <- of_pattern(right_expr, expected_expr, stack, context) do
47+
of_pattern(left_expr, {type, right_expr}, stack, context)
48+
end
49+
50+
{false, true} ->
51+
with {:ok, type, context} <- of_pattern(left_expr, expected_expr, stack, context) do
52+
of_pattern(right_expr, {type, left_expr}, stack, context)
53+
end
54+
55+
{_, _} ->
56+
with {:ok, _, context} <- of_pattern(left_expr, expected_expr, stack, context),
57+
{:ok, _, context} <- of_pattern(right_expr, expected_expr, stack, context),
58+
do: {:ok, dynamic(), context}
59+
end
4660
end
4761

4862
# %_{...}

lib/elixir/lib/uri.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ defmodule URI do
235235

236236
{{key, value}, rest} ->
237237
# Avoid warnings about Dict being deprecated
238-
dict_module = Dict
238+
dict_module = String.to_atom("Dict")
239239
decode_query_into_dict(rest, dict_module.put(dict, key, value), encoding)
240240
end
241241
end

lib/elixir/test/elixir/enum_test.exs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,12 +94,12 @@ defmodule EnumTest do
9494
end
9595

9696
test "chunk/3" do
97-
enum = Enum
97+
enum = String.to_atom("Elixir.Enum")
9898
assert enum.chunk(1..5, 2, 1) == Enum.chunk_every(1..5, 2, 1, :discard)
9999
end
100100

101101
test "chunk/4" do
102-
enum = Enum
102+
enum = String.to_atom("Elixir.Enum")
103103
assert enum.chunk(1..5, 2, 1, nil) == Enum.chunk_every(1..5, 2, 1, :discard)
104104
end
105105

lib/elixir/test/elixir/fixtures/dialyzer/remote_call.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ defmodule Dialyzer.RemoteCall do
2323
end
2424

2525
def mod_var() do
26-
module = Hello
26+
module = String.to_atom("Elixir.Hello")
2727
module.fun()
2828
end
2929
end

lib/elixir/test/elixir/integer_test.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ defmodule IntegerTest do
189189
end
190190

191191
test "to_charlist/2" do
192-
module = Integer
192+
module = String.to_atom("Elixir.Integer")
193193

194194
assert Integer.to_charlist(42) == ~c"42"
195195
assert Integer.to_charlist(+42) == ~c"42"

lib/elixir/test/elixir/kernel/macros_test.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ defmodule Kernel.MacrosTest do
6363
end
6464

6565
test "macros cannot be called dynamically" do
66-
x = Nested
66+
x = String.to_atom("Elixir.Nested")
6767
assert_raise UndefinedFunctionError, fn -> x.func() end
6868
end
6969

lib/elixir/test/elixir/macro_test.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -599,7 +599,7 @@ defmodule MacroTest do
599599

600600
describe "to_string/2" do
601601
defp macro_to_string(var, fun \\ fn _ast, string -> string end) do
602-
module = Macro
602+
module = String.to_atom("Elixir.Macro")
603603
module.to_string(var, fun)
604604
end
605605

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

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -244,8 +244,21 @@ defmodule Module.Types.DescrTest do
244244
end
245245
end
246246

247-
describe "map operations" do
248-
test "get field" do
247+
describe "projections" do
248+
test "atom_fetch" do
249+
assert atom_fetch(term()) == :error
250+
assert atom_fetch(union(term(), dynamic(atom([:foo, :bar])))) == :error
251+
252+
assert atom_fetch(atom()) == {:ok, []}
253+
254+
assert atom_fetch(atom([:foo, :bar])) ==
255+
{:ok, [:foo, :bar] |> :sets.from_list(version: 2) |> :sets.to_list()}
256+
257+
assert atom_fetch(union(atom([:foo, :bar]), dynamic(atom()))) == {:ok, []}
258+
assert atom_fetch(union(atom([:foo, :bar]), dynamic(term()))) == {:ok, []}
259+
end
260+
261+
test "map_fetch" do
249262
assert map_fetch(closed_map(a: integer()), :a) == {false, integer()}
250263

251264
assert map_fetch(term(), :a) == :error

0 commit comments

Comments
 (0)