Dialyzer - wondering why I don't get an error

I have this simple module:

-module(foobar).
-export([ test/1 ]).

-spec test(list()) -> boolean().
test(x) -> baz;
test(_) -> true.

When I run dialyzer (OTP 27.1) over it, it doesn’t complain.
Which is a bit of a surprise to me.

How can I make it complain that the first clause does not match the spec?
Or might it be that I have some (hidden) Dialyzer options enabled…

3 Likes

Did you try

-module(foobar).
-export([test/1, run/0]).

-spec test(list()) -> boolean().
test(x) -> baz;
test(_) -> true.

run() -> test(x).

?
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?

Hmm… maybe Gradualizer is a method here.

Is that production ready? And can it handle types defined in a behaviour?

1 Like

It does complain if you enable underspecs and overspecs warnings.
I have this config in my rebar.config:

{dialyzer,
 [{warnings, [no_return, unmatched_returns, error_handling, underspecs, overspecs, unknown]}]}.

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'
7 Likes

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.

1 Like

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.

3 Likes

Thanks, I will definitely check that one out.

In the meantime I’ve added the Dialyzer option missing_return, which kind-of does what I am looking for.