Elixir V 1.18 behaviours and different builds warnings

Hello all,

Where I work, we like to inject module dependencies during compile time.
So for example, I can have MyModule for dev and prod builds, but for test I might have MyModuleStub.

So let’s say I have a code like this:

# ...
@the_module Application.compile_env!(:my_app, :the_module)
#...
case @the_module.execute() do
  {:ok, response} -> do_something(response)
  :error -> do_something_else()
end

It works fine in dev and prod, however in test the module is a simplified version:

defmodule MyModuleStub do
# ...
  def execute, do: {:ok, fixture()}
end

Then, when compiling in test, we get the following warning:

     warning: the following clause will never match:

         :error

     because it attempts to match on the result of:

        MyModuleStub.execute()

     which has type:

         none()

     typing violation found at:
     │
     │       :error ->
     │       ~~~~~~~~~~~~~~~~

It seems the compiler warning is right…however, the situation is less than an ideal. Ideally, we should have a way to tell the compiler that we’re talking about a behaviour, or it should understand alone. Is there any way of suppressing this warning? Have you used modules this way? How did you work around for Elixir v1.18 warnings?

3 Likes

I’m not sure how to solve this with the current capabilities, but I think it’s funny that the main complaint about the type system is that it is too smart :grinning:

2 Likes
2 Likes

Thank you. It is what we ended up doing.

Another option, if Process.get(...) is too magical, is to pretend that you can return something else:

if :rand.uniform() >= 0 do
  # always true
  {:ok, ...}
else
  :error
end

Once we add behaviours to the type system, this warning will disappear, but for now we need to rely on one of the following approaches.

3 Likes

I’m also encountering a similar issue in my ExUnit tests.

For example, I have tests like this:

defmodule MyApp.MyTest do
  use ExUnit.Case, async: true

  setup do
    foo = produce_test_data(1, 2, 3)
    bar = produce_test_data(4, 5, 6)

    refute match?(^foo, bar)
  end

  test "..." do
    # use foo and bar in interesting ways
  end
end

It fails on that refute with:

warning: the following clause will never match:

    ^foo

because it attempts to match on the result of:

    right

which has type:

    dynamic(%MyStruct{...omitted...})

 where "foo" was given the type:

    dynamic(%MyStruct{...omitted...})

Now, I know that that refute line is useless. I’m setting up foo and bar to be the same type but with different values, I know that. But the refute is living documentation. It basically tells readers of the code “hey, these two things are meant to be different, that’s important”.

I guess I wish that there was support for magic comments to disable these warnings.

My solution, at the moment, is to pull that bit of test setup verification out of setup and into its own test:

test "verify test setup", %{foo: foo, bar: bar} do
  refute match? ^foo, bar
end

That produces no warnings.