?
Dialyzer uses success typing, meaning it does not check for strict type adherence like a traditional type checker. Instead, it infers the most general type that makes the code valid. If Dialyzer does not find any inconsistencies that could cause a runtime error, it might remain silent.
Hmmm, that is a bit nasty. For my purpose I want to type an API, which isn’t directly used but only indirectly called. (Zotonic event observers.) They need to adhere to a defined type, so I can make a behaviour. But apparently we can’t check the types of these functions?
That is a major bummer.
No way around this? Any other type checker that would be able to do this?
And sure enough, I do get these warnings with your module:
src/foobar.erl
Line 4 Column 2: The specification for foobar:test/1 states that the function might also return 'false' but the inferred return is 'baz' | 'true'
Line 4 Column 2: The success typing for foobar:test/1 implies that the function might also return 'baz' but the specification return is boolean()
and if I change the spec to…
-spec test(list()) -> baz | true.
I get…
src/foobar.erl
Line 4 Column 2: Type specification foobar:test([any()]) -> 'baz' | 'true' is a subtype of the success typing: foobar:test(_) -> 'baz' | 'true'
This touches on something that’s irked me for awhile. I’d far rather be able to specify “all warnings except [list-of-warnings]”, rather than just have the choice of default vs setting each one explicitly. This is particularly annoying with xref.
Dialyzer isn’t a type checker in the classical sense, which ends up causing a lot of gotchas for its users. I covered that a bit in a talk at CodeBEAM here: https://youtu.be/prjuvh0IN90?t=196
The short answer is that Dialyzer’s model is that it only complains if it thinks something is “definitely” wrong. In the OP’s snippet, there’s a codepath which satisfies the spec - namely, when the argument is a list. One of Dialyzer’s design goals was for it to not noisily report issues if the code could actually execute directly, and this is a corollary of that decision.
As others have commented, invocations of this function which clearly always fail, such as when the call test(x) is made, should report errors.
For my purpose I want to type an API, which isn’t directly used but only indirectly called. (Zotonic event observers.) They need to adhere to a defined type, so I can make a behaviour. But apparently we can’t check the types of these functions?
In some languages, you might have a sound or sound-enough type checker to make this work nicely. In Erlang, I don’t know of any checker at that level. The best I know of is eqwalizer, insofar as it has been used in production at WhatsApp with success for quite a while now, so it’s pretty robust. Since it’s made for migrating existing codebases and using untyped libraries, etc., eqwalizer has a lot of escape hatches which might be fine for you, or might make it too hard to ensure the API is used in precisely the right way. I think you’d just need to try it and see.