Skip to content

Commit 9bedb0f

Browse files
committed
Change typespec guards to keyword list
Previous syntax: @SPEC fun(a) :: b when is_var(a) and is_subtype(b, [a]) New syntax: @SPEC fun(a) :: b when a: var, b: [a]
1 parent 965915b commit 9bedb0f

File tree

3 files changed

+86
-59
lines changed

3 files changed

+86
-59
lines changed

lib/elixir/lib/kernel/typespec.ex

Lines changed: 74 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -243,24 +243,41 @@ defmodule Kernel.Typespec do
243243
Converts a spec clause back to Elixir AST.
244244
"""
245245
def spec_to_ast(name, { :type, line, :fun, [{:type, _, :product, args}, result] }) do
246-
args = lc arg inlist args, do: typespec_to_ast(arg)
247-
{ :::, [line: line], [{ name, [line: line], args }, typespec_to_ast(result)] }
246+
ast_args = lc arg inlist args, do: typespec_to_ast(arg)
247+
ast = { :::, [line: line], [{ name, [line: line], ast_args }, typespec_to_ast(result)] }
248+
249+
vars = args ++ [result]
250+
|> Enum.flat_map(&collect_vars/1)
251+
|> Enum.uniq
252+
|> Enum.map(&{ &1, { :var, [line: line], nil } })
253+
254+
unless vars == [] do
255+
ast = { :when, [line: line], [ast, vars] }
256+
end
257+
258+
ast
248259
end
249260

250261
def spec_to_ast(name, { :type, line, :fun, [] }) do
251262
{ :::, [line: line], [{ name, [line: line], [] }, quote(do: term)] }
252263
end
253264

254265
def spec_to_ast(name, { :type, line, :bounded_fun, [{ :type, _, :fun, [{ :type, _, :product, args }, result] }, constraints] }) do
255-
[h|t] =
256-
lc {:type, line, :constraint, [{:atom, _, :is_subtype}, [var, type]]} inlist constraints do
257-
{ :is_subtype, [line: line], [typespec_to_ast(var), typespec_to_ast(type)] }
266+
guards =
267+
lc {:type, _, :constraint, [{:atom, _, :is_subtype}, [{ :var, _, var }, type]]} inlist constraints do
268+
{ var, typespec_to_ast(type) }
258269
end
259270

260-
args = lc arg inlist args, do: typespec_to_ast(arg)
261-
guards = Enum.reduce t, h, fn(x, acc) -> { :and, line, [acc, x] } end
271+
ast_args = lc arg inlist args, do: typespec_to_ast(arg)
272+
273+
vars = args ++ [result]
274+
|> Enum.flat_map(&collect_vars/1)
275+
|> Enum.uniq
276+
277+
vars = vars -- Keyword.keys(guards)
278+
|> Enum.map(&{ &1, { :var, [line: line], nil } })
262279

263-
{ :when, [line: line], [{ :::, [line: line], [{ name, [line: line], args }, typespec_to_ast(result)] }, guards] }
280+
{ :when, [line: line], [{ :::, [line: line], [{ name, [line: line], ast_args }, typespec_to_ast(result)] }, guards ++ vars] }
264281
end
265282

266283
@doc """
@@ -418,28 +435,27 @@ defmodule Kernel.Typespec do
418435
end
419436

420437
@doc false
421-
def defspec(type, { :when, _, [{ :::, _, [{ name, meta, args }, return] }, constraints_guard] }, caller) do
438+
def defspec(type, { :when, meta2, [{ :::, _, [{ name, meta, args }, return] }, guard] }, caller) do
422439
if is_atom(args), do: args = []
423-
vars = guard_to_vars(constraints_guard)
424-
constraints = guard_to_constraints(constraints_guard, vars, caller)
440+
441+
unless Keyword.keyword?(guard) do
442+
guard = Macro.to_string(guard)
443+
compile_error caller, "invalid guard in function type specification `#{guard}`"
444+
end
445+
446+
vars = Keyword.keys(guard)
447+
constraints = guard_to_constraints(guard, vars, meta2, caller)
448+
425449
spec = { :type, line(meta), :fun, fn_args(meta, args, return, vars, caller) }
426450
if constraints != [] do
427451
spec = { :type, line(meta), :bounded_fun, [spec, constraints] }
428452
end
453+
429454
code = { { name, Kernel.length(args) }, spec }
430455
Module.compile_typespec(caller.module, type, code)
431456
code
432457
end
433458

434-
def defspec(type, { :when, _, [fun, { :::, _, [guards, return] }] } = spec, caller) do
435-
new_spec = { :when, [], [{ :::, [], [fun, return] }, guards] }
436-
IO.write "typespec format is deprecated `#{Macro.to_string(spec)}`\n" <>
437-
"new format is: `#{Macro.to_string(new_spec)}`\n" <>
438-
Exception.format_stacktrace
439-
440-
defspec(type, new_spec, caller)
441-
end
442-
443459
def defspec(type, { :::, _, [{ name, meta, args }, return] }, caller) do
444460
if is_atom(args), do: args = []
445461
spec = { :type, line(meta), :fun, fn_args(meta, args, return, [], caller) }
@@ -453,40 +469,48 @@ defmodule Kernel.Typespec do
453469
compile_error caller, "invalid function type specification `#{spec}`"
454470
end
455471

456-
defp guard_to_vars({ :is_subtype, _, [{ name, _, _ }, _] }) do
457-
[name]
472+
defp guard_to_constraints(guard, vars, meta, caller) do
473+
line = line(meta)
474+
475+
Enum.reduce(guard, [], fn
476+
{ _name, { :var, _, context } }, acc when is_atom(context) ->
477+
acc
478+
{ name, type }, acc ->
479+
constraint = [{ :atom, line, :is_subtype }, [{:var, line, name}, typespec(type, vars, caller)]]
480+
type = { :type, line, :constraint, constraint }
481+
[type|acc]
482+
end) |> Enum.reverse
458483
end
459484

460-
defp guard_to_vars({ :is_var, _, [{ name, _, _ }] }) do
461-
[name]
485+
## To AST conversion
486+
487+
defp collect_vars({ :ann_type, _line, args }) do
488+
Enum.flat_map(args, &collect_vars/1)
462489
end
463490

464-
defp guard_to_vars({ :and, _, [left, right] }) do
465-
guard_to_vars(left) ++ guard_to_vars(right)
491+
defp collect_vars({ :type, _line, _kind, args }) do
492+
Enum.flat_map(args, &collect_vars/1)
466493
end
467494

468-
defp guard_to_constraints({ :is_subtype, meta, [{ name, _, context }, type] }, vars, caller)
469-
when is_atom(name) and is_atom(context) do
470-
line = line(meta)
471-
contraints = [{ :atom, line, :is_subtype }, [{:var, line, name}, typespec(type, vars, caller)]]
472-
[{ :type, line, :constraint, contraints }]
495+
defp collect_vars({ :remote_type, _line, args }) do
496+
Enum.flat_map(args, &collect_vars/1)
473497
end
474498

475-
defp guard_to_constraints({ :is_var, _, [{ name, _, context }] }, _, _)
476-
when is_atom(name) and is_atom(context) do
477-
[]
499+
defp collect_vars({ :typed_record_field, _line, type }) do
500+
collect_vars(type)
478501
end
479502

480-
defp guard_to_constraints({ :and, _, [left, right] }, vars, caller) do
481-
guard_to_constraints(left, vars, caller) ++ guard_to_constraints(right, vars, caller)
503+
defp collect_vars({:paren_type, _line, [type]}) do
504+
collect_vars(type)
482505
end
483506

484-
defp guard_to_constraints(other, _vars, caller) do
485-
guard = Macro.to_string(other)
486-
compile_error caller, "invalid guard in function type specification `#{guard}`"
507+
defp collect_vars({:var, _line, var}) do
508+
[erl_to_ex_var(var)]
487509
end
488510

489-
## To AST conversion
511+
defp collect_vars(_) do
512+
[]
513+
end
490514

491515
defp typespec_to_ast({ :type, line, :tuple, :any }) do
492516
typespec_to_ast({:type, line, :tuple, []})
@@ -549,14 +573,7 @@ defmodule Kernel.Typespec do
549573
end
550574

551575
defp typespec_to_ast({ :var, line, var }) do
552-
var =
553-
case atom_to_binary(var) do
554-
<<"_", c :: [binary, size(1)], rest :: binary>> ->
555-
binary_to_atom("_#{String.downcase(c)}#{rest}")
556-
<<c :: [binary, size(1)], rest :: binary>> ->
557-
binary_to_atom("#{String.downcase(c)}#{rest}")
558-
end
559-
{ var, line, nil }
576+
{ erl_to_ex_var(var), line, nil }
560577
end
561578

562579
# Special shortcut(s)
@@ -598,6 +615,15 @@ defmodule Kernel.Typespec do
598615

599616
defp typespec_to_ast(other), do: other
600617

618+
defp erl_to_ex_var(var) do
619+
case atom_to_binary(var) do
620+
<<"_", c :: [binary, size(1)], rest :: binary>> ->
621+
binary_to_atom("_#{String.downcase(c)}#{rest}")
622+
<<c :: [binary, size(1)], rest :: binary>> ->
623+
binary_to_atom("#{String.downcase(c)}#{rest}")
624+
end
625+
end
626+
601627
## From AST conversion
602628

603629
defp line(meta) do

lib/elixir/lib/list.ex

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ defmodule List do
4848
iex> List.duplicate([1, 2], 2)
4949
[[1,2],[1,2]]
5050
"""
51-
@spec duplicate(elem, non_neg_integer) :: [elem] when is_var(elem)
51+
@spec duplicate(elem, non_neg_integer) :: [elem] when elem: var
5252
def duplicate(elem, n) do
5353
:lists.duplicate(n, elem)
5454
end
@@ -62,7 +62,7 @@ defmodule List do
6262
[1,2,3]
6363
6464
"""
65-
@spec flatten(deep_list) :: list when is_subtype(deep_list, [any | deep_list])
65+
@spec flatten(deep_list) :: list when deep_list: [any | deep_list]
6666
def flatten(list) do
6767
:lists.flatten(list)
6868
end
@@ -78,7 +78,7 @@ defmodule List do
7878
[1,2,3,4,5]
7979
8080
"""
81-
@spec flatten(deep_list, [elem]) :: [elem] when is_subtype(deep_list, [elem | deep_list]) and is_var(elem)
81+
@spec flatten(deep_list, [elem]) :: [elem] when elem: var, deep_list: [elem | deep_list]
8282
def flatten(list, tail) do
8383
:lists.flatten(list, tail)
8484
end
@@ -96,7 +96,7 @@ defmodule List do
9696
2
9797
9898
"""
99-
@spec foldl([elem], acc, (elem, acc -> acc)) :: acc when is_var(elem) and is_var(acc)
99+
@spec foldl([elem], acc, (elem, acc -> acc)) :: acc when elem: var, acc: var
100100
def foldl(list, acc, function) when is_list(list) and is_function(function) do
101101
:lists.foldl(function, acc, list)
102102
end
@@ -111,7 +111,7 @@ defmodule List do
111111
-2
112112
113113
"""
114-
@spec foldr([elem], acc, (elem, acc -> acc)) :: acc when is_var(elem) and is_var(acc)
114+
@spec foldr([elem], acc, (elem, acc -> acc)) :: acc when elem: var, acc: var
115115
def foldr(list, acc, function) when is_list(list) and is_function(function) do
116116
:lists.foldr(function, acc, list)
117117
end
@@ -129,7 +129,7 @@ defmodule List do
129129
3
130130
131131
"""
132-
@spec last([elem]) :: nil | elem when is_var(elem)
132+
@spec last([elem]) :: nil | elem when elem: var
133133

134134
def last([]), do: nil
135135

@@ -399,7 +399,7 @@ defmodule List do
399399
[1, 2, 3]
400400
401401
"""
402-
@spec update_at([elem], integer, (elem -> any)) :: list when is_var(elem)
402+
@spec update_at([elem], integer, (elem -> any)) :: list when elem: var
403403
def update_at(list, index, fun) do
404404
if index < 0 do
405405
do_update_at(list, length(list) + index, fun)

lib/elixir/test/elixir/typespec_test.exs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -322,13 +322,13 @@ defmodule Typespec.TypeTest do
322322
test "@spec(spec) with guards" do
323323
{ spec1, spec2, spec3 } = test_module do
324324
def myfun1(x), do: x
325-
spec1 = @spec myfun1(x) :: boolean when is_subtype(x, integer)
325+
spec1 = @spec myfun1(x) :: boolean when [x: integer]
326326
327327
def myfun2(x), do: x
328-
spec2 = @spec myfun2(x) :: x when is_var(x)
328+
spec2 = @spec myfun2(x) :: x when [x: var]
329329
330330
def myfun3(_x, y), do: y
331-
spec3 = @spec myfun3(x, y) :: y when is_subtype(y, x) and is_var(x)
331+
spec3 = @spec myfun3(x, y) :: y when [y: x, x: var]
332332
333333
{ spec1, spec2, spec3 }
334334
end
@@ -455,7 +455,8 @@ defmodule Typespec.TypeTest do
455455
specs = [
456456
(quote do: @spec a() :: integer()),
457457
(quote do: @spec a(atom()) :: integer()),
458-
(quote do: @spec a(b) :: integer() when is_subtype(b, integer())),
458+
(quote do: @spec a(b) :: integer() when [b: integer()]),
459+
(quote do: @spec a(b) :: b when [b: var])
459460
]
460461
461462
compiled = test_module do

0 commit comments

Comments
 (0)