Skip to content

Version 0.10.0

Compare
Choose a tag to compare
@Qqwy Qqwy released this 17 Oct 19:39

Version 0.10.0 has been released! Ⲗ

Additions & Improvements

Support for function-types (for typechecks as well as property-testing generators):

  • (-> result_type)
  • (...-> result_type)
  • (param_type, param2_type -> result_type)

Type-checking function-types

Type-checking a function value against a function-type works a bit differently from most other types.
The reason for this is that we can only ascertain whether the function-value works correctly when the function-value is called.

Specifically:

  • When a call to TypeCheck.conforms/3 (and variants) or a function wrapped with a @spec! is called, we can immediately check whether a particular parameter:
    • is a function
    • accepts the expected arity
  • Then, the parameter-which-is-a-function is wrapped in a 'wrapper function' which, when called:
    • typechecks whether the passed parameters are of the expected types (This checks whether your function uses the parameter-function correctly.)
    • calls the original function with the parameters.
    • typechecks whether the result is of the expected type. (This checks whether the parameter-function works correctly.)
    • returns the result.

In other words, the 'wrapper function' which is added for a type (param_type, param_type2 -> result_type) works similarly
to a named function with the spec @spec! myfunction(param_type, param_type2) :: result_type.

As an example:

      iex> # The following passes the first check...
      iex> fun = TypeCheck.conforms!(&div/2, (integer(), integer() -> boolean()))
      iex> # ... but once the function returns, the wrapper will raise
      iex> fun.(20, 5)
      ** (TypeCheck.TypeError) The call to `#Function<...>/2` failed,
          because the returned result does not adhere to the spec `boolean()`.
          Rather, its value is: `4`.
          Details:
            The result of calling `#Function<...>.(20, 5)`
            does not adhere to spec `(integer(), integer() -> boolean())`. Reason:
              Returned result:
                `4` is not a boolean.

This was quite the adventure to implement. I am happy that it turned out to be possible, and it is working great!

Data generation for function types

For property-testing generators, the data passed to a generated function is converted into a seed (using [param1, param2, param3] |> :erlang.term_to_binary |> Murmur.hash_x86_32) and this seed is then used as seed for the data returned from the function.
This means that for any particular test run, any generated function will be pure (i.e. when given the same input multiple times, the same output will be returned).

Fixes

  • Wrapping private functions no longer make the function public. (c.f. #64)
  • Wrapping macros now works correctly. (also related to #64)
  • Using __MODULE__ inside a struct inside a type now expands correctly. (c.f. #66)