Hello everyone,
we have a dialyzer typespec error over at benchee that I can’t wrap my head around. It only seems to fail on Erlang 19+ and the issues seems to be with a struct type that we provide (typespec says we take that type and dialyzer seems to understand but still complains when it’s there).
The weirdest thing is that we have what I’d call virtually identical case that passes just fine (see below).
This includes code samples, for the full code you can check out the benchee PR trying to introduce the type spec
Basically it is the following (the only place where the offending function format_scenario_extended/3
is called):
@spec extended_statistics([Scenario.t], unit_per_statistic, integer)
:: [String.t]
defp extended_statistics(scenarios, units, label_width) do
Enum.map(scenarios, fn(scenario) ->
format_scenario_extended(scenario, units, label_width)
end)
end
# problem seems to be Scenario.t, pattern match on Scenario omitted for brevity
@spec format_scenario_extended(Scenario.t, unit_per_statistic, integer)
:: String.t
defp format_scenario_extended(%Scenario{}, %{run_time: run_time_unit}, label_width) do
"~*s~*ts~*ts~*ts~*ts\n"
|> :io_lib.format([lots_of_options])
|> to_string
end
We get the following error:
lib/benchee/formatters/console.ex:173: Invalid type specification for function 'Elixir.Benchee.Formatters.Console':format_scenario_extended/3. The success typing is (#{'__struct__':='Elixir.Benchee.Benchmark.Scenario', 'job_name':=_, 'run_time_statistics':=#{'__struct__':='Elixir.Benchee.Statistics', 'maximum':=number(), 'minimum':=number(), 'mode':=[number()], 'sample_size':=_, _=>_}, _=>_},#{'ips':=#{'__struct__':='Elixir.Benchee.Conversion.Unit', 'label':=binary(), 'long':=binary(), 'magnitude':=non_neg_integer(), 'name':=atom()}, 'run_time':=#{'__struct__':='Elixir.Benchee.Conversion.Unit', 'label':=binary(), 'long':=binary(), 'magnitude':=non_neg_integer(), 'name':=atom()}},integer()) -> binary(
The problem seems to be with dialyzers interpretation what Scenario.t
is. If it is exchanged against any
or map
for format_scenario_extended/3
dialyzer instead complains about underspecs:
lib/benchee/formatters/console.ex:173: Type specification 'Elixir.Benchee.Formatters.Console':format_scenario_extended(map(),unit_per_statistic(),integer()) -> 'Elixir.String':t() is a supertype of the success typing: 'Elixir.Benchee.Formatters.Console':format_scenario_extended(#{'__struct__':='Elixir.Benchee.Benchmark.Scenario', 'job_name':=_, 'run_time_statistics':=#{'__struct__':='Elixir.Benchee.Statistics', 'maximum':=number(), 'minimum':=number(), 'mode':=[number()], 'sample_size':=_, _=>_}, _=>_},#{'ips':=#{'__struct__':='Elixir.Benchee.Conversion.Unit', 'label':=binary(), 'long':=binary(), 'magnitude':=non_neg_integer(), 'name':=atom()}, 'run_time':=#{'__struct__':='Elixir.Benchee.Conversion.Unit', 'label':=binary(), 'long':=binary(), 'magnitude':=non_neg_integer(), 'name':=atom()}},integer()) -> binary()
One of the weirdest things about this is that we have virtually the same structure elsewhere in the same file and it works just fine:
@spec scenario_reports([Scenario.t], unit_per_statistic, integer)
:: [String.t]
defp scenario_reports(scenarios, units, label_width) do
Enum.map(scenarios, fn(scenario) ->
format_scenario(scenario, units, label_width)
end)
end
# pattern match on scenario omitted for brevity
@spec format_scenario(Scenario.t, unit_per_statistic, integer) :: String.t
defp format_scenario(%Scenario{},
%{run_time: run_time_unit,
ips: ips_unit,
}, label_width) do
"~*s~*ts~*ts~*ts~*ts~*ts\n"
|> :io_lib.format([options])
|> to_string
end
So umm help - help is rewarded with gratefulness and cute bunny pictures () Bonus points if you can lay out how to debug something like this
Thanks!
Tobi