dialyze because I wanted a
dialyzer task to make very different decisions to
dialyxir. I would have liked to have contributed these changes but I wanted to change nearly everything.
dialyze is designed for zero/minimal configuration, repeatable/accurate warnings, smart PLT creation, sane defaults and to automatically make decisions it knows should be done without manual user commands.
In the majority of cases all the information required to build a PLT already exists if an application adheres to OTP principles. The PLT should contain the dependencies of the project and on application should have all it dependent applications listed in its
applications. This means
dialyze won’t work if not creating OTP compliant applications. I think a lot of people don’t. For example I think phoenix users can’t use this task very well because phoenix does not generate the correct applications list.
dialyxir will likely be more appropriate if you are a new user because it will show unknown warnings when run and then the relevant application(s) can be manually added to the configuration. I am probably more anal than average about OTP principles so I was confident for my own use that
dialyze wouldn’t be a problem here. However if you need the configuration then
dialyxir is your only option.
Since the modules added to the PLT are completely controlled by the
dialyze task it can change the PLT to reflect changes in dependencies. It does this by adding 2 extra phases to
dialyxir doesn’t do. It builds the application dependency tree (by using the zero configuration PLT approach above) and works out which files should be in the PLT. Then it removes any modules that shouldn’t be there, then it checks the modules in the PLT are up to date with the latest versions of the modules and then it adds any missing modules. The 3 stage “check plt” is run every time by default (and only takes about a second if no changes to be made) so the PLT should always be the correct one. This feature might crash for a number of reasons prior to OTP 18.3 due to bugs in
dialyxir works at the application level it will only perform the middle change using modules that are already in the PLT. Therefore if deps change adds/removes modules the PLT is out of sync which can cause false positives and negatives. I think the only way to avoid this with
dialyxir is to completely rebuild the PLT when a dep changes but please correct me if this is wrong.
I could be wrong but I imagine the very vast majority of
dialyxir users aren’t doing this and are missing warnings and getting invalid warnings.
Smart PLT creation:
dialyze will create 1 PLT per project per Elixir version per OTP version. The reason for this is that different projects may use different dependencies and different versions of dependencies. This may sound like it would be very slow but
dialyze does smart caching of PLTs so if another project has already created a PLT for that version of Elixir and OTP, or just for OTP, all or part of the PLT can be reused. This means a project that doesn’t have any dependencies except for Elixir (and dependencies of Elixir like
:stdlib) it will build in a matter of seconds. Without this caching it will probably take a long time, maybe more than 10 minutes. This means getting started using dialyzer on a new project is fast. Because of the smart “check-plt” for repeatable warnings feature (above) the minimal changes are done to a PLT automatically when a dependency is added and the PLT does not need to rebuilt.
On the other hand
dialyxir uses a global PLT per OTP version, which means you would can only use one global version of elixir and deps. Otherwise you will need to rebuild the PLT or end up using the incorrect PLT! It is configurable to avoid this but it is not the default. This default could mean using another project could break the PLT usage for different project.
I could be wrong but I imagine for many
dialyxir users they don’t realise that PLTs from one project can interact with another. And for all
dialyxir users that have realised this and set local PLTs they will have had to spend a lot of extra time waiting for PLTs to build.
As well as the (forced) defaults above
dialyze does not add any extra warnings that
dialyzer does not run. Apart from opting out of checking the PLT or opting out of running analysis this is the only part of
dialyze that can be configured and requires command line arguments to turn warnings on and off.
dialyxir adds extra default warnings:
"-Wunmatched_returns", "-Werror_handling", "-Wrace_conditions", "-Wunderspecs". These warnings are not on by default because success typing should not produce false positives. All of these except
-Wrace_conditions can cause them. These warnings are extra features that
dialyzer is able to produce.
"-Wunmatched_returns" is a useful warning but perhaps a little to strict for a beginner, in most cases of this warning it is fine to ignore the return of a function - false positive. Requiring
_ = call() in these cases is of questionable style. I like it but some don’t.
"-Werror_handling" can lead to false positive because it might be intentional that a function will always raise. I am unsure of a case where dialyzer won’t producer other warnings when this is not intentional.
"-Wrace_conditions" tries to find race conditions in the code. I am unsure what the warnings are because it has never found any in my code. Please let me know if it has found any for you. However it can add a lot of extra time to running
dialyzer, I think it tries brute force. Some people have even reported it get stuck in an apparent infinite loop on the erlang mailing list. Therefore I would advise against running this warning. If you need race condition testing try concuerror it is much better at this job.
"-Wunderspecs" shows when specifications are too allowing. It may not be practical or helpful to use the more specific specification, i.e. false positives. For example a macro may return a subset of
Macro.t and this warning will appear if dialyzer a subset that it can produce. However to a user of the macro it should only be applicable that the macro returns
Macro.t. Enforcing such a strict spec often leads to requiring a backwards incompatible change in spec for what would otherwise be a backwards compatible change.
Automatically run commands that need to be run:
If a computer can work out that something needs to run then it should run it for the user without being prompted.
dialyze will automatically do everything required: ensure the PLT is using the current deps and is up to date unless explicitly told not.
dialyxir requires manually building and checking the PLT. Even if checking was automatic the check would not be enough to ensure the PLT is up to date with deps.
Another difference is that
dialyzer inside the same VM. Whereas
dialyxir starts another VM. I am unsure if there are bugs here like getting the wrong version of Erlang but if
dialyer is slow and the user decides to kill mix, the extra VM will keep running and wont exit until the analysis finishes. You will need to look up the processes and kill it manually. There is a benefit to using the CLI though, unknown warnings are shown by default, whereas for the erlang API there are not and have to be turned on. Therefore
dialyze does not show unknown warnings by default but
dialyze is no longer maintained because no one was contributing and only people complaining. Instead I maintain the
rebar3 provider which has all the features of
dialyze (but is faster) plus the same (and more) configuration that
dialyxir has. The
rebar3 provider also adds various colours to different parts of the warnings to make them easier to understand and groups warnings by module.
I would suggest that any one interested in using
dialyzer in elixir gets behind
dialyxir and resolves the issues I have mentioned above. Otherwise the experience of using
dialyzer will remain poor (and IMO broken) in elixir: Wasting ~10 minutes per PLT build (after the first global) that is not going to be managed properly and generate false positives and false negatives. Once that has been handled people may want to look at formatting the errors in a clearer style.