{"id":13491609,"url":"https://github.com/Qqwy/elixir-type_check","last_synced_at":"2025-03-28T08:33:20.012Z","repository":{"id":37923747,"uuid":"274214639","full_name":"Qqwy/elixir-type_check","owner":"Qqwy","description":"TypeCheck: Fast and flexible runtime type-checking for your Elixir projects.","archived":false,"fork":false,"pushed_at":"2023-07-04T22:04:03.000Z","size":1504,"stargazers_count":511,"open_issues_count":17,"forks_count":23,"subscribers_count":11,"default_branch":"main","last_synced_at":"2024-05-16T09:21:19.920Z","etag":null,"topics":["elixir-lang","hacktoberfest","metaprogramming","property-based-testing","type-checking"],"latest_commit_sha":null,"homepage":"","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Qqwy.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2020-06-22T18:34:22.000Z","updated_at":"2024-05-13T17:02:07.000Z","dependencies_parsed_at":"2024-01-13T17:05:03.480Z","dependency_job_id":"724c3aea-09d6-4bcf-bb0f-5f0639e4f067","html_url":"https://github.com/Qqwy/elixir-type_check","commit_stats":{"total_commits":699,"total_committers":19,"mean_commits":36.78947368421053,"dds":0.1688125894134478,"last_synced_commit":"969997fbd72083fe542206d04da38a6376b0a345"},"previous_names":[],"tags_count":12,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Qqwy%2Felixir-type_check","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Qqwy%2Felixir-type_check/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Qqwy%2Felixir-type_check/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Qqwy%2Felixir-type_check/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Qqwy","download_url":"https://codeload.github.com/Qqwy/elixir-type_check/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245377954,"owners_count":20605374,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["elixir-lang","hacktoberfest","metaprogramming","property-based-testing","type-checking"],"created_at":"2024-07-31T19:00:58.600Z","updated_at":"2025-03-28T08:33:19.964Z","avatar_url":"https://github.com/Qqwy.png","language":"Elixir","readme":"![](https://raw.githubusercontent.com/Qqwy/elixir-type_check/master/media/type_check_logo_flat.svg)\n\n# TypeCheck: Fast and flexible runtime type-checking for your Elixir projects.\n\n[![hex.pm version](https://img.shields.io/hexpm/v/type_check.svg)](https://hex.pm/packages/type_check)\n[![Documentation](https://img.shields.io/badge/hexdocs-latest-blue.svg)](https://hexdocs.pm/type_check/index.html)\n[![ci](https://github.com/Qqwy/elixir-type_check/actions/workflows/ci.yml/badge.svg)](https://github.com/Qqwy/elixir-type_check/actions/workflows/ci.yml)\n[![Coverage Status](https://coveralls.io/repos/github/Qqwy/elixir-type_check/badge.svg)](https://coveralls.io/github/Qqwy/elixir-type_check)\n\n## Core ideas\n\n- Type- and function specifications are constructed using (essentially) the **same syntax** as built-in Elixir Typespecs.\n- When a value does not match a type check, the user is shown **human-friendly error messages**.\n- Types and type-checks are generated at compiletime.\n  - This means **type-checking code is optimized** rigorously by the compiler.\n- **Property-checking generators** can be extracted from type specifications without extra work.\n  - Automatically create a **spectest** which checks for each function if it adheres to its spec.\n- Flexibility to add **custom checks**: Subparts of a type can be named, and 'type guards' can be specified to restrict what values are allowed to match that refer to these types.\n\n\nPrefer to watch a presentation instead of reading? See [\"TypeCheck: Effortless Runtime Type Checking\" - Marten Wijnja - _ElixirConf EU 2022_](https://www.youtube.com/watch?v=7ykfO2tBwYw).\n\n## Usage Example\n\nWe add `use TypeCheck` to a module \nand wherever we want to add runtime type-checks \nwe replace the normal calls to `@type` and `@spec` with `@type!` and `@spec!` respectively.\n\n```elixir\ndefmodule User do\n  use TypeCheck\n  defstruct [:name, :age]\n\n  @type! t :: %User{name: binary, age: integer}\nend\n\ndefmodule AgeCheck do\n  use TypeCheck\n\n  @spec! user_older_than?(User.t, integer) :: boolean\n  def user_older_than?(user, age) do\n    user.age \u003e= age\n  end\nend\n```\n\nNow we can try the following:\n\n```elixir\niex\u003e AgeCheck.user_older_than?(%User{name: \"Qqwy\", age: 11}, 10)\ntrue\niex\u003e AgeCheck.user_older_than?(%User{name: \"Qqwy\", age: 9}, 10)\nfalse\n```\n\nSo far so good. Now let's see what happens when we pass values that are incorrect:\n\n```elixir\niex\u003e AgeCheck.user_older_than?(\"foobar\", 42)\n** (TypeCheck.TypeError) At lib/type_check_example.ex:28:\nThe call to `user_older_than?/2` failed,\nbecause parameter no. 1 does not adhere to the spec `%User{age: integer(), name: binary()}`.\nRather, its value is: `\"foobar\"`.\nDetails:\n  The call `user_older_than?(\"foobar\", 42)` \n  does not adhere to spec `user_older_than?(%User{age: integer(), name: binary()},  integer()) :: boolean()`. Reason:\n    parameter no. 1:\n      `\"foobar\"` does not check against `%User{age: integer(), name: binary()}`. Reason:\n        `\"foobar\"` is not a map.\n    (type_check_example 0.1.0) lib/type_check_example.ex:28: AgeCheck.user_older_than?/2\n```\n\n```elixir\niex\u003e AgeCheck.user_older_than?(%User{name: nil, age: 11}, 10)\n** (TypeCheck.TypeError) At lib/type_check_example.ex:28:\nThe call to `user_older_than?/2` failed,\nbecause parameter no. 1 does not adhere to the spec `%User{age: integer(), name: binary()}`.\nRather, its value is: `%User{age: 11, name: nil}`.\nDetails:\n  The call `user_older_than?(%User{age: 11, name: nil}, 10)` \n  does not adhere to spec `user_older_than?(%User{age: integer(), name: binary()},  integer()) :: boolean()`. Reason:\n    parameter no. 1:\n      `%User{age: 11, name: nil}` does not check against `%User{age: integer(), name: binary()}`. Reason:\n        under key `:name`:\n          `nil` is not a binary.\n    (type_check_example 0.1.0) lib/type_check_example.ex:28: AgeCheck.user_older_than?/2\n```\n\n```elixir\niex\u003e AgeCheck.user_older_than?(%User{name: \"Aaron\", age: nil}, 10) \n** (TypeCheck.TypeError) At lib/type_check_example.ex:28:\nThe call to `user_older_than?/2` failed,\nbecause parameter no. 1 does not adhere to the spec `%User{age: integer(), name: binary()}`.\nRather, its value is: `%User{age: nil, name: \"Aaron\"}`.\nDetails:\n  The call `user_older_than?(%User{age: nil, name: \"Aaron\"}, 10)` \n  does not adhere to spec `user_older_than?(%User{age: integer(), name: binary()},  integer()) :: boolean()`. Reason:\n    parameter no. 1:\n      `%User{age: nil, name: \"Aaron\"}` does not check against `%User{age: integer(), name: binary()}`. Reason:\n        under key `:age`:\n          `nil` is not an integer.\n    (type_check_example 0.1.0) lib/type_check_example.ex:28: AgeCheck.user_older_than?/2\n```\n\n```elixir\n    \niex\u003e AgeCheck.user_older_than?(%User{name: \"José\", age: 11}, 10.0) \n** (TypeCheck.TypeError) At lib/type_check_example.ex:28:\nThe call to `user_older_than?/2` failed,\nbecause parameter no. 2 does not adhere to the spec `integer()`.\nRather, its value is: `10.0`.\nDetails:\n  The call `user_older_than?(%User{age: 11, name: \"José\"}, 10.0)` \n  does not adhere to spec `user_older_than?(%User{age: integer(), name: binary()},  integer()) :: boolean()`. Reason:\n    parameter no. 2:\n      `10.0` is not an integer.\n    (type_check_example 0.1.0) lib/type_check_example.ex:28: AgeCheck.user_older_than?/2\n```\n\nAnd if we were to introduce an error in the function definition:\n\n```elixir\ndefmodule AgeCheck do\n  use TypeCheck\n\n  @spec! user_older_than?(User.t, integer) :: boolean\n  def user_older_than?(user, age) do\n    user.age\n  end\nend\n```\n\nThen we get a nice error message explaining that problem as well:\n\n```elixir\n** (TypeCheck.TypeError) The call to `user_older_than?/2` failed,\nbecause the returned result does not adhere to the spec `boolean()`.\nRather, its value is: `26`.\nDetails:\n  The result of calling `user_older_than?(%User{age: 26, name: \"Marten\"}, 10)` \n  does not adhere to spec `user_older_than?(%User{age: integer(), name: binary()},  integer()) :: boolean()`. Reason:\n    Returned result:\n      `26` is not a boolean.\n    (type_check_example 0.1.0) lib/type_check_example.ex:28: AgeCheck.user_older_than?/2\n```\n\n## Features \u0026 Roadmap\n\n\n### Implemented\n\n- [x] Proof and implementation of the basic concept\n- [x] Custom type definitions (type, typep, opaque)\n  - [x] Basic\n  - [x] Parameterized\n- [x] Hide implementation of `opaque` from documentation\n- [x] Spec argument types checking\n- [x] Spec return type checking\n- [x] Spec possibly named arguments\n- [x] Implementation of Elixir's builtin types\n  - [x] Primitive types\n  - [x] More primitive types\n  - [x] Compound types\n  - [x] special forms like `|`, `a..b` etc.\n  - [x] Literal lists\n  - [x] Maps with keys =\u003e types\n  - [x] Structs with keys =\u003e types\n  - [x] More map/list-based structures.\n  - [x] Bitstring type syntax (`\u003c\u003c\u003e\u003e`, `\u003c\u003c_ :: size\u003e\u003e`, `\u003c\u003c_ :: _ * unit\u003e\u003e`, `\u003c\u003c_ :: size, _ :: _ * unit\u003e\u003e`)\n- [x] A `when` to add guards to typedefs for more power.\n- [x] Make errors raised when types do not match humanly readable\n  - [x] Improve readability of spec-errors by repeating spec and which parameter did not match.\n- [x] Creating generators from types\n- [x] Don't warn on zero-arity types used without parentheses.\n- [x] Hide structure of `opaque` and `typep` from documentation\n- [x] Make sure to handle recursive (and mutually recursive) types without hanging.\n  - [x] A compile-error is raised when a type is expanded more than a million times\n  - [x] A macro called `lazy` is introduced to allow to defer type expansion to runtime (to _within_ the check).\n- [x] the Elixir formatter likes the way types+specs are constructed\n- [x] A type `impl(ProtocolName)` to work with 'any type implementing protocol `Protocolname`'.\n  - [x] Type checks.\n  - [x] StreamData generator.\n- [x] High code-coverage to ensure stability of implementation.\n- [x] Make sure we handle most (if not all) of Typespec's primitive types and syntax. (With the exception of functions and binary pattern matching)\n- [x] Option to turn `@type/@opaque/@typep`-injection off for the cases in which it generates improper results.\n- [x] Manually overriding generators for user-specified types if so desired.\n- [x] Creating generators from specs\n  - [x] Wrap spec-generators so you have a single statement to call in the test suite which will prop-test your function against all allowed inputs/outputs.\n- [x] Option to turn the generation of runtime checks off for a given module in a particular environment (`enable_runtime_checks`).\n- [x] Support for function-types (for typechecks as well as property-testing generators):\n  - `(-\u003e result_type)`\n  - `(...-\u003e result_type)`\n  - `(param_type, param2_type -\u003e result_type)`\n- [x] Basic support for maps with a single `required(type)` or `optional(type)`.\n- [x] Overrides for builtin remote types (`String.t`,`Enum.t`, `Range.t`, `MapSet.t` etc.) **(75% done)** [Details](https://hexdocs.pm/type_check/comparing-typecheck-and-elixir-typespecs.html#elixir-standard-library-types)\n- [x] Overrides for more builtin remote types\n- [x] Support for maps with mixed `required(type)` and `optional(type)` syntaxes.\n- [x] Configurable setting to turn checks on/off at compile-time, on a per-OTP-app basis (so you have control over your dependencies) as well as your individual modules.\n- [x] Hide named types from opaque types.\n- [x] A way to define structs and their field types at the same time.\n- [x] Finalize formatter specification and make a generator for this so that people can easily test their own formatters.\n\n### Pre-stable\n\n\n### Longer-term future ideas\n\n- [ ] Per-module or even per-spec settings to turn on/off, configure formatter, etc.\n\n\n## Installation\n\nTypeCheck [is available in Hex](https://hex.pm/docs/publish). The package can be installed\nby adding `type_check` to your list of dependencies in `mix.exs`:\n\n```elixir\ndef deps do\n  [\n    {:type_check, \"~\u003e 0.13.3\"},\n    # To allow spectesting and property-testing data generators (optional):\n    {:stream_data, \"~\u003e 0.5.0\", only: :test}, \n  ]\nend\n```\n\nThe documentation can be found at [https://hexdocs.pm/type_check](https://hexdocs.pm/type_check).\n\n\n### Formatter\n\nTypeCheck 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.\n\n## Changelog\n\nThe full changelog can be found [here](https://github.com/Qqwy/elixir-type_check/blob/main/CHANGELOG.md)\n\n## TypeCheck compared to other tools\n\nTypeCheck is by no means the other solution out there to reduce the number of bugs in your code.\n\n### Elixir's builtin typespecs and Dialyzer\n\n[Elixir's builtin type-specifications](https://hexdocs.pm/elixir/typespecs.html) use the same syntax as TypeCheck.\nThey are however not used by the compiler or the runtime, and therefore mainly exist to improve your documentation.\n\nBesides documentation, extra external tools like [Dialyzer](http://erlang.org/doc/apps/dialyzer/dialyzer_chapter.html) can be used to perform static analysis of the types used in your application.\n\nDialyzer is an opt-in static analysis tool. This means that it can point out some inconsistencies or bugs, but because of its opt-in nature, there are also many problems it cannot detect, and it requires your dependencies to have written all of their typespecs correctly.\n\nDialyzer is also (unfortunately) infamous for its at times difficult-to-understand error messages.\n\nAn advantage that Dialyzer has over TypeCheck is that its checking is done without having to execute your program code (thus not having any effect on the runtime behaviour or efficiency of your projects).\n\nBecause TypeCheck adds `@type`, `@typep`, `@opaque` and `@spec`-attributes based on the types that are defined, it is possible to use Dialyzer together with TypeCheck.\n\n### Norm\n\n[Norm](https://github.com/keathley/norm/) is an Elixir library for specifying the structure of data that can be used for both validation and data-generation.\n\nOn a superficial level, Norm and TypeCheck seem similar. However, there are [important differences in their design considerations](https://github.com/Qqwy/elixir-type_check/blob/master/Comparing%20TypeCheck%20and%20Norm.md).\n\n\n## Is it any good?\n\n[yes](https://news.ycombinator.com/item?id=3067434)\n","funding_links":[],"categories":["Elixir","[🎓 research](https://github.com/stars/ketsapiwiq/lists/research)"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FQqwy%2Felixir-type_check","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FQqwy%2Felixir-type_check","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FQqwy%2Felixir-type_check/lists"}