Skip to content

Commit a1743d4

Browse files
committed
Return if the key is optional on map_get
1 parent e625466 commit a1743d4

File tree

2 files changed

+82
-105
lines changed

2 files changed

+82
-105
lines changed

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

Lines changed: 33 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -568,61 +568,56 @@ defmodule Module.Types.Descr do
568568
defp optional?(%{bitmap: bitmap}) when (bitmap &&& @bit_optional) != 0, do: true
569569
defp optional?(_), do: false
570570

571-
defp remove_not_set(type) do
571+
defp pop_not_set(type) do
572572
case type do
573-
%{bitmap: @bit_optional} -> Map.delete(type, :bitmap)
574-
%{bitmap: bitmap} -> %{type | bitmap: bitmap &&& ~~~@bit_optional}
575-
_ -> type
573+
%{bitmap: @bit_optional} ->
574+
{true, Map.delete(type, :bitmap)}
575+
576+
%{bitmap: bitmap} when (bitmap &&& @bit_optional) != 0 ->
577+
{true, %{type | bitmap: bitmap - @bit_optional}}
578+
579+
_ ->
580+
{false, type}
576581
end
577582
end
578583

579584
defp map_tag_to_type(:open), do: term_or_not_set()
580585
defp map_tag_to_type(:closed), do: not_set()
586+
581587
defp map_descr(tag, fields), do: %{map: [{tag, fields, []}]}
582588

583589
@doc """
584590
Gets the type of the value returned by accessing `key` on `map`.
585-
Does not guarantee the key exists. To do that, use `map_has_key?`.
586-
"""
587-
def map_get!(%{} = descr, key) do
588-
if not gradual?(descr) do
589-
map_get_static(descr, key)
590-
else
591-
{dynamic, static} = Map.pop(descr, :dynamic)
592-
dynamic_value_type = map_get_static(dynamic, key)
593-
static_value_type = map_get_static(static, key)
594-
union(intersection(dynamic(), dynamic_value_type), static_value_type)
595-
end
596-
end
597591
598-
@doc """
599-
Check that a key is present.
592+
It returns a two element tuple. The first element says if the type
593+
is optional or not, the second element is the type. In static mode,
594+
we likely want to raise if `map.field` (or pattern matching?) is
595+
called on an optional key.
600596
"""
601-
def map_has_key?(descr, key) do
602-
subtype?(descr, open_map([{key, term()}]))
603-
end
597+
def map_get(%{} = descr, key) do
598+
case :maps.take(:dynamic, descr) do
599+
:error ->
600+
map_get_static(descr, key)
604601

605-
# Assuming `descr` is a static type. Accessing `key` will, if it succeeds,
606-
# return a value of the type returned. To guarantee that the key is always
607-
# present, use `map_has_key?`. To guarantee that the key may be present
608-
# use `map_may_have_key?`. If key is never present, result will be `none()`.
609-
defp map_get_static(descr, key) when is_atom(key) do
610-
descr_map = intersection(descr, open_map())
602+
{dynamic, static} ->
603+
{dynamic_optional?, dynamic_type} = map_get_static(dynamic, key)
604+
{static_optional?, static_type} = map_get_static(static, key)
611605

612-
if empty?(descr_map) do
613-
none()
614-
else
615-
map_split_on_key(descr_map.map, key)
616-
|> Enum.reduce(none(), fn typeof_key, union -> union(typeof_key, union) end)
617-
|> remove_not_set()
606+
{dynamic_optional? or static_optional?,
607+
union(intersection(dynamic(), dynamic_type), static_type)}
618608
end
619609
end
620610

621-
@doc """
622-
Check that a key may be present.
623-
"""
624-
def map_may_have_key?(descr, key) do
625-
compatible?(descr, open_map([{key, term()}]))
611+
defp map_get_static(descr, key) when is_atom(key) do
612+
case descr do
613+
%{map: map} ->
614+
map_split_on_key(map, key)
615+
|> Enum.reduce(none(), &union/2)
616+
|> pop_not_set()
617+
618+
%{} ->
619+
{false, none()}
620+
end
626621
end
627622

628623
# Union is list concatenation

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

Lines changed: 49 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -246,95 +246,77 @@ defmodule Module.Types.DescrTest do
246246

247247
describe "map operations" do
248248
test "get field" do
249-
assert map_get!(closed_map(a: integer()), :a) == integer()
250-
assert map_get!(dynamic(), :a) == dynamic()
249+
assert map_get(closed_map(a: integer()), :a) == {false, integer()}
250+
assert map_get(dynamic(), :a) == {true, dynamic()}
251251

252252
assert intersection(dynamic(), open_map(a: integer()))
253-
|> map_get!(:a) == intersection(integer(), dynamic())
253+
|> map_get(:a) == {false, intersection(integer(), dynamic())}
254254

255-
assert open_map(my_map: open_map(foo: integer()))
256-
|> intersection(open_map(my_map: open_map(bar: boolean())))
257-
|> map_get!(:my_map)
258-
|> equal?(open_map(foo: integer(), bar: boolean()))
255+
{false, value_type} =
256+
open_map(my_map: open_map(foo: integer()))
257+
|> intersection(open_map(my_map: open_map(bar: boolean())))
258+
|> map_get(:my_map)
259259

260-
assert map_get!(union(closed_map(a: integer()), closed_map(a: atom())), :a) ==
261-
union(integer(), atom())
260+
assert equal?(value_type, open_map(foo: integer(), bar: boolean()))
262261

263-
assert map_get!(union(closed_map(a: integer()), closed_map(b: atom())), :a) == integer()
264-
assert map_get!(term(), :a) == term()
262+
assert map_get(union(closed_map(a: integer()), closed_map(a: atom())), :a) ==
263+
{false, union(integer(), atom())}
265264

266-
assert closed_map(a: union(integer(), atom()))
267-
|> difference(open_map(a: integer()))
268-
|> map_get!(:a)
269-
|> equal?(atom())
265+
assert map_get(union(closed_map(a: integer()), closed_map(b: atom())), :a) ==
266+
{true, integer()}
270267

271-
assert closed_map(a: integer(), b: atom())
272-
|> difference(closed_map(a: integer(), b: atom([:foo])))
273-
|> map_get!(:a)
274-
|> equal?(integer())
268+
assert map_get(term(), :a) == {true, term()}
275269

276-
assert closed_map(a: integer())
277-
|> difference(closed_map(a: atom()))
278-
|> map_get!(:a)
279-
|> equal?(integer())
270+
{false, value_type} =
271+
closed_map(a: union(integer(), atom()))
272+
|> difference(open_map(a: integer()))
273+
|> map_get(:a)
280274

281-
assert open_map(a: integer(), b: atom())
282-
|> union(closed_map(a: tuple()))
283-
|> map_get!(:a)
284-
|> equal?(union(integer(), tuple()))
275+
assert equal?(value_type, atom())
276+
277+
{false, value_type} =
278+
closed_map(a: integer(), b: atom())
279+
|> difference(closed_map(a: integer(), b: atom([:foo])))
280+
|> map_get(:a)
281+
282+
assert equal?(value_type, integer())
283+
284+
{false, value_type} =
285+
closed_map(a: integer())
286+
|> difference(closed_map(a: atom()))
287+
|> map_get(:a)
288+
289+
assert equal?(value_type, integer())
290+
291+
{false, value_type} =
292+
open_map(a: integer(), b: atom())
293+
|> union(closed_map(a: tuple()))
294+
|> map_get(:a)
295+
296+
assert equal?(value_type, union(integer(), tuple()))
297+
298+
{false, value_type} =
299+
closed_map(a: atom())
300+
|> difference(closed_map(a: atom([:foo, :bar])))
301+
|> difference(closed_map(a: atom([:bar])))
302+
|> map_get(:a)
285303

286-
assert closed_map(a: atom())
287-
|> difference(closed_map(a: atom([:foo, :bar])))
288-
|> difference(closed_map(a: atom([:bar])))
289-
|> map_get!(:a)
290-
|> equal?(intersection(atom(), negation(atom([:foo, :bar]))))
304+
assert equal?(value_type, intersection(atom(), negation(atom([:foo, :bar]))))
291305

292306
assert closed_map(a: union(atom(), pid()), b: integer(), c: tuple())
293307
|> difference(open_map(a: atom(), b: integer()))
294308
|> difference(open_map(a: atom(), c: tuple()))
295-
|> map_get!(:a) == pid()
309+
|> map_get(:a) == {false, pid()}
296310

297311
assert closed_map(a: union(atom([:foo]), pid()), b: integer(), c: tuple())
298312
|> difference(open_map(a: atom([:foo]), b: integer()))
299313
|> difference(open_map(a: atom(), c: tuple()))
300-
|> map_get!(:a) == pid()
314+
|> map_get(:a) == {false, pid()}
301315

302316
assert closed_map(a: union(atom([:foo, :bar, :baz]), integer()))
303317
|> difference(open_map(a: atom([:foo, :bar])))
304318
|> difference(open_map(a: atom([:foo, :baz])))
305-
|> map_get!(:a) == integer()
306-
end
307-
308-
test "key presence" do
309-
assert map_has_key?(closed_map(a: integer()), :a)
310-
refute map_has_key?(closed_map(a: integer()), :b)
311-
refute map_has_key?(open_map(), :a)
312-
refute map_has_key?(closed_map(a: union(integer(), not_set())), :a)
313-
refute map_has_key?(union(closed_map(a: integer()), closed_map(b: atom())), :a)
314-
assert map_has_key?(union(closed_map(a: integer()), closed_map(a: atom())), :a)
315-
assert map_has_key?(intersection(dynamic(), closed_map(a: integer())), :a)
316-
refute map_has_key?(intersection(dynamic(), closed_map(a: integer())), :b)
317-
318-
refute map_may_have_key?(closed_map(foo: integer()), :bar)
319-
assert map_may_have_key?(closed_map(foo: integer()), :foo)
320-
assert map_may_have_key?(dynamic(), :foo)
321-
refute map_may_have_key?(intersection(dynamic(), open_map(foo: not_set())), :foo)
322-
end
323-
324-
test "type-checking map access" do
325-
# dynamic() and %{..., :a => integer(), b: not_set()}
326-
t = intersection(dynamic(), open_map(a: integer(), c: not_set()))
327-
328-
assert subtype?(map_get!(t, :a), integer())
329-
assert map_get!(t, :b) == dynamic()
330-
331-
assert map_has_key?(t, :a)
332-
refute map_has_key?(t, :b)
333-
refute map_has_key?(t, :c)
334-
335-
assert map_may_have_key?(t, :a)
336-
assert map_may_have_key?(t, :b)
337-
refute map_may_have_key?(t, :c)
319+
|> map_get(:a) == {false, integer()}
338320
end
339321
end
340322

0 commit comments

Comments
 (0)