Skip to content

Commit f60bdb8

Browse files
author
José Valim
committed
Support maps in typespecs
1 parent c3ef484 commit f60bdb8

File tree

2 files changed

+53
-1
lines changed

2 files changed

+53
-1
lines changed

lib/elixir/lib/kernel/typespec.ex

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -640,6 +640,21 @@ defmodule Kernel.Typespec do
640640
for arg <- args, do: typespec_to_ast(arg)
641641
end
642642

643+
defp typespec_to_ast({ :type, line, :map, fields }) do
644+
fields = Enum.map fields, fn { :type, _, :map_field_assoc, k, v } ->
645+
{ typespec_to_ast(k), typespec_to_ast(v) }
646+
end
647+
648+
{ struct, fields } = Keyword.pop(fields, :__struct__)
649+
map = { :%{}, [line: line], fields }
650+
651+
if struct do
652+
{ :%, [line: line], [struct, map] }
653+
else
654+
map
655+
end
656+
end
657+
643658
defp typespec_to_ast({ :type, line, :binary, [arg1, arg2] }) do
644659
[arg1, arg2] = for arg <- [arg1, arg2], do: typespec_to_ast(arg)
645660
cond do
@@ -764,6 +779,18 @@ defmodule Kernel.Typespec do
764779
{:type, line(meta), :binary, [{:integer, line(meta1), base}, {:integer, line(meta2), 0}]}
765780
end
766781

782+
## Handle maps and structs
783+
defp typespec({:%{}, meta, fields}, vars, caller) do
784+
fields = Enum.map(fields, fn { k, v } ->
785+
{:type, line(meta), :map_field_assoc, typespec(k, vars, caller), typespec(v, vars, caller)}
786+
end)
787+
{:type, line(meta), :map, fields}
788+
end
789+
790+
defp typespec({:%, _, [name, {:%{}, meta, fields}]}, vars, caller) do
791+
typespec({:%{}, meta, [{:__struct__, name}|fields]}, vars, caller)
792+
end
793+
767794
# Handle ranges
768795
defp typespec({:.., meta, args}, vars, caller) do
769796
typespec({:range, meta, args}, vars, caller)

lib/elixir/test/elixir/kernel/typespec_test.exs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,29 @@ defmodule Kernel.TypespecTest do
122122
assert {:mytype, {:type, _, :range, [{:integer, _, 1}, {:integer, _, 10}]}, []} = spec
123123
end
124124

125+
test "@type with a map" do
126+
spec = test_module do
127+
@type mytype :: %{hello: :world}
128+
end
129+
assert {:mytype,
130+
{:type, _, :map, [
131+
{:type, _, :map_field_assoc, {:atom, _, :hello}, {:atom, _, :world}}
132+
]},
133+
[]} = spec
134+
end
135+
136+
test "@type with a struct" do
137+
spec = test_module do
138+
@type mytype :: %User{hello: :world}
139+
end
140+
assert {:mytype,
141+
{:type, _, :map, [
142+
{:type, _, :map_field_assoc, {:atom, _, :__struct__}, {:atom, _, User}},
143+
{:type, _, :map_field_assoc, {:atom, _, :hello}, {:atom, _, :world}}
144+
]},
145+
[]} = spec
146+
end
147+
125148
test "@type with a tuple" do
126149
{spec1, spec2, spec3} = test_module do
127150
t1 = @type mytype :: tuple
@@ -252,7 +275,7 @@ defmodule Kernel.TypespecTest do
252275
@type
253276
end
254277

255-
assert [{:t, {:type, 251, :map, []}, []}] = types
278+
assert [{:t, {:type, _, :map, []}, []}] = types
256279
end
257280

258281
test "@type unquote fragment" do
@@ -380,6 +403,8 @@ defmodule Kernel.TypespecTest do
380403
(quote do: @type rng() :: 1 .. 10),
381404
(quote do: @type opts() :: [first: integer(), step: integer(), last: integer()]),
382405
(quote do: @type ops() :: {+1,-1}),
406+
(quote do: @type my_map() :: %{hello: :world}),
407+
(quote do: @type my_struct() :: %User{hello: :world}),
383408
]
384409

385410
types = test_module do

0 commit comments

Comments
 (0)