Skip to content

Shrinkable ​

Rasuvaeff\PropertyTesting\Shrinkable

Class — Package: property-testing-core — Source — Version: v1.0.0-1-g88fdee2

Type parameters:

  • TValue of mixed = mixed The type of the carried value

A generated value together with a lazy tree of progressively "smaller" variants of it — the unit of integrated shrinking.

Every ArbitraryInterface::generate() call returns one of these. The property runner reads $value, and when the property fails it walks shrinks(): each child is a smaller candidate that carries its own subtree, so accepting a candidate immediately provides the next round of even smaller candidates. Because the tree is built at generation time, a transformed arbitrary (Gen::map(), Gen::flatMap()) shrinks in the source domain and re-applies the transformation — no inverse function is ever needed.

Children are produced lazily (the closure runs only when the runner asks), so building a node costs nothing until shrinking actually happens.

Constructor ​

php
__construct(
    \TValue $value,
    callable $shrinks,
)
ParameterTypeDefaultDescription
$valueTValuerequired
$shrinkscallablerequired

Methods ​

leaf() ​

php
static leaf(\T $value): self

A value with no smaller variants (terminal node).

of() ​

php
static of(\T $value, callable $shrinks): self

A value with lazily-computed smaller variants, ordered most aggressive first (typically toward a zero/empty/identity element).

shrinks() ​

php
shrinks(): iterable

The smaller variants of this value, each with its own subtree.

map() ​

php
map(callable $map): self

Transform the whole tree through a pure function: the value and, lazily, every shrink candidate. This is what makes Gen::map() shrink.

A candidate the transformation refuses — it throws an Exception for the smaller source value, the way a validating constructor does — is not a smaller value, it is no value at all: the candidate is skipped together with its subtree and the enumeration moves on to the next sibling. Only exceptions are treated as a refusal; an Error (a TypeError above all) says the transformation itself is broken and propagates, so a bug does not silently empty a value space.

The root value is transformed eagerly, and an exception there propagates from this call: the value being mapped was generated, not proposed as a smaller variant, so there is nothing to skip to.