Dialyzer works on success typing, which only tells you when it's certain there's a bug, but will stay silent if it's unsure. Even at the highest settings I couldn't get the same lovely iteration loop that you get in an ML-style language (like Haskell, Elm, Rust, etc) that I've come to expect, where you feel like you are having a conversation with the tool.
At the time I was attempting to use it, it was also super slow, and libraries were not keeping their typespecs up-to-date. Also a big one: no parametric polymorphism results in `any`s galore. :/
It basically starts with a notion that your program is correct, and then runs through all the facts it can collects about dependencies and your code, and then if it finds some contradiction, it warns. It does weird things though for optimization, such as replacing long lists of literals with any() after a certain point, or merging structs into bizarre-o franeknstructs like %{:__struct__ => A | B, :a => int(), :b => int(), :shared => int()} if A and B have that :shared field in common and your typespec is A.t() | B.t().
At the time I was attempting to use it, it was also super slow, and libraries were not keeping their typespecs up-to-date. Also a big one: no parametric polymorphism results in `any`s galore. :/