Weird error with dialyxir

I am getting a weird error and I don’t know why it is wrong

Legacy warning:
lib/strategy/strategy.ex:33: Invalid type specification for function 'Elixir.Strategy':new/1.
 The success typing is 'Elixir.Strategy':new(_) -> #{'__struct__':='Elixir.Strategy', 'id':=<<_:288>>, 'name':=_, 'type':='static'}

 But the spec is 'Elixir.Strategy':new('Elixir.StringHelpers':word()) -> 'Elixir.Strategy'
 The return types do not overlap
defmodule Strategy.StrategyId do
  @type t :: %__MODULE__{
    strategy_id: Ecto.UUID.t()
  }
  defstruct [strategy_id: nil]
end


defmodule Strategy do
  alias Strategy.StrategyId
  alias __MODULE__

  @type strategy_type() :: :dynamic | :static
  @type t() :: %__MODULE__{
    id: StrategyId.t(),
    name: String.t(),
    type: strategy_type(),
  }
  defstruct [name: "", id: Ecto.UUID.generate(), type: :static]




  @spec new(String.t()) ::  __MODULE__.t()
  def new(word) do
    id = Ecto.UUID.generate()
    %__MODULE__{id: id, name: word}
  end


end

I can’t believe I found the answer 2 min after posting it

  @spec new(String.t()) ::  %Strategy{}
  def new(word) do
    id = Ecto.UUID.generate()
    %__MODULE__{id: id, name: word}
  end

My expectations was to use the @spec not the actual struct

Dialyzer is (as always) right: you defined StrategyId as a struct but there you are directly assigning an Ecto.UUID.t().

You can probably directly define id: Ecto.UUID.t() and simplify your type definition.

It wasn’t the StrategyId() type… It was MODULE.t()… I had to change it to %Strategy{}

The reason it “worked” is because %Strategy{} doesn’t type the keys, but it is probably not what you want most of the time.

If you mean to use the type defined above with @type t() ::, Strategy.t() is what you should be using (but you need to fix the definition first).

1 Like

Thanks for chiming in

Do you mean somehting like this:

defmodule Model.Strategy do
  alias __MODULE__

  @opaque strategy_type() :: :dynamic | :static
  @opaque strategy_id() :: Ecto.UUID.t()
  @type t() :: %__MODULE__{
          id: strategy_id(),
          name: String.t(),
          type: strategy_type()
        }
        
  defstruct name: "", id: Ecto.UUID.generate(), type: :static

  @spec new(String.t()) :: Strategy.t()
  def new(name) do
    id = Ecto.UUID.generate()
    %__MODULE__{id: id, name: name}
  end
end

Exactly!

Side note regarding the id: Ecto.UUID.generate() default, please note this will generate a random default UUID at compile time, reused for all instances at runtime, which might not be what you expect.
If you mean this key to always be set when building the struct, you could use @enforce_keys instead of having to define a default.

1 Like

+1 this warning; here’s an example of where a similar “default defined at the wrong time” issue caused a startup a lot of headaches:

That issue was harder to troubleshoot because the “compilation” happens later in Python, so each instance of the application had a distinct always-the-same default ID.

3 Likes

Thanks
Figured about Ecto.UUID.generate() so my new() sets a fresh one each time.
Still dont’ know what was wrong with the previous one… but I got the patternt

People blindly trusting ChatGPT and losing money will be forever amusing. :003:

1 Like