View Source Changelog

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog and this project adheres to Semantic Versioning.

[0.11.0] - 2026-05-21

0.11.0 reshapes the conditional-compilation config introduced in 0.10.0 around a new value space — true | false | :purge per kind — and adds two new features that compose on top of it: runtime toggling without recompilation, and per-module overrides.

Breaking changes (minor)

  • config :bond, <kind>: false no longer compiles contracts out. It now means "compiled in, runtime guard defaults to off." If you used false in 0.10.0 to get zero-overhead behaviour, change it to :purge to preserve that behaviour. true continues to work as before (with the addition of runtime toggleability — see below).

Added

  • :purge mode for each contract kind. Setting any of :preconditions, :postconditions, or :checks to :purge causes Bond to emit no code for that kind. The resulting BEAM contains no contract logic; per-call overhead is zero. Contract documentation for that kind is also suppressed.

  • Runtime toggling. When a kind is compiled with true or false, the emitted override carries a runtime guard: Application.get_env(:bond, <kind>, <compile_time_value>). The contract is evaluated unless the runtime value is exactly false. Operators can flip contracts on or off via Application.put_env/3 from a remote console — no recompilation needed. The compile-time value sets the default for the runtime guard.

    Benchmark on the project fixture (bench/runtime_check_overhead.exs, trivial @pre is_number(x) in a tight loop): :purge ~48 ns/call, false ~89 ns/call (~40 ns guard overhead), true ~155 ns/call (guard plus assertion eval).

  • :overrides config for per-module rules. A list of {Module | Regex, opts} tuples. Module-atom keys match exactly; Regex keys match against the source-visible module name (no Elixir. prefix). Use this to opt specific modules in or out of contract compilation without touching their source. Example:

    config :bond,
      preconditions: true,
      overrides: [
        {MyApp.HotPath, preconditions: :purge, postconditions: :purge},
        {~r/Workers\\./, postconditions: false}
      ]
  • use Bond, opts per-module options. Pass any of :preconditions, :postconditions, :checks directly at the use site to override global and :overrides settings for that module.

    defmodule MyApp.HotPath do
      use Bond, preconditions: :purge, postconditions: :purge
    end

    Precedence: use Bond opts > exact-atom :overrides match > first Regex :overrides match > global config.

  • Bond.Compiler.resolve_config/3 — internal helper exposed for testing that combines global config, :overrides, and use Bond opts into the final per-module mode map.

Changed

  • Bond.Compiler.AnnotatedFunction.apply_contract/2 now expects each kind in the config map to be true | false | :purge rather than a boolean. The function returns nil when both kinds resolve to :purge; in all other cases it emits the override with the appropriate runtime guards.

  • Bond.check/1,2 now expands to a runtime-guarded call when the resolved :checks mode is true or false, and to :ok (a compile-time no-op) when the mode is :purge.

Requirements

  • Unchanged. Elixir ~> 1.14.

[0.10.0] - 2026-05-21

The headline feature of 0.10.0 is conditional compilation of contracts. You can now compile some or all of your contracts out entirely via application config, with zero per-call overhead for disabled contracts. The release also adds an ExUnit helper module, polishes error reporting, and substantially rewrites the user-facing documentation.

Added

  • Conditional compilation via :bond application config. Three keys, read at compile time via Application.compile_env/3:

    • :preconditions (default true) — when false, no precondition evaluation is emitted in override clauses, and the auto-generated #### Preconditions doc section is omitted.
    • :postconditions (default true) — same for postconditions.
    • :checks (default true) — when false, every check/1,2 macro call in modules that use Bond expands to :ok and the wrapped expression is not evaluated. (Don't put side effects inside check.)

    When both :preconditions and :postconditions are disabled for a function, Bond emits no override at all. The function runs exactly as written, with zero per-call overhead. The function's auto-generated contract docs are also suppressed in that case.

    See the new "Conditional compilation" section in the Bond moduledoc.

  • Bond.Test module with assert_precondition_violation/2, assert_postcondition_violation/2, and assert_check_violation/2 macros for testing contract violations in ExUnit. Field expectations (:label, :expression, etc.) can be exact values or Regex patterns.

  • New guides/faq.md answering the questions that come up most: why contracts when I have ExUnit, will contracts slow down prod, how does Bond compare to Norm, what does Bond do that typespecs don't, the Assertion Evaluation rule, default-arg behaviour, multi-clause handling.

Changed

  • Assertion failure messages pretty-print the captured binding/0 with inspect/2 ... pretty: true, limit: 20, printable_limit: 200, width: 80, so small bindings stay compact and large structs no longer dominate the failure output.

  • Stack traces of raised assertion exceptions are pruned to omit Bond.* frames. Failures point at the user's call site rather than into Bond.Runtime.Eval.

  • Bond moduledoc / README restructured. Leads with a five-line Account.withdraw example and a one-paragraph elevator pitch. The Wikipedia quote moves out. Assertion syntax recommends the keyword-list form as primary. New Conditional compilation section. The Math.sqrt example remains as the "showing everything" sample.

  • guides/getting-started.md expanded into a step-by-step walkthrough: first @pre, postcondition with result, labelled assertions, predicates, old expressions, inline checks, disabling in prod, and ExUnit integration.

Internal

  • New private function in Bond.Runtime.Eval that prunes Bond frames from the captured stack trace before raising.
  • Bond.Compiler.AnnotatedFunction.apply_contract/1 is now apply_contract/2 taking a contract_config map. The __before_compile__/1 callback reads the config from a @__bond_contract_config__ module attribute set by Bond's __using__/1.

Requirements

  • Unchanged. Elixir ~> 1.14.

[0.9.1] - 2026-05-21

A patch release covering documentation cleanup left over from the 0.9.0 refactor plus a handful of usability improvements.

Added

  • .formatter.exs is now published with the Hex package and declares locals_without_parens for check/1, check/2, and old/1. Downstream projects can pick these up with import_deps: [:bond] in their own .formatter.exs.
  • Assertion-failure messages now include an at: <file>:<line> line so the source location is clickable in editors.

Changed

  • binding/0 captured in assertion-failure info is sorted by name so failure messages are reproducible across runs.

Fixed

  • README/moduledoc no longer references the removed Bond.def/2 and Bond.defp/2 macros, eliminating mix docs cross-reference warnings.
  • The getting-started guide installation hint now references the current version.
  • CHANGELOG no longer auto-links the removed define_function_with_contract/4 helper.

Internal

[0.9.0] - 2026-05-21

This release is a large internal refactor with no breaking changes to the public API. @pre, @post, and check/1,2 all behave the same as in 0.8.x.

Changed

  • Bond no longer overrides Kernel.def/2 and Kernel.defp/2. Contracts are now applied via Elixir compiler hooks (@on_definition, @before_compile, @after_compile). This makes Bond more robust against changes in Elixir's macro expansion semantics, eliminates a class of macro-hygiene issues, and plays nicer with other macros that produce function definitions.
  • Multi-clause functions are now wrapped by a single override clause that delegates to super/1 rather than having contract logic inlined into each clause. Elixir's normal pattern matching handles dispatch inside the super call.
  • Assertion failures are signalled by a throw / catch instead of being raised inline. Each @pre/@post group compiles to an anonymous function that throws {:assertion_failure, info} on the first failure; Bond.Runtime.Eval catches it and raises the appropriate exception type.
  • Functions with contracts now get auto-generated Preconditions and Postconditions sections in their documentation even if the user did not attach a @doc themselves. Previously contract documentation was only emitted when a @doc was present.
  • Internal modules are reorganised into Bond.Compiler.* (compile-time) and Bond.Runtime.* (run-time) namespaces.

Internal

Requirements

  • Unchanged. Elixir ~> 1.14.

[0.8.3] - 2024-11-08

Released before this changelog was established. See the git history for details.