View Source gradualizer (gradualizer v0.2.0)
Gradualizer is a static type checker for Erlang with support for gradual typing.
A static type checker detects errors thanks to type analysis of a given program. Gradualizer can infer some types, but mostly relies on function specs. In a nutshell, we could say that it checks for consistency of a function body with its declared spec and for consistency of a callee's spec with passed in arguments. Specifically, Gradualizer does not perform entire program flow analysis.
Gradual typing means that full type information is not required to raise warnings - any type information is better than no type information. It allows for a _gradual_ transition between fully static typing, which offers the best correctness guarantee, and fully dynamic typing, which means the least overhead of writing specs, the full Erlang expressiveness, and _letting it crash_ at runtime. We can choose the right balance between the static/dynamic extremes depending on the application, project maturity, team size, the need for reusability or for documentation consistency.
If you are interested in using Gradualizer to type check your code, then you might be looking for the Rebar3 plugin rebar_prv_gradualizer
or the command-line interface gradualizer_cli
.
This module contains entry points for calling into Gradualizer as a library, as well as some Erlang shell utilities for working directly with the type checker internals.
Let's try out the shell utilities by running:
$ rebar3 shell
Now, we can play with some examples:
> gradualizer:type_of("[ a || _ <- lists:seq(1, 5) ]").
{type,0,list,[{atom,0,a}]}
> typelib:pp_type(v(-1)).
"[a]"
> typechecker:normalize(gradualizer:type("a()"), gradualizer:env("-type a() :: integer().", [])).
{type,0,integer,[]}
> typelib:pp_type(v(-1)).
"integer()"
> gradualizer:type_of("fun (A) -> #{tag => my_map, list_of_as => [ A || _ <- lists:seq(1, 5) ]} end").
{type,0,'fun',
[{type,0,product,[{type,0,any,[]}]},
{type,0,map,
[{type,0,map_field_assoc,[{atom,0,tag},{atom,0,my_map}]},
{type,0,map_field_assoc,
[{atom,0,list_of_as},{type,0,list,[{type,0,any,[]}]}]}]}]}
> typelib:pp_type(v(-1)).
"fun((any()) -> #{tag => my_map, list_of_as => [any()]})"
The main library API of Gradualizer are type_check_(file|module|dir)
functions, which accept options described by the options()
type.
Link to this section Summary
Functions
Create a type checker environment populated by types defined in a source code snippet via -type ...
and -record(...)
attributes.
Return a Gradualizer type for the passed in Erlang type definition.
Type check an abstract syntax tree of a module. This can be useful for tools where the abstract forms are generated in memory.
See also: type_of/2.
Infer type of an Erlang expression.
Link to this section Types
Specs
options() :: proplists:proplist().
The options accepted by the Gradualizer API.
{i, Dir}
: include path for-include
and-include_lib
when checking Erlang source files. Specify multiple times for multiple include paths.stop_on_first_error
: iftrue
stop type checking on the first error, iffalse
continue checking all functions in the given file and all files in the given directory.crash_on_error
: iftrue
crash on the first produced error.return_errors
: return a list of errors instead of printing them and returning a single condensedok | nok
.fmt_location
: how to format location when pretty printing errors:none
: no location for easier comparisonbrief
: for machine processing (<line>:<column>:
before message text)verbose
(default): for human readers (on line <line> at column <column>
within the message text)
fmt_expr_fun
: function to pretty print an expression AST (useful to support other languages)fmt_type_fun
: function to pretty print a type AST (useful to support other languages){color, always | never | auto}
: Use colors when printing fancy messages. Auto is the default but auto-detection of tty doesn't work when running as an escript. It works when running from the Erlang shell though.{fancy, boolean()}
: Use fancy error messages when possible. True by default. Doesn't work when a customfmt_expr_fun
is used.
Specs
top()
Link to this section Functions
Specs
env() -> typechecker:env().
See also: env/2.
Specs
env(gradualizer:options()) -> typechecker:env().
See also: env/2.
Specs
env(string(), gradualizer:options()) -> typechecker:env().
Create a type checker environment populated by types defined in a source code snippet via -type ...
and -record(...)
attributes.
Currently, it's not possible to define variable bindings in the environment.
> rr(typechecker).
> gradualizer:env("-type a() :: integer().", []).
#env{fenv = #{},imported = #{},venv = #{},
tenv = #{module => undefined,records => #{},
types => #{{a,0} => {[],{type,0,integer,[]}}}},
infer = false,verbose = false,exhaust = true,
clauses_stack = [],union_size_limit = 30,
current_spec = none}
> gradualizer:env("-record(r, {f}).", []).
#env{
fenv = #{},imported = #{},venv = #{},
tenv =
#{module => undefined,
records =>
#{r =>
[{typed_record_field,
{record_field,0,{atom,0,f},{atom,1,undefined}},
{type,0,any,[]}}]},
types => #{}},
infer = false,verbose = false,exhaust = true,
clauses_stack = [],union_size_limit = 30,
current_spec = none}
Specs
type(string()) -> typechecker:type().
Return a Gradualizer type for the passed in Erlang type definition.
> gradualizer:type("[integer()]").
{type,0,list,[{type,0,integer,[]}]}
Specs
type_check_dir(file:filename()) -> ok | nok | [{file:filename(), any()}].
Specs
type_check_dir(file:filename(), options()) -> ok | nok | [{file:filename(), any()}].
Specs
type_check_file(file:filename()) -> ok | nok | [{file:filename(), any()}].
Specs
type_check_file(file:filename(), options()) -> ok | nok | [{file:filename(), any()}].
Specs
type_check_files([file:filename()]) -> ok | nok | [{file:filename(), any()}].
Specs
type_check_files([file:filename()], options()) -> ok | nok | [{file:filename(), any()}].
Specs
type_check_forms([erl_parse:abstract_form()], options()) -> ok | nok | [{file:filename(), any()}].
Type check an abstract syntax tree of a module. This can be useful for tools where the abstract forms are generated in memory.
If the first form is a file attribute (as in forms returned by e.g. epp:parse_file/1,2), that filename will be used in error messages. The second form is typically the module attribute.Specs
type_check_module(module()) -> ok | nok | [{file:filename(), any()}].
Specs
type_check_module(module(), options()) -> ok | nok | [{file:filename(), any()}].
Specs
type_of(string()) -> typechecker:type().
See also: type_of/2.
Specs
type_of(string(), typechecker:env()) -> typechecker:type().
Infer type of an Erlang expression.
> gradualizer:type_of("[ a || _ <- lists:seq(1, 5) ]").
{type,0,list,[{atom,0,a}]}
> gradualizer:type_of("case 5 of 1 -> one; _ -> more end").
{type,0,union,[{atom,0,more},{atom,0,one}]}
> Env = gradualizer:env([{union_size_limit, 1}]).
> gradualizer:type_of("case 5 of 1 -> one; _ -> more end", Env).
{type,0,any,[]}