TypeCheck: Fast and flexible runtime type-checking for your Elixir projects.

hex.pm versionBuild StatusDocumentation

Core ideas

Usage Example

defmodule User do
  use TypeCheck
  defstruct [:name, :age]

  type t :: %__MODULE__{name: binary, age: integer}
end

defmodule AgeCheck do
  use TypeCheck

  spec is_user_older_than?(User.t, integer) :: boolean
  def is_user_older_than?(user, age) do
    user.age >= age
  end
end

Now we can try the following:

iex> AgeCheck.is_user_older_than?(%User{name: "Qqwy", age: 11}, 10)
true
iex> AgeCheck.is_user_older_than?(%User{name: "Qqwy", age: 9}, 10)
false

So far so good. Now let's see what happens when we pass values that are incorrect:

iex> AgeCheck.is_user_older_than?("foobar", 42)
** (TypeCheck.TypeError) The call `is_user_older_than?("foobar", 42)` does not adhere to spec `is_user_older_than?(%User{age: integer(), name: binary()},  integer())
::
boolean()`. Reason:
  parameter no. 1:
    `"foobar"` does not check against `%User{age: integer(), name: binary()}`. Reason:
      `"foobar"` is not a map.

iex> AgeCheck.is_user_older_than?(%User{name: nil, age: 11}, 10)
** (TypeCheck.TypeError) The call `is_user_older_than?(%Example3.User{age: 11, name: nil}, 10)` does not adhere to spec `is_user_older_than?(%User{age: integer(), name: binary()},  integer())
::
boolean()`. Reason:
  parameter no. 1:
    `%Example3.User{age: nil, name: nil}` does not check against `%User{age: integer(), name: binary()}`. Reason:
      under key `:name`:
        `nil` is not a binary.

iex> AgeCheck.is_user_older_than?(%User{name: "Aaron", age: nil}, 10) 
** (TypeCheck.TypeError) The call `is_user_older_than?(%User{age: nil, name: "Aaron"}, 10)` does not adhere to spec `is_user_older_than?(%User{age: integer(), name: binary()},  integer())
::
boolean()`. Reason:
  parameter no. 1:
    `%Example3.User{age: nil, name: "Aaron"}` does not check against `%User{age: integer(), name: binary()}`. Reason:
      under key `:age`:
        `nil` is not an integer.

iex> AgeCheck.is_user_older_than?(%User{name: "José", age: 11}, 10.0) 
** (TypeCheck.TypeError) The call `is_user_older_than?(%User{age: 11, name: "José"}, 10.0)` does not adhere to spec `is_user_older_than?(%User{age: integer(), name: binary()},  integer())
::
boolean()`. Reason:
  parameter no. 2:
    `10.0` is not an integer.

And if we were to introduce an error in the function definition:

defmodule AgeCheck do
  use TypeCheck

  spec is_user_older_than?(User.t, integer) :: boolean
  def is_user_older_than?(user, age) do
    user.age
  end
end

Then we get a nice error message explaining that problem as well:

** (TypeCheck.TypeError) The result of calling `is_user_older_than?(%User{age: 26, name: "Marten"}, 10)` does not adhere to spec `is_user_older_than?(%User{age: integer(), name: binary()},  integer())
::
boolean()`. Reason:
  Returned result:
    `2` is not a boolean.

Features & Roadmap

Implemented

Pre-stable

Longer-term future ideas

Installation

TypeCheck is available in Hex. The package can be installed by adding type_check to your list of dependencies in mix.exs:

def deps do
  [
    {:type_check, "~> 0.1.0"}
  ]
end

The documentation can be found at https://hexdocs.pm/type_check.

Formatter

TypeCheck exports a couple of macros that you might want to use without parentheses. To make mix format respect this setting, add import_deps: [:type_check] to your .formatter.exs file.