Skip to content

Commit 1066589

Browse files
dmurvihilldubzzz
andauthored
🏷️ Add union type overloads for nat() and bigInt() (#6119)
**Description** Adds the following function definitions: ```typescript declare function nat(arg?: number | NatConstraints): Arbitrary<number>; declare function bigInt(...args: [] | [bigint, bigint] | [BigIntConstraints]): Arbitrary<bigint>; ``` Previously, passing a `number | NatConstraints` to `nat` would fail: ``` nat can accept a number, but number | NatConstraints is not assignable to number. nat can accept a NatConstraints, but number | NatConstraints is not assignable to NatConstraints. ``` And similar behavior for `bigInt`. These are the only arbitraries that require appropriate union type overloads. Fixes #6073. <!-- You can provide any additional context to help into understanding what's this PR is attempting to solve: reproduction of a bug, code snippets... --> **Checklist** — _Don't delete this checklist and make sure you do the following before opening the PR_ - [x] The name of my PR follows [gitmoji](https://gitmoji.dev/) specification - [x] My PR references one of several related issues (if any) - [x] New features or breaking changes must come with an associated Issue or Discussion - [x] My PR does not add any new dependency without an associated Issue or Discussion - [x] My PR includes bumps details, please run `pnpm run bump` and flag the impacts properly - [x] My PR adds relevant tests and they would have failed without my PR (when applicable) <!-- More about contributing at https://github.com/dubzzz/fast-check/blob/main/CONTRIBUTING.md --> **Advanced** <!-- How to fill the advanced section is detailed below! --> - [x] Category: 🏷️ Types change. - [x] Impacts: Reflection on calls to `bigInt` or `nat` may be impacted (see the changes to `double.spec.ts` for an example). This shouldn't be guaranteed stable IMO, since it's very unlikely that production software would need rely on such a thing. --------- Co-authored-by: Nicolas DUBIEN <github@dubien.org>
1 parent 77c42c2 commit 1066589

File tree

6 files changed

+106
-2
lines changed

6 files changed

+106
-2
lines changed

.changeset/light-eggs-cut.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'fast-check': patch
3+
---
4+
5+
Add union type overloads for `nat` and `bigInt`

packages/fast-check/src/arbitrary/bigInt.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,15 @@ function bigInt(min: bigint, max: bigint): Arbitrary<bigint>;
8181
* @public
8282
*/
8383
function bigInt(constraints: BigIntConstraints): Arbitrary<bigint>;
84+
/**
85+
* For bigint between min (included) and max (included)
86+
*
87+
* @param args - Either min/max bounds as an object or constraints to apply when building instances
88+
*
89+
* @remarks Since 2.6.0
90+
* @public
91+
*/
92+
function bigInt(...args: [] | [bigint, bigint] | [BigIntConstraints]): Arbitrary<bigint>;
8493
function bigInt(...args: [] | [bigint, bigint] | [BigIntConstraints]): Arbitrary<bigint> {
8594
const constraints = buildCompleteBigIntConstraints(extractBigIntConstraints(args));
8695
if (constraints.min > constraints.max) {

packages/fast-check/src/arbitrary/nat.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,15 @@ function nat(max: number): Arbitrary<number>;
4242
* @public
4343
*/
4444
function nat(constraints: NatConstraints): Arbitrary<number>;
45+
/**
46+
* For positive integers between 0 (included) and max (included)
47+
*
48+
* @param arg - Either a maximum number or constraints to apply when building instances
49+
*
50+
* @remarks Since 2.6.0
51+
* @public
52+
*/
53+
function nat(arg?: number | NatConstraints): Arbitrary<number>;
4554
function nat(arg?: number | NatConstraints): Arbitrary<number> {
4655
const max = typeof arg === 'number' ? arg : arg && arg.max !== undefined ? arg.max : 0x7fffffff;
4756
if (max < 0) {

packages/fast-check/test/unit/arbitrary/bigInt.spec.ts

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { describe, it, expect, vi } from 'vitest';
22
import * as fc from 'fast-check';
3-
import { bigInt } from '../../../src/arbitrary/bigInt';
3+
import { bigInt, type BigIntConstraints } from '../../../src/arbitrary/bigInt';
44

55
import { fakeArbitrary } from './__test-helpers__/ArbitraryHelpers';
66
import { declareCleaningHooksForSpies } from './__test-helpers__/SpyCleaner';
@@ -89,4 +89,44 @@ describe('bigInt', () => {
8989
expect(() => bigInt({ min: high, max: low })).toThrowError();
9090
}),
9191
));
92+
93+
it('should handle union type of [number, number] | BigIntConstraints', () =>
94+
fc.assert(
95+
fc.property(
96+
fc.oneof(
97+
// no argument
98+
fc.tuple(),
99+
// min, max range
100+
fc.tuple(fc.bigInt(), fc.bigInt()).map<[bigint, bigint]>((t) => (t[0] < t[1] ? [t[0], t[1]] : [t[1], t[0]])),
101+
// both min and max defined
102+
fc.tuple(
103+
fc.record({ min: fc.bigInt(), max: fc.bigInt() }).map((c) => ({
104+
min: c.min < c.max ? c.min : c.max,
105+
max: c.min < c.max ? c.max : c.min,
106+
})),
107+
),
108+
// one or the other maybe defined
109+
fc.tuple(fc.record({ min: fc.option(fc.bigInt(), { nil: undefined }) })),
110+
fc.tuple(fc.record({ max: fc.option(fc.bigInt(), { nil: undefined }) })),
111+
),
112+
(args: [] | [bigint, bigint] | [BigIntConstraints]) => {
113+
// Arrange
114+
const [expectedMin, expectedMax] =
115+
args.length === 0 ? [null, null] : args.length === 1 ? [args[0].min, args[0].max] : [args[0], args[1]];
116+
const instance = fakeBigIntArbitrary();
117+
const BigIntArbitrary = vi.spyOn(BigIntArbitraryMock, 'BigIntArbitrary');
118+
BigIntArbitrary.mockImplementation(() => instance);
119+
120+
// Act
121+
const arb = bigInt(...args);
122+
123+
// Assert
124+
expect(BigIntArbitrary).toHaveBeenCalledWith(
125+
expectedMin ?? expect.any(BigInt),
126+
expectedMax ?? expect.any(BigInt),
127+
);
128+
expect(arb).toBe(instance);
129+
},
130+
),
131+
));
92132
});

packages/fast-check/test/unit/arbitrary/double.spec.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,16 @@ describe('double', () => {
190190
expect(bigInt).toHaveBeenCalledTimes(2);
191191
const constraintsNoNaN = bigInt.mock.calls[0];
192192
const constraintsWithNaN = bigInt.mock.calls[1];
193+
if (constraintsNoNaN.length !== 1) {
194+
expect.fail(`Expected bigInt to be called with one constraints object; got ${constraintsNoNaN}`);
195+
}
196+
if (constraintsWithNaN.length !== 1) {
197+
expect.fail(`Expected bigInt to be called with one constraints object; got ${constraintsWithNaN}`);
198+
}
193199
if (max > Number.MIN_VALUE || (max > 0 && !ct.maxExcluded)) {
200+
if (constraintsNoNaN.length !== 1) {
201+
expect.fail(`Expected bigInt to be called with one constraints object; got ${constraintsNoNaN}`);
202+
}
194203
// max > 0 --> NaN will be added as the greatest value
195204
expect(constraintsWithNaN[0]).toEqual({
196205
min: constraintsNoNaN[0].min,
@@ -226,6 +235,12 @@ describe('double', () => {
226235
double({ ...ct, noNaN: true });
227236
const arb = double(ct);
228237
// Extract NaN "index"
238+
if (bigInt.mock.calls[0].length !== 1) {
239+
expect.fail(`Expected bigInt to be called with one constraints object; got ${bigInt.mock.calls[0]}`);
240+
}
241+
if (bigInt.mock.calls[1].length !== 1) {
242+
expect.fail(`Expected bigInt to be called with one constraints object; got ${bigInt.mock.calls[1]}`);
243+
}
229244
const [{ min: minNonNaN }] = bigInt.mock.calls[0];
230245
const [{ min: minNaN, max: maxNaN }] = bigInt.mock.calls[1];
231246
const indexForNaN = minNonNaN !== minNaN ? minNaN : maxNaN;

packages/fast-check/test/unit/arbitrary/nat.spec.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { describe, it, expect, vi } from 'vitest';
22
import * as fc from 'fast-check';
3-
import { nat } from '../../../src/arbitrary/nat';
3+
import { nat, type NatConstraints } from '../../../src/arbitrary/nat';
44

55
import { fakeArbitrary } from './__test-helpers__/ArbitraryHelpers';
66

@@ -96,4 +96,30 @@ describe('nat', () => {
9696
}),
9797
);
9898
});
99+
100+
it('should handle union type of number | NatConstraints', () =>
101+
fc.assert(
102+
fc.property(
103+
fc.oneof(
104+
fc.constant(undefined),
105+
fc.integer({ min: 0 }),
106+
fc.record({}),
107+
fc.record({ max: fc.integer({ min: 0 }) }),
108+
),
109+
(constraints: number | NatConstraints | undefined) => {
110+
// Arrange
111+
const expectedMax = typeof constraints === 'number' ? constraints : (constraints?.max ?? null);
112+
const instance = fakeIntegerArbitrary();
113+
const IntegerArbitrary = vi.spyOn(IntegerArbitraryMock, 'IntegerArbitrary');
114+
IntegerArbitrary.mockImplementation(() => instance);
115+
116+
// Act
117+
const arb = constraints === undefined ? nat() : nat(constraints);
118+
119+
// Assert
120+
expect(IntegerArbitrary).toHaveBeenCalledWith(0, expectedMax ?? expect.anything());
121+
expect(arb).toBe(instance);
122+
},
123+
),
124+
));
99125
});

0 commit comments

Comments
 (0)