Dialyzer - Function has no local return

Hello guys,

When running dialyzer on this function, it complains that the function get has no local return and that build_forecast will never be called.

 def get(%Params{} = params, options \\ []) do
    http_options = Keyword.merge([timeout: 20000], options)
    file_url = PathBuilder.path_for(params)
    case fetch(file_url, http_options) do
      {:ok, forecast_grib2} -> {:ok, build_forecast(params, forecast_grib2)}
      {:error, explanation} -> {:error, explanation}
    end
  end
  defp build_forecast(params, grib2) do
    %__MODULE__{
      params: params,
      data: grib2
    }
  end

I can’t quite understand what it is trying to tell me, as I manually tested and build_forecast gets called.

Full file here:

Any ideas?

Thanks

Check this post out. I’ve not tried Dialyzer yet, but was just reading about it: http://learningelixir.joekain.com/elixir-type-safety/

1 Like

As far as I understand, it is because of the used fetched. fetch may raise and get doesn’t handle, in a consequence there is the possibility that get won’t return but raise itself.

For a quick test just change raise e into {:error, :unknown}.

Same error, I’m quite puzzled.

This one throws the exact same warnings:

directory_listing_html_parser.ex:32: Function parse_date/1 has no local return
directory_listing_html_parser.ex:33: The call 'Elixir.Timex':to_date(#{}) breaks the contract ('Elixir.Timex.Convertable') -> 'Elixir.Timex.Date':t() | {'error',term()}
directory_listing_html_parser.ex:36: Function parse_cycle/1 will never be called```

Usually, when you see a has no local return, it means that the cause is an error deeper in the stack. It should be one of the subsequently reported errors.

In this case, your error is The call 'Elixir.Timex':to_date(#{}) breaks the contract ('Elixir.Timex.Convertable') -> 'Elixir.Timex.Date':t() | {'error',term()}

If you look closer, and keep in mind that dialyzer uses Erlang syntax, you can see that you’re passing a map (#{}), while the contract expects 'Elixir.Timex.Convertable'. In Erlang, single quotes denote an atom, so in Elixir syntax this means that according to typespec, Timex.to_date expects Timex.Convertable. I.e. it expects an alias constant, and not a map (which is probably a struct). In other words, a typespec of Timex.to_date is wrong, since the function takes a struct as the first argument (a thing to convert).

Looking at the master, I actually see that the typespec is correct, but it seems that the fix has been done recently. Perhaps you’re not using the latest version, or otherwise it’s not pushed to hex. If that’s the case, you can try using the latest master from GitHub.

21 Likes

That’s starting to make sense, thank you!

Also found this: http://learnyousomeerlang.com/dialyzer which is a good starting point.

So I reckon that the fact that the call to fetch/2 might raise a error is the reason for the no local return error. Question is, how do I make it go away? I’ve tried spec’ing get/2 as returning either a map or none. But dialyzer still thinks it will never return the map. (forecast.ex:28: The specification for 'Elixir.Weather.NOAA.Forecast':get/2 states that the function might also return #{} but the inferred return is none())

So somehow, dialyzer is expecting the fetch function to always raise an error which is not true. I’ve tried removing the rescue statement but it still results in the same error.

EDIT: I managed to make the error go away by spec’ing get/2 as returning no_return, but that’s a bad solution as it is not true and will eventually propagate the same error somewhere else.

Could you provide the full output of dialyzer? Also, having the whole code available might be helpful.

The the inferred return is none() means that dialyzer concludes that a function call will always fail. Usually this is caused by a wrong spec for something deeper in the callstack, as I’ve explained in my previous post.

These are the three files that relate to each other (I haven’t manually spec’ed anything yet):




Dialyzer output is:

Compiling 2 files (.ex)
Generated weather app
Starting Dialyzer
dialyzer --no_check_plt --plt /home/vagrant/.dialyxir_core_18_1.3.0.plt -Wunmatched_returns -Werror_handling -Wrace_conditions -Wunderspecs /home/vagrant/weather/_build/dev/lib/weather/ebin
  Proceeding with analysis...
directory_listing_html_parser.ex:20: Function parse_date_and_cycle/1 has no local return
directory_listing_html_parser.ex:32: Function parse_date/1 has no local return
directory_listing_html_parser.ex:33: The call 'Elixir.Timex':to_date(#{}) breaks the contract ('Elixir.Timex.Convertable') -> 'Elixir.Timex.Date':t() | {'error',term()}
directory_listing_html_parser.ex:36: Function parse_cycle/1 will never be called
forecast.ex:29: Function get/1 has no local return
forecast.ex:29: Function get/2 has no local return
forecast.ex:39: Function build_forecast/2 will never be called
 done in 0m1.78s
done (warnings were emitted)

To fix errors in directory_listing_html_parser, as I said, you should upgrade your timex.

I can’t make the obvious reason for the errors in forecast.ex. Something seems to be wrong inside get/2 or the code it calls. If you can push the whole project and a single line command to run dialyzer, I’ll be happy to take a look.

Can I have your github username? (I don’t want to push this as a public repo)

I’ll still write the conclusions here if we reach any :slight_smile:

This is me on GH. I can’t promise when exactly will I look at it, but I’ll try to do it within a reasonable time :slight_smile:

Just added you!

Thank you so much :slight_smile: The community is a big part of why I’m having great fun with Elixir.

1 Like

Upgrading to Timex 2.2.1 will fix all issues. Once I updated the dep, I rebuilt the code, removed the PLT, generated it again, and there were no errors. I’m not familiar with dialyxir, so not sure if there’s a better way of refreshing PLT.

The root cause of the second error was again a malformed spec in older Timex. The place in your code that triggered it is here. Since Timex spec was wrong, Dialyzer concluded it will always fail, and hence all that call this function unconditionally will also always fail.

It’s unfortunate that dialyzer didn’t report this error, and I’m not quite sure why. Either way, I’ve traced it by starting from get/2 and commenting out parts of it until I narrowed it down to PathBuilder.path_for. A quick glance at PathBuilder revealed the usage of Timex so I suspected that might be the culprit. Substituting the line in question with an empty string "" removed the error, so it was clear that the problem was indeed in Timex. A quick look at its spec in the deps folder confirmed it.

As I said, the required fixes are already done here, so all you need to do is use the more recent version of Timex, rebuild your PLT, and you should be fine.

4 Likes

That’s very reasonable timing :stuck_out_tongue:

Thanks for the quick response. Guess I’m still getting used to tracking weird errors in Elixir.

Mistery solved :slight_smile:

1 Like