gradient icon indicating copy to clipboard operation
gradient copied to clipboard

Clause cannot be reached error

Open Fl4m3Ph03n1x opened this issue 4 years ago • 9 comments

Background

I am trying to implement a couple of protocols in a module but Gradualizer is complaining saying my code is not reachable. The code works without issues, so I am confused.

Code

Here is a simplification of the source code:

defmodule Option do

  defmodule Some do
    @type t(elem) :: %__MODULE__{val: elem}

    defstruct [:val]

    defimpl Collectable do
      @impl Collectable
      def into(option), do: {option, fn acc, _command -> {:done, acc} end}
    end

    defimpl Enumerable do
      @impl Enumerable
      def count(_some), do: {:ok, 1}

      @impl Enumerable
      def member?(some, element), do: {:ok, some.val == element}

      @impl Enumerable
      def reduce(some, acc, fun)

      def reduce(_some, {:halt, acc}, _fun), do: {:halted, acc}
      def reduce(some, {:suspend, acc}, fun), do: {:suspended, acc, &reduce(some, &1, fun)}
      def reduce([], {:cont, acc}, _fun), do: {:done, acc}

      def reduce(%Option.Some{} = some, {:cont, acc}, fun),
        do: reduce([], fun.(some.val, acc), fun)

      def reduce(val, {:cont, acc}, fun), do: reduce([], fun.(val, acc), fun)

      @impl Enumerable
      @spec slice(%Option.Some{}) :: {:error, Enumerable.Option.Some}
      def slice(_option), do: {:error, __MODULE__}
    end
  end

end

Here the errors:

$ mix gradient
Compiling 1 file (.ex)
Generated simple_monads app
Typechecking files...
lib/option.ex: The clause on line 9 cannot be reached

lib/option.ex: The clause on line 14 cannot be reached

lib/option.ex: The pattern %Option.Some{} on line 28 doesn't have the type any()

line 9: defimpl Collectable do line 14: defimpl Enumerable do line 28 does not accept any, but there is a clause for that after. By that logic, the line def reduce([], {:cont, acc}, _fun), do: {:done, acc} does not accept any as the first parameter and yet the tool wont complain.

Simply put, I cover all cases of the spec.

I would expect to see no errors.

Fl4m3Ph03n1x avatar Feb 23 '22 09:02 Fl4m3Ph03n1x

Hi, @Fl4m3Ph03n1x!

Thanks for the new report. I'll start from the bottom:

lib/option.ex: The pattern %Option.Some{} on line 28 doesn't have the type any()

This is a known issue most recently reported in #51. There's an upstream ticket tracking it josefs/gradualizer#387.

These two, on the other hand, look similar to example of the regressions mentioned in josefs/gradualizer#359:

lib/option.ex: The clause on line 9 cannot be reached

lib/option.ex: The clause on line 14 cannot be reached

This needs a double check on our side, but at least we know where to start. If you have some extra time, could you run the check again with Gradient from this branch - https://github.com/esl/gradient/tree/use-erszcz-master? It's not ready for prime time yet, but it incorporates the WIP fixes from https://github.com/josefs/Gradualizer/pull/359.

erszcz avatar Feb 23 '22 10:02 erszcz

Same issue:

lib/option.ex: The clause on line 9 cannot be reached

lib/option.ex: The clause on line 14 cannot be reached

lib/option.ex: The pattern %Option.Some{} on line 28 doesn't have the type any()

This is how my mix.exs looks:

defp deps,
    do: [
      {:gradient,
       git: "[email protected]:esl/gradient.git",
       branch: "use-erszcz-master",
       only: [:dev],
       runtime: false}
    ]

Fl4m3Ph03n1x avatar Feb 23 '22 10:02 Fl4m3Ph03n1x

Thanks, @Fl4m3Ph03n1x :+1: This means the cannot be reached errors above are independent of https://github.com/josefs/Gradualizer/pull/359.

erszcz avatar Feb 23 '22 10:02 erszcz

Hello there, any news regarding this issue? (I am just curious)

Fl4m3Ph03n1x avatar Apr 20 '22 14:04 Fl4m3Ph03n1x

Hey, @Fl4m3Ph03n1x! https://github.com/esl/gradient/issues/61 and https://github.com/josefs/Gradualizer/issues/387 are fixed and closed, so

lib/option.ex: The pattern %Option.Some{} on line 28 doesn't have the type any()

shouldn't happen anymore. If you still see it happening with structs, please let us know here or create a new ticket.

Whether

lib/option.ex: The clause on line 9 cannot be reached

lib/option.ex: The clause on line 14 cannot be reached

are a manifestation of the list exhaustiveness checking regressions is not completely clear yet, but there's ongoing work upstream, most recently tracked as https://github.com/josefs/Gradualizer/pull/404, to fix that. Apparently, it's not trivial 😆 We're going in the right direction, though!

erszcz avatar Apr 21 '22 08:04 erszcz

I've encountered a similar error but without using protocols.

defmodule Keymap.ListHelper do
  def update!(list, key, fun) when is_list(list) and is_function(fun, 1) do
    update!(list, key, fun, list)
  end

  defp update!([{key, value} | list], key, fun, _original) do
    [{key, fun.(value)} | list]
  end

  defp update!([{_, _} = pair | list], key, fun, original) do
    [pair | update!(list, key, fun, original)]
  end

  defp update!([], key, _fun, original) do
    raise KeyError, key: key, term: original
  end
end

The function is basically Keyword.update with two caveats:

  • keys don't have to be atoms
  • it throws on an empty list

And that last clause with throwing causes:

lib/keymap/list_helper.ex: The clause on line 14 cannot be reached

I figured you might like more data points to test :)

tomekowal avatar Apr 22 '22 07:04 tomekowal

Hi, @tomekowal!

I figured you might like more data points to test :)

Sure, these are always welcome! Thanks for the example :)

erszcz avatar Apr 22 '22 07:04 erszcz

This might be a duplicate with #32.

erszcz avatar Apr 27 '22 11:04 erszcz

Ok, even with #72 the errors:

lib/option.ex: The clause on line 8 cannot be reached

lib/option.ex: The clause on line 13 cannot be reached

Are returned for this code:

$ nl -b a lib/option.ex
     1	defmodule Option do
     2
     3	  defmodule Some do
     4	    @type t(elem) :: %__MODULE__{val: elem}
     5
     6	    defstruct [:val]
     7
     8	    defimpl Collectable do
     9	      @impl Collectable
    10	      def into(option), do: {option, fn acc, _command -> {:done, acc} end}
    11	    end
    12
    13	    defimpl Enumerable do
    14	      @impl Enumerable
    15	      def count(_some), do: {:ok, 1}
    16
    17	      @impl Enumerable
    18	      def member?(some, element), do: {:ok, some.val == element}
    19
    20	      @impl Enumerable
    21	      def reduce(some, acc, fun)
    22
    23	      def reduce(_some, {:halt, acc}, _fun), do: {:halted, acc}
    24	      def reduce(some, {:suspend, acc}, fun), do: {:suspended, acc, &reduce(some, &1, fun)}
    25	      def reduce([], {:cont, acc}, _fun), do: {:done, acc}
    26
    27	      def reduce(%Option.Some{} = some, {:cont, acc}, fun),
    28	        do: reduce([], fun.(some.val, acc), fun)
    29
    30	      def reduce(val, {:cont, acc}, fun), do: reduce([], fun.(val, acc), fun)
    31
    32	      @impl Enumerable
    33	      @spec slice(%Option.Some{}) :: {:error, Enumerable.Option.Some}
    34	      def slice(_option), do: {:error, __MODULE__}
    35	    end
    36	  end
    37
    38	end

So it means the problem is specific to protocols, i.e. using defimpl.

erszcz avatar Apr 28 '22 10:04 erszcz