diff --git a/lib/elixir/lib/kernel/typespec.ex b/lib/elixir/lib/kernel/typespec.ex index 7417296a00..03897966ab 100644 --- a/lib/elixir/lib/kernel/typespec.ex +++ b/lib/elixir/lib/kernel/typespec.ex @@ -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 diff --git a/lib/elixir/lib/macro.ex b/lib/elixir/lib/macro.ex index 21ef156e6b..a3bef1fdf0 100644 --- a/lib/elixir/lib/macro.ex +++ b/lib/elixir/lib/macro.ex @@ -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} diff --git a/lib/elixir/src/elixir_parser.yrl b/lib/elixir/src/elixir_parser.yrl index 6b2a3079a3..ac20974042 100644 --- a/lib/elixir/src/elixir_parser.yrl +++ b/lib/elixir/src/elixir_parser.yrl @@ -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. %% | diff --git a/lib/elixir/test/elixir/typespec_test.exs b/lib/elixir/test/elixir/typespec_test.exs index 046e6fe8a9..66925c8ebb 100644 --- a/lib/elixir/test/elixir/typespec_test.exs +++ b/lib/elixir/test/elixir/typespec_test.exs @@ -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