robbplo

robbplo

Dialyzer: inferring types when using Mox

I ran into this issue the other day and it’s been bugging me ever since. Below is a basic behaviour/implementation, and function which returns a module at runtime. This is the recommended way to decouple your modules when using Mox. It seems to me that this breaks Dialyzer analysis, since no errors are produced with this clearly incorrect typing.

defmodule MyBehaviour do
  @callback get_integer() :: integer
end

defmodule MyImplementation do
  @behaviour MyBehaviour

  @impl MyBehaviour
  def get_integer, do: 100
end

defmodule DialyzerMoxBehaviours do
  @moduledoc """
  Using the recommended implementation of a behaviour for Mox, Dialyzer is unable to infer the type of `get_integer()`.
  By decoupling this module from `MyImplementation`, we lose the typespec information from the behaviour.
  """

  @spec dialyze_me() :: binary
  def dialyze_me, do: "hello" # valid type

  @spec dialyze_me_two() :: binary
  def dialyze_me_two, do: impl().get_integer() # invalid type, but dialyzer does not complain.

  @spec impl() :: MyBehaviour
  defp impl(), do: Application.get_env(:dialyzer_mox_behaviours, :impl, MyImplementation)
end

Am I missing something? I like using Mox but this seems like a major drawback to me. If anyone else has encountered this issue I’m interested in how you’ve solved it.

Marked As Solved

LostKobrakai

LostKobrakai

Mox.stub_with can help there.

Also Liked

LostKobrakai

LostKobrakai

The approach as stated does loose type information, but not because of mox or behaviours, but because of the runtime selection of the used implementation. No static analysis can catch errors based on information not available statically in the code.

If you make impl() return MyImplementation statically or correctly hardcode the typespec return value of it as MyImplementation then dialyzer can help you. But loosening the type information to not return a specific module means dropping to any module(), which is an alias for atom(). There’s nothing to check if the returned module is unknown.

The typesystem dialyzer uses doesn’t allow you to say “but the module returned implements that behaviour”. It’s either one specific module or any module for that typesystem.

Given the typesystem doesn’t help you can however go the “statically known” route. If you don’t need runtime selection you can compile the configured implementation into the module statically.

@impl Application.compile_env(:dialyzer_mox_behaviours, :impl, MyImplementation)
@spec impl() :: module()
defp impl(), do: @impl

Last Post!

LostKobrakai

LostKobrakai

You could go with the approach the elixir codebase takes:

Where Next?

Popular in Questions Top

RisingFromAshes
I’ve read in another post that it may be possible with a router helper - but I couldn’t find an appropriate one, and tbh, I’m still just ...
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 records...
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New
jerry
Good day to you all. I have been struggling to get a query involving like and ilike to work. Can anyone assist me on this, please? pro...
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
vrod
I am using the Starship cross-shell prompt – it seems pretty nice, but I get some errors: [WARN] - (starship::utils): Executing command ...
New
senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New

Other popular topics Top

hariharasudhan94
Lets say I have map like this fetching from my database %{"_id" => #BSON.ObjectId<58eb1a7a9ad169198c3dXXXX>, "email" => ...
New
JakeBecker
TL;DR: I’ve just released an implementation of Microsoft’s IDE-independent Language Server Protocol for Elixir. It adds language support ...
1144 55125 245
New
jononomo
For some reason my phoenix channels are working for me in my local dev environment, but as soon as I deploy via Docker, I get a 403 error...
New
dogweather
I wrote this comment on r/haskell, and it’s not popular there. :wink: But I think I’m on to something… Haskell reminds me of Java, and e...
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New

We're in Beta

About us Mission Statement