Repository: dubzzz/fast-check
Pull request #1918 (2021-05-27 20:30:44)

♻️ Enforce explicit context-passing for `NextArbitrary` and related
<!-- Context of the PR: short description and potentially linked issues -->

<!-- ...a few words to describe the content of this PR...               -->
<!-- ... -->

<!-- Type of PR: [ ] unchecked / [ ] checked -->
**_Category:_**

- [ ] ✨ Introduce new features
- [ ] 📝 Add or update documentation
- [ ] ✅ Add or update tests
- [ ] 🐛 Fix a bug
- [ ] 🏷️ Add or update types
- [x] _Other(s):_ _Safer API for `NextArbitrary::shrink` before first release_
<!-- Don't forget to add the gitmoji icon in the name of the PR -->
<!-- See: https://gitmoji.dev/                                  -->

<!-- Fixing bugs, adding feastures... may impact existing ones           -->
<!-- in order to track potential issues that could be related to your PR -->
<!-- please check the impacts and describe more precisely what to expect -->

**_Potential impacts:_**

<!-- Generated values: Can your change impact any of the existing generators in terms of generated values, if so which ones? when? -->
<!-- Shrink values:    Can your change impact any of the existing generators in terms of shrink values, if so which ones? when? -->
<!-- Performance:      Can it require some typings changes on user side? Please give more details -->
<!-- Typings:          Is there a potential performance impact? In which cases? -->
- [ ] Generated values
- [ ] Shrink values
- [ ] Performance
- [ ] Typings
- [ ] _Other(s):_ ...


codesandbox[bot]: This pull request is automatically built and testable in [CodeSandbox](https://codesandbox.io).

  To see build info of the built libraries, click [here](https://ci.codesandbox.io/status/dubzzz/fast-check/pr/1918/builds/134516) or the icon next to each commit SHA.

Latest deployment of this branch, based on commit 591788ac65bb62213338e45c9ac514f25fa1d81d:

|Sandbox| Source |
|--|--|
|[Vanilla](https://codesandbox.io/s/vanilla-coigh)| Configuration |
|[fast-check-examples](https://codesandbox.io/s/fast-check-examples-110yt)| Configuration |

codecov[bot]: # [Codecov](https://codecov.io/gh/dubzzz/fast-check/pull/1918?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Nicolas+DUBIEN) Report
> Merging [#1918](https://codecov.io/gh/dubzzz/fast-check/pull/1918?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Nicolas+DUBIEN) (156c639) into [main](https://codecov.io/gh/dubzzz/fast-check/commit/b8a8bb08153f840a230809917c739bb97d1df249?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Nicolas+DUBIEN) (b8a8bb0) will **not change** coverage.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/dubzzz/fast-check/pull/1918/graphs/tree.svg?width=650&height=150&src=pr&token=fRi2fJN730&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Nicolas+DUBIEN)](https://codecov.io/gh/dubzzz/fast-check/pull/1918?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Nicolas+DUBIEN)

```diff
@@           Coverage Diff           @@
##             main    #1918   +/-   ##
=======================================
  Coverage   97.51%   97.51%           
=======================================
  Files         166      166           
  Lines        4107     4107           
  Branches      795      795           
=======================================
  Hits         4005     4005           
  Misses         98       98           
  Partials        4        4           
```

| Flag | Coverage Δ | |
|---|---|---|
| unit-tests | `97.51% <100.00%> (ø)` | |
| unit-tests-10.x-Linux | `?` | |
| unit-tests-12.x-Linux | `97.51% <100.00%> (ø)` | |
| unit-tests-14.x-Linux | `?` | |
| unit-tests-16.x-Linux | `97.51% <100.00%> (ø)` | |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Nicolas+DUBIEN#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Impacted Files](https://codecov.io/gh/dubzzz/fast-check/pull/1918?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Nicolas+DUBIEN) | Coverage Δ | |
|---|---|---|
| [src/arbitrary/\_internals/ArrayArbitrary.ts](https://codecov.io/gh/dubzzz/fast-check/pull/1918/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Nicolas+DUBIEN#diff-c3JjL2FyYml0cmFyeS9faW50ZXJuYWxzL0FycmF5QXJiaXRyYXJ5LnRz) | `94.05% <100.00%> (ø)` | |
| [src/arbitrary/\_internals/FrequencyArbitrary.ts](https://codecov.io/gh/dubzzz/fast-check/pull/1918/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Nicolas+DUBIEN#diff-c3JjL2FyYml0cmFyeS9faW50ZXJuYWxzL0ZyZXF1ZW5jeUFyYml0cmFyeS50cw==) | `98.07% <100.00%> (ø)` | |
| [src/arbitrary/\_internals/MixedCaseArbitrary.ts](https://codecov.io/gh/dubzzz/fast-check/pull/1918/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Nicolas+DUBIEN#diff-c3JjL2FyYml0cmFyeS9faW50ZXJuYWxzL01peGVkQ2FzZUFyYml0cmFyeS50cw==) | `89.70% <100.00%> (ø)` | |
| [src/arbitrary/\_internals/SchedulerArbitrary.ts](https://codecov.io/gh/dubzzz/fast-check/pull/1918/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Nicolas+DUBIEN#diff-c3JjL2FyYml0cmFyeS9faW50ZXJuYWxzL1NjaGVkdWxlckFyYml0cmFyeS50cw==) | `100.00% <100.00%> (ø)` | |
| [src/arbitrary/\_internals/StreamArbitrary.ts](https://codecov.io/gh/dubzzz/fast-check/pull/1918/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Nicolas+DUBIEN#diff-c3JjL2FyYml0cmFyeS9faW50ZXJuYWxzL1N0cmVhbUFyYml0cmFyeS50cw==) | `100.00% <100.00%> (ø)` | |
| [src/check/arbitrary/definition/NextArbitrary.ts](https://codecov.io/gh/dubzzz/fast-check/pull/1918/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Nicolas+DUBIEN#diff-c3JjL2NoZWNrL2FyYml0cmFyeS9kZWZpbml0aW9uL05leHRBcmJpdHJhcnkudHM=) | `96.87% <100.00%> (ø)` | |
| [src/check/arbitrary/definition/NextValue.ts](https://codecov.io/gh/dubzzz/fast-check/pull/1918/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Nicolas+DUBIEN#diff-c3JjL2NoZWNrL2FyYml0cmFyeS9kZWZpbml0aW9uL05leHRWYWx1ZS50cw==) | `100.00% <100.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/dubzzz/fast-check/pull/1918?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Nicolas+DUBIEN).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Nicolas+DUBIEN)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/dubzzz/fast-check/pull/1918?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Nicolas+DUBIEN). Last update [b8a8bb0...156c639](https://codecov.io/gh/dubzzz/fast-check/pull/1918?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Nicolas+DUBIEN). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Nicolas+DUBIEN).


Patch:
diff --git a/src/arbitrary/_internals/ArrayArbitrary.ts b/src/arbitrary/_internals/ArrayArbitrary.ts
index fd2360cf..536194f1 100644
--- a/src/arbitrary/_internals/ArrayArbitrary.ts
+++ b/src/arbitrary/_internals/ArrayArbitrary.ts
@@ -137,7 +137,7 @@ export class ArrayArbitrary<T> extends NextArbitrary<T[]> {
     }
     // `preFilter` only drops items, it does not reorder them or add some more
     // if calling it with `value` results into a smaller array it means that the value was not generated by this instance
-    const filtered = this.preFilter(value.map((item) => new NextValue(item)));
+    const filtered = this.preFilter(value.map((item) => new NextValue(item, undefined)));
     return filtered.length === value.length;
   }
 
diff --git a/src/arbitrary/_internals/FrequencyArbitrary.ts b/src/arbitrary/_internals/FrequencyArbitrary.ts
index 9b547e3d..aad08457 100644
--- a/src/arbitrary/_internals/FrequencyArbitrary.ts
+++ b/src/arbitrary/_internals/FrequencyArbitrary.ts
@@ -107,7 +107,7 @@ export class FrequencyArbitrary<T> extends NextArbitrary<T> {
     }
     return this.defaultShrinkForFirst(potentialSelectedIndex).join(
       this.warbs[potentialSelectedIndex].arbitrary
-        .shrink(value)
+        .shrink(value, undefined) // re-checked by canShrinkWithoutContextIndex
         .map((v) => this.mapIntoNextValue(potentialSelectedIndex, v, null, undefined))
     );
   }
@@ -123,7 +123,8 @@ export class FrequencyArbitrary<T> extends NextArbitrary<T> {
     } finally {
       --this.context.depth; // decrease depth (reset depth)
     }
-    const rawShrinkValue = new NextValue(this.warbs[0].fallbackValue.default);
+    // The arbitrary at [0] accepts to shrink fallbackValue.default without any context (context=undefined)
+    const rawShrinkValue = new NextValue(this.warbs[0].fallbackValue.default, undefined);
     return Stream.of(this.mapIntoNextValue(0, rawShrinkValue, null, undefined));
   }
 
@@ -210,6 +211,7 @@ export type _Constraints = {
 interface _WeightedArbitrary<T> {
   weight: number;
   arbitrary: Arbitrary<T>;
+  // If specified, the arbitrary must accept to shrink fallbackValue.default without any context
   fallbackValue?: { default: T };
 }
 
@@ -217,6 +219,7 @@ interface _WeightedArbitrary<T> {
 interface _WeightedNextArbitrary<T> {
   weight: number;
   arbitrary: NextArbitrary<T>;
+  // If specified, the arbitrary must accept to shrink fallbackValue.default without any context
   fallbackValue?: { default: T };
 }
 
diff --git a/src/arbitrary/_internals/MixedCaseArbitrary.ts b/src/arbitrary/_internals/MixedCaseArbitrary.ts
index bc1bb11d..32ab1e55 100644
--- a/src/arbitrary/_internals/MixedCaseArbitrary.ts
+++ b/src/arbitrary/_internals/MixedCaseArbitrary.ts
@@ -125,7 +125,12 @@ export class MixedCaseArbitrary extends NextArbitrary<string> {
         // Potentially new value for nTogglePositions.length, new value for nFlags
         // so flagsContext is not applicable anymore
         this.applyFlagsOnChars(nChars, nFlags, nTogglePositions);
-        return new NextValue(nChars.join(''), this.buildContextFor(nRawStringNextValue, new NextValue(nFlags)));
+        // Remark: Value nFlags can be attached to a context equal to undefined
+        // as `canShrinkWithoutContext(nFlags) === true` for the bigint arbitrary
+        return new NextValue(
+          nChars.join(''),
+          this.buildContextFor(nRawStringNextValue, new NextValue(nFlags, undefined))
+        );
       })
       .join(
         makeLazy(() => {
diff --git a/src/arbitrary/_internals/SchedulerArbitrary.ts b/src/arbitrary/_internals/SchedulerArbitrary.ts
index 2245c21e..6d01b8a7 100644
--- a/src/arbitrary/_internals/SchedulerArbitrary.ts
+++ b/src/arbitrary/_internals/SchedulerArbitrary.ts
@@ -27,7 +27,7 @@ export class SchedulerArbitrary<TMetaData> extends NextArbitrary<Scheduler<TMeta
   }
 
   generate(mrng: Random, _biasFactor: number | undefined): NextValue<Scheduler<TMetaData>> {
-    return new NextValue(new SchedulerImplem<TMetaData>(this.act, buildNextTaskIndex(mrng.clone())));
+    return new NextValue(new SchedulerImplem<TMetaData>(this.act, buildNextTaskIndex(mrng.clone())), undefined);
   }
 
   canShrinkWithoutContext(value: unknown): value is Scheduler<TMetaData> {
diff --git a/src/arbitrary/_internals/StreamArbitrary.ts b/src/arbitrary/_internals/StreamArbitrary.ts
index e6cac5b5..2edbe778 100644
--- a/src/arbitrary/_internals/StreamArbitrary.ts
+++ b/src/arbitrary/_internals/StreamArbitrary.ts
@@ -26,7 +26,7 @@ export class StreamArbitrary<T> extends NextArbitrary<Stream<T>> {
       const toString = () => `Stream(${seenValues.map(stringify).join(',')}…)`;
       return Object.assign(s, { toString, [cloneMethod]: enrichedProducer });
     };
-    return new NextValue(enrichedProducer());
+    return new NextValue(enrichedProducer(), undefined);
   }
 
   canShrinkWithoutContext(value: unknown): value is Stream<T> {
diff --git a/src/check/arbitrary/definition/NextArbitrary.ts b/src/check/arbitrary/definition/NextArbitrary.ts
index 36cbd63a..e3d9ed16 100644
--- a/src/check/arbitrary/definition/NextArbitrary.ts
+++ b/src/check/arbitrary/definition/NextArbitrary.ts
@@ -46,12 +46,12 @@ export abstract class NextArbitrary<T> {
    * by calling `canShrinkWithoutContext` first on the value.
    *
    * @param value - The value to shrink
-   * @param context - Its associated context (the one returned by generate)
+   * @param context - Its associated context (the one returned by generate) or `undefined` if no context but `canShrinkWithoutContext(value) === true`
    * @returns Stream of shrinks for value based on context (if provided)
    *
    * @remarks Since 2.15.0
    */
-  abstract shrink(value: T, context?: unknown): Stream<NextValue<T>>;
+  abstract shrink(value: T, context: unknown | undefined): Stream<NextValue<T>>;
 
   /**
    * Create another arbitrary by filtering values against `predicate`
@@ -290,9 +290,9 @@ class MapArbitrary<T, U> extends NextArbitrary<U> {
     if (this.unmapper !== undefined) {
       const unmapped = this.unmapper(value);
       // As `shrink` should never be called without a valid context
-      // except if `canGenerate` tells that the value was compatible with a shrink without any context
-      // we can safely consider `this.arb.canGenerate(unmapped)` to be true at that point.
-      return this.arb.shrink(unmapped).map(this.bindValueMapper);
+      // except if `canShrinkWithoutContext` tells that the value was compatible with a shrink without any context
+      // we can safely consider `this.arb.canShrinkWithoutContext(unmapped)` to be true at that point.
+      return this.arb.shrink(unmapped, undefined).map(this.bindValueMapper);
     }
     return Stream.nil();
   }
diff --git a/src/check/arbitrary/definition/NextValue.ts b/src/check/arbitrary/definition/NextValue.ts
index 92f35369..6b3eda60 100644
--- a/src/check/arbitrary/definition/NextValue.ts
+++ b/src/check/arbitrary/definition/NextValue.ts
@@ -43,7 +43,7 @@ export class NextValue<T> {
    * @param context - Context associated to the generated value (useful for shrink)
    * @param customGetValue - Limited to internal usages (to ease migration to next), it will be removed on next major
    */
-  constructor(value_: T, context: unknown = undefined, customGetValue: (() => T) | undefined = undefined) {
+  constructor(value_: T, context: unknown, customGetValue: (() => T) | undefined = undefined) {
     this.value_ = value_;
     this.context = context;
     this.hasToBeCloned = hasCloneMethod(value_);


Test patch:
diff --git a/test/unit/arbitrary/_internals/BigIntArbitrary.spec.ts b/test/unit/arbitrary/_internals/BigIntArbitrary.spec.ts
index f8855c86..26768a30 100644
--- a/test/unit/arbitrary/_internals/BigIntArbitrary.spec.ts
+++ b/test/unit/arbitrary/_internals/BigIntArbitrary.spec.ts
@@ -290,14 +290,18 @@ describe('BigIntArbitrary (integration)', () => {
             const arbNegate = new BigIntArbitrary(-max, -min);
 
             // Act
-            const tree = buildNextShrinkTree(arb, new NextValue(mid));
-            const treeNegate = buildNextShrinkTree(arbNegate, new NextValue(-mid));
+            const source = new NextValue(mid, undefined);
+            const sourceNegate = new NextValue(-mid, undefined);
+            const tree = buildNextShrinkTree(arb, source);
+            const treeNegate = buildNextShrinkTree(arbNegate, sourceNegate);
             const flat: bigint[] = [];
             const flatNegate: bigint[] = [];
             walkTree(tree, (v) => flat.push(v));
             walkTree(treeNegate, (v) => flatNegate.push(-v));
 
             // Assert
+            expect(arb.canShrinkWithoutContext(source.value)).toBe(true);
+            expect(arbNegate.canShrinkWithoutContext(sourceNegate.value)).toBe(true);
             expect(flatNegate).toEqual(flat);
           }
         )
@@ -320,14 +324,18 @@ describe('BigIntArbitrary (integration)', () => {
             const arbOffset = new BigIntArbitrary(min + offset, max + offset);
 
             // Act
-            const tree = buildNextShrinkTree(arb, new NextValue(mid));
-            const treeOffset = buildNextShrinkTree(arbOffset, new NextValue(mid + offset));
+            const source = new NextValue(mid, undefined);
+            const sourceOffset = new NextValue(mid + offset, undefined);
+            const tree = buildNextShrinkTree(arb, source);
+            const treeOffset = buildNextShrinkTree(arbOffset, sourceOffset);
             const flat: bigint[] = [];
             const flatOffset: bigint[] = [];
             walkTree(tree, (v) => flat.push(v));
             walkTree(treeOffset, (v) => flatOffset.push(v - offset));
 
             // Assert
+            expect(arb.canShrinkWithoutContext(source.value)).toBe(true);
+            expect(arbOffset.canShrinkWithoutContext(sourceOffset.value)).toBe(true);
             expect(flatOffset).toEqual(flat);
           }
         )
@@ -336,12 +344,14 @@ describe('BigIntArbitrary (integration)', () => {
     it('should shrink strictly positive value for positive range including zero', () => {
       // Arrange
       const arb = new BigIntArbitrary(BigInt(0), BigInt(10));
+      const source = new NextValue(BigInt(8), undefined);
 
       // Act
-      const tree = buildNextShrinkTree(arb, new NextValue(BigInt(8)));
+      const tree = buildNextShrinkTree(arb, source);
       const renderedTree = renderTree(tree).join('\n');
 
       // Assert
+      expect(arb.canShrinkWithoutContext(source.value)).toBe(true);
       //   When there is no more option, the shrinker retry one time with the value
       //   current-1 to check if something that changed outside (another value not itself)
       //   may have changed the situation.
@@ -398,12 +408,14 @@ describe('BigIntArbitrary (integration)', () => {
     it('should shrink strictly positive value for range not including zero', () => {
       // Arrange
       const arb = new BigIntArbitrary(BigInt(10), BigInt(20));
+      const source = new NextValue(BigInt(18), undefined);
 
       // Act
-      const tree = buildNextShrinkTree(arb, new NextValue(BigInt(18)));
+      const tree = buildNextShrinkTree(arb, source);
       const renderedTree = renderTree(tree).join('\n');
 
       // Assert
+      expect(arb.canShrinkWithoutContext(source.value)).toBe(true);
       //   As the range [10, 20] and the value 18
       //   are just offset by +10 compared to the first case,
       //   the rendered tree will be offset by 10 too
@@ -451,12 +463,14 @@ describe('BigIntArbitrary (integration)', () => {
     it('should shrink strictly negative value for negative range including zero', () => {
       // Arrange
       const arb = new BigIntArbitrary(BigInt(-10), BigInt(0));
+      const source = new NextValue(BigInt(-8), undefined);
 
       // Act
-      const tree = buildNextShrinkTree(arb, new NextValue(BigInt(-8)));
+      const tree = buildNextShrinkTree(arb, source);
       const renderedTree = renderTree(tree).join('\n');
 
       // Assert
+      expect(arb.canShrinkWithoutContext(source.value)).toBe(true);
       //   As the range [-10, 0] and the value -8
       //   are the opposite of first case, the rendered tree will be the same except
       //   it contains opposite values
diff --git a/test/unit/arbitrary/_internals/CloneArbitrary.spec.ts b/test/unit/arbitrary/_internals/CloneArbitrary.spec.ts
index 4c944dbc..6924a48f 100644
--- a/test/unit/arbitrary/_internals/CloneArbitrary.spec.ts
+++ b/test/unit/arbitrary/_internals/CloneArbitrary.spec.ts
@@ -30,7 +30,7 @@ describe('CloneArbitrary', () => {
       const { instance: mrngClone2 } = fakeRandom();
       clone.mockReturnValueOnce(mrngClone1).mockReturnValueOnce(mrngClone2);
       const { instance: sourceArb, generate } = fakeNextArbitrary<symbol>();
-      generate.mockReturnValue(new NextValue(producedValue));
+      generate.mockReturnValue(new NextValue(producedValue, undefined));
 
       // Act
       const arb = new CloneArbitrary(sourceArb, numValues);
@@ -53,8 +53,8 @@ describe('CloneArbitrary', () => {
       const numValues = 1;
       const { instance: mrng } = fakeRandom();
       const { instance: sourceArb, generate } = fakeNextArbitrary<unknown>();
-      if (cloneable) generate.mockReturnValue(new NextValue({ [cloneMethod]: jest.fn() }));
-      else generate.mockReturnValue(new NextValue({ m: jest.fn() }));
+      if (cloneable) generate.mockReturnValue(new NextValue({ [cloneMethod]: jest.fn() }, undefined));
+      else generate.mockReturnValue(new NextValue({ m: jest.fn() }, undefined));
 
       // Act
       const arb = new CloneArbitrary(sourceArb, numValues);
@@ -130,9 +130,9 @@ describe('CloneArbitrary', () => {
       const numValues = 3;
       const { instance: sourceArb, shrink } = fakeNextArbitrary<symbol>();
       shrink
-        .mockReturnValueOnce(Stream.of<NextValue<symbol>>(new NextValue(s1), new NextValue(s2)))
-        .mockReturnValueOnce(Stream.of<NextValue<symbol>>(new NextValue(s1), new NextValue(s2)))
-        .mockReturnValueOnce(Stream.of<NextValue<symbol>>(new NextValue(s1), new NextValue(s2)));
+        .mockReturnValueOnce(Stream.of<NextValue<symbol>>(new NextValue(s1, undefined), new NextValue(s2, undefined)))
+        .mockReturnValueOnce(Stream.of<NextValue<symbol>>(new NextValue(s1, undefined), new NextValue(s2, undefined)))
+        .mockReturnValueOnce(Stream.of<NextValue<symbol>>(new NextValue(s1, undefined), new NextValue(s2, undefined)));
 
       // Act
       const arb = new CloneArbitrary(sourceArb, numValues);
diff --git a/test/unit/arbitrary/_internals/FrequencyArbitrary.spec.ts b/test/unit/arbitrary/_internals/FrequencyArbitrary.spec.ts
index 03e0e942..149e6d5f 100644
--- a/test/unit/arbitrary/_internals/FrequencyArbitrary.spec.ts
+++ b/test/unit/arbitrary/_internals/FrequencyArbitrary.spec.ts
@@ -296,7 +296,7 @@ describe('FrequencyArbitrary', () => {
               arbitraryMeta.generate.mockImplementation(() => {
                 calledOnce = true;
                 expect(depthContext).toEqual({ depth: initialDepth + 1 });
-                return new NextValue(expectedValue);
+                return new NextValue(expectedValue, undefined);
               });
             }
 
@@ -526,7 +526,9 @@ describe('FrequencyArbitrary', () => {
             const totalWeightBefore = warbs.slice(0, selectedArbitraryIndex).reduce((acc, cur) => acc + cur.weight, 0);
             const { instance: mrng, nextInt } = fakeRandom();
             nextInt.mockImplementation(() => totalWeightBefore + (generateSeed % selectedArbitrary.weight));
-            selectedArbitrary.arbitraryMeta.shrink.mockReturnValue(Stream.of(new NextValue(1), new NextValue(42)));
+            selectedArbitrary.arbitraryMeta.shrink.mockReturnValue(
+              Stream.of(new NextValue(1, undefined), new NextValue(42, undefined))
+            );
 
             // Act
             const arb = FrequencyArbitrary.from(warbs, constraints, 'test');
@@ -567,7 +569,9 @@ describe('FrequencyArbitrary', () => {
             const { instance: anotherMrng } = fakeRandom();
             clone.mockReturnValue(anotherMrng);
             nextInt.mockImplementation(() => totalWeightBefore + (generateSeed % selectedArbitrary.weight));
-            selectedArbitrary.arbitraryMeta.shrink.mockReturnValue(Stream.of(new NextValue(1), new NextValue(42)));
+            selectedArbitrary.arbitraryMeta.shrink.mockReturnValue(
+              Stream.of(new NextValue(1, undefined), new NextValue(42, undefined))
+            );
 
             // Act
             const arb = FrequencyArbitrary.from(warbs, { ...constraints, withCrossShrink: true }, 'test');
@@ -600,7 +604,9 @@ describe('FrequencyArbitrary', () => {
 
             const { instance: mrng, nextInt } = fakeRandom();
             nextInt.mockReturnValue(0);
-            warbs[0].arbitraryMeta.shrink.mockReturnValue(Stream.of(new NextValue(1), new NextValue(42)));
+            warbs[0].arbitraryMeta.shrink.mockReturnValue(
+              Stream.of(new NextValue(1, undefined), new NextValue(42, undefined))
+            );
 
             // Act
             const arb = FrequencyArbitrary.from(warbs, { ...constraints, withCrossShrink: true }, 'test');
@@ -635,17 +641,19 @@ describe('FrequencyArbitrary', () => {
               const input = warbs[index];
               const can = index === selectedIndex;
               input.arbitraryMeta.canShrinkWithoutContext.mockReturnValue(can);
-              input.arbitraryMeta.shrink.mockReturnValue(Stream.of(new NextValue(42), new NextValue(index)));
+              input.arbitraryMeta.shrink.mockReturnValue(
+                Stream.of(new NextValue(42, undefined), new NextValue(index, undefined))
+              );
             }
 
             // Act
             const arb = FrequencyArbitrary.from(warbs, constraints, 'test');
-            const shrinks = [...arb.shrink(value as any)];
+            const shrinks = [...arb.shrink(value as any, undefined)];
 
             // Assert
             expect(shrinks.map((v) => v.value)).toEqual([42, selectedIndex]);
             expect(warbs[selectedIndex].arbitraryMeta.canShrinkWithoutContext).toHaveBeenCalledWith(value);
-            expect(warbs[selectedIndex].arbitraryMeta.shrink).toHaveBeenCalledWith(value);
+            expect(warbs[selectedIndex].arbitraryMeta.shrink).toHaveBeenCalledWith(value, undefined);
           }
         )
       ));
@@ -670,13 +678,15 @@ describe('FrequencyArbitrary', () => {
               const input = warbs[index];
               const can = index === selectedIndex;
               input.arbitraryMeta.canShrinkWithoutContext.mockReturnValue(can);
-              input.arbitraryMeta.shrink.mockReturnValue(Stream.of(new NextValue(42), new NextValue(index)));
+              input.arbitraryMeta.shrink.mockReturnValue(
+                Stream.of(new NextValue(42, undefined), new NextValue(index, undefined))
+              );
             }
             warbs[0].fallbackValue = { default: 48 };
 
             // Act
             const arb = FrequencyArbitrary.from(warbs, constraints, 'test');
-            const shrinks = [...arb.shrink(value as any)];
+            const shrinks = [...arb.shrink(value as any, undefined)];
 
             // Assert
             if (warbs[0].weight !== 0 && selectedIndex !== 0 && constraints.withCrossShrink) {
@@ -689,7 +699,7 @@ describe('FrequencyArbitrary', () => {
               expect(shrinks.map((v) => v.value)).toEqual([42, selectedIndex]);
             }
             expect(warbs[selectedIndex].arbitraryMeta.canShrinkWithoutContext).toHaveBeenCalledWith(value);
-            expect(warbs[selectedIndex].arbitraryMeta.shrink).toHaveBeenCalledWith(value);
+            expect(warbs[selectedIndex].arbitraryMeta.shrink).toHaveBeenCalledWith(value, undefined);
           }
         )
       ));
diff --git a/test/unit/arbitrary/_internals/IntegerArbitrary.spec.ts b/test/unit/arbitrary/_internals/IntegerArbitrary.spec.ts
index 9fe0f58d..bbac6d89 100644
--- a/test/unit/arbitrary/_internals/IntegerArbitrary.spec.ts
+++ b/test/unit/arbitrary/_internals/IntegerArbitrary.spec.ts
@@ -279,17 +279,21 @@ describe('IntegerArbitrary (integration)', () => {
             const min = start;
             const [mid, max] = o1 < o2 ? [min + o1, min + o2] : [min + o2, min + o1];
             const arb = new IntegerArbitrary(min, max);
-            const arbNegate = new IntegerArbitrary(-max, -min);
+            const arbNegate = new IntegerArbitrary(max !== 0 ? -max : 0, min !== 0 ? -min : min); // !==0 to avoid -0
 
             // Act
-            const tree = buildNextShrinkTree(arb, new NextValue(mid));
-            const treeNegate = buildNextShrinkTree(arbNegate, new NextValue(-mid));
+            const source = new NextValue(mid, undefined);
+            const sourceNegate = new NextValue(-mid, undefined);
+            const tree = buildNextShrinkTree(arb, source);
+            const treeNegate = buildNextShrinkTree(arbNegate, sourceNegate);
             const flat: number[] = [];
             const flatNegate: number[] = [];
             walkTree(tree, (v) => flat.push(v));
             walkTree(treeNegate, (v) => flatNegate.push(v !== 0 ? -v : 0));
 
             // Assert
+            expect(arb.canShrinkWithoutContext(source.value)).toBe(true);
+            expect(arbNegate.canShrinkWithoutContext(sourceNegate.value)).toBe(true);
             expect(flatNegate).toEqual(flat);
           }
         )
@@ -312,14 +316,18 @@ describe('IntegerArbitrary (integration)', () => {
             const arbOffset = new IntegerArbitrary(min + offset, max + offset);
 
             // Act
-            const tree = buildNextShrinkTree(arb, new NextValue(mid));
-            const treeOffset = buildNextShrinkTree(arbOffset, new NextValue(mid + offset));
+            const source = new NextValue(mid, undefined);
+            const sourceOffset = new NextValue(mid + offset, undefined);
+            const tree = buildNextShrinkTree(arb, source);
+            const treeOffset = buildNextShrinkTree(arbOffset, sourceOffset);
             const flat: number[] = [];
             const flatOffset: number[] = [];
             walkTree(tree, (v) => flat.push(v));
             walkTree(treeOffset, (v) => flatOffset.push(v - offset));
 
             // Assert
+            expect(arb.canShrinkWithoutContext(source.value)).toBe(true);
+            expect(arbOffset.canShrinkWithoutContext(sourceOffset.value)).toBe(true);
             expect(flatOffset).toEqual(flat);
           }
         )
@@ -328,12 +336,14 @@ describe('IntegerArbitrary (integration)', () => {
     it('should shrink strictly positive value for positive range including zero', () => {
       // Arrange
       const arb = new IntegerArbitrary(0, 10);
+      const source = new NextValue(8, undefined);
 
       // Act
-      const tree = buildNextShrinkTree(arb, new NextValue(8));
+      const tree = buildNextShrinkTree(arb, source);
       const renderedTree = renderTree(tree).join('\n');
 
       // Assert
+      expect(arb.canShrinkWithoutContext(source.value)).toBe(true);
       //   When there is no more option, the shrinker retry one time with the value
       //   current-1 to check if something that changed outside (another value not itself)
       //   may have changed the situation.
@@ -390,12 +400,14 @@ describe('IntegerArbitrary (integration)', () => {
     it('should shrink strictly positive value for range not including zero', () => {
       // Arrange
       const arb = new IntegerArbitrary(10, 20);
+      const source = new NextValue(18, undefined);
 
       // Act
-      const tree = buildNextShrinkTree(arb, new NextValue(18));
+      const tree = buildNextShrinkTree(arb, source);
       const renderedTree = renderTree(tree).join('\n');
 
       // Assert
+      expect(arb.canShrinkWithoutContext(source.value)).toBe(true);
       //   As the range [10, 20] and the value 18
       //   are just offset by +10 compared to the first case,
       //   the rendered tree will be offset by 10 too
@@ -443,12 +455,14 @@ describe('IntegerArbitrary (integration)', () => {
     it('should shrink strictly negative value for negative range including zero', () => {
       // Arrange
       const arb = new IntegerArbitrary(-10, 0);
+      const source = new NextValue(-8, undefined);
 
       // Act
-      const tree = buildNextShrinkTree(arb, new NextValue(-8));
+      const tree = buildNextShrinkTree(arb, source);
       const renderedTree = renderTree(tree).join('\n');
 
       // Assert
+      expect(arb.canShrinkWithoutContext(source.value)).toBe(true);
       //   As the range [-10, 0] and the value -8
       //   are the opposite of first case, the rendered tree will be the same except
       //   it contains opposite values
diff --git a/test/unit/arbitrary/_internals/LazyArbitrary.spec.ts b/test/unit/arbitrary/_internals/LazyArbitrary.spec.ts
index c3d66a8a..eae2678c 100644
--- a/test/unit/arbitrary/_internals/LazyArbitrary.spec.ts
+++ b/test/unit/arbitrary/_internals/LazyArbitrary.spec.ts
@@ -71,7 +71,7 @@ describe('LazyArbitrary', () => {
       // Arrange
       const value = Symbol();
       const context = Symbol();
-      const streamOutput = Stream.of(new NextValue(1));
+      const streamOutput = Stream.of(new NextValue(1, undefined));
       const { instance: underlying, shrink } = fakeNextArbitrary();
       shrink.mockReturnValue(streamOutput);
       const lazy = new LazyArbitrary('id007');
diff --git a/test/unit/arbitrary/_internals/StreamArbitrary.spec.ts b/test/unit/arbitrary/_internals/StreamArbitrary.spec.ts
index 1a4f4cbb..c9d886bf 100644
--- a/test/unit/arbitrary/_internals/StreamArbitrary.spec.ts
+++ b/test/unit/arbitrary/_internals/StreamArbitrary.spec.ts
@@ -78,7 +78,7 @@ describe('StreamArbitrary', () => {
       let index = 0;
       const expectedValues = [...Array(numValuesToPull)].map(() => Symbol());
       const { instance: sourceArb, generate } = fakeNextArbitrary();
-      generate.mockImplementation(() => new NextValue(expectedValues[index++]));
+      generate.mockImplementation(() => new NextValue(expectedValues[index++], undefined));
       const { instance: mrng, clone, nextInt } = fakeRandom();
       nextInt.mockReturnValueOnce(1); // for bias
       const { instance: mrngCloned } = fakeRandom();
@@ -103,7 +103,7 @@ describe('StreamArbitrary', () => {
       const numValuesToPullS2 = 3;
       const biasFactor = 48;
       const { instance: sourceArb, generate } = fakeNextArbitrary();
-      generate.mockImplementation(() => new NextValue(Symbol()));
+      generate.mockImplementation(() => new NextValue(Symbol(), undefined));
       const { instance: mrng, clone, nextInt } = fakeRandom();
       nextInt.mockReturnValueOnce(1); // for bias
       const { instance: mrngClonedA } = fakeRandom();
@@ -148,7 +148,7 @@ describe('StreamArbitrary', () => {
           const biasFactor = 48;
           let index = 0;
           const { instance: sourceArb, generate } = fakeNextArbitrary<number>();
-          generate.mockImplementation(() => new NextValue(expectedValues[index++]));
+          generate.mockImplementation(() => new NextValue(expectedValues[index++], undefined));
           const { instance: mrng, clone, nextInt } = fakeRandom();
           nextInt.mockReturnValueOnce(2); // for no bias
           const { instance: mrngCloned } = fakeRandom();
@@ -178,7 +178,7 @@ describe('StreamArbitrary', () => {
       const biasFactor = 48;
       let index = 0;
       const { instance: sourceArb, generate } = fakeNextArbitrary<number>();
-      generate.mockImplementation(() => new NextValue(index++));
+      generate.mockImplementation(() => new NextValue(index++, undefined));
       const { instance: mrng, clone, nextInt } = fakeRandom();
       nextInt.mockReturnValueOnce(2); // for no bias
       const { instance: mrngCloned } = fakeRandom();
@@ -248,7 +248,7 @@ describe('StreamArbitrary', () => {
     it('should always shrink to nil', () => {
       // Arrange
       const { instance: sourceArb, generate, shrink } = fakeNextArbitrary<number>();
-      generate.mockReturnValue(new NextValue(0));
+      generate.mockReturnValue(new NextValue(0, undefined));
       const { instance: mrng } = fakeRandom();
 
       // Act
diff --git a/test/unit/arbitrary/_internals/TupleArbitrary.spec.ts b/test/unit/arbitrary/_internals/TupleArbitrary.spec.ts
index 8c1af361..bdb18f81 100644
--- a/test/unit/arbitrary/_internals/TupleArbitrary.spec.ts
+++ b/test/unit/arbitrary/_internals/TupleArbitrary.spec.ts
@@ -28,9 +28,9 @@ describe('TupleArbitrary', () => {
       const { instance: instanceA, generate: generateA } = fakeNextArbitrary<symbol>();
       const { instance: instanceB, generate: generateB } = fakeNextArbitrary<symbol>();
       const { instance: instanceC, generate: generateC } = fakeNextArbitrary<symbol>();
-      generateA.mockReturnValueOnce(new NextValue(vA));
-      generateB.mockReturnValueOnce(new NextValue(vB));
-      generateC.mockReturnValueOnce(new NextValue(vC));
+      generateA.mockReturnValueOnce(new NextValue(vA, undefined));
+      generateB.mockReturnValueOnce(new NextValue(vB, undefined));
+      generateC.mockReturnValueOnce(new NextValue(vC, undefined));
       const { instance: mrng } = fakeRandom();
 
       // Act
@@ -48,8 +48,8 @@ describe('TupleArbitrary', () => {
       // Arrange
       const { instance: fakeArbitraryNotCloneableA, generate: generateA } = fakeNextArbitrary<string[]>();
       const { instance: fakeArbitraryCloneableB, generate: generateB } = fakeNextArbitrary<string[]>();
-      generateA.mockReturnValue(new NextValue([]));
-      generateB.mockReturnValue(new NextValue(Object.defineProperty([], cloneMethod, { value: jest.fn() })));
+      generateA.mockReturnValue(new NextValue([], undefined));
+      generateB.mockReturnValue(new NextValue(Object.defineProperty([], cloneMethod, { value: jest.fn() }), undefined));
       const { instance: mrng } = fakeRandom();
 
       // Act
@@ -65,8 +65,8 @@ describe('TupleArbitrary', () => {
       // Arrange
       const { instance: fakeArbitraryNotCloneableA, generate: generateA } = fakeNextArbitrary<string[]>();
       const { instance: fakeArbitraryNotCloneableB, generate: generateB } = fakeNextArbitrary<string[]>();
-      generateA.mockReturnValue(new NextValue([]));
-      generateB.mockReturnValue(new NextValue([]));
+      generateA.mockReturnValue(new NextValue([], undefined));
+      generateB.mockReturnValue(new NextValue([], undefined));
       const { instance: mrng } = fakeRandom();
 
       // Act
@@ -83,8 +83,10 @@ describe('TupleArbitrary', () => {
       const { instance: fakeArbitraryNotCloneableA, generate: generateA } = fakeNextArbitrary<string[]>();
       const { instance: fakeArbitraryCloneableB, generate: generateB } = fakeNextArbitrary<string[]>();
       const cloneMethodImpl = jest.fn();
-      generateA.mockReturnValue(new NextValue([]));
-      generateB.mockReturnValue(new NextValue(Object.defineProperty([], cloneMethod, { value: cloneMethodImpl })));
+      generateA.mockReturnValue(new NextValue([], undefined));
+      generateB.mockReturnValue(
+        new NextValue(Object.defineProperty([], cloneMethod, { value: cloneMethodImpl }), undefined)
+      );
       const { instance: mrng } = fakeRandom();
 
       // Act
@@ -172,10 +174,16 @@ describe('TupleArbitrary', () => {
       const shrinkC1 = Symbol();
       const shrinkC2 = Symbol();
       const shrinkC3 = Symbol();
-      shrinkA.mockReturnValueOnce(Stream.of(new NextValue(shrinkA1 as symbol), new NextValue(shrinkA2)));
-      shrinkB.mockReturnValueOnce(Stream.of(new NextValue(shrinkB1 as symbol)));
+      shrinkA.mockReturnValueOnce(
+        Stream.of(new NextValue(shrinkA1 as symbol, undefined), new NextValue(shrinkA2, undefined))
+      );
+      shrinkB.mockReturnValueOnce(Stream.of(new NextValue(shrinkB1 as symbol, undefined)));
       shrinkC.mockReturnValueOnce(
-        Stream.of(new NextValue(shrinkC1 as symbol), new NextValue(shrinkC2), new NextValue(shrinkC3))
+        Stream.of(
+          new NextValue(shrinkC1 as symbol, undefined),
+          new NextValue(shrinkC2, undefined),
+          new NextValue(shrinkC3, undefined)
+        )
       );
       const { instance: mrng } = fakeRandom();
 
@@ -216,18 +224,27 @@ describe('TupleArbitrary', () => {
       const cloneMethodImpl = jest
         .fn()
         .mockImplementation(() => Object.defineProperty([], cloneMethod, { value: cloneMethodImpl }));
-      generateA.mockReturnValue(new NextValue([]));
-      shrinkA.mockReturnValue(Stream.of(new NextValue([]), new NextValue([])));
-      generateB.mockReturnValue(new NextValue(Object.defineProperty([], cloneMethod, { value: cloneMethodImpl })));
+      generateA.mockReturnValue(new NextValue([], undefined));
+      shrinkA.mockReturnValue(Stream.of(new NextValue([], undefined), new NextValue([], undefined)));
+      generateB.mockReturnValue(
+        new NextValue(Object.defineProperty([], cloneMethod, { value: cloneMethodImpl }), undefined)
+      );
       shrinkB.mockReturnValue(
         Stream.of(
-          new NextValue(Object.defineProperty([], cloneMethod, { value: cloneMethodImpl })),
-          new NextValue(Object.defineProperty([], cloneMethod, { value: cloneMethodImpl })),
-          new NextValue(Object.defineProperty([], cloneMethod, { value: cloneMethodImpl }))
+          new NextValue(Object.defineProperty([], cloneMethod, { value: cloneMethodImpl }), undefined),
+          new NextValue(Object.defineProperty([], cloneMethod, { value: cloneMethodImpl }), undefined),
+          new NextValue(Object.defineProperty([], cloneMethod, { value: cloneMethodImpl }), undefined)
+        )
+      );
+      generateC.mockReturnValue(new NextValue([], undefined));
+      shrinkC.mockReturnValue(
+        Stream.of(
+          new NextValue([], undefined),
+          new NextValue([], undefined),
+          new NextValue([], undefined),
+          new NextValue([], undefined)
         )
       );
-      generateC.mockReturnValue(new NextValue([]));
-      shrinkC.mockReturnValue(Stream.of(new NextValue([]), new NextValue([]), new NextValue([]), new NextValue([])));
       const { instance: mrng } = fakeRandom();
 
       // Act
diff --git a/test/unit/arbitrary/_internals/helpers/BuildCompareFilter.spec.ts b/test/unit/arbitrary/_internals/helpers/BuildCompareFilter.spec.ts
index 7031b36c..22cd4da7 100644
--- a/test/unit/arbitrary/_internals/helpers/BuildCompareFilter.spec.ts
+++ b/test/unit/arbitrary/_internals/helpers/BuildCompareFilter.spec.ts
@@ -8,7 +8,7 @@ describe('buildCompareFilter', () => {
       fc.property(fc.array(fc.nat()), (tab) => {
         // Arrange
         const filter = buildCompareFilter<number>((a, b) => a === b);
-        const adaptedTab = tab.map((v) => new NextValue(v));
+        const adaptedTab = tab.map((v) => new NextValue(v, undefined));
 
         // Act
         const filteredTab = filter(adaptedTab);
diff --git a/test/unit/arbitrary/base64String.spec.ts b/test/unit/arbitrary/base64String.spec.ts
index 9804e4d6..f7b6ab1e 100644
--- a/test/unit/arbitrary/base64String.spec.ts
+++ b/test/unit/arbitrary/base64String.spec.ts
@@ -167,12 +167,13 @@ describe('base64String (integration)', () => {
   `('should be able to shrink $rawValue', ({ rawValue }) => {
     // Arrange
     const arb = convertToNext(base64String());
-    const value = new NextValue(rawValue);
+    const value = new NextValue(rawValue, undefined);
 
     // Act
     const renderedTree = renderTree(buildNextShrinkTree(arb, value, { numItems: 100 })).join('\n');
 
     // Assert
+    expect(arb.canShrinkWithoutContext(rawValue)).toBe(true);
     expect(renderedTree).toMatchSnapshot();
   });
 });
diff --git a/test/unit/arbitrary/dictionary.spec.ts b/test/unit/arbitrary/dictionary.spec.ts
index a9820dab..a01449c3 100644
--- a/test/unit/arbitrary/dictionary.spec.ts
+++ b/test/unit/arbitrary/dictionary.spec.ts
@@ -61,7 +61,7 @@ class FromValuesArbitrary<T> extends NextArbitrary<T> {
   }
   generate(mrng: Random, _biasFactor: number): NextValue<T> {
     const index = mrng.nextInt(0, this.source.length - 1);
-    return new NextValue(this.source[index]);
+    return new NextValue(this.source[index], undefined);
   }
   canShrinkWithoutContext(value: unknown): value is T {
     // includes might mix 0 and -0
diff --git a/test/unit/arbitrary/fullUnicodeString.spec.ts b/test/unit/arbitrary/fullUnicodeString.spec.ts
index 2013533c..c721b108 100644
--- a/test/unit/arbitrary/fullUnicodeString.spec.ts
+++ b/test/unit/arbitrary/fullUnicodeString.spec.ts
@@ -109,12 +109,13 @@ describe('fullUnicodeString (integration)', () => {
   `('should be able to shrink $rawValue', ({ rawValue }) => {
     // Arrange
     const arb = convertToNext(fullUnicodeString());
-    const value = new NextValue(rawValue);
+    const value = new NextValue(rawValue, undefined);
 
     // Act
     const renderedTree = renderTree(buildNextShrinkTree(arb, value, { numItems: 100 })).join('\n');
 
     // Assert
+    expect(arb.canShrinkWithoutContext(rawValue)).toBe(true);
     expect(renderedTree).toMatchSnapshot();
   });
 });
diff --git a/test/unit/arbitrary/ipV4.spec.ts b/test/unit/arbitrary/ipV4.spec.ts
index a8ea9c5e..2c939eef 100644
--- a/test/unit/arbitrary/ipV4.spec.ts
+++ b/test/unit/arbitrary/ipV4.spec.ts
@@ -87,12 +87,13 @@ describe('ipV4 (integration)', () => {
   `('should be able to shrink $rawValue', ({ rawValue }) => {
     // Arrange
     const arb = convertToNext(ipV4());
-    const value = new NextValue(rawValue);
+    const value = new NextValue(rawValue, undefined);
 
     // Act
     const renderedTree = renderTree(buildNextShrinkTree(arb, value, { numItems: 100 })).join('\n');
 
     // Assert
+    expect(arb.canShrinkWithoutContext(rawValue)).toBe(true);
     expect(renderedTree).toMatchSnapshot();
   });
 });
diff --git a/test/unit/arbitrary/ipV4Extended.spec.ts b/test/unit/arbitrary/ipV4Extended.spec.ts
index e9f10ae9..a29b82a4 100644
--- a/test/unit/arbitrary/ipV4Extended.spec.ts
+++ b/test/unit/arbitrary/ipV4Extended.spec.ts
@@ -106,12 +106,13 @@ describe('ipV4Extended (integration)', () => {
   `('should be able to shrink $rawValue', ({ rawValue }) => {
     // Arrange
     const arb = convertToNext(ipV4Extended());
-    const value = new NextValue(rawValue);
+    const value = new NextValue(rawValue, undefined);
 
     // Act
     const renderedTree = renderTree(buildNextShrinkTree(arb, value, { numItems: 100 })).join('\n');
 
     // Assert
+    expect(arb.canShrinkWithoutContext(rawValue)).toBe(true);
     expect(renderedTree).toMatchSnapshot();
   });
 });
diff --git a/test/unit/arbitrary/ipV6.spec.ts b/test/unit/arbitrary/ipV6.spec.ts
index cfbb91a4..e391d849 100644
--- a/test/unit/arbitrary/ipV6.spec.ts
+++ b/test/unit/arbitrary/ipV6.spec.ts
@@ -164,12 +164,13 @@ describe('ipV6 (integration)', () => {
   `('should be able to shrink $rawValue', ({ rawValue }) => {
     // Arrange
     const arb = convertToNext(ipV6());
-    const value = new NextValue(rawValue);
+    const value = new NextValue(rawValue, undefined);
 
     // Act
     const renderedTree = renderTree(buildNextShrinkTree(arb, value, { numItems: 100 })).join('\n');
 
     // Assert
+    expect(arb.canShrinkWithoutContext(rawValue)).toBe(true);
     expect(renderedTree).toMatchSnapshot();
   });
 });
diff --git a/test/unit/arbitrary/letrec.spec.ts b/test/unit/arbitrary/letrec.spec.ts
index 7fa6166a..d578e2f1 100644
--- a/test/unit/arbitrary/letrec.spec.ts
+++ b/test/unit/arbitrary/letrec.spec.ts
@@ -154,7 +154,7 @@ describe('letrec', () => {
       // Arrange
       const biasFactor = 69;
       const { instance: simpleArb, generate } = fakeNextArbitrary();
-      generate.mockReturnValueOnce(new NextValue(null));
+      generate.mockReturnValueOnce(new NextValue(null, undefined));
       const { instance: mrng } = fakeRandom();
 
       // Act
@@ -210,7 +210,7 @@ describe('letrec', () => {
       // Arrange
       const biasFactor = 42;
       const { instance: simpleArb, generate } = fakeNextArbitrary();
-      generate.mockReturnValueOnce(new NextValue(null));
+      generate.mockReturnValueOnce(new NextValue(null, undefined));
       const { tie } = letrec((tie) => ({
         tie: tie('__proto__'),
         ['__proto__']: tie('__defineGetter__​​'),
@@ -242,7 +242,7 @@ describe('letrec', () => {
       // Arrange
       const biasFactor = 42;
       const { instance: simpleArb, generate } = fakeNextArbitrary();
-      generate.mockReturnValueOnce(new NextValue(null));
+      generate.mockReturnValueOnce(new NextValue(null, undefined));
       const { a } = letrec((tie) =>
         Object.assign(Object.create(null), {
           a: tie('b'),
@@ -306,7 +306,7 @@ describe('letrec', () => {
       // Arrange
       const expectedValue = Symbol();
       const expectedContext = Symbol();
-      const expectedStream = Stream.of(new NextValue(Symbol()));
+      const expectedStream = Stream.of(new NextValue(Symbol(), undefined));
       const { instance: simpleArb, shrink } = fakeNextArbitrary();
       shrink.mockReturnValueOnce(expectedStream);
       const { arb1 } = letrec((tie) => {
diff --git a/test/unit/arbitrary/mapToConstant.spec.ts b/test/unit/arbitrary/mapToConstant.spec.ts
index 260f5411..a42d3653 100644
--- a/test/unit/arbitrary/mapToConstant.spec.ts
+++ b/test/unit/arbitrary/mapToConstant.spec.ts
@@ -110,12 +110,13 @@ describe('mapToConstant (integration)', () => {
         { num: 6, build: (index) => String.fromCodePoint(index + 97) } // a-f
       )
     );
-    const value = new NextValue('c');
+    const value = new NextValue('c', undefined);
 
     // Act
     const renderedTree = renderTree(buildNextShrinkTree(arb, value)).join('\n');
 
     // Assert
+    expect(arb.canShrinkWithoutContext('c')).toBe(true);
     expect(renderedTree).toMatchSnapshot();
   });
 });
diff --git a/test/unit/arbitrary/string.spec.ts b/test/unit/arbitrary/string.spec.ts
index 5e624002..82788e16 100644
--- a/test/unit/arbitrary/string.spec.ts
+++ b/test/unit/arbitrary/string.spec.ts
@@ -99,12 +99,13 @@ describe('string (integration)', () => {
   `('should be able to shrink $rawValue', ({ rawValue }) => {
     // Arrange
     const arb = convertToNext(string());
-    const value = new NextValue(rawValue);
+    const value = new NextValue(rawValue, undefined);
 
     // Act
     const renderedTree = renderTree(buildNextShrinkTree(arb, value, { numItems: 100 })).join('\n');
 
     // Assert
+    expect(arb.canShrinkWithoutContext(rawValue)).toBe(true);
     expect(renderedTree).toMatchSnapshot();
   });
 });
diff --git a/test/unit/arbitrary/stringOf.spec.ts b/test/unit/arbitrary/stringOf.spec.ts
index 1c4e44c5..6a08b88b 100644
--- a/test/unit/arbitrary/stringOf.spec.ts
+++ b/test/unit/arbitrary/stringOf.spec.ts
@@ -60,12 +60,13 @@ describe('stringOf (integration)', () => {
   `('should be able to shrink $rawValue', ({ rawValue, patterns }) => {
     // Arrange
     const arb = convertToNext(stringOf(convertFromNext(new PatternsArbitrary(patterns))));
-    const value = new NextValue(rawValue);
+    const value = new NextValue(rawValue, undefined);
 
     // Act
     const renderedTree = renderTree(buildNextShrinkTree(arb, value, { numItems: 100 })).join('\n');
 
     // Assert
+    expect(arb.canShrinkWithoutContext(rawValue)).toBe(true);
     expect(renderedTree).toMatchSnapshot();
   });
 });
@@ -77,7 +78,7 @@ class PatternsArbitrary extends NextArbitrary<string> {
     super();
   }
   generate(mrng: Random): NextValue<string> {
-    return new NextValue(this.patterns[mrng.nextInt(0, this.patterns.length - 1)]);
+    return new NextValue(this.patterns[mrng.nextInt(0, this.patterns.length - 1)], undefined);
   }
   canShrinkWithoutContext(value: unknown): value is string {
     if (typeof value !== 'string') return false;
@@ -88,6 +89,6 @@ class PatternsArbitrary extends NextArbitrary<string> {
     if (patternIndex <= 0) {
       return Stream.nil();
     }
-    return Stream.of(new NextValue(this.patterns[0]));
+    return Stream.of(new NextValue(this.patterns[0], undefined));
   }
 }
diff --git a/test/unit/check/arbitrary/definition/ConverterFromNext.utest.spec.ts b/test/unit/check/arbitrary/definition/ConverterFromNext.utest.spec.ts
index f56ca5cf..739fcaa3 100644
--- a/test/unit/check/arbitrary/definition/ConverterFromNext.utest.spec.ts
+++ b/test/unit/check/arbitrary/definition/ConverterFromNext.utest.spec.ts
@@ -53,7 +53,7 @@ describe('ConverterFromNext', () => {
     it('should be able to generate values using the underlying NextArbitrary', () => {
       // Arrange
       const expectedValue = 1;
-      const generate = jest.fn().mockReturnValueOnce(new NextValue(expectedValue));
+      const generate = jest.fn().mockReturnValueOnce(new NextValue(expectedValue, undefined));
       const shrink = jest.fn();
       class MyNextArbitrary extends NextArbitrary<number> {
         generate = generate;
@@ -78,7 +78,7 @@ describe('ConverterFromNext', () => {
     it('should be able to generate biased values using the underlying NextArbitrary', () => {
       // Arrange
       const expectedValue = 1;
-      const generate = jest.fn().mockReturnValueOnce(new NextValue(expectedValue));
+      const generate = jest.fn().mockReturnValueOnce(new NextValue(expectedValue, undefined));
       const shrink = jest.fn();
       class MyNextArbitrary extends NextArbitrary<number> {
         generate = generate;
@@ -108,7 +108,7 @@ describe('ConverterFromNext', () => {
       const expectedShrunkValues = [2, 3, 4];
       const shrink = jest
         .fn<Stream<NextValue<number>>, any[]>()
-        .mockReturnValueOnce(Stream.of(...expectedShrunkValues.map((v) => new NextValue(v))));
+        .mockReturnValueOnce(Stream.of(...expectedShrunkValues.map((v) => new NextValue(v, undefined))));
       const generate = jest.fn().mockReturnValueOnce(new NextValue(expectedFirstValue, expectedFirstContext));
       class MyNextArbitrary extends NextArbitrary<number> {
         generate = generate;
diff --git a/test/unit/check/arbitrary/definition/NextArbitrary.itest.spec.ts b/test/unit/check/arbitrary/definition/NextArbitrary.itest.spec.ts
index 99d351c1..8c8c6ffc 100644
--- a/test/unit/check/arbitrary/definition/NextArbitrary.itest.spec.ts
+++ b/test/unit/check/arbitrary/definition/NextArbitrary.itest.spec.ts
@@ -107,7 +107,7 @@ describe('NextArbitrary', () => {
           return complexInstance;
         }
         generate(_mrng: Random): NextValue<MyArbitraryOutput> {
-          return new NextValue(this.create(10));
+          return new NextValue(this.create(10), undefined);
         }
         canShrinkWithoutContext(value: unknown): value is MyArbitraryOutput {
           throw new Error('No call expected in current scenario');
@@ -115,8 +115,8 @@ describe('NextArbitrary', () => {
         shrink(v: MyArbitraryOutput, _context?: unknown): Stream<NextValue<MyArbitraryOutput>> {
           const value = v.value;
           return Stream.of(
-            ...(value - 2 >= 0 ? [new NextValue(this.create(value - 2))] : []),
-            ...(value - 1 >= 0 ? [new NextValue(this.create(value - 1))] : [])
+            ...(value - 2 >= 0 ? [new NextValue(this.create(value - 2), undefined)] : []),
+            ...(value - 1 >= 0 ? [new NextValue(this.create(value - 1), undefined)] : [])
           );
         }
       }
diff --git a/test/unit/check/arbitrary/definition/NextArbitrary.utest.spec.ts b/test/unit/check/arbitrary/definition/NextArbitrary.utest.spec.ts
index 8c71fd90..8db4dbf0 100644
--- a/test/unit/check/arbitrary/definition/NextArbitrary.utest.spec.ts
+++ b/test/unit/check/arbitrary/definition/NextArbitrary.utest.spec.ts
@@ -383,7 +383,7 @@ describe('NextArbitrary', () => {
 
       // Act
       const arb = new MyNextArbitrary().map(() => '');
-      const shrinks = arb.shrink('');
+      const shrinks = arb.shrink('', undefined);
 
       // Assert
       expect([...shrinks]).toHaveLength(0);
@@ -453,9 +453,9 @@ describe('NextArbitrary', () => {
     it('should return a mapped version of the stream produced by the source arbitrary for the unmapped value when provided an unmapper function', () => {
       // Arrange
       const expectedStreamValuesFromSource = Stream.of(
-        new NextValue('titi'),
-        new NextValue('toto'),
-        new NextValue('tutu')
+        new NextValue('titi', undefined),
+        new NextValue('toto', undefined),
+        new NextValue('tutu', undefined)
       );
       const generate = jest.fn();
       const canShrinkWithoutContext = jest.fn();
@@ -471,14 +471,14 @@ describe('NextArbitrary', () => {
 
       // Act
       const arb = new MyNextArbitrary().map((tag) => Symbol.for(tag), unmapper);
-      const shrinks = [...arb.shrink(originalValue)];
+      const shrinks = [...arb.shrink(originalValue, undefined)];
 
       // Assert
       expect(shrinks.map((s) => s.value)).toEqual([Symbol.for('titi'), Symbol.for('toto'), Symbol.for('tutu')]);
       expect(unmapper).toHaveBeenCalledTimes(1);
       expect(unmapper).toHaveBeenCalledWith(originalValue);
       expect(shrink).toHaveBeenCalledTimes(1);
-      expect(shrink).toHaveBeenCalledWith(unmapperOutput);
+      expect(shrink).toHaveBeenCalledWith(unmapperOutput, undefined);
     });
   });
 
@@ -727,7 +727,7 @@ describe('NextArbitrary', () => {
 
       // Act
       const arb = new MyNextArbitrary().chain(() => new MyNextArbitrary());
-      const shrinks = arb.shrink('');
+      const shrinks = arb.shrink('', undefined);
 
       // Assert
       expect([...shrinks]).toHaveLength(0);
diff --git a/test/unit/check/arbitrary/definition/NextValue.utest.spec.ts b/test/unit/check/arbitrary/definition/NextValue.utest.spec.ts
index 9d40abff..3f4e1a84 100644
--- a/test/unit/check/arbitrary/definition/NextValue.utest.spec.ts
+++ b/test/unit/check/arbitrary/definition/NextValue.utest.spec.ts
@@ -9,7 +9,7 @@ describe('NextValue', () => {
       const instance = { [cloneMethod]: clone };
 
       // Act
-      const nextValue = new NextValue(instance);
+      const nextValue = new NextValue(instance, undefined);
       const value = nextValue.value;
 
       // Assert
@@ -23,7 +23,7 @@ describe('NextValue', () => {
       const instance = { [cloneMethod]: clone };
 
       // Act
-      const nextValue = new NextValue(instance);
+      const nextValue = new NextValue(instance, undefined);
       const value = nextValue.value;
       const value2 = nextValue.value;
 
diff --git a/test/unit/check/arbitrary/helpers/ArrayInt64Arbitrary.utest.spec.ts b/test/unit/check/arbitrary/helpers/ArrayInt64Arbitrary.utest.spec.ts
index e1461284..ccdafc94 100644
--- a/test/unit/check/arbitrary/helpers/ArrayInt64Arbitrary.utest.spec.ts
+++ b/test/unit/check/arbitrary/helpers/ArrayInt64Arbitrary.utest.spec.ts
@@ -196,13 +196,14 @@ describe('arrayInt64', () => {
     it('should shrink strictly positive value for positive range including zero', () => {
       // Arrange
       const arb = arrayInt64({ sign: 1, data: [0, 0] }, { sign: 1, data: [0, 10] });
-      const source = new NextValue({ sign: 1, data: [0, 8] }); // no context
+      const source = new NextValue({ sign: 1, data: [0, 8] }, undefined); // no context
 
       // Act
       const tree = buildNextShrinkTree(arb, source);
       const renderedTree = renderTree(tree).join('\n');
 
       // Assert
+      expect(arb.canShrinkWithoutContext(source.value)).toBe(true);
       //   When there is no more option, the shrinker retry one time with the value
       //   current-1 to check if something that changed outside (another value not itself)
       //   may have changed the situation
@@ -249,13 +250,14 @@ describe('arrayInt64', () => {
     it('should shrink strictly positive value for range not including zero', () => {
       // Arrange
       const arb = arrayInt64({ sign: 1, data: [1, 10] }, { sign: 1, data: [1, 20] });
-      const source = new NextValue({ sign: 1, data: [1, 18] }); // no context
+      const source = new NextValue({ sign: 1, data: [1, 18] }, undefined); // no context
 
       // Act
       const tree = buildNextShrinkTree(arb, source);
       const renderedTree = renderTree(tree).join('\n');
 
       // Assert
+      expect(arb.canShrinkWithoutContext(source.value)).toBe(true);
       //   As the range [[1,10], [1,20]] and the value [1,18]
       //   are just offset by +[1,10] compared to the first case,
       //   the rendered tree will be offset by [1,10] too
@@ -302,13 +304,14 @@ describe('arrayInt64', () => {
     it('should shrink strictly negative value for negative range including zero', () => {
       // Arrange
       const arb = arrayInt64({ sign: -1, data: [0, 10] }, { sign: 1, data: [0, 0] });
-      const source = new NextValue({ sign: -1, data: [0, 8] }); // no context
+      const source = new NextValue({ sign: -1, data: [0, 8] }, undefined); // no context
 
       // Act
       const tree = buildNextShrinkTree(arb, source);
       const renderedTree = renderTree(tree).join('\n');
 
       // Assert
+      expect(arb.canShrinkWithoutContext(source.value)).toBe(true);
       //   As the range [-10, 0] and the value -8
       //   are the opposite of first case, the rendered tree will be the same except
       //   it contains opposite values
@@ -362,7 +365,7 @@ describe('arrayInt64', () => {
       const sourceValue: ArrayInt64 = { sign: 1, data: [0, 8] };
 
       // Act
-      const treeNew = buildNextShrinkTree(arbNew, new NextValue(sourceValue));
+      const treeNew = buildNextShrinkTree(arbNew, new NextValue(sourceValue, undefined));
       const treeOld = buildShrinkTree(arbOld.contextualShrinkableFor(sourceValue));
 
       // Assert
@@ -375,7 +378,7 @@ describe('arrayInt64', () => {
       const sourceValue: ArrayInt64 = { sign: 1, data: [1, 18] };
 
       // Act
-      const treeNew = buildNextShrinkTree(arbNew, new NextValue(sourceValue));
+      const treeNew = buildNextShrinkTree(arbNew, new NextValue(sourceValue, undefined));
       const treeOld = buildShrinkTree(arbOld.contextualShrinkableFor(sourceValue));
 
       // Assert
@@ -388,7 +391,7 @@ describe('arrayInt64', () => {
       const sourceValue: ArrayInt64 = { sign: -1, data: [0, 8] };
 
       // Act
-      const treeNew = buildNextShrinkTree(arbNew, new NextValue(sourceValue));
+      const treeNew = buildNextShrinkTree(arbNew, new NextValue(sourceValue, undefined));
       const treeOld = buildShrinkTree(arbOld.contextualShrinkableFor(sourceValue));
 
       // Assert