Dialyzer испускает Тип спецификации является подтипом успешной типизации для нескольких функций с одинаковым именем - PullRequest
0 голосов
/ 17 июня 2019

У меня есть следующие две функции, и я получаю предупреждение о них от диализатора:

"Type specification 'Elixir.MyModule':calculate(arg1::'Elixir.String':t(),arg2::'Elixir.CustomType':t(),arg3::'Elixir.String':t()) -> 'ok' | 'error'; calculate(arg1::'Elixir.String':t(),arg2::'Elixir.CustomType':t(),arg3::maybe_improper_list()) -> 'ok' | 'error' is a subtype of the success typing: 'Elixir.MyModule':calculate(binary(),#{'__struct__':='Elixir.CustomType', _=>_},binary() | maybe_improper_list()) -> 'error' | 'ok'"

Вот функции:

@spec calculate(arg1 :: String.t, arg2 :: CustomType.t, arg3 :: String.t)
def calculate(arg1, %CustomType{} = arg2, arg3) when is_binary(arg1) and is_binary(arg3) do
  calculate(arg1, arg2, [arg3])
end

@spec calculate(arg1 :: String.t, arg2 :: CustomType.t, arg3 :: maybe_improper_list())
def calculate(arg1, %CustomType{prop1: val, prop2: val2}, arg3) when is_binary(arg1) and is_integer(val2) and is_binary(val) and is_list(arg3) do
...
end

Я не понимаю, почему яполучать это предупреждение.Я думал, что это правильный способ написания функций с различными типами аргументов в Elixir, но учитывая, что Dialyzer продолжает выдавать предупреждения, я начинаю задумываться, пишу ли я этот код неправильно?


defmodule CustomType do
  @type t :: %CustomType{
    prop1: String.t(),
    prop2: integer(),
    prop3: String.t(),
    prop4: boolean(),
    ...
  }
end

Это флаги диализатора, с которыми я работаю:

dialyzer: [
  flags: ~w[underspecs overspecs race_conditions error_handling unmatched_returns]a
]

Образец репро:

defmodule MyCustomType do
    @type t :: %MyCustomType{
        prop1: String.t(),
        prop2: integer(),
        prop3: String.t()
    }

    defstruct [:prop1, :prop2, :prop3]
end

defmodule MyModule do
    @spec calculate(String.t, 
                    MyCustomType.t, 
                    String.t) 
    :: :ok
    def calculate(arg1, %MyCustomType{} = arg2, arg3) when is_binary(arg1) and is_binary(arg3) do
        calculate(arg1, arg2, [arg3])
    end

    @spec calculate(String.t, 
                    MyCustomType.t, 
                    maybe_improper_list) 
    :: :ok
    def calculate(arg1, %MyCustomType{prop1: val, prop2: val2}, arg3) when is_binary(arg1) and is_list(arg3) and is_binary(val) and is_integer(val2) do
        :ok
    end
end

Вот предупреждения, которые я получаю:

      Type specification 'Elixir.MyModule':calculate
          ('Elixir.String':t(),
          'Elixir.MyCustomType':t(),
          'Elixir.String':t()) ->
             'ok';
         ('Elixir.String':t(),
          'Elixir.MyCustomType':t(),
          maybe_improper_list()) ->
             'ok' is a subtype of the success typing: 'Elixir.MyModule':calculate
          (binary(),
          #{'__struct__' := 'Elixir.MyCustomType',
            'prop1' := binary(),
            'prop2' := integer(),
            _ => _},
          binary() | maybe_improper_list()) ->
             'ok'

1 Ответ

1 голос
/ 17 июня 2019

Во-первых, я не вижу учебных пособий по эликсиру типов, в которых они пишут @spec с именами переменных в спецификации - вместо этого все, что я нахожу, это учебники с типами только в спецификации типов:

@spec calculate(arg1 :: String.t, arg2 :: CustomType.t, arg3 :: String.t)

v.

@spec calculate(String.t, CustomType.t, String.t)

Тем не менее, следующий пропущенный для меня диализатор:

defmodule CustomType do
  @type t :: %CustomType{}
  defstruct a: nil, b: nil
end

defmodule MyModule do

  @spec calculate(arg1::String.t, arg2::CustomType.t, arg3::String.t) :: number

  #def calculate(<<arg1::binary>>, %CustomType{} = arg2, <<arg3::binary>>) do
  def calculate(arg1, %CustomType{} = arg2, arg3) when is_binary(arg1) and is_binary(arg3) do
    calculate(arg1, arg2, [arg3])
  end

  @spec calculate(String.t, CustomType.t, maybe_improper_list()) :: number

  def calculate(<<arg1::binary>>, %CustomType{} = arg2, arg3) when is_list(arg3) do
    123
  end

end

~/elixir_programs/friends$ mix dialyzer
Compiling 1 file (.ex)
warning: variable "arg1" is unused (if the variable is not meant to be used, prefix it with an underscore)
  lib/friends/my_module.ex:17

warning: variable "arg2" is unused (if the variable is not meant to be used, prefix it with an underscore)
  lib/friends/my_module.ex:17

Checking PLT...
[:compiler, :connection, :crypto, :db_connection, :decimal, :ecto, :elixir,
 :kernel, :logger, :poolboy, :postgrex, :stdlib]
PLT is up to date!
Starting Dialyzer
dialyzer args: [
  check_plt: false,
  init_plt: '/Users/7stud/elixir_programs/friends/_build/dev/dialyxir_erlang-20.3_elixir-1.8.2_deps-dev.plt',
  files_rec: ['/Users/7stud/elixir_programs/friends/_build/dev/lib/friends/ebin'],
  warnings: [:unknown]
]
done in 0m1.43s
done (passed successfully)

Я скажу, что нахожу этот синтаксис:

@spec calculate(String.t, CustomType.t, String.t)

гораздо проще для чтения.

Согласно Learn You Some Erlang :

is a subtype of the success typing

Это предупреждает вас о том, что на самом деле ваша спецификация слишком строга для того, что ваш код должен принятьи сообщает вам (хотя и косвенно), что вы должны либо сделать свою спецификацию типа более свободной, либо лучше проверить свои входы и выходы в своих функциях, чтобы отразить спецификацию типа.

Однако я не могу произвестиваш вывод диализатора:

defmodule CustomType do
  @type t :: %CustomType{}
  defstruct a: nil, b: nil
end

defmodule MyModule do

  @spec calculate(arg1 :: String.t, 
                  arg2 :: CustomType.t, 
                  arg3 :: String.t) 
  :: :ok | :error

  def calculate(arg1, %CustomType{} = arg2, arg3) 
  when is_binary(arg1) and is_binary(arg3) do
    calculate(arg1, arg2, [arg3])
  end

  @spec calculate(arg1 :: String.t, 
                  arg2 :: CustomType.t, 
                  arg3 :: maybe_improper_list()) 
  :: :ok | :error

  def calculate(arg1, %CustomType{} = arg2, arg3) 
  when is_binary(arg1) and is_list(arg3) do
    case arg1 do
      "hello" -> :ok
      "goodbye"  -> :error
    end
  end

end

$ mix dialyzer
Compiling 1 file (.ex)
warning: variable "arg2" is unused (if the variable is not meant to be used, prefix it with an underscore)
  lib/friends/my_module.ex:19

Checking PLT...
[:compiler, :connection, :crypto, :db_connection, :decimal, :ecto, :elixir,
 :kernel, :logger, :poolboy, :postgrex, :stdlib]
PLT is up to date!
Starting Dialyzer
dialyzer args: [
  check_plt: false,
  init_plt: '/Users/7stud/elixir_programs/friends/_build/dev/dialyxir_erlang-20.3_elixir-1.8.2_deps-dev.plt',
  files_rec: ['/Users/7stud/elixir_programs/friends/_build/dev/lib/friends/ebin'],
  warnings: [:unknown]
]
done in 0m1.46s
done (passed successfully)

Итак, вам нужно опубликовать минимальный пример, который воспроизводит ваш вывод диализатора.Я отмечу, что arg3 должен быть двоичным в вашем первом предложении, поэтому, когда вы вызываете calculate(arg1, arg2, [arg3]) в теле первого предложения, аргумент [arg3] никогда не будет неправильным списком, поэтому вы можете сжать эту спецификацию до: list(binary) для второго предложения.

============

Вот код, который я собрал:

defmodule CustomType do
  @type t :: %CustomType {
    prop1: String.t(),
    prop2: integer(),
    prop3: String.t(),
    prop4: boolean()
  }

  defstruct prop1: nil, prop2: nil, prop3: nil, prop4: nil
end

defmodule MyModule do
  @spec calculate(arg1 :: String.t, 
                  arg2 :: CustomType.t, 
                  arg3 :: String.t) 
  :: :ok | :error

  def calculate(arg1, %CustomType{} = arg2, arg3) 
  when is_binary(arg1) and is_binary(arg3) do
    calculate(arg1, arg2, [arg3])
  end

  @spec calculate(arg1 :: String.t, 
                  arg2 :: CustomType.t, 
                  arg3 :: maybe_improper_list) 
  :: :ok | :error

  def calculate(arg1, %CustomType{prop1: val, prop2: val2}, arg3) 
  when is_binary(arg1) and is_binary(val) and is_integer(val2) and is_list(arg3) do
    case arg1 do
      "hello" -> :ok
      "goodbye"  -> :error
    end
  end

end

Выполнениедиализатор:

~/elixir_programs/friends$ mix dialyzer 
Checking PLT... [:artificery,  
 :compiler, :connection, :crypto, :db_connection, :decimal,  :distillery,  
 :ecto, :elixir, :kernel, :logger, :poolboy, :postgrex,  :runtime_tools,  
 :stdlib] Finding suitable PLTs Looking up modules in dialyxir_erlang-  
20.3_elixir-1.8.2_deps-dev.plt Finding applications for dialyxir_erlang-  
20.3_elixir-1.8.2_deps-dev.plt Finding modules for dialyxir_erlang-  
20.3_elixir-1.8.2_deps-dev.plt Checking 718 modules in dialyxir_erlang-  
20.3_elixir-1.8.2_deps-dev.plt Adding 56 modules to dialyxir_erlang-  
20.3_elixir-1.8.2_deps-dev.plt Starting Dialyzer dialyzer args: [   
  check_plt: false,   
init_plt: '/Users/7stud/elixir_programs/friends/_build/dev/dialyxir_erlang-  
20.3_elixir-1.8.2_deps-dev.plt', files_rec:   
['/Users/7stud/elixir_programs/friends/_build/dev/lib/friends/ebin'],  
  warnings: [:unknown] ] done in 0m1.26s done (passed successfully)  

Со следующим в mix.exs:

  def project do
    [
      app: :friends,
      version: "0.1.0",
      elixir: "~> 1.6",
      start_permanent: Mix.env() == :prod,
      deps: deps(),
      dialyzer: [
            flags: ~w[underspecs 
                      overspecs 
                      race_conditions
                      error_handling 
                      unmatched_returns]a
      ]
    ]
  end

Вот вывод:

~/elixir_programs/friends$ mix dialyzer
Checking PLT...
[:artificery, :compiler, :connection, :crypto, :db_connection, :decimal,  
:distillery, :ecto, :elixir, :kernel, :logger, :poolboy, :postgrex,  
:runtime_tools, :stdlib]  
PLT is up to date!  
Starting Dialyzer  
dialyzer args: [
  check_plt: false,
  init_plt: '/Users/7stud/elixir_programs/friends/_build/dev/dialyxir_erlang-20.3_elixir-1.8.2_deps-dev.plt',
  files_rec: ['/Users/7stud/elixir_programs/friends/_build/dev/lib/friends/ebin'],
  warnings: [:underspecs, :overspecs, :race_conditions, :error_handling,
   :unmatched_returns, :unknown]
]
done in 0m1.38s  
done (passed successfully) 
...