diff --git a/lib/gradient/elixir_checker.ex b/lib/gradient/elixir_checker.ex index 564afdad..0c16a141 100644 --- a/lib/gradient/elixir_checker.ex +++ b/lib/gradient/elixir_checker.ex @@ -59,15 +59,16 @@ defmodule Gradient.ElixirChecker do # Spec name doesn't match the function name {fun, [{:spec_error, :wrong_spec_name, anno, n, a} | errors]} - {:spec, {n, a}, anno} = s1, {{:spec, _, _}, errors} -> - # Only one spec per function clause is allowed - {s1, [{:spec_error, :spec_after_spec, anno, n, a} | errors]} + {:spec, {n, a}, anno} = s1, {{:spec, {n2, a2}, _}, errors} when n != n2 or a != a2 -> + # Specs with diffrent name/arity are mixed + {s1, [{:spec_error, :mixed_specs, anno, n, a} | errors]} x, {_, errors} -> {x, errors} end) |> elem(1) |> Enum.map(&{file, &1}) + |> Enum.reverse() end # Filter out __info__ and other generated functions with the same name pattern diff --git a/lib/gradient/elixir_fmt.ex b/lib/gradient/elixir_fmt.ex index ba92a537..7c20bfe4 100644 --- a/lib/gradient/elixir_fmt.ex +++ b/lib/gradient/elixir_fmt.ex @@ -119,9 +119,9 @@ defmodule Gradient.ElixirFmt do ) end - def format_type_error({:spec_error, :spec_after_spec, anno, name, arity}, opts) do + def format_type_error({:spec_error, :mixed_specs, anno, name, arity}, opts) do :io_lib.format( - "~sThe spec ~p/~p~s follows another spec, but only one spec per function clause is allowed~n", + "~sThe spec ~p/~p~s follows a spec with different name/arity~n", [ format_location(anno, :brief, opts), name, diff --git a/test/examples/spec_correct.ex b/test/examples/spec_correct.ex index 77b17f83..94e47e69 100644 --- a/test/examples/spec_correct.ex +++ b/test/examples/spec_correct.ex @@ -3,4 +3,13 @@ defmodule CorrectSpec do def convert(int) when is_integer(int), do: int / 1 @spec convert(atom()) :: binary() def convert(atom) when is_atom(atom), do: to_string(atom) + + @spec encode(integer()) :: float() + @spec encode(atom()) :: binary() + def encode(val) do + case val do + _ when is_integer(val) -> val / 1 + _ when is_atom(val) -> to_string(val) + end + end end diff --git a/test/examples/spec_after_spec.ex b/test/examples/spec_mixed.ex similarity index 56% rename from test/examples/spec_after_spec.ex rename to test/examples/spec_mixed.ex index 4d8066e8..2c93ecf0 100644 --- a/test/examples/spec_after_spec.ex +++ b/test/examples/spec_mixed.ex @@ -1,6 +1,8 @@ -defmodule SpecAfterSpec do +defmodule SpecMixed do @spec convert(integer()) :: float() - @spec convert(atom()) :: binary() + @spec encode(atom()) :: binary() def convert(int) when is_integer(int), do: int / 1 def convert(atom) when is_atom(atom), do: to_string(atom) + + def encode(atom) when is_atom(atom), do: to_string(atom) end diff --git a/test/gradient/elixir_checker_test.exs b/test/gradient/elixir_checker_test.exs index 9b291135..296b4e05 100644 --- a/test/gradient/elixir_checker_test.exs +++ b/test/gradient/elixir_checker_test.exs @@ -23,16 +23,17 @@ defmodule Gradient.ElixirCheckerTest do ast = load("Elixir.SpecWrongName.beam") assert [ - {_, {:spec_error, :wrong_spec_name, 11, :last_two, 1}}, - {_, {:spec_error, :wrong_spec_name, 5, :convert, 1}} + {_, {:spec_error, :wrong_spec_name, 5, :convert, 1}}, + {_, {:spec_error, :wrong_spec_name, 11, :last_two, 1}} ] = ElixirChecker.check(ast, []) end - test "more than one spec per function clause is not allowed" do - ast = load("Elixir.SpecAfterSpec.beam") + test "mixing specs names is not allowed" do + ast = load("Elixir.SpecMixed.beam") assert [ - {_, {:spec_error, :spec_after_spec, 3, :convert, 1}} + {_, {:spec_error, :mixed_specs, 3, :encode, 1}}, + {_, {:spec_error, :wrong_spec_name, 3, :encode, 1}} ] = ElixirChecker.check(ast, []) end end diff --git a/test/gradient/elixir_fmt_test.exs b/test/gradient/elixir_fmt_test.exs index a2dde51a..b70f0a5e 100644 --- a/test/gradient/elixir_fmt_test.exs +++ b/test/gradient/elixir_fmt_test.exs @@ -251,11 +251,11 @@ defmodule Gradient.ElixirFmtTest do test "follows another spec" do msg = - {:spec_error, :spec_after_spec, 3, :convert, 1} + {:spec_error, :mixed_specs, 3, :encode, 1} |> ElixirFmt.format_error([]) |> :erlang.iolist_to_binary() - assert "The spec convert/1 on line 3 follows another spec, but only one spec per function clause is allowed\n" = + assert "The spec encode/1 on line 3 follows a spec with different name/arity\n" = msg end end diff --git a/test/mix/tasks/gradient_test.exs b/test/mix/tasks/gradient_test.exs index b21546d5..36ec3ad2 100644 --- a/test/mix/tasks/gradient_test.exs +++ b/test/mix/tasks/gradient_test.exs @@ -93,8 +93,8 @@ defmodule Mix.Tasks.GradientTest do end test "--no-ex-check option" do - beam = Path.join(@build_path, "Elixir.SpecAfterSpec.beam") - ex_spec_error_msg = "The spec convert/1 on line" + beam = Path.join(@build_path, "Elixir.SpecMixed.beam") + ex_spec_error_msg = "The spec encode/1 on line" output = run_task(test_opts([beam])) assert String.contains?(output, ex_spec_error_msg)