【问题标题】:Dialyzer error for struct结构的透析器错误
【发布时间】:2016-12-12 12:45:49
【问题描述】:

这是 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

这无法通过以下方式进行类型检查:foo.ex:9: The variable _@1 can never match since previous clauses completely covered the type #{'__struct__':='Elixir.Foo', _=>_}

我一直在搜索,但终生无法找到解释这意味着什么,或者如何解决它。

【问题讨论】:

    标签: types elixir dialyzer


    【解决方案1】:

    如果将代码简化为:

    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
    

    然后反编译生成的.beam文件,你会得到:

    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.
    

    如果您仔细查看 Elixir 为 %__MODULE__{foo | x: 5} 生成的 case 语句,您会发现它包含一个永远无法匹配的分支,因为 __struct__ 在该函数中被保证为 Foo。这是由 Elixir 生成的,因为如果您将 %Struct{map | ...} 语法与不同结构的 map 一起使用,Elixir 会引发错误:

    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}
    

    要解决此问题,您可以删除 __MODULE__ 部分并执行以下操作:

    %{foo | x: 5}
    

    结果将是相同的,您不会收到任何警告。

    【讨论】:

    • 天哪,这是我在 StackOverflow 上收到的最快和最好的答案之一 - 非常感谢!
    猜你喜欢
    • 1970-01-01
    • 2019-01-20
    • 1970-01-01
    • 1970-01-01
    • 2016-12-13
    • 1970-01-01
    • 1970-01-01
    • 2016-01-20
    • 1970-01-01
    相关资源
    最近更新 更多