Skip to content

Releases: Qqwy/elixir-type_check

v0.12.0

06 Jun 20:20
Compare
Choose a tag to compare

Additions:

  • The default options used are now fetched from the application configuration. This means that you can configure a default for your app as well as for each of your dependencies(!) by adding config :app_name, :type_check [...] to your configuration file(s). (c.f. #61)
  • TypeCheck.External module, with functions to work with typespecs in modules outside of your control. Thank you very much, @orsinium! (c.f. #113)
    • fetch_spec to build a TypeCheck type from any function that has a @spec.
    • fetch_type to build a TypeCheck type from any @type.
    • enforce_spec! to wrap a call to any function that has a @spec with a runtime type-check on the input parameters and return value.
    • apply and apply! to wrap a call to any function with the function spec type that you give it.
  • TypeCheck.Defstruct.defstruct!, a way to combine defstruct, @enforce_keys and the creation of the struct's type, reducing boilerplate and the possibility of mistakes. (c.f. #118 )

Fixes:

  • Long-standing issue where Dialyzer would sometimes complain in apps using TypeCheck is resolved. (c.f. #95)
  • Creation of the new maybe_nonempty_list type will no longer get stuck in an infinite loop on creation. (c.f. #120)

v0.11.0

29 May 18:04
Compare
Choose a tag to compare

Wooh, this is a big release!

Most important features:

  • We now support all of Elixir's builtin basic types!
  • We now support all of the remote types of the Elixir standard library!
  • Support for most of the map-type syntactic sugars.
  • An optional Credo check to enforce that all your functions have a spec.

Full changelog

Additions

  • Support for fancier map syntaxes:
    • %{required(key_type) => value_type} Maps with a single kind of required key-value type.
    • %{optional(key_type) => value_type} Maps with a single kind of optional key-value type.
    • %{:some => a(), :fixed => b(), :keys => c(), optional(atom()) => any()} Maps with any number of fixed keys and a single optional key-value type.
    • TypeCheck now supports nearly all kinds of map types that see use. Archaic combinations of optional and required are not supported, but also not very useful types in practice.
    • Because of this, the inspection of the builtin type map(key, value) has been changed to look the same as an optional map. This is a minor backwards-incompatible change.
  • Desugaring %{} has changed from 'any map' to 'the empty map' in line with Elixir's Typespecs. This is a minor backwards-incompatible change.
  • Support for the builtin types port(), reference() and (based on these) identifier().
  • Support for the builtin type struct().
  • Support for the builtin type timeout().
  • Support for the builtin type nonempty_charlist() and maybe_improper_list and (based on these) iolist() and iodata().
  • Adding types depending on these builtins to the default type overrides. We now support all modules of the full standard library!
  • TypeCheck.Credo.Check.Readability.Specs: an opt-in alternative Credo check which will check whether all functions have either a @spec! or 'normal' @spec. (Fixes #102).

Fixes

  • The TypeCheck.Builtin module is now actually spectested itself. Some consistency bugs were found and solved as a result.

v0.10.7

12 Feb 19:24
Compare
Choose a tag to compare

Fixes:

  • Ensures fixed-maps are checked for superfluous keys (c.f. #96). Thank you very much, @spatrik!

v0.10.4

20 Nov 12:10
Compare
Choose a tag to compare

Fixes

  • Fixes issue where sometimes results of specs returning a struct would remove some or all struct fields. (c.f. #78 and PR #82 )
  • Related: Ensures that when a @type! containing a %__MODULE__{} is used before that module's defstruct, a clear CompileError is created (c.f. #83).

v0.10.3

30 Oct 13:04
feebe3f
Compare
Choose a tag to compare

Fixes

Ensures calls to def/defp/defmacro/defmacrop are always qualified

(i.e. prefixed with 'Kernel.'), so they will also work in defprotocol
modules or other places where the normal versions of these macros are
hidden/overridden.

This fixes a bug reported on the Elixir forums by user 'belteconti'.

(c.f. PR #75 )

v0.10.2

26 Oct 09:24
Compare
Choose a tag to compare

Fixes:

  • Fixes issue where FixedMaps would accept maps any maps (even those missing the required keys) sometimes. (c.f. #74)

Version 0.10.0

17 Oct 19:39
Compare
Choose a tag to compare

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)

Version 0.9.0

09 Oct 20:50
Compare
Choose a tag to compare

Version 0.9.0 has been released! 😊

Additions & Improvements

Support for bitstring literal types.

The types bitstring(), binary() and String.t() were already supported.
However, support has now been added for the types:

  • <<>> (matching an empty bitstring),
  • <<_ :: size>> (matching a bitstring of exactly size bits long),
  • <<_ :: _ * unit>> (matching any bitstring of x * unit where x :: pos_integer()),
  • <<_ :: size, _ :: _ * unit>> (matching any bitstring of size + x * unit where x :: pos_integer()).

Property-testing generators have also been constructed for them, so you can immediatly start using them with spectests.

Version 0.8.1

04 Oct 08:42
Compare
Choose a tag to compare

Fixes

  • Improved documentation with a page comparing TypeCheck with Elixir's plain typespecs. c.f. #59
  • Addition of missing override for the type Range.t(). c.f. #58

Thank you very much, @baldwindavid 💚 !

Version 0.8.0

04 Oct 08:40
Compare
Choose a tag to compare

Version 0.8.0 has been released! 🚢

Additions & Improvements

  • Pretty-printing of types and TypeError output in multiple colors:

image

(This is the 'Rating' example from the Type-checking and spec-testing with TypeCheck article).
Types and values are pretty-printed in colour, similarly to how this is normally done in IEx.

  • Nicer indentation of errors. Amongst other things, this means that error-highlighting in the documentation now works correctly (although in 100% 'red').
  • use TypeCheck now also calls require TypeCheck.Type so there no longer is a need to call this manually if you want to e.g. use TypeCheck.Type.build/1 (which is rather common if you want to test out particular types quickly in IEx).
  • named types are now printed in abbreviated fashion if they are repeated multiple times in an error message. This makes a nested error message much easier to read, especially for larger specs.
  • Remote/user-defined types are now also 'named types' which profit from this change.
    For instance in above example picture, we first talk about Rating.t() and String.t() and only when looking at the problem in detail do we expand this to %Rating{} and binary().
  • [type] no longer creates a fixed_list(type) but instead a list(type) (just as Elixir's own typespecs.)
  • Support for [...] and [type, ...]as alias for nonempty_list() and nonempty_list(type) respectively.

Fixes

  • Fixes prettyprinting of TypeCheck.Builtin.Range.
  • Remove support for list literals with multiple elements.
  • Improved documentation. c.f. #56

Thank you very much, @baldwindavid and @0urobor0s !