README.md (16969B)
1 # Dialyxir 2 3 [![Build Status](https://travis-ci.org/jeremyjh/dialyxir.svg?branch=master)](https://travis-ci.org/jeremyjh/dialyxir) 4 [![Module Version](https://img.shields.io/hexpm/v/dialyxir.svg)](https://hex.pm/packages/dialyxir) 5 [![Hex Docs](https://img.shields.io/badge/hex-docs-lightgreen.svg)](https://hexdocs.pm/dialyxir/) 6 [![Total Download](https://img.shields.io/hexpm/dt/dialyxir.svg)](https://hex.pm/packages/dialyxir) 7 [![License](https://img.shields.io/hexpm/l/dialyxir.svg)](https://github.com/jeremyjh/dialyxir/blob/master/LICENSE) 8 [![Last Updated](https://img.shields.io/github/last-commit/jeremyjh/dialyxir.svg)](https://github.com/jeremyjh/dialyxir/commits/master) 9 10 Mix tasks to simplify use of Dialyzer in Elixir projects. 11 12 ## Changes in 1.0 13 14 Elixir 1.6 is required, to support the new pretty printing feature. If your 15 project is not yet on 1.6, continue to specify 0.5 in your mix deps. 16 17 Warning messages have been greatly improved, but are filtered through the legacy formatter to support your existing ignore files. You can optionally use the new Elixir [term format](#elixir-term-format) for ignore files. You may want to use the `--format short` argument in your CI pipelines. There are several formats, also there is a new `explain` feature - for details see CLI [options](#command-line-options). 18 19 ## Quickstart 20 If you are planning to use Dialyzer with an application built with the [Phoenix Framework](http://www.phoenixframework.org/), check out the [Quickstart wiki](https://github.com/jeremyjh/dialyxir/wiki/Phoenix-Dialyxir-Quickstart). 21 22 ## Installation 23 24 Dialyxir is available on [hex.pm](https://hex.pm/packages/dialyxir). 25 26 You can either add it as a dependency in your mix.exs, or install it globally as an archive task. 27 28 To add it to a mix project, just add a line like this in your deps function in mix.exs: 29 30 ```elixir 31 defp deps do 32 [ 33 {:dialyxir, "~> 1.0", only: [:dev], runtime: false}, 34 ] 35 end 36 ``` 37 38 ```console 39 mix do deps.get, deps.compile 40 ``` 41 42 ## Usage 43 44 Use dialyxir from the directory of the mix project you want to analyze; a PLT file will be created or updated if required and the project will be automatically compiled. 45 46 ```console 47 mix dialyzer 48 ``` 49 50 ### Command line options 51 52 * `--no-compile` - do not compile even if needed. 53 * `--no-check` - do not perform (quick) check to see if PLT needs to be updated. 54 * `--ignore-exit-status` - display warnings but do not halt the VM or return an exit status code. 55 * `--format short` - format the warnings in a compact format, suitable for ignore file using Elixir term format. 56 * `--format raw` - format the warnings in format returned before Dialyzer formatting. 57 * `--format dialyxir` - format the warnings in a pretty printed format. 58 * `--format dialyzer` - format the warnings in the original Dialyzer format, suitable for ignore file using simple string matches. 59 * `--format github` - format the warnings in the Github Actions message format. 60 * `--format ignore_file` - format the warnings to be suitable for adding to Elixir Format ignore file. 61 * `--quiet` - suppress all informational messages. 62 63 Warning flags passed to this task are passed on to `:dialyzer` - e.g. 64 65 ```console 66 mix dialyzer --unmatched_returns 67 ``` 68 69 There is information available about the warnings via the explain task - e.g. 70 71 ```console 72 mix dialyzer.explain unmatched_return 73 ``` 74 75 If invoked without arguments, `mix dialyzer.explain` will list all the known warnings. 76 77 ## Continuous Integration 78 79 To use Dialyzer in CI, you must be aware of several things: 80 81 1) Building the PLT file may take a while if a project has many dependencies 82 2) The PLT should be cached using the CI caching system 83 3) The PLT will need to be rebuilt whenever adding a new Erlang or Elixir version to build matrix 84 85 ### Travis 86 87 `.travis.yml` 88 ```markdown 89 language: elixir 90 91 elixir: 92 - 1.8 93 94 otp_release: 95 - 21.0 96 97 script: 98 - mix dialyzer 99 100 cache: 101 directories: 102 - priv/plts 103 ``` 104 105 ### Github Actions 106 107 `dialyzer.yml` 108 ```yaml 109 ... 110 steps: 111 - uses: actions/checkout@v2 112 - name: Set up Elixir 113 id: beam 114 uses: erlef/setup-beam@v1 115 with: 116 elixir-version: "1.12.3" # Define the elixir version 117 otp-version: "24.1" # Define the OTP version 118 119 # Don't cache PLTs based on mix.lock hash, as Dialyzer can incrementally update even old ones 120 # Cache key based on Elixir & Erlang version (also useful when running in matrix) 121 - name: Restore PLT cache 122 uses: actions/cache@v2 123 id: plt_cache 124 with: 125 key: | 126 ${{ runner.os }}-${{ steps.beam.outputs.elixir-version }}-${{ steps.beam.outputs.otp-version }}-plt 127 restore-keys: | 128 ${{ runner.os }}-${{ steps.beam.outputs.elixir-version }}-${{ steps.beam.outputs.otp-version }}-plt 129 path: | 130 priv/plts 131 132 # Create PLTs if no cache was found 133 - name: Create PLTs 134 if: steps.plt_cache.outputs.cache-hit != 'true' 135 run: mix dialyzer --plt 136 137 - name: Run dialyzer 138 run: mix dialyzer --format github 139 140 ``` 141 142 `mix.exs` 143 ```elixir 144 def project do 145 [ 146 ... 147 dialyzer: [ 148 plt_file: {:no_warn, "priv/plts/dialyzer.plt"} 149 ] 150 ] 151 end 152 ``` 153 154 `.gitignore` 155 ``` 156 /priv/plts/*.plt 157 /priv/plts/*.plt.hash 158 ``` 159 160 ## With Explaining Stuff 161 162 [Dialyzer](http://www.erlang.org/doc/apps/dialyzer/dialyzer_chapter.html) is a static analysis tool for Erlang and other languages that compile to BEAM bytecode for the Erlang VM. It can analyze the BEAM files and provide warnings about problems in your code including type mismatches and other issues that are commonly detected by static language compilers. The analysis can be improved by inclusion of type hints (called [specs](https://hexdocs.pm/elixir/typespecs.html)) but it can be useful even without those. For more information I highly recommend the [Success Typings](http://user.it.uu.se/~kostis/Papers/succ_types.pdf) paper that describes the theory behind the tool. 163 164 165 Usage is straightforward but you should be aware of the available configuration settings you may wish to add to your mix.exs file. 166 167 ### PLT 168 169 The Persistent Lookup Table (PLT) is basically a cached output of the analysis. This is important because you'd probably stab yourself in the eye with 170 a fork if you had to wait for Dialyzer to analyze all the standard library and OTP modules you are using every time you ran it. 171 Running the mix task `dialyzer` by default builds several PLT files: 172 173 * A core Erlang file in `$MIX_HOME/dialyxir_erlang-[OTP Version].plt` 174 * A core Elixir file in `$MIX_HOME/dialyxir_erlang-[OTP Version]_elixir-[Elixir Version].plt` 175 * A project environment specific file in `_build/env/dialyze_erlang-[OTP Version]_elixir-[Elixir Version]_deps-dev.plt` 176 177 The core files are simply copied to your project folder when you run `dialyxir` for the first time with a given version of Erlang and Elixir. By default, all 178 the modules in the project PLT are checked against your dependencies to be sure they are up to date. If you do not want to use MIX_HOME to store your core Erlang and Elixir files, you can provide a `:plt_core_path` key with a file path. You can specify a different directory for the project PLT file with the `:plt_local_path keyword`. You can specify a different filename for the project PLT file with the `:plt_file keyword` - this is deprecated because people were using it with the old `dialyxir` to have project-specific PLTs, which are now the default. To silence the deprecation warning, specify this value as `plt_file: {:no_warn, "/myproject/mypltfile"}`. 179 180 The core PLTs include a basic set of OTP applications, as well as all of the Elixir standard libraries. 181 The apps included by default are `[:erts, :kernel, :stdlib, :crypto]`. 182 183 If you don't want to include the default apps you can specify a `:plt_apps` key and list there only the apps you want in the PLT. Using this option will mean dependencies are not added automatically (see below). If you want to just add an application to the list of defaults and dependencies you can use the `:plt_add_apps` key. 184 185 If you want to ignore a specific dependency, you can specify it in the `:plt_ignore_apps` key. 186 187 #### Dependencies 188 189 OTP application dependencies are (transitively) added to your PLT by default. The applications added are the same as you would see displayed with the command `mix app.tree`. There is also a `:plt_add_deps` option you can set to control the dependencies added. The following options are supported: 190 191 * `:apps_direct` - Only Direct OTP runtime application dependencies - not the entire tree 192 * `:app_tree` - Transitive OTP runtime application dependencies e.g. `mix app.tree` (default) 193 194 195 The example below changes the default to include only direct OTP dependencies, adds another specific dependency, and removes a dependency from the list. This can be helpful if a large dependency tree is creating memory issues and only some of the transitive dependencies are required for analysis. 196 197 ```elixir 198 def project do 199 [ 200 app: :my_app, 201 version: "0.0.1", 202 deps: deps, 203 dialyzer: [ 204 plt_add_deps: :apps_direct, 205 plt_add_apps: [:wx], 206 plt_ignore_apps: [:mnesia] 207 ] 208 ] 209 end 210 ``` 211 212 #### Explanations 213 214 Explanations are available for classes of warnings by executing `mix dialyzer.explain warning_name`. It will include a description about the type of warning, as well as a small example that would also cause that warning. Poor explanations and examples should be considered issues in this library, and pull requests are very welcome! The warning name is returned from the `--format short` and `--format dialyzer` flags. List available warnings with `mix dialyzer.explain`. 215 216 #### Formats 217 218 Dialyxir supports formatting the errors in several different ways: 219 220 * Short - By passing `--format short`, the structs and other spec/type information will be dropped from the error message, with a minimal message. This is useful for CI environments. Includes `warning_name ` for use in explanations. 221 * Dialyzer - By passing `--format dialyzer`, the messages will be printed in the default Dialyzer format. This format is used in [legacy string matching](#simple-string-matches) ignore files. 222 * Raw - By passing `--format raw`, messages will be printed in their form before being pretty printed by Dialyzer or Dialyxir. 223 * Dialyxir (default) -- By passing `--format dialyxir`, messages will be converted to Elixir style messages then pretty printed and formatted. Includes `warning_name ` for use in explanations. 224 225 ### Flags 226 227 Dialyzer supports a number of warning flags used to enable or disable certain kinds of analysis features. Until version 0.4, `dialyxir` used by default the additional warning flags shown in the example below. However some of these create warnings that are often more confusing than helpful, particularly to new users of Dialyzer. As of 0.4, there are no longer any flags used by default. To get the old behavior, specify them in your Mix project file. For compatibility reasons you can use either the `-Wwarning` convention of the dialyzer CLI, or (preferred) the `WarnOpts` atoms supported by the [API](http://erlang.org/doc/man/dialyzer.html#gui-1). e.g. 228 229 ```elixir 230 def project do 231 [ 232 app: :my_app, 233 version: "0.0.1", 234 deps: deps, 235 dialyzer: [flags: ["-Wunmatched_returns", :error_handling, :race_conditions, :underspecs]] 236 ] 237 end 238 ``` 239 240 ### Paths 241 242 By default only the ebin in the `_build` directory for the current mix environment of your project is included in paths to search for BEAM files to perform analysis on. You can specify a list of locations to find BEAMS for analysis with :paths keyword. 243 244 ```elixir 245 def project do 246 [ 247 app: :my_app, 248 version: "0.0.1", 249 deps: deps, 250 dialyzer: [ 251 plt_add_apps: [:mnesia], 252 flags: [:unmatched_returns, :error_handling, :race_conditions, :no_opaque], 253 paths: ["_build/dev/lib/my_app/ebin", "_build/dev/lib/foo/ebin"] 254 ] 255 ] 256 end 257 ``` 258 259 ### Ignore Warnings 260 #### Dialyxir defaults 261 262 By default `dialyxir` has always included the `:unknown` warning option so that warnings about unknown functions are returned. This is usually a clue that the PLT is not complete and it may be best to leave it on, but it can be disabled entirely by specifying `remove_defaults: [:unknown]` in your config. 263 264 A better option is to ignore the specific warnings you can't fix (maybe due to a bug upstream, or a dependency you just don't want to include in your PLT due to time/memory in building the PLT file.) 265 266 #### Module attribute 267 268 Dialyzer has a built-in support for ignoring warnings through a `@dialyzer` module attribute. For example: 269 270 ```elixir 271 defmodule Myapp.Repo do 272 use Ecto.Repo, otp_app: :myapp 273 @dialyzer {:nowarn_function, rollback: 1} 274 end 275 ``` 276 277 More details can be found in the [erlang documentation](http://erlang.org/doc/man/dialyzer.html#requesting-or-suppressing-warnings-in-source-files) 278 279 #### Ignore file 280 281 If you want to ignore well-known warnings, you can specify a file path in `:ignore_warnings`. 282 283 ```elixir 284 def project do 285 [ 286 app: :my_app, 287 version: "0.0.1", 288 deps: deps, 289 dialyzer: [ignore_warnings: "dialyzer.ignore-warnings"] 290 ] 291 end 292 ``` 293 294 This file comes in two formats: `--format dialyzer` string matches (compatible with `<= 0.5.1` ignore files), and the [term format](#elixir-term-format). 295 296 Dialyzer will look for an ignore file using the term format with the name `.dialyzer_ignore.exs` by default if you don't specify something otherwise. 297 298 #### Simple String Matches 299 300 Any line of dialyzer format output (partially) matching a line in `"dialyzer.ignore-warnings"` is filtered. 301 302 Note that copying output in the default format will not work! Run `mix dialyzer --format dialyzer` to produce output suitable for the ignore file. 303 304 For example, in a project where `mix dialyzer --format dialyzer` outputs: 305 306 ``` 307 Proceeding with analysis... 308 config.ex:64: The call ets:insert('Elixir.MyApp.Config',{'Elixir.MyApp.Config',_}) might have an unintended effect due to a possible race condition caused by its combination with the ets:lookup('Elixir.MyApp.Config','Elixir.MyApp.Config') call in config.ex on line 26 309 config.ex:79: Guard test is_binary(_@5::#{'__exception__':='true', '__struct__':=_, _=>_}) can never succeed 310 config.ex:79: Guard test is_atom(_@6::#{'__exception__':='true', '__struct__':=_, _=>_}) can never succeed 311 done in 0m1.32s 312 done (warnings were emitted) 313 ``` 314 315 If you wanted to ignore the last two warnings about guard tests, you could add to `dialyzer.ignore-warnings`: 316 317 ``` 318 Guard test is_binary(_@5::#{'__exception__':='true', '__struct__':=_, _=>_}) can never succeed 319 Guard test is_atom(_@6::#{'__exception__':='true', '__struct__':=_, _=>_}) can never succeed 320 ``` 321 322 And then run `mix dialyzer` would output: 323 324 ``` 325 Proceeding with analysis... 326 config.ex:64: The call ets:insert('Elixir.MyApp.Config',{'Elixir.MyApp.Config',_}) might have an unintended effect due to a possible race condition caused by its combination with the ets:lookup('Elixir.MyApp.Config','Elixir.MyApp.Config') call in config.ex on line 26 327 done in 0m1.32s 328 done (warnings were emitted) 329 ``` 330 331 #### Elixir Term Format 332 333 Dialyxir also recognizes an Elixir format of the ignore file. If your ignore file is an `exs` file, Dialyxir will evaluate it and process its data structure. A line may be either a tuple or an arbitrary Regex 334 applied to the *short-description* format of Dialyzer output (`mix dialyzer --format short`). The file looks like the following: 335 336 ```elixir 337 # .dialyzer_ignore.exs 338 [ 339 # {short_description} 340 {":0:unknown_function Function :erl_types.t_is_opaque/1/1 does not exist."}, 341 # {short_description, warning_type} 342 {":0:unknown_function Function :erl_types.t_to_string/1 does not exist.", :unknown_function}, 343 # {short_description, warning_type, line} 344 {":0:unknown_function Function :erl_types.t_to_string/1 does not exist.", :unknown_function, 0}, 345 # {file, warning_type, line} 346 {"lib/dialyxir/pretty_print.ex", :no_return, 100}, 347 # {file, warning_type} 348 {"lib/dialyxir/warning_helpers.ex", :no_return}, 349 # {file} 350 {"lib/dialyxir/warnings/app_call.ex"}, 351 # regex 352 ~r/my_file\.ex.*my_function.*no local return/ 353 ] 354 ``` 355 356 Entries for existing warnings can be generated with `mix dialyzer --format short`. Just remember to put the output in quotes and braces to match the format above. 357 358 359 #### List unused Filters 360 361 As filters tend to become obsolete (either because a discrepancy was fixed, or because the location 362 for which a filter is needed changes), listing unused filters might be useful. This can be done by 363 setting the `:list_unused_filters` option to `true` in `mix.exs`. For example: 364 365 ```elixir 366 dialyzer: [ 367 ignore_warnings: "ignore_test.exs", 368 list_unused_filters: true 369 ] 370 ``` 371 372 This option can also be set on the command line with `--list-unused-filters`. When used without 373 `--ignore-exit-status`, this option will result in an error status code.