diff --git a/.changeset/three-eggs-doubt.md b/.changeset/three-eggs-doubt.md new file mode 100644 index 00000000000..6bd3e56c831 --- /dev/null +++ b/.changeset/three-eggs-doubt.md @@ -0,0 +1,5 @@ +--- +"fast-check": minor +--- + +⚡️ Faster initialization of `string` with pre-cached slices diff --git a/packages/fast-check/src/arbitrary/_internals/helpers/SlicesForStringBuilder.ts b/packages/fast-check/src/arbitrary/_internals/helpers/SlicesForStringBuilder.ts index c386c99b4e1..aed02c30040 100644 --- a/packages/fast-check/src/arbitrary/_internals/helpers/SlicesForStringBuilder.ts +++ b/packages/fast-check/src/arbitrary/_internals/helpers/SlicesForStringBuilder.ts @@ -1,5 +1,5 @@ import type { Arbitrary } from '../../../check/arbitrary/definition/Arbitrary'; -import { safePush } from '../../../utils/globals'; +import { safeGet, safePush, safeSet } from '../../../utils/globals'; import type { StringSharedConstraints } from '../../_shared/StringSharedConstraints'; import { patternsToStringUnmapperIsValidLength } from '../mappers/PatternsToString'; import { tokenizeString } from './TokenizeString'; @@ -71,16 +71,35 @@ export function createSlicesForStringLegacy( } /** @internal */ -export function createSlicesForString( - charArbitrary: Arbitrary, - constraints: StringSharedConstraints, -): string[][] { +const slicesPerArbitrary = new WeakMap, string[][]>(); + +/** @internal */ +function createSlicesForStringNoConstraints(charArbitrary: Arbitrary): string[][] { const slicesForString: string[][] = []; for (const dangerous of dangerousStrings) { const candidate = tokenizeString(charArbitrary, dangerous); - if (candidate !== undefined && patternsToStringUnmapperIsValidLength(candidate, constraints)) { + if (candidate !== undefined) { safePush(slicesForString, candidate); } } return slicesForString; } + +/** @internal */ +export function createSlicesForString( + charArbitrary: Arbitrary, + constraints: StringSharedConstraints, +): string[][] { + let slices = safeGet(slicesPerArbitrary, charArbitrary); + if (slices === undefined) { + slices = createSlicesForStringNoConstraints(charArbitrary); + safeSet(slicesPerArbitrary, charArbitrary, slices); + } + const slicesForConstraints: string[][] = []; + for (const slice of slices) { + if (patternsToStringUnmapperIsValidLength(slice, constraints)) { + safePush(slicesForConstraints, slice); + } + } + return slicesForConstraints; +} diff --git a/packages/fast-check/src/utils/globals.ts b/packages/fast-check/src/utils/globals.ts index 2351639532e..a2c0edb7aa5 100644 --- a/packages/fast-check/src/utils/globals.ts +++ b/packages/fast-check/src/utils/globals.ts @@ -296,6 +296,37 @@ export function safeHas(instance: Set, value: T): boolean { return safeApply(untouchedHas, instance, [value]); } +// WeakMap + +const untouchedSet = WeakMap.prototype.set; +const untouchedGet = WeakMap.prototype.get; +function extractSet(instance: WeakMap) { + try { + return instance.set; + } catch (err) { + return undefined; + } +} +function extractGet(instance: WeakMap) { + try { + return instance.get; + } catch (err) { + return undefined; + } +} +export function safeSet(instance: WeakMap, key: T, value: U): WeakMap { + if (extractSet(instance) === untouchedSet) { + return instance.set(key, value); + } + return safeApply(untouchedSet, instance, [key, value]); +} +export function safeGet(instance: WeakMap, key: T): U | undefined { + if (extractGet(instance) === untouchedGet) { + return instance.get(key); + } + return safeApply(untouchedGet, instance, [key]); +} + // String const untouchedSplit: (separator: string | RegExp, limit?: number) => string[] = String.prototype.split;