1/**
2 * Derives, samples, and checks generated values from Effect Schema.
3 *
4 * @stability unstable
5 * @since 4.0.0
6 */
7import * as Cause from "./Cause.ts"
8import type * as Effect from "./Effect.ts"
9import type * as Filter from "./Filter.ts"
10import * as Formatter from "./Formatter.ts"
11import { dual } from "./Function.ts"
12import type * as Model from "./internal/arbitrary/model.ts"
13import * as Internal from "./internal/arbitrary/runner.ts"
14import type { Pipeable } from "./Pipeable.ts"
15import { hasProperty, type Predicate, type Refinement } from "./Predicate.ts"
16import type * as Schema_ from "./Schema.ts"
17import type * as Types from "./Types.ts"
18
19/**
20 * Runtime type identifier for `Arbitrary` values.
21 *
22 * @stability unstable
23 * @category type IDs
24 * @since 4.0.0
25 */
26export const TypeId: TypeId = Internal.TypeId
27
28/**
29 * Type of the runtime identifier for `Arbitrary` values.
30 *
31 * @stability unstable
32 * @category type IDs
33 * @since 4.0.0
34 */
35export type TypeId = "~effect/arbitrary/Arbitrary"
36
37/**
38 * Represents a pure description of values that can be generated and shrunk.
39 *
40 * **When to use**
41 *
42 * Use as the result of {@link schema}, {@link Constant}, and composition, and as the input to {@link sampleEffect} or
43 * {@link checkEffect}.
44 *
45 * **Details**
46 *
47 * Arbitraries implement `Pipeable`, so data-last combinators can be composed with `.pipe(...)`.
48 *
49 * @stability unstable
50 * @category models
51 * @since 4.0.0
52 */
53export interface Arbitrary<out A> extends Pipeable {
54 readonly [TypeId]: TypeId
55 readonly "~A": Types.Covariant<A>
56 /**
57 * @internal
58 */
59 readonly gen: Model.Generator<A>
60}
61
62/**
63 * Configures Schema-derived generation.
64 *
65 * **Details**
66 *
67 * `shrink` returns the immediate semantic simplifications of a failing value. Each returned candidate is validated
68 * against the decoded side of the original Schema before it can reach the property.
69 *
70 * @stability unstable
71 * @category models
72 * @since 4.0.0
73 */
74export interface SchemaOptions<A> {
75 readonly shrink?: ((value: A) => ReadonlyArray<A>) | undefined
76}
77
78/**
79 * Configures the lengths generated and preserved during shrinking by {@link array}.
80 *
81 * **Details**
82 *
83 * `minLength` defaults to zero. Generation grows with `size`, honors `minLength` even at size zero, and is capped by
84 * `maxLength` when provided. Both bounds must be integers between zero and 4294967295, with `minLength <= maxLength`.
85 *
86 * @stability unstable
87 * @category models
88 * @since 4.0.0
89 */
90export interface ArrayOptions {
91 readonly minLength?: number | undefined
92 readonly maxLength?: number | undefined
93}
94
95/**
96 * Checks whether a value is an `Arbitrary`.
97 *
98 * **When to use**
99 *
100 * Use when accepting both Arbitrary values and other input descriptions.
101 *
102 * @stability unstable
103 * @category guards
104 * @since 4.0.0
105 */
106export const isArbitrary = (u: unknown): u is Arbitrary<unknown> => hasProperty(u, TypeId)
107
108/**
109 * Configures direct sampling from an `Arbitrary`.
110 *
111 * **Details**
112 *
113 * `size` is a local complexity scale, not a global bound on the complete value. Each unconstrained string, collection,
114 * or object property observes the same size independently, while recursive branches share one recursion allowance.
115 * Explicit Schema minima and required members are still honored, while explicit maxima clamp generation.
116 *
117 * @stability unstable
118 * @category models
119 * @since 4.0.0
120 */
121export interface SampleOptions {
122 readonly count?: number | undefined
123 readonly size?: number | undefined
124 readonly maxDiscards?: number | undefined
125 readonly seed?: string | number | undefined
126}
127
128/**
129 * Describes sampling exhaustion before the requested number of values was generated.
130 *
131 * **Details**
132 *
133 * The effective `seed` can be passed to {@link sampleEffect} to reproduce the exhausted run, including when sampling
134 * originally selected a seed from the Effect `Random` service.
135 *
136 * @stability unstable
137 * @category errors
138 * @since 4.0.0
139 */
140export interface SampleError {
141 readonly _tag: "SampleError"
142 readonly generated: number
143 readonly discards: number
144 readonly seed: string | number
145}
146
147/**
148 * Opaque string token that replays a falsification and its complete shrink path.
149 *
150 * **When to use**
151 *
152 * Use with {@link CheckOptions.replay} to copy, store, and reproduce a `Falsified` result from the same
153 * implementation.
154 *
155 * **Details**
156 *
157 * The token records whether the property returned `false` or failed its Effect, but does not record a typed error value
158 * or input fingerprint.
159 *
160 * **Gotchas**
161 *
162 * Replay compatibility is not guaranteed across releases of this unstable module.
163 *
164 * @stability unstable
165 * @category models
166 * @since 4.0.0
167 */
168export type Replay = string
169
170/**
171 * Configures property checking, shrinking, and replay.
172 *
173 * **Details**
174 *
175 * `size` is the maximum local complexity scale. Checking starts with smaller values and grows to that size according
176 * to completed runs; discarded attempts do not advance the progression. A single-run check uses the configured size.
177 * Each unconstrained string, collection, or object property observes the current size independently. Recursive
178 * branches instead consume one shared recursion allowance. Explicit Schema bounds and required members still apply.
179 *
180 * `maxShrinks` bounds the number of shrink candidates inspected after the initial failure. Candidates rejected by a
181 * Schema check, `filter`, `filterMap`, or dependent generation consume the same budget even though the property is not
182 * evaluated. Candidates that produce a different failure class also consume the budget. When the budget is exhausted,
183 * checking returns the best shrunk input found so far. The `shrinks` field in a `Falsified` result counts only
184 * candidates that were accepted as smaller failures.
185 *
186 * **Gotchas**
187 *
188 * When `replay` is present, its recorded seed, attempt, size, and shrink path control the run. The `runs`, `size`,
189 * `maxDiscards`, `maxShrinks`, and `seed` options are ignored.
190 *
191 * @stability unstable
192 * @category models
193 * @since 4.0.0
194 */
195export interface CheckOptions {
196 readonly runs?: number | undefined
197 readonly size?: number | undefined
198 readonly maxDiscards?: number | undefined
199 readonly maxShrinks?: number | undefined
200 readonly seed?: string | number | undefined
201 readonly replay?: Replay | undefined
202}
203
204/**
205 * Independent defaults for property checking and sampling, excluding replay tokens.
206 *
207 * @stability unstable
208 * @category models
209 * @since 4.0.0
210 */
211export interface GlobalOptions {
212 readonly check?: Omit<CheckOptions, "replay"> | undefined
213 readonly sample?: SampleOptions | undefined
214}
215
216/**
217 * Replaces the defaults used by {@link checkEffect} and {@link sampleEffect}.
218 *
219 * **Details**
220 *
221 * Options are resolved once per execution: explicit non-`undefined` values, then configured defaults, then built-in
222 * defaults. The supplied configuration is copied. Pass `{}` to reset it. Replay tokens retain control of replayed checks.
223 *
224 * **Gotchas**
225 *
226 * Configure defaults before starting concurrent tests. Changes affect subsequent executions, including Effects created
227 * earlier, but do not affect active runs.
228 *
229 * @stability unstable
230 * @category configuration
231 * @since 4.0.0
232 */
233export const configureGlobal = Internal.configureGlobal
234
235/**
236 * Identifies a property that returned `false`.
237 *
238 * @stability unstable
239 * @category models
240 * @since 4.0.0
241 */
242export interface ReturnedFalse {
243 readonly _tag: "ReturnedFalse"
244}
245
246/**
247 * Preserves a typed failure produced by an effectful property.
248 *
249 * @stability unstable
250 * @category models
251 * @since 4.0.0
252 */
253export interface PropertyError<out E> {
254 readonly _tag: "PropertyError"
255 readonly error: E
256}
257
258/**
259 * Represents the reason a property was falsified.
260 *
261 * @stability unstable
262 * @category models
263 * @since 4.0.0
264 */
265export type PropertyFailure<E> = ReturnedFalse | PropertyError<E>
266
267/**
268 * Reports that every requested property run passed.
269 *
270 * @stability unstable
271 * @category models
272 * @since 4.0.0
273 */
274export interface Passed {
275 readonly _tag: "Passed"
276 readonly runs: number
277 readonly discards: number
278}
279
280/**
281 * Reports a generated failure and its shrunk input.
282 *
283 * **Details**
284 *
285 * `initialInput` is the generated value that first falsified the property. `shrunkInput` is the best failing value found
286 * by the bounded shrink search and may be equal to `initialInput`.
287 *
288 * `runs` counts main property evaluations through the falsifying evaluation. It excludes evaluations performed while
289 * shrinking. A replay reports one run.
290 *
291 * @stability unstable
292 * @category models
293 * @since 4.0.0
294 */
295export interface Falsified<out A, out E> {
296 readonly _tag: "Falsified"
297 readonly initialInput: A
298 readonly shrunkInput: A
299 readonly failure: PropertyFailure<E>
300 readonly runs: number
301 readonly discards: number
302 readonly shrinks: number
303 readonly replay: Replay
304}
305
306/**
307 * Reports that bounded generation discarded too many candidates.
308 *
309 * **Details**
310 *
311 * The effective `seed` can be passed to {@link checkEffect} to reproduce the exhausted run, including when checking
312 * originally selected a seed from the Effect `Random` service.
313 *
314 * @stability unstable
315 * @category models
316 * @since 4.0.0
317 */
318export interface Exhausted {
319 readonly _tag: "Exhausted"
320 readonly runs: number
321 readonly discards: number
322 readonly seed: string | number
323}
324
325/**
326 * Reports that replay coordinates no longer reproduce the recorded failure class.
327 *
328 * **Details**
329 *
330 * - `PropertyPassed` means that the regenerated root passed.
331 * - `ShrinkPassed` means that the root switched failure class, or that a recorded shrink either passed or switched
332 * failure class.
333 *
334 * @stability unstable
335 * @category models
336 * @since 4.0.0
337 */
338export interface ReplayMismatch {
339 readonly _tag: "ReplayMismatch"
340 readonly reason: "AttemptDiscarded" | "PropertyPassed" | "ShrinkPathUnavailable" | "ShrinkPassed"
341}
342
343/**
344 * Represents every ordinary outcome of property checking.
345 *
346 * **Details**
347 *
348 * Defects and fiber interruption are not converted to this data type and continue through the returned `Effect`.
349 *
350 * @stability unstable
351 * @category models
352 * @since 4.0.0
353 */
354export type CheckResult<A, E> = Passed | Falsified<A, E> | Exhausted | ReplayMismatch
355
356/**
357 * Formats an unsuccessful property-check result as a diagnostic message, returning `undefined` for a passed result.
358 *
359 * **When to use**
360 *
361 * Use when integrating `checkEffect` with a test runner or another reporting interface.
362 *
363 * @stability unstable
364 * @category converting
365 * @since 4.0.0
366 */
367export function formatCheckFailure<A, E>(result: CheckResult<A, E>): string | undefined {
368 switch (result._tag) {
369 case "Passed":
370 return undefined
371 case "Falsified":
372 return `Property falsified after ${result.runs} run(s) and ${result.shrinks} shrink(s)\n` +
373 `Shrunk input: ${Formatter.format(result.shrunkInput, { space: 2 })}\n` +
374 `${
375 result.failure._tag === "ReturnedFalse"
376 ? "Failure: returned false"
377 : `Failure: ${
378 Cause.isCause(result.failure.error)
379 ? Cause.pretty(result.failure.error)
380 : Formatter.format(result.failure.error, { space: 2 })
381 }`
382 }\n` +
383 `Replay: ${result.replay}`
384 case "Exhausted":
385 return `Property exhausted after ${result.runs} run(s) and ${result.discards} discard(s)\n` +
386 `Seed: ${Formatter.format(result.seed, { space: 2 })}`
387 case "ReplayMismatch":
388 return `Property replay failed: ${result.reason}`
389 }
390}
391
392/**
393 * Derives an `Arbitrary` from the decoded `Type` of a Schema.
394 *
395 * **When to use**
396 *
397 * Use when you want Schema-aware generation without exposing a third-party property-testing engine.
398 *
399 * **Details**
400 *
401 * When `options.shrink` is provided, generated roots still come from Schema derivation, while the callback defines the
402 * complete shrink tree. Invalid candidates are skipped and count against `maxShrinks` without reaching the property.
403 *
404 * **Gotchas**
405 *
406 * Derivation is immediate and throws when the current unstable implementation cannot compile the Schema or prove a
407 * finite route through a recursive component.
408 *
409 * A custom shrinker replaces Schema-derived shrinking. It is evaluated lazily after a property failure and must be
410 * synchronous, deterministic, terminating, and free of mutation.
411 *
412 * @stability unstable
413 * @category constructors
414 * @since 4.0.0
415 */
416export function schema<S extends Schema_.Constraint>(
417 schema: S,
418 options?: SchemaOptions<S["Type"]>
419): Arbitrary<S["Type"]> {
420 return Internal.schema(schema, options)
421}
422
423/**
424 * Creates an `Arbitrary` that always generates `value` and has no shrink candidates.
425 *
426 * **When to use**
427 *
428 * Use when a branch of dependent generation should produce an already constructed value.
429 *
430 * **Gotchas**
431 *
432 * Every generation returns the same value. Objects are not cloned, so properties must not mutate them.
433 *
434 * @see {@link flatMap} for selecting dependent Arbitraries
435 * @stability unstable
436 * @category constructors
437 * @since 4.0.0
438 */
439export function Constant<const A>(value: A): Arbitrary<A> {
440 return Internal.constant(value)
441}
442
443/**
444 * Generates variable-length arrays from an existing element `Arbitrary`.
445 *
446 * **When to use**
447 *
448 * Use when generating command sequences or collections whose elements are built with Arbitrary composition.
449 *
450 * **Details**
451 *
452 * Shrinking tries removing blocks, including prefixes and interior blocks, while preserving the order and values of
453 * retained elements. It also tries the elements' own shrink candidates. Length bounds remain valid during shrinking,
454 * and elements share the generation budget for recursion.
455 *
456 * **Gotchas**
457 *
458 * Invalid length bounds throw a `RangeError` when this function is called. Retained objects are not cloned, so
459 * properties must not mutate generated values. Bounded shrinking does not guarantee a globally minimal failure.
460 *
461 * **Example** (Removing irrelevant commands)
462 *
463 * ```ts import.meta.vitest
464 * import { Arbitrary, Effect, Schema } from "effect"
465 *
466 * const command = Arbitrary.schema(Schema.Literals(["Add", "Reset", "Stop"]))
467 * const commands = Arbitrary.array(command, { maxLength: 50 })
468 * const result = await Effect.runPromise(
469 * Arbitrary.checkEffect(commands, (values) => {
470 * // The state machine fails when Reset occurs before a later Stop.
471 * const reset = values.indexOf("Reset")
472 * return reset === -1 || !values.slice(reset + 1).includes("Stop")
473 * }, { runs: 1, size: 4, seed: 0 })
474 * )
475 *
476 * result._tag // => "Falsified"
477 * if (result._tag === "Falsified") {
478 * result.initialInput // => ["Add", "Add", "Reset", "Stop"]
479 * result.shrunkInput // => ["Reset", "Stop"]
480 * }
481 * ```
482 *
483 * @see {@link ArrayOptions} for length bounds
484 * @see {@link all} for fixed tuples and records of Arbitraries
485 * @see {@link schema} for deriving arrays described by Schema
486 * @stability unstable
487 * @category constructors
488 * @since 4.0.0
489 */
490export const array: <A>(item: Arbitrary<A>, options?: ArrayOptions) => Arbitrary<Array<A>> = Internal.array
491
492/**
493 * Transforms every generated value and its shrink candidates.
494 *
495 * **When to use**
496 *
497 * Use when you want to derive generated values from an existing `Arbitrary` without changing its generation or shrink
498 * structure.
499 *
500 * @stability unstable
501 * @category mapping
502 * @since 4.0.0
503 */
504export const map: {
505 <A, B>(f: (value: A) => B): (self: Arbitrary<A>) => Arbitrary<B>
506 <A, B>(self: Arbitrary<A>, f: (value: A) => B): Arbitrary<B>
507} = dual(2, <A, B>(self: Arbitrary<A>, f: (value: A) => B): Arbitrary<B> => Internal.map(self, f))
508
509/**
510 * Keeps generated values and shrink candidates that satisfy a predicate or refinement.
511 *
512 * **When to use**
513 *
514 * Use when a condition cannot be expressed constructively by the source Schema or after values have been transformed.
515 *
516 * **Gotchas**
517 *
518 * Rejected generated values count against `maxDiscards`. Prefer Schema checks when possible because the Schema compiler
519 * may generate matching values directly.
520 *
521 * @see {@link filterMap} for transforming and filtering simultaneously
522 * @stability unstable
523 * @category filtering
524 * @since 4.0.0
525 */
526export const filter: {
527 <A, B extends A>(refinement: Refinement<A, B>): (self: Arbitrary<A>) => Arbitrary<B>
528 <A>(predicate: Predicate<A>): <B extends A>(self: Arbitrary<B>) => Arbitrary<B>
529 <A, B extends A>(self: Arbitrary<A>, refinement: Refinement<A, B>): Arbitrary<B>
530 <A>(self: Arbitrary<A>, predicate: Predicate<A>): Arbitrary<A>
531} = dual(2, <A>(self: Arbitrary<A>, predicate: Predicate<A>): Arbitrary<A> => Internal.filter(self, predicate))
532
533/**
534 * Transforms accepted generated values and discards rejected values.
535 *
536 * **When to use**
537 *
538 * Use when transformation and validation need to happen in one step after constructing an `Arbitrary`.
539 *
540 * **Gotchas**
541 *
542 * Failed filters discard generated roots and count against `maxDiscards`. Failures are not exposed in sampling or
543 * checking results.
544 *
545 * @see {@link map} for transformations that cannot reject
546 * @see {@link filter} for retaining original values that satisfy a condition
547 * @stability unstable
548 * @category filtering
549 * @since 4.0.0
550 */
551export const filterMap: {
552 <A, B, X>(f: Filter.Filter<A, B, X>): (self: Arbitrary<A>) => Arbitrary<B>
553 <A, B, X>(self: Arbitrary<A>, f: Filter.Filter<A, B, X>): Arbitrary<B>
554} = dual(
555 2,
556 <A, B, X>(self: Arbitrary<A>, f: Filter.Filter<A, B, X>): Arbitrary<B> => Internal.filterMap(self, f)
557)
558
559/**
560 * Sequentially selects an `Arbitrary` from a generated value.
561 *
562 * **When to use**
563 *
564 * Use when the domain or shape of a generated value depends on another generated value.
565 *
566 * **Details**
567 *
568 * Shrinking first tries smaller source values and regenerates their dependent Arbitraries. It then shrinks the
569 * selected dependent value. After a dependent shrink is selected, source shrinking is closed for that branch.
570 *
571 * **Gotchas**
572 *
573 * The callback must be synchronous, deterministic, and terminating. It can be evaluated again during shrinking and
574 * replay. Deriving a Schema inside the callback also repeats that derivation, so precompile finite dependent
575 * Arbitraries when possible.
576 *
577 * @see {@link map} for total transformations that do not select another Arbitrary
578 * @see {@link Constant} for dependent branches that return an existing value
579 * @stability unstable
580 * @category sequencing
581 * @since 4.0.0
582 */
583export const flatMap: {
584 <A, B>(f: (value: A) => Arbitrary<B>): (self: Arbitrary<A>) => Arbitrary<B>
585 <A, B>(self: Arbitrary<A>, f: (value: A) => Arbitrary<B>): Arbitrary<B>
586} = dual(2, <A, B>(self: Arbitrary<A>, f: (value: A) => Arbitrary<B>): Arbitrary<B> => Internal.flatMap(self, f))
587
588/**
589 * Combines Arbitraries into one `Arbitrary` whose generated value mirrors the input shape.
590 *
591 * **When to use**
592 *
593 * Use when you need to generate several independent values together.
594 *
595 * **Details**
596 *
597 * Accepts a tuple or array, an iterable, or a record of Arbitraries. Tuple positions and record keys are preserved in
598 * the generated value. Members are generated in a randomized internal order so recursive members share the generation
599 * budget fairly, while shrinking changes one member at a time.
600 *
601 * **Gotchas**
602 *
603 * Iterable inputs are consumed when `all` is called. If any member discards a generated root, the complete generated
604 * value is discarded.
605 *
606 * @see {@link array} for variable-length arrays that shrink by removing elements
607 * @stability unstable
608 * @category constructors
609 * @since 4.0.0
610 */
611export function all<const Input extends Iterable<Arbitrary<any>> | Record<string, Arbitrary<any>>>(
612 input: Input
613): Arbitrary<
614 [Input] extends [ReadonlyArray<Arbitrary<any>>] ? {
615 -readonly [K in keyof Input]: [Input[K]] extends [Arbitrary<infer A>] ? A : never
616 }
617 : [Input] extends [Iterable<Arbitrary<infer A>>] ? Array<A>
618 : [Input] extends [Record<string, Arbitrary<any>>] ? {
619 -readonly [K in keyof Input]: [Input[K]] extends [Arbitrary<infer A>] ? A : never
620 }
621 : never
622> {
623 return Internal.all(input)
624}
625
626/**
627 * Generates a bounded collection of values from an `Arbitrary`.
628 *
629 * **When to use**
630 *
631 * Use when you need generated examples without running a property.
632 *
633 * @stability unstable
634 * @category running
635 * @since 4.0.0
636 */
637export function sampleEffect<A>(
638 self: Arbitrary<A>,
639 options?: SampleOptions
640): Effect.Effect<ReadonlyArray<A>, SampleError> {
641 return Internal.sampleEffect(self, options)
642}
643
644/**
645 * Checks a pure or effectful property and shrinks the first falsification.
646 *
647 * **When to use**
648 *
649 * Use when you want deterministic, interruptible property checking with typed property failures and replay.
650 *
651 * **Details**
652 *
653 * Returning `false` and failing an Effect are shrinkable falsifications. Shrinking preserves which of these two
654 * failure classes caused the initial falsification. Typed error values may change while shrinking and are not compared
655 * for equality. Defects and interruption continue through the returned Effect instead of becoming `CheckResult`
656 * values.
657 *
658 * **Gotchas**
659 *
660 * Properties must treat generated values as immutable. The runner does not clone values before evaluation, so
661 * mutation can change reported shrunk inputs or interfere with shrinking and replay.
662 *
663 * A property must also produce the same outcome for the same input and initial environment. The runner may evaluate
664 * it repeatedly and does not restore mutable services between evaluations. Stateful properties should acquire and
665 * release an independent fixture inside each evaluation.
666 *
667 * @stability unstable
668 * @category running
669 * @since 4.0.0
670 */
671export function checkEffect<A, E = never, R = never>(
672 self: Arbitrary<A>,
673 property: (value: A) => boolean | Effect.Effect<boolean, E, R>,
674 options?: CheckOptions
675): Effect.Effect<CheckResult<A, E>, never, R> {
676 return Internal.checkEffect(self, property, options)
677}