Dialyzer error for struct

走远了吗. 提交于 2019-12-11 06:36:32

问题


Here's a minimum broken example in Elixir 1.3:

defmodule Foo do
  @type t :: %__MODULE__{x: non_neg_integer}
  defstruct x: 0

  @spec test(t) :: t
  def test(%__MODULE__{} = foo), do: test2(foo)

  @spec test2(t) :: t
  defp test2(%__MODULE__{} = foo), do: %__MODULE__{foo | x: 5}

end

This fails to type check with: foo.ex:9: The variable _@1 can never match since previous clauses completely covered the type #{'__struct__':='Elixir.Foo', _=>_}.

I've searched and searched and cannot for the life of me find an explanation of what this means, or how to fix it.


回答1:


If you simplify the code to:

defmodule Foo do
  @type t :: %__MODULE__{x: non_neg_integer}
  defstruct x: 0

  @spec set_x_to_5(t) :: t
  def set_x_to_5(%__MODULE__{} = foo), do: %__MODULE__{foo | x: 5}
end

and then decompile the resulting .beam file, you get:

set_x_to_5(#{'__struct__' := 'Elixir.Foo'} = foo@1) ->
    case foo@1 of
      _@1 = #{'__struct__' := 'Elixir.Foo'} -> _@1#{x := 5};
      _@1 -> erlang:error({badstruct, 'Elixir.Foo', _@1})
    end.

If you see the case statement Elixir generated for %__MODULE__{foo | x: 5} carefully, you'll see that it contains a branch that can never match because __struct__ is guaranteed to be Foo inside that function. This is generated by Elixir because Elixir throws an error if you use the %Struct{map | ...} syntax with a map of a different struct:

iex(1)> defmodule Foo, do: defstruct [:x]
iex(2)> defmodule Bar, do: defstruct [:x]
iex(3)> %Foo{%Bar{x: 1} | x: 2}
** (BadStructError) expected a struct named Foo, got: %Bar{x: 1}

To fix this, you can remove the __MODULE__ part and just do:

%{foo | x: 5}

The result will be the same and you'll get no warnings.



来源:https://stackoverflow.com/questions/38800459/dialyzer-error-for-struct

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!