-
-
Notifications
You must be signed in to change notification settings - Fork 200
✨ Add circular option to fc.letrec
#6040
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
b797107
b924c68
1568369
6231a35
f7cdd37
a766dff
6b98ec7
d57b84c
7d0427b
224ba73
bc21ce1
c2e0ad9
25d6ea6
1cda443
2f7caab
9a4d162
85bc340
8e81a17
0069116
a0763ee
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,13 @@ | ||
| import { LazyArbitrary } from './_internals/LazyArbitrary'; | ||
| import type { Arbitrary } from '../check/arbitrary/definition/Arbitrary'; | ||
| import { safeHasOwnProperty } from '../utils/globals'; | ||
| import { nat } from './nat.js'; | ||
| import { record } from './record.js'; | ||
| import { array } from './array.js'; | ||
|
|
||
| const safeArrayIsArray = Array.isArray; | ||
| const safeObjectCreate = Object.create; | ||
| const safeObjectEntries = Object.entries; | ||
|
|
||
| /** | ||
| * Type of the value produced by {@link letrec} | ||
|
|
@@ -50,6 +55,131 @@ | |
| */ | ||
| export type LetrecLooselyTypedBuilder<T> = (tie: LetrecLooselyTypedTie) => LetrecValue<T>; | ||
|
|
||
| /** | ||
| * Constraints to be applied on {@link letrec} | ||
| * @remarks Since 4.2.0 | ||
| * @public | ||
| */ | ||
| export interface LetrecConstraints { | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's do that in a follow up PR so that we unlock this one: We should import and export it from fast-check-default.ts so that end users can import it too. It should be a type rather than an interface to fest with what we currently do for all others (to be confirmed, I have rechecked). |
||
| /** | ||
| * Generate objects with circular references | ||
| * @defaultValue false | ||
| * @remarks Since 4.2.0 | ||
dubzzz marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| */ | ||
| circular?: boolean; | ||
| } | ||
|
|
||
| function nonCircularLetrec<T>(builder: LetrecLooselyTypedBuilder<T> | LetrecTypedBuilder<T>): LetrecValue<T> { | ||
| const lazyArbs: { [K in keyof T]?: LazyArbitrary<unknown> } = safeObjectCreate(null); | ||
| const tie = (key: keyof T): Arbitrary<any> => { | ||
| if (!safeHasOwnProperty(lazyArbs, key)) { | ||
| // Call to hasOwnProperty ensures that the property key will be defined | ||
| lazyArbs[key] = new LazyArbitrary(String(key)); | ||
| } | ||
| // eslint-disable-next-line @typescript-eslint/no-non-null-assertion | ||
| return lazyArbs[key]!; | ||
| }; | ||
| const strictArbs = builder(tie as any); | ||
| for (const key in strictArbs) { | ||
| if (!safeHasOwnProperty(strictArbs, key)) { | ||
| // Prevents accidental iteration over properties inherited from an object’s prototype | ||
| continue; | ||
| } | ||
| const lazyAtKey: LazyArbitrary<unknown> | undefined = lazyArbs[key]; | ||
| const lazyArb = lazyAtKey !== undefined ? lazyAtKey : new LazyArbitrary(key); | ||
| lazyArb.underlying = strictArbs[key]; | ||
| lazyArbs[key] = lazyArb; | ||
| } | ||
| return strictArbs; | ||
| } | ||
|
|
||
| function circularLetrec<T>(builder: LetrecLooselyTypedBuilder<T> | LetrecTypedBuilder<T>): LetrecValue<T> { | ||
| const lazyArbs: { [K in keyof T]?: LazyArbitrary<unknown> } = safeObjectCreate(null); | ||
| const tie = (key: keyof T): Arbitrary<any> => { | ||
| if (!safeHasOwnProperty(lazyArbs, key)) { | ||
| // Call to hasOwnProperty ensures that the property key will be defined | ||
| lazyArbs[key] = new LazyArbitrary(String(key)); | ||
| } | ||
| // eslint-disable-next-line @typescript-eslint/no-non-null-assertion | ||
| return lazyArbs[key]!; | ||
| }; | ||
| const strictArbs = builder(tie as any); | ||
TomerAberbach marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| // Symbol to replace with a potentially circular reference later. | ||
| const placeholderSymbol = Symbol('placeholder'); | ||
| const poolArbs: { [K in keyof T]: Arbitrary<unknown[]> } = safeObjectCreate(null); | ||
| for (const key in strictArbs) { | ||
| if (!safeHasOwnProperty(strictArbs, key)) { | ||
| // Prevents accidental iteration over properties inherited from an object’s prototype | ||
| continue; | ||
| } | ||
| const lazyAtKey: LazyArbitrary<unknown> | undefined = lazyArbs[key]; | ||
| const lazyArb = lazyAtKey !== undefined ? lazyAtKey : new LazyArbitrary(key); | ||
| lazyArb.underlying = nat().map((index) => ({ | ||
| [placeholderSymbol]: { key, index }, | ||
| })); | ||
| lazyArbs[key] = lazyArb; | ||
| // eslint-disable-next-line @typescript-eslint/no-non-null-assertion | ||
| poolArbs[key] = array(strictArbs[key]!, { minLength: 1 }); | ||
TomerAberbach marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| for (const key in strictArbs) { | ||
| if (!safeHasOwnProperty(strictArbs, key)) { | ||
| // Prevents accidental iteration over properties inherited from an object’s prototype | ||
| continue; | ||
| } | ||
|
|
||
| const poolsArb = record(poolArbs as any) as Arbitrary<{ [K in keyof T]: unknown[] }>; | ||
| strictArbs[key] = poolsArb.map((pools) => { | ||
TomerAberbach marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| const visited = new WeakSet(); | ||
| function deref(value: unknown, source?: Record<PropertyKey, unknown>, sourceKey?: PropertyKey): unknown { | ||
| if (typeof value !== 'object' || value === null) { | ||
| return value; | ||
| } | ||
|
|
||
| if (visited.has(value)) { | ||
| return value; | ||
| } | ||
| visited.add(value); | ||
|
|
||
| if (safeHasOwnProperty(value, placeholderSymbol)) { | ||
| const { key, index } = ( | ||
| value as { | ||
| [placeholderSymbol]: { key: keyof T; index: number }; | ||
| } | ||
| )[placeholderSymbol]; | ||
| const pool = pools[key]; | ||
| const poolValue = pool[index % pool.length]; | ||
| if (source !== undefined && sourceKey !== undefined) { | ||
| source[sourceKey] = poolValue; | ||
| return value; | ||
| } else { | ||
| return poolValue; | ||
| } | ||
| } | ||
|
|
||
| if (safeArrayIsArray(value)) { | ||
| for (let i = 0; i < value.length; i++) { | ||
| deref(value[i], value as unknown as Record<PropertyKey, unknown>, i); | ||
| } | ||
| } else { | ||
| for (const [key, item] of safeObjectEntries(value)) { | ||
| deref(item, value as Record<PropertyKey, unknown>, key); | ||
| } | ||
| } | ||
|
|
||
| return value; | ||
| } | ||
|
|
||
| // TODO: Do we need to clone here? | ||
| deref(pools); | ||
| return pools[key][0]; | ||
| }) as (typeof strictArbs)[typeof key]; | ||
| } | ||
|
|
||
| return strictArbs; | ||
| } | ||
|
|
||
| /** | ||
| * For mutually recursive types | ||
| * | ||
|
|
@@ -72,7 +202,10 @@ | |
| * @remarks Since 1.16.0 | ||
| * @public | ||
| */ | ||
| export function letrec<T>(builder: T extends Record<string, unknown> ? LetrecTypedBuilder<T> : never): LetrecValue<T>; | ||
| export function letrec<T>( | ||
| builder: T extends Record<string, unknown> ? LetrecTypedBuilder<T> : never, | ||
| constraints?: LetrecConstraints, | ||
| ): LetrecValue<T>; | ||
| /** | ||
| * For mutually recursive types | ||
| * | ||
|
|
@@ -92,27 +225,10 @@ | |
| * @remarks Since 1.16.0 | ||
| * @public | ||
| */ | ||
| export function letrec<T>(builder: LetrecLooselyTypedBuilder<T>): LetrecValue<T>; | ||
| export function letrec<T>(builder: LetrecLooselyTypedBuilder<T> | LetrecTypedBuilder<T>): LetrecValue<T> { | ||
| const lazyArbs: { [K in keyof T]?: LazyArbitrary<unknown> } = safeObjectCreate(null); | ||
| const tie = (key: keyof T): Arbitrary<any> => { | ||
| if (!safeHasOwnProperty(lazyArbs, key)) { | ||
| // Call to hasOwnProperty ensures that the property key will be defined | ||
| lazyArbs[key] = new LazyArbitrary(String(key)); | ||
| } | ||
| // eslint-disable-next-line @typescript-eslint/no-non-null-assertion | ||
| return lazyArbs[key]!; | ||
| }; | ||
| const strictArbs = builder(tie as any); | ||
| for (const key in strictArbs) { | ||
| if (!safeHasOwnProperty(strictArbs, key)) { | ||
| // Prevents accidental iteration over properties inherited from an object’s prototype | ||
| continue; | ||
| } | ||
| const lazyAtKey: LazyArbitrary<unknown> | undefined = lazyArbs[key]; | ||
| const lazyArb = lazyAtKey !== undefined ? lazyAtKey : new LazyArbitrary(key); | ||
| lazyArb.underlying = strictArbs[key]; | ||
| lazyArbs[key] = lazyArb; | ||
| } | ||
| return strictArbs; | ||
| export function letrec<T>(builder: LetrecLooselyTypedBuilder<T>, constraints?: LetrecConstraints): LetrecValue<T>; | ||
| export function letrec<T>( | ||
| builder: LetrecLooselyTypedBuilder<T> | LetrecTypedBuilder<T>, | ||
| constraints: LetrecConstraints = {}, | ||
| ): LetrecValue<T> { | ||
| return (constraints.circular ? circularLetrec : nonCircularLetrec)(builder); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,12 @@ | ||
| packages: | ||
| - 'examples' | ||
| - 'packages/*' | ||
| - 'website' | ||
| - examples | ||
| - packages/* | ||
| - website | ||
|
|
||
| onlyBuiltDependencies: | ||
| - '@swc/core' | ||
| - core-js | ||
| - core-js-pure | ||
| - es5-ext | ||
| - esbuild | ||
| - unrs-resolver |
Uh oh!
There was an error while loading. Please reload this page.