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.14.0] - 2026-05-24

0.14.0 adds Bond.PropertyTest — a property-based testing layer that uses Bond's contracts as the oracle. The hard part of PBT is usually writing the predicate that distinguishes right from wrong outputs; contracts already supply that at every call site. PBT just feeds random inputs through already-instrumented code.

Added

  • Bond.PropertyTest.contract_holds/2 — single macro, two forms:

    • Form 1 (single function). contract_holds &Mod.fn/N, args: [gen0, ...] expands to a property block that calls the function with random arguments and lets Bond's runtime contracts fail the property on any violation. StreamData shrinks to the minimal counterexample.

    • Form 2 (module sequence). contract_holds Module, constructors:, transformers:, observers: expands to a property block that generates random sequences of operations over a struct module and runs them. State is threaded through transformers; observers don't advance state but the pre-invariant still fires. The module's @invariants are the oracle. Supports %Mod{} and {:ok, %Mod{}} return shapes; {:error, _} terminates the sequence cleanly. Common option :name overrides the auto-generated property description.

    The macro dispatches by first-arg AST shape (function reference vs module alias).

  • use Bond.PropertyTest — brings in ExUnitProperties and imports the contract_holds macro. Raises a CompileError at the use site with installation instructions if :stream_data isn't available.

  • Bond.PropertyTest.Sequence — internal helper module owning the sequence generator and runner used by Form 2.

  • New FAQ entry: "How does Bond compose with StreamData / property-based testing?".

Changed

  • :stream_data moves from only: [:dev, :test] to a regular dep with optional: true. Users who want PBT now add {:stream_data, "~> 0.6"} to their own deps; users who don't pay no cost.

Requirements

  • Unchanged. Elixir ~> 1.14.

[0.13.0] - 2026-05-23

0.13.0 adds @invariant declarations for struct modules — module-scoped properties that hold across every public function in the struct's defining module. Where @pre/@post constrain a single function call, @invariant constrains the struct itself.

Added

  • @invariant <name>, <kw_or_expression> annotation. Same shape as @pre/@post: a labelled keyword-list of assertions, or a single unlabelled expression. The first argument is the variable name the expression refers to (e.g. stack in @invariant stack, length(stack.items) <= stack.capacity).

    Invariants are checked at the boundaries of every public function in the module:

    • On entry, when the function head pattern-matches %__MODULE__{} = name or has an is_struct(name, __MODULE__) guard.
    • On exit, against the return value if it's %__MODULE__{} or {:ok, %__MODULE__{}}. Other return shapes fall through with no check.
    • Never for defp — private functions are exempt by the Eiffel convention (they often hold transiently-invalid state).

    When a function destructures %__MODULE__{...} in its head without binding the whole struct to a variable, Bond emits a compile-time warning suggesting %__MODULE__{...} = name to enable the pre-check.

  • Bond.InvariantError — new exception parallel to PreconditionError/PostconditionError/CheckError. Raised on invariant violation; carries the same metadata shape.

  • Bond.Test.assert_invariant_violation/2 — ExUnit helper mirroring the existing pre/post/check helpers.

  • :invariants conditional-compilation key. Joins :preconditions, :postconditions, and :checks. Same true | false | :purge value space; same runtime toggleability via Application.put_env/3; same :overrides and use Bond, invariants: … support.

  • Bond.Compiler.Invariants — new internal module owning the invariant emission logic (struct-arg detection, pre-/post-invariant call sites, the lifted invariants defp). Kept separate from Bond.Compiler.AnnotatedFunction for separation of concerns and to avoid parallel-compile scheduling issues with the larger combined file.

Changed

  • [:bond, :assertion, :failure] telemetry events now also fire for invariant violations, with :kind => :invariant in the metadata. No subscriber changes are needed — existing handlers attached to the event automatically pick up the new kind.

  • The internal Bond.Compiler.Assertion struct gains a :binding_name field, populated only on :invariant assertions from the declaration's first argument.

  • Bond.Compiler.AnnotatedFunction gains an :invariants field plus put_invariants/2 and has_invariants?/1 helpers. override?/1 widens to emit overrides for public functions in modules with @invariants, even when the function has no per-function @pre/@post.

  • Bond.Compiler.CompileStateFSM tracks module-scoped invariants alongside the per-function preconditions/postconditions. Invariants don't transition the FSM into :contracts_pending (they don't attach to a "next function") and aren't flushed by function definitions.

Requirements

  • Unchanged. Elixir ~> 1.14.

[0.12.0] - 2026-05-22

0.12.0 lands two internal-shape changes that compose on top of the 0.11.0 conditional-compilation work: contract closures move out of override clauses into named private functions on the user's module (reducing injected code per contract'd function), and :telemetry events fire on assertion failures.

Added

  • [:bond, :assertion, :failure] telemetry event. Fires once per contract violation — @pre, @post, or check — immediately before the corresponding Bond.PreconditionError / Bond.PostconditionError / Bond.CheckError is raised. Single event family for all three kinds; consumers filter on the :kind metadata. Measurements carry :system_time and :monotonic_time; metadata carries :kind, :module, :function, :label, :expression, :assertion_id, :file, :line, and :binding. See the new "Telemetry" section in the Bond moduledoc / README. {:telemetry, "~> 1.0"} is now a regular dependency.

  • Bond.Runtime.Eval.should_evaluate?/2 — internal helper that performs the Application.get_env/3 runtime guard. Used by the emission shape (see "Internal" below) to avoid allocating the assertion-evaluation closure when the runtime guard says skip.

Changed

  • Per-function assertion closures are lifted into named defps on the using module: __bond_preconditions__<fun>__<arity> and __bond_postconditions__<fun>__<arity>. The override clause itself is now a small wrapper that calls these via Bond.Runtime.Eval.evaluate_preconditions/1 / evaluate_postconditions/1. The big inline assertion-evaluation AST that used to be re-emitted into every override is gone; the BEAM carries one tiny override + one defp per non-purged kind, rather than the whole eval body inlined per function.

  • Runtime guard moved into Bond.Runtime.Eval. The override calls should_evaluate?(:preconditions, <compile_time_mode>) and only builds the assertion-evaluation closure when that returns true. The Application.get_env/3 lookup logic lives entirely in Bond.Runtime rather than being inlined at every contract'd function.

  • Bond.check/1,2 routes through the same throw/catch path as @pre/@post. All three kinds now produce {:assertion_failure, info} throws caught by Bond.Runtime.Eval, which fires the telemetry event and raises. This unifies the plumbing across the three kinds; previously check raised inline.

  • Stacktrace pruning now also filters frames whose function name starts with __bond_ (the lifted defps), so failures continue to point at the user's call site rather than into Bond-generated plumbing.

  • Benchmark on the project fixture (bench/runtime_check_overhead.exs, trivial @pre is_number(x) in a tight loop):

    mode0.11.00.12.0
    :purge~48 ns~34 ns
    true~155 ns~143 ns
    false~89 ns~91 ns

    The true path improves because the override no longer re-emits the full assertion-eval AST inline. The false (runtime-skip) path is flat within noise — should_evaluate?/2 short-circuits before the closure is allocated.

Fixed

  • Bond.CheckError's message/1 no longer crashes when the error's :function metadata is missing (regression introduced and fixed internally during the check plumbing unification).

Requirements

  • Unchanged. Elixir ~> 1.14.

[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.