Help Dialyzer Help You!

…or Why you should use specs if you use opaque types

Brujo Benavides
Jun 18 · 6 min read
Help me help you — Jerry Maguire

TL;DR

If you define an opaque type, you have to add specs to all the exported functions that use it (i.e. your module’s API).


Opaque Types

@type t1 :: boolean | atom # this type is exported
@typep t2 :: String.t # this type is private
@opaque t3 :: t1 | t2 # this type is opaque
-type t1 :: boolean() | atom().
-export_type [t1/0]. % This makes t1 exported
-type
t2 :: string:t().
-opaque t3 :: t1 | t2.

Dialyzer and Opaque Types


The Setup

Again… very dumb names. Not the original ones, of course.
lib/dialyzer_example.ex:19: Function print_odt/1 has no local return
lib/dialyzer_example.ex:19: The call 'Elixir.MyODT':f1(Vodt@1::#{'f2':=_, _=>_}) does not have an opaque term of type 'Elixir.MyODT':t() as 1st argument
WHAAAAT?

What’s going on here?

Dialyzer is NEVER wrong

What dialyzer says

What dialyzer MEANS

If you define an opaque type, you have to add specs to all the exported functions that use it (i.e. your module’s API).

What I would LIKE dialyzer to say

lib/dialyzer_example.ex:19: Function print_odt/1 has no local return
lib/dialyzer_example.ex:19: The call to 'Elixir.MyODT':f1/1 requires an opaque term of type 'Elixir.MyODT':t() as 1st argument and the variable that you're using for it (Vodt@1) must have type #{'f2':=_, _=>_} since it's also used in a call to 'Elixir.MyODT':f2/1

In Other News…

SpawnFest

ElixirConfLA

Erlang Battleground

Erlang Battleground

Strange and funny battle stories found while programming in the Erlang Ecosystem

Thanks to Juan Facorro.

Brujo Benavides

Written by

Father / Long Distance Walker / Erlanger @ AdRoll via BairesDev / Trainer @ CodeMentor - You can… https://www.buymeacoffee.com/elbrujohalcon

Erlang Battleground

Strange and funny battle stories found while programming in the Erlang Ecosystem