smangelsdorf
I’ve encountered some unusual behavior from Dialyzer while validating values from Application.get_env and reduced it down to the following code with a deliberately incorrect typespec (should be list(module()) on the return):
@spec actually_a_list_of_modules(list(term())) :: list(DateTime.t())
def actually_a_list_of_modules(list) when is_list(list) do
validate_modules(list)
list
end
defp validate_modules(modules), do: Enum.each(modules, &ensure_valid_module/1)
defp ensure_valid_module(a) when is_atom(a), do: :ok
defp ensure_valid_module(a), do: raise("Not a module: #{inspect(a)}")
Dialyzer accepts this without complaint, and I’m not sure why. When I change the validate_modules function to destructure the list manually, Dialyzer complains as expected:
# This implementation fixes the problem, and Dialyzer correctly shows an error.
defp validate_modules([]), do: nil
defp validate_modules([a | b]) do
ensure_valid_module(a)
validate_modules(b)
end
I also tried implementing this with a comprehension to see if something specific to Enum.each was affecting the type checking, but it behaves in the same way as the first example.
# This is incorrectly accepted, the same as the Enum.each version.
defp validate_modules(modules), do: for(m <- modules, do: ensure_valid_module(m))
I understand this could just be a limitation of Dialyzer, and I’m happy to refactor my code accordingly. I’d love to know what’s causing this behaviour, though. Why’s Dialyzer getting confused?
(Reproduced on Elixir 1.16.3 + Erlang/OTP 26.2 and also Elixir 1.14.3 + Erlang/OTP 25.3)
Trending in Questions
Other Trending Topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #library
- #deployment
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #elixirconf
- #channels
- #exunit
- #discussion
- #code-sync
- #podcasts
- #javascript
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixirconf-us
- #ai
- #blog-post
- #elixir-ls
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #hex
- #security
- #metaprogramming










Showing Posts 1 to 3- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
sabiwara
Dialyzer is unfortunately clueless about protocols in general and
Enumerablein particular.It also doesn’t have the notion of generics:
Function.identity/1will basically do type-laundering and its output will just end up being aterm(instead of preserving the type of the input).Indeed, this is a limitation of Dialyzer’s success typing, this great article can help set up the right expectations and avoid expecting it to behave like a static type system.
smangelsdorf
Thanks, that’s useful. Am I correct in thinking that
forcomprehensions are desugared intoEnumcalls, and that’s why it has the same behaviour?sabiwara
Indeed you are correct!