Merge pull request #1868 from ericmj/typespecs

Add is_var/1 to typespecs and move guards to end of spec
This commit is contained in:
José Valim
2013-11-16 00:27:12 -08:00
4 changed files with 72 additions and 27 deletions
+49 -14
View File
@@ -260,7 +260,7 @@ defmodule Kernel.Typespec do
args = lc arg inlist args, do: typespec_to_ast(arg)
guards = Enum.reduce t, h, fn(x, acc) -> { :and, line, [acc, x] } end
{ :::, [line: line], [{ :when, [line: line], [{ name, [line: line], args }, guards] }, typespec_to_ast(result)] }
{ :when, [line: line], [{ :::, [line: line], [{ name, [line: line], args }, typespec_to_ast(result)] }, guards] }
end
@doc """
@@ -397,7 +397,7 @@ defmodule Kernel.Typespec do
def deftype(_kind, other, caller) do
type_spec = Macro.to_string(other)
compile_error caller, "invalid type specification #{type_spec}"
compile_error caller, "invalid type specification `#{type_spec}`"
end
defp do_deftype(kind, { name, _, args }, definition, caller) do
@@ -418,17 +418,29 @@ defmodule Kernel.Typespec do
end
@doc false
def defspec(type, {:::, _, [{ :when, _, [{ name, meta, args }, constraints_guard] }, return] }, caller) do
def defspec(type, { :when, _, [{ :::, _, [{ name, meta, args }, return] }, constraints_guard] }, caller) do
if is_atom(args), do: args = []
constraints = guard_to_constraints(constraints_guard, caller)
spec = { :type, line(meta), :fun, fn_args(meta, args, return, Keyword.keys(constraints), caller) }
spec = { :type, line(meta), :bounded_fun, [spec, Keyword.values(constraints)] }
vars = guard_to_vars(constraints_guard)
constraints = guard_to_constraints(constraints_guard, vars, caller)
spec = { :type, line(meta), :fun, fn_args(meta, args, return, vars, caller) }
if constraints != [] do
spec = { :type, line(meta), :bounded_fun, [spec, constraints] }
end
code = { { name, Kernel.length(args) }, spec }
Module.compile_typespec(caller.module, type, code)
code
end
def defspec(type, {:::, _, [{ name, meta, args }, return]}, caller) do
def defspec(type, { :when, _, [fun, { :::, _, [guards, return] }] } = spec, caller) do
new_spec = { :when, [], [{ :::, [], [fun, return] }, guards] }
IO.write "typespec format is deprecated `#{Macro.to_string(spec)}`\n" <>
"new format is: `#{Macro.to_string(new_spec)}`\n" <>
Exception.format_stacktrace
defspec(type, new_spec, caller)
end
def defspec(type, { :::, _, [{ name, meta, args }, return] }, caller) do
if is_atom(args), do: args = []
spec = { :type, line(meta), :fun, fn_args(meta, args, return, [], caller) }
code = { { name, Kernel.length(args) }, spec }
@@ -438,17 +450,40 @@ defmodule Kernel.Typespec do
def defspec(_type, other, caller) do
spec = Macro.to_string(other)
compile_error caller, "invalid function type specification #{spec}"
compile_error caller, "invalid function type specification `#{spec}`"
end
defp guard_to_constraints({ :is_subtype, meta, [{ name, _, _ }, type] }, caller) do
defp guard_to_vars({ :is_subtype, _, [{ name, _, _ }, _] }) do
[name]
end
defp guard_to_vars({ :is_var, _, [{ name, _, _ }] }) do
[name]
end
defp guard_to_vars({ :and, _, [left, right] }) do
guard_to_vars(left) ++ guard_to_vars(right)
end
defp guard_to_constraints({ :is_subtype, meta, [{ name, _, context }, type] }, vars, caller)
when is_atom(name) and is_atom(context) do
line = line(meta)
contraints = [{ :atom, line, :is_subtype }, [{:var, line, name}, typespec(type, [], caller)]]
[{ name, { :type, line, :constraint, contraints } }]
contraints = [{ :atom, line, :is_subtype }, [{:var, line, name}, typespec(type, vars, caller)]]
[{ :type, line, :constraint, contraints }]
end
defp guard_to_constraints({ :and, _, [left, right] }, caller) do
guard_to_constraints(left, caller) ++ guard_to_constraints(right, caller)
defp guard_to_constraints({ :is_var, _, [{ name, _, context }] }, _, _)
when is_atom(name) and is_atom(context) do
[]
end
defp guard_to_constraints({ :and, _, [left, right] }, vars, caller) do
guard_to_constraints(left, vars, caller) ++ guard_to_constraints(right, vars, caller)
end
defp guard_to_constraints(other, _vars, caller) do
guard = Macro.to_string(other)
compile_error caller, "invalid guard in function type specification `#{guard}`"
end
## To AST conversion
@@ -743,7 +778,7 @@ defmodule Kernel.Typespec do
defp validate_kw({ key, _ } = t, _, _caller) when is_atom(key), do: t
defp validate_kw(_, original, caller) do
compile_error(caller, "unexpected list #{Macro.to_string original} in typespec")
compile_error(caller, "unexpected list `#{Macro.to_string original}` in typespec")
end
defp fn_args(meta, args, return, vars, caller) do
+2 -2
View File
@@ -27,8 +27,8 @@ defmodule Macro do
@spec binary_op_props(atom) :: { :left | :right, precedence :: integer }
defp binary_op_props(o) do
case o do
::: -> {:right, 30}
:when -> {:right, 40}
:when -> {:right, 30}
::: -> {:right, 40}
o when o in [:inlist, :inbits] -> {:left, 50}
:// -> {:right, 60}
:| -> {:left, 70}
+2 -2
View File
@@ -46,8 +46,8 @@ Expect 2.
Left 5 do.
Right 10 stab_op_eol. %% ->
Left 20 ','.
Right 30 type_op_eol. %% ::
Right 40 when_op_eol. %% when
Right 30 when_op_eol. %% when
Right 40 type_op_eol. %% ::
Left 50 inc_op_eol. %% inlist, inbits
Right 60 default_op_eol. %% //
Left 70 tail_op_eol. %% |
+19 -9
View File
@@ -18,17 +18,17 @@ defmodule Typespec.TypeTest do
end
test "invalid type specification" do
assert_raise CompileError, %r"invalid type specification mytype = 1", fn ->
assert_raise CompileError, %r"invalid type specification `mytype = 1`", fn ->
test_module do
@type mytype = 1
end
end
assert_raise CompileError, %r"invalid type specification mytype = 1", fn ->
assert_raise CompileError, %r"invalid type specification `mytype = 1`", fn ->
test_module do
@typep mytype = 1
end
end
assert_raise CompileError, %r"invalid type specification mytype = 1", fn ->
assert_raise CompileError, %r"invalid type specification `mytype = 1`", fn ->
test_module do
@opaque mytype = 1
end
@@ -36,7 +36,7 @@ defmodule Typespec.TypeTest do
end
test "invalid function specification" do
assert_raise CompileError, %r"invalid function type specification myfun = 1", fn ->
assert_raise CompileError, %r"invalid function type specification `myfun = 1`", fn ->
test_module do
@spec myfun = 1
end
@@ -307,11 +307,21 @@ defmodule Typespec.TypeTest do
end
test "@spec(spec) with guards" do
spec1 = test_module do
def myfun(x), do: x
@spec myfun(x) when is_subtype(x, integer) :: boolean
{ spec1, spec2, spec3 } = test_module do
def myfun1(x), do: x
spec1 = @spec myfun1(x) :: boolean when is_subtype(x, integer)
def myfun2(x), do: x
spec2 = @spec myfun2(x) :: x when is_var(x)
def myfun3(_x, y), do: y
spec3 = @spec myfun3(x, y) :: y when is_subtype(y, x) and is_var(x)
{ spec1, spec2, spec3 }
end
assert {{:myfun, 1}, {:type, _, :bounded_fun, [{:type, _, :fun, [{:type, _, :product, [{:var, _, :x}]}, {:type, _, :boolean, []}]}, [{:type, _, :constraint, [{:atom, _, :is_subtype}, [{:var, _, :x}, {:type, _, :integer, []}]]}]]}} = spec1
assert {{:myfun1, 1}, {:type, _, :bounded_fun, [{:type, _, :fun, [{:type, _, :product, [{:var, _, :x}]}, {:type, _, :boolean, []}]}, [{:type, _, :constraint, [{:atom, _, :is_subtype}, [{:var, _, :x}, {:type, _, :integer, []}]]}]]}} = spec1
assert {{:myfun2, 1}, {:type, _, :fun, [{:type, _, :product, [{:var, _, :x}]}, {:var, _, :x}]}} = spec2
assert {{:myfun3, 2}, {:type, _, :bounded_fun, [{:type, _, :fun, [{:type, _, :product, [{:var, _, :x}, {:var, _, :y}]}, {:var, _, :y}]}, [{:type, _, :constraint, [{:atom, _, :is_subtype}, [{:var, _, :y}, {:var, _, :x}]]}]]}} = spec3
end
test "@callback(callback)" do
@@ -432,7 +442,7 @@ defmodule Typespec.TypeTest do
specs = [
(quote do: @spec a() :: integer()),
(quote do: @spec a(atom()) :: integer()),
(quote do: @spec a(b) when is_subtype(b, integer()) :: integer()),
(quote do: @spec a(b) :: integer() when is_subtype(b, integer())),
]
compiled = test_module do