effect 3.11.5 → 3.11.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/Arbitrary.ts CHANGED
@@ -11,6 +11,7 @@ import * as Option from "./Option.js"
11
11
  import * as Predicate from "./Predicate.js"
12
12
  import type * as Schema from "./Schema.js"
13
13
  import * as AST from "./SchemaAST.js"
14
+ import type * as Types from "./Types.js"
14
15
 
15
16
  /**
16
17
  * @category model
@@ -25,8 +26,9 @@ export interface LazyArbitrary<A> {
25
26
  * @since 3.10.0
26
27
  */
27
28
  export interface ArbitraryGenerationContext {
28
- readonly depthIdentifier?: string
29
29
  readonly maxDepth: number
30
+ readonly depthIdentifier?: string
31
+ readonly constraints?: StringConstraints | NumberConstraints | BigIntConstraints | ArrayConstraints
30
32
  }
31
33
 
32
34
  /**
@@ -59,58 +61,196 @@ export const make = <A, I, R>(schema: Schema.Schema<A, I, R>): FastCheck.Arbitra
59
61
 
60
62
  const getArbitraryAnnotation = AST.getAnnotation<ArbitraryAnnotation<any, any>>(AST.ArbitraryAnnotationId)
61
63
 
62
- const getRefinementFromArbitrary = (
63
- ast: AST.Refinement,
64
- ctx: Context,
65
- path: ReadonlyArray<PropertyKey>
66
- ) => {
67
- const constraints = combineConstraints(ctx.constraints, getConstraints(ast))
68
- return go(ast.from, constraints ? { ...ctx, constraints } : ctx, path)
69
- }
64
+ type Op = Succeed | Deferred
70
65
 
71
- const getSuspendedContext = (
72
- ctx: Context,
73
- ast: AST.Suspend
74
- ): Context => {
75
- if (ctx.depthIdentifier !== undefined) {
76
- return ctx
66
+ /**
67
+ * Represents an arbitrary with optional filters.
68
+ */
69
+ class Succeed {
70
+ readonly _tag = "Succeed"
71
+ constructor(
72
+ readonly lazyArbitrary: LazyArbitrary<any>,
73
+ readonly filters: Array<Predicate.Predicate<any>> = []
74
+ ) {}
75
+
76
+ toLazyArbitrary(): LazyArbitrary<any> {
77
+ return (fc) => {
78
+ let out = this.lazyArbitrary(fc)
79
+ for (const f of this.filters) {
80
+ out = out.filter(f)
81
+ }
82
+ return out
83
+ }
77
84
  }
78
- const depthIdentifier = AST.getIdentifierAnnotation(ast).pipe(
79
- Option.orElse(() => AST.getIdentifierAnnotation(ast.f())),
80
- Option.getOrElse(() => "SuspendDefaultDepthIdentifier")
81
- )
82
- return { ...ctx, depthIdentifier }
83
85
  }
84
86
 
85
- const getSuspendedArray = (
86
- fc: typeof FastCheck,
87
- depthIdentifier: string,
88
- maxDepth: number,
89
- item: FastCheck.Arbitrary<any>,
90
- constraints?: FastCheck.ArrayConstraints
91
- ) => {
92
- let minLength = 1
93
- let maxLength = 2
94
- if (constraints && constraints.minLength !== undefined && constraints.minLength > minLength) {
95
- minLength = constraints.minLength
96
- if (minLength > maxLength) {
97
- maxLength = minLength
87
+ /**
88
+ * Represents a deferred arbitrary value generator with optional filters.
89
+ */
90
+ class Deferred {
91
+ readonly _tag = "Deferred"
92
+ constructor(
93
+ readonly config: Config,
94
+ readonly filters: Array<Predicate.Predicate<any>> = []
95
+ ) {}
96
+
97
+ toLazyArbitrary(ctx: ArbitraryGenerationContext, path: ReadonlyArray<PropertyKey>): LazyArbitrary<any> {
98
+ const config = this.config
99
+ switch (config._tag) {
100
+ case "StringConstraints": {
101
+ const pattern = config.pattern
102
+ return pattern !== undefined ?
103
+ (fc) => fc.stringMatching(new RegExp(pattern)) :
104
+ (fc) => fc.string(config.constraints)
105
+ }
106
+ case "NumberConstraints": {
107
+ return config.isInteger ?
108
+ (fc) => fc.integer(config.constraints) :
109
+ (fc) => fc.float(config.constraints)
110
+ }
111
+ case "BigIntConstraints":
112
+ return (fc) => fc.bigInt(config.constraints)
113
+ case "ArrayConstraints":
114
+ return goTupleType(config.ast, ctx, path, config.constraints)
98
115
  }
99
116
  }
100
- return fc.oneof(
101
- { maxDepth, depthIdentifier },
102
- fc.constant([]),
103
- fc.array(item, { minLength, maxLength })
104
- )
105
117
  }
106
118
 
107
- interface Context extends ArbitraryGenerationContext {
108
- readonly constraints?: Constraints
119
+ interface StringConstraints {
120
+ readonly _tag: "StringConstraints"
121
+ readonly constraints: FastCheck.StringSharedConstraints
122
+ readonly pattern?: string
123
+ }
124
+
125
+ /** @internal */
126
+ export const makeStringConstraints = (options: {
127
+ readonly minLength?: number | undefined
128
+ readonly maxLength?: number | undefined
129
+ readonly pattern?: string | undefined
130
+ }): StringConstraints => {
131
+ const out: Types.Mutable<StringConstraints> = {
132
+ _tag: "StringConstraints",
133
+ constraints: {}
134
+ }
135
+ if (Predicate.isNumber(options.minLength)) {
136
+ out.constraints.minLength = options.minLength
137
+ }
138
+ if (Predicate.isNumber(options.maxLength)) {
139
+ out.constraints.maxLength = options.maxLength
140
+ }
141
+ if (Predicate.isString(options.pattern)) {
142
+ out.pattern = options.pattern
143
+ }
144
+ return out
145
+ }
146
+
147
+ interface NumberConstraints {
148
+ readonly _tag: "NumberConstraints"
149
+ readonly constraints: FastCheck.FloatConstraints
150
+ readonly isInteger: boolean
109
151
  }
110
152
 
153
+ /** @internal */
154
+ export const makeNumberConstraints = (options: {
155
+ readonly isInteger?: boolean | undefined
156
+ readonly min?: number | undefined
157
+ readonly minExcluded?: boolean | undefined
158
+ readonly max?: number | undefined
159
+ readonly maxExcluded?: boolean | undefined
160
+ readonly noNaN?: boolean | undefined
161
+ readonly noDefaultInfinity?: boolean | undefined
162
+ }): NumberConstraints => {
163
+ const out: Types.Mutable<NumberConstraints> = {
164
+ _tag: "NumberConstraints",
165
+ constraints: {},
166
+ isInteger: options.isInteger ?? false
167
+ }
168
+ if (Predicate.isNumber(options.min)) {
169
+ out.constraints.min = Math.fround(options.min)
170
+ }
171
+ if (Predicate.isBoolean(options.minExcluded)) {
172
+ out.constraints.minExcluded = options.minExcluded
173
+ }
174
+ if (Predicate.isNumber(options.max)) {
175
+ out.constraints.max = Math.fround(options.max)
176
+ }
177
+ if (Predicate.isBoolean(options.maxExcluded)) {
178
+ out.constraints.maxExcluded = options.maxExcluded
179
+ }
180
+ if (Predicate.isBoolean(options.noNaN)) {
181
+ out.constraints.noNaN = options.noNaN
182
+ }
183
+ if (Predicate.isBoolean(options.noDefaultInfinity)) {
184
+ out.constraints.noDefaultInfinity = options.noDefaultInfinity
185
+ }
186
+ return out
187
+ }
188
+
189
+ interface BigIntConstraints {
190
+ readonly _tag: "BigIntConstraints"
191
+ readonly constraints: FastCheck.BigIntConstraints
192
+ }
193
+
194
+ /** @internal */
195
+ export const makeBigIntConstraints = (options: {
196
+ readonly min?: bigint | undefined
197
+ readonly max?: bigint | undefined
198
+ }): BigIntConstraints => {
199
+ const out: Types.Mutable<BigIntConstraints> = {
200
+ _tag: "BigIntConstraints",
201
+ constraints: {}
202
+ }
203
+ if (Predicate.isBigInt(options.min)) {
204
+ out.constraints.min = options.min
205
+ }
206
+ if (Predicate.isBigInt(options.max)) {
207
+ out.constraints.max = options.max
208
+ }
209
+ return out
210
+ }
211
+
212
+ interface ArrayConstraints {
213
+ readonly _tag: "ArrayConstraints"
214
+ readonly constraints: FastCheck.ArrayConstraints
215
+ }
216
+
217
+ /** @internal */
218
+ export const makeArrayConstraints = (options: {
219
+ readonly minLength?: number | undefined
220
+ readonly maxLength?: number | undefined
221
+ }): ArrayConstraints => {
222
+ const out: Types.Mutable<ArrayConstraints> = {
223
+ _tag: "ArrayConstraints",
224
+ constraints: {}
225
+ }
226
+ if (Predicate.isNumber(options.minLength)) {
227
+ out.constraints.minLength = options.minLength
228
+ }
229
+ if (Predicate.isNumber(options.maxLength)) {
230
+ out.constraints.maxLength = options.maxLength
231
+ }
232
+ return out
233
+ }
234
+
235
+ interface ArrayConfig extends ArrayConstraints {
236
+ readonly ast: AST.TupleType
237
+ }
238
+
239
+ const makeArrayConfig = (options: {
240
+ readonly minLength?: number | undefined
241
+ readonly maxLength?: number | undefined
242
+ }, ast: AST.TupleType): ArrayConfig => {
243
+ return {
244
+ ast,
245
+ ...makeArrayConstraints(options)
246
+ }
247
+ }
248
+
249
+ type Config = StringConstraints | NumberConstraints | BigIntConstraints | ArrayConfig
250
+
111
251
  const go = (
112
252
  ast: AST.AST,
113
- ctx: Context,
253
+ ctx: ArbitraryGenerationContext,
114
254
  path: ReadonlyArray<PropertyKey>
115
255
  ): LazyArbitrary<any> => {
116
256
  const hook = getArbitraryAnnotation(ast)
@@ -118,70 +258,73 @@ const go = (
118
258
  switch (ast._tag) {
119
259
  case "Declaration":
120
260
  return hook.value(...ast.typeParameters.map((p) => go(p, ctx, path)), ctx)
121
- case "Refinement":
122
- return hook.value(getRefinementFromArbitrary(ast, ctx, path), ctx)
261
+ case "Refinement": {
262
+ const op = toOp(ast, ctx, path)
263
+ ctx = op._tag === "Deferred" ? { ...ctx, constraints: op.config } : ctx
264
+ const from = go(ast.from, ctx, path)
265
+ return new Succeed(hook.value(from, ctx), op.filters).toLazyArbitrary()
266
+ }
123
267
  default:
124
268
  return hook.value(ctx)
125
269
  }
126
270
  }
271
+ if (AST.isDeclaration(ast)) {
272
+ throw new Error(errors_.getArbitraryMissingAnnotationErrorMessage(path, ast))
273
+ }
274
+ const op = toOp(ast, ctx, path)
275
+ switch (op._tag) {
276
+ case "Succeed":
277
+ return op.toLazyArbitrary()
278
+ case "Deferred":
279
+ return new Succeed(op.toLazyArbitrary(ctx, path), op.filters).toLazyArbitrary()
280
+ }
281
+ }
282
+
283
+ const constStringConstraints = makeStringConstraints({})
284
+ const constNumberConstraints = makeNumberConstraints({})
285
+ const constBigIntConstraints = makeBigIntConstraints({})
286
+
287
+ /** @internal */
288
+ export const toOp = (
289
+ ast: AST.AST,
290
+ ctx: ArbitraryGenerationContext,
291
+ path: ReadonlyArray<PropertyKey>
292
+ ): Op => {
127
293
  switch (ast._tag) {
128
- case "Declaration": {
129
- throw new Error(errors_.getArbitraryMissingAnnotationErrorMessage(path, ast))
130
- }
294
+ case "Declaration":
295
+ return new Succeed(go(ast, ctx, path))
131
296
  case "Literal":
132
- return (fc) => fc.constant(ast.literal)
297
+ return new Succeed((fc) => fc.constant(ast.literal))
133
298
  case "UniqueSymbol":
134
- return (fc) => fc.constant(ast.symbol)
299
+ return new Succeed((fc) => fc.constant(ast.symbol))
135
300
  case "UndefinedKeyword":
136
- return (fc) => fc.constant(undefined)
301
+ return new Succeed((fc) => fc.constant(undefined))
137
302
  case "NeverKeyword":
138
- return () => {
139
- throw new Error(errors_.getArbitraryUnsupportedErrorMessage(path, ast))
140
- }
303
+ throw new Error(errors_.getArbitraryMissingAnnotationErrorMessage(path, ast))
304
+ case "VoidKeyword":
141
305
  case "UnknownKeyword":
142
306
  case "AnyKeyword":
143
- case "VoidKeyword":
144
- return (fc) => fc.anything()
307
+ return new Succeed((fc) => fc.anything())
145
308
  case "StringKeyword":
146
- return (fc) => {
147
- if (ctx.constraints) {
148
- switch (ctx.constraints._tag) {
149
- case "StringConstraints":
150
- return fc.string(ctx.constraints.constraints)
151
- }
152
- }
153
- return fc.string()
154
- }
309
+ return new Deferred(constStringConstraints)
155
310
  case "NumberKeyword":
156
- return (fc) => {
157
- if (ctx.constraints) {
158
- switch (ctx.constraints._tag) {
159
- case "NumberConstraints":
160
- return fc.float(ctx.constraints.constraints)
161
- case "IntegerConstraints":
162
- return fc.integer(ctx.constraints.constraints)
163
- }
164
- }
165
- return fc.float()
166
- }
311
+ return new Deferred(constNumberConstraints)
167
312
  case "BooleanKeyword":
168
- return (fc) => fc.boolean()
313
+ return new Succeed((fc) => fc.boolean())
169
314
  case "BigIntKeyword":
170
- return (fc) => {
171
- if (ctx.constraints) {
172
- switch (ctx.constraints._tag) {
173
- case "BigIntConstraints":
174
- return fc.bigInt(ctx.constraints.constraints)
175
- }
176
- }
177
- return fc.bigInt()
178
- }
315
+ return new Deferred(constBigIntConstraints)
179
316
  case "SymbolKeyword":
180
- return (fc) => fc.string().map((s) => Symbol.for(s))
317
+ return new Succeed((fc) => fc.string().map((s) => Symbol.for(s)))
181
318
  case "ObjectKeyword":
182
- return (fc) => fc.oneof(fc.object(), fc.array(fc.anything()))
183
- case "TemplateLiteral": {
184
- return (fc) => {
319
+ return new Succeed((fc) => fc.oneof(fc.object(), fc.array(fc.anything())))
320
+ case "Enums": {
321
+ if (ast.enums.length === 0) {
322
+ throw new Error(errors_.getArbitraryEmptyEnumErrorMessage(path))
323
+ }
324
+ return new Succeed((fc) => fc.oneof(...ast.enums.map(([_, value]) => fc.constant(value))))
325
+ }
326
+ case "TemplateLiteral":
327
+ return new Succeed((fc) => {
185
328
  const string = fc.string({ maxLength: 5 })
186
329
  const number = fc.float({ noDefaultInfinity: true }).filter((n) => !Number.isNaN(n))
187
330
 
@@ -208,72 +351,30 @@ const go = (
208
351
  })
209
352
 
210
353
  return fc.tuple(...components).map((spans) => spans.join(""))
211
- }
212
- }
213
- case "TupleType": {
214
- const elements: Array<LazyArbitrary<any>> = []
215
- let hasOptionals = false
216
- let i = 0
217
- for (const element of ast.elements) {
218
- elements.push(go(element.type, ctx, path.concat(i++)))
219
- if (element.isOptional) {
220
- hasOptionals = true
221
- }
222
- }
223
- const rest = ast.rest.map((annotatedAST) => go(annotatedAST.type, ctx, path))
224
- return (fc) => {
225
- // ---------------------------------------------
226
- // handle elements
227
- // ---------------------------------------------
228
- let output = fc.tuple(...elements.map((arb) => arb(fc)))
229
- if (hasOptionals) {
230
- const indexes = fc.tuple(
231
- ...ast.elements.map((element) => element.isOptional ? fc.boolean() : fc.constant(true))
232
- )
233
- output = output.chain((tuple) =>
234
- indexes.map((booleans) => {
235
- for (const [i, b] of booleans.reverse().entries()) {
236
- if (!b) {
237
- tuple.splice(booleans.length - i, 1)
238
- }
239
- }
240
- return tuple
241
- })
242
- )
354
+ })
355
+ case "Refinement": {
356
+ const from = toOp(ast.from, ctx, path)
357
+ const filters: Op["filters"] = [
358
+ ...from.filters,
359
+ (a) => Option.isNone(ast.filter(a, AST.defaultParseOption, ast))
360
+ ]
361
+ switch (from._tag) {
362
+ case "Succeed": {
363
+ return new Succeed(from.lazyArbitrary, filters)
243
364
  }
244
-
245
- // ---------------------------------------------
246
- // handle rest element
247
- // ---------------------------------------------
248
- if (Arr.isNonEmptyReadonlyArray(rest)) {
249
- const [head, ...tail] = rest
250
- const item = head(fc)
251
- const constraints: FastCheck.ArrayConstraints | undefined =
252
- ctx.constraints && ctx.constraints._tag === "ArrayConstraints"
253
- ? ctx.constraints.constraints
254
- : undefined
255
- output = output.chain((as) => {
256
- return (ctx.depthIdentifier !== undefined
257
- ? getSuspendedArray(fc, ctx.depthIdentifier, ctx.maxDepth, item, constraints)
258
- : fc.array(item, constraints)).map((rest) => [...as, ...rest])
259
- })
260
- // ---------------------------------------------
261
- // handle post rest elements
262
- // ---------------------------------------------
263
- for (let j = 0; j < tail.length; j++) {
264
- output = output.chain((as) => tail[j](fc).map((a) => [...as, a]))
265
- }
365
+ case "Deferred": {
366
+ return new Deferred(merge(from.config, getConstraints(from.config._tag, ast)), filters)
266
367
  }
267
-
268
- return output
269
368
  }
270
369
  }
370
+ case "TupleType":
371
+ return new Deferred(makeArrayConfig({}, ast))
271
372
  case "TypeLiteral": {
272
373
  const propertySignaturesTypes = ast.propertySignatures.map((ps) => go(ps.type, ctx, path.concat(ps.name)))
273
374
  const indexSignatures = ast.indexSignatures.map((is) =>
274
375
  [go(is.parameter, ctx, path), go(is.type, ctx, path)] as const
275
376
  )
276
- return (fc) => {
377
+ return new Succeed((fc) => {
277
378
  const arbs: any = {}
278
379
  const requiredKeys: Array<PropertyKey> = []
279
380
  // ---------------------------------------------
@@ -304,274 +405,222 @@ const go = (
304
405
  }
305
406
 
306
407
  return output
307
- }
408
+ })
308
409
  }
309
410
  case "Union": {
310
411
  const types = ast.types.map((member) => go(member, ctx, path))
311
- return (fc) => fc.oneof(...types.map((arb) => arb(fc)))
312
- }
313
- case "Enums": {
314
- if (ast.enums.length === 0) {
315
- throw new Error(errors_.getArbitraryEmptyEnumErrorMessage(path))
316
- }
317
- return (fc) => fc.oneof(...ast.enums.map(([_, value]) => fc.constant(value)))
318
- }
319
- case "Refinement": {
320
- const from = getRefinementFromArbitrary(ast, ctx, path)
321
- return (fc) => from(fc).filter((a) => Option.isNone(ast.filter(a, AST.defaultParseOption, ast)))
412
+ return new Succeed((fc) => fc.oneof(...types.map((arb) => arb(fc))))
322
413
  }
323
414
  case "Suspend": {
324
415
  const get = util_.memoizeThunk(() => {
325
416
  return go(ast.f(), getSuspendedContext(ctx, ast), path)
326
417
  })
327
- return (fc) => fc.constant(null).chain(() => get()(fc))
418
+ return new Succeed((fc) => fc.constant(null).chain(() => get()(fc)))
328
419
  }
329
420
  case "Transformation":
330
- return go(ast.to, ctx, path)
421
+ return new Succeed(go(ast.to, ctx, path))
331
422
  }
332
423
  }
333
424
 
334
- /** @internal */
335
- export class NumberConstraints {
336
- readonly _tag = "NumberConstraints"
337
- readonly constraints: FastCheck.FloatConstraints
338
- constructor(options: {
339
- readonly min?: number | undefined
340
- readonly max?: number | undefined
341
- readonly noNaN?: boolean | undefined
342
- readonly noDefaultInfinity?: boolean | undefined
343
- }) {
344
- this.constraints = {}
345
- if (Predicate.isNumber(options.min)) {
346
- this.constraints.min = Math.fround(options.min)
347
- }
348
- if (Predicate.isNumber(options.max)) {
349
- this.constraints.max = Math.fround(options.max)
350
- }
351
- if (Predicate.isBoolean(options.noNaN)) {
352
- this.constraints.noNaN = options.noNaN
353
- }
354
- if (Predicate.isBoolean(options.noDefaultInfinity)) {
355
- this.constraints.noDefaultInfinity = options.noDefaultInfinity
425
+ const goTupleType = (
426
+ ast: AST.TupleType,
427
+ ctx: ArbitraryGenerationContext,
428
+ path: ReadonlyArray<PropertyKey>,
429
+ constraints: FastCheck.ArrayConstraints
430
+ ): LazyArbitrary<any> => {
431
+ const elements: Array<LazyArbitrary<any>> = []
432
+ let hasOptionals = false
433
+ let i = 0
434
+ for (const element of ast.elements) {
435
+ elements.push(go(element.type, ctx, path.concat(i++)))
436
+ if (element.isOptional) {
437
+ hasOptionals = true
356
438
  }
357
439
  }
358
- }
359
-
360
- /** @internal */
361
- export class StringConstraints {
362
- readonly _tag = "StringConstraints"
363
- readonly constraints: FastCheck.StringSharedConstraints
364
- constructor(options: {
365
- readonly minLength?: number | undefined
366
- readonly maxLength?: number | undefined
367
- }) {
368
- this.constraints = {}
369
- if (Predicate.isNumber(options.minLength)) {
370
- this.constraints.minLength = options.minLength
371
- }
372
- if (Predicate.isNumber(options.maxLength)) {
373
- this.constraints.maxLength = options.maxLength
440
+ const rest = ast.rest.map((annotatedAST) => go(annotatedAST.type, ctx, path))
441
+ return (fc) => {
442
+ // ---------------------------------------------
443
+ // handle elements
444
+ // ---------------------------------------------
445
+ let output = fc.tuple(...elements.map((arb) => arb(fc)))
446
+ if (hasOptionals) {
447
+ const indexes = fc.tuple(
448
+ ...ast.elements.map((element) => element.isOptional ? fc.boolean() : fc.constant(true))
449
+ )
450
+ output = output.chain((tuple) =>
451
+ indexes.map((booleans) => {
452
+ for (const [i, b] of booleans.reverse().entries()) {
453
+ if (!b) {
454
+ tuple.splice(booleans.length - i, 1)
455
+ }
456
+ }
457
+ return tuple
458
+ })
459
+ )
374
460
  }
375
- }
376
- }
377
461
 
378
- /** @internal */
379
- export class IntegerConstraints {
380
- readonly _tag = "IntegerConstraints"
381
- readonly constraints: FastCheck.IntegerConstraints
382
- constructor(options: {
383
- readonly min?: number | undefined
384
- readonly max?: number | undefined
385
- }) {
386
- this.constraints = {}
387
- if (Predicate.isNumber(options.min)) {
388
- this.constraints.min = options.min
389
- }
390
- if (Predicate.isNumber(options.max)) {
391
- this.constraints.max = options.max
462
+ // ---------------------------------------------
463
+ // handle rest element
464
+ // ---------------------------------------------
465
+ if (Arr.isNonEmptyReadonlyArray(rest)) {
466
+ const [head, ...tail] = rest
467
+ const item = head(fc)
468
+ output = output.chain((as) => {
469
+ return (ctx.depthIdentifier !== undefined
470
+ ? getSuspendedArray(fc, ctx.depthIdentifier, ctx.maxDepth, item, constraints)
471
+ : fc.array(item, constraints)).map((rest) => [...as, ...rest])
472
+ })
473
+ // ---------------------------------------------
474
+ // handle post rest elements
475
+ // ---------------------------------------------
476
+ for (let j = 0; j < tail.length; j++) {
477
+ output = output.chain((as) => tail[j](fc).map((a) => [...as, a]))
478
+ }
392
479
  }
393
- }
394
- }
395
480
 
396
- /** @internal */
397
- export class ArrayConstraints {
398
- readonly _tag = "ArrayConstraints"
399
- readonly constraints: FastCheck.ArrayConstraints
400
- constructor(options: {
401
- readonly minLength?: number | undefined
402
- readonly maxLength?: number | undefined
403
- }) {
404
- this.constraints = {}
405
- if (Predicate.isNumber(options.minLength)) {
406
- this.constraints.minLength = options.minLength
407
- }
408
- if (Predicate.isNumber(options.maxLength)) {
409
- this.constraints.maxLength = options.maxLength
410
- }
481
+ return output
411
482
  }
412
483
  }
413
484
 
414
- /** @internal */
415
- export class BigIntConstraints {
416
- readonly _tag = "BigIntConstraints"
417
- readonly constraints: FastCheck.BigIntConstraints
418
- constructor(options: {
419
- readonly min?: bigint | undefined
420
- readonly max?: bigint | undefined
421
- }) {
422
- this.constraints = {}
423
- if (Predicate.isBigInt(options.min)) {
424
- this.constraints.min = options.min
425
- }
426
- if (Predicate.isBigInt(options.max)) {
427
- this.constraints.max = options.max
428
- }
429
- }
430
- }
485
+ type Constraints = StringConstraints | NumberConstraints | BigIntConstraints | ArrayConstraints
431
486
 
432
- /** @internal */
433
- export type Constraints =
434
- | NumberConstraints
435
- | StringConstraints
436
- | IntegerConstraints
437
- | ArrayConstraints
438
- | BigIntConstraints
487
+ const getConstraints = (_tag: Constraints["_tag"], ast: AST.Refinement): Constraints | undefined => {
488
+ const TypeAnnotationId: any = ast.annotations[AST.SchemaIdAnnotationId]
489
+ const jsonSchema: Record<string, any> = Option.getOrElse(AST.getJSONSchemaAnnotation(ast), () => ({}))
439
490
 
440
- /** @internal */
441
- export const getConstraints = (ast: AST.Refinement): Constraints | undefined => {
442
- const TypeAnnotationId = ast.annotations[AST.SchemaIdAnnotationId]
443
- const jsonSchema: any = ast.annotations[AST.JSONSchemaAnnotationId]
444
- switch (TypeAnnotationId) {
445
- // int
446
- case filters_.IntSchemaId:
447
- return new IntegerConstraints({})
448
- // number
449
- case filters_.GreaterThanSchemaId:
450
- case filters_.GreaterThanOrEqualToSchemaId:
451
- case filters_.LessThanSchemaId:
452
- case filters_.LessThanOrEqualToSchemaId:
453
- case filters_.BetweenSchemaId:
454
- return new NumberConstraints({
455
- min: jsonSchema.exclusiveMinimum ?? jsonSchema.minimum,
456
- max: jsonSchema.exclusiveMaximum ?? jsonSchema.maximum
457
- })
458
- // bigint
459
- case filters_.GreaterThanBigintSchemaId:
460
- case filters_.GreaterThanOrEqualToBigIntSchemaId:
461
- case filters_.LessThanBigIntSchemaId:
462
- case filters_.LessThanOrEqualToBigIntSchemaId:
463
- case filters_.BetweenBigintSchemaId: {
464
- const constraints: any = ast.annotations[TypeAnnotationId]
465
- return new BigIntConstraints(constraints)
491
+ switch (_tag) {
492
+ case "StringConstraints":
493
+ return makeStringConstraints(jsonSchema)
494
+ case "NumberConstraints": {
495
+ switch (TypeAnnotationId) {
496
+ case filters_.NonNaNSchemaId:
497
+ return makeNumberConstraints({ noNaN: true })
498
+ default:
499
+ return makeNumberConstraints({
500
+ isInteger: "type" in jsonSchema && jsonSchema.type === "integer",
501
+ noNaN: "type" in jsonSchema && jsonSchema.type === "number" ? true : undefined,
502
+ noDefaultInfinity: "type" in jsonSchema && jsonSchema.type === "number" ? true : undefined,
503
+ min: jsonSchema.exclusiveMinimum ?? jsonSchema.minimum,
504
+ minExcluded: "exclusiveMinimum" in jsonSchema ? true : undefined,
505
+ max: jsonSchema.exclusiveMaximum ?? jsonSchema.maximum,
506
+ maxExcluded: "exclusiveMaximum" in jsonSchema ? true : undefined
507
+ })
508
+ }
466
509
  }
467
- // string
468
- case filters_.MinLengthSchemaId:
469
- case filters_.MaxLengthSchemaId:
470
- case filters_.LengthSchemaId:
471
- return new StringConstraints(jsonSchema)
472
- // array
473
- case filters_.MinItemsSchemaId:
474
- case filters_.MaxItemsSchemaId:
475
- case filters_.ItemsCountSchemaId:
476
- return new ArrayConstraints({
510
+ case "BigIntConstraints":
511
+ return makeBigIntConstraints(ast.annotations[TypeAnnotationId] as any)
512
+ case "ArrayConstraints":
513
+ return makeArrayConstraints({
477
514
  minLength: jsonSchema.minItems,
478
515
  maxLength: jsonSchema.maxItems
479
516
  })
480
517
  }
481
518
  }
482
519
 
483
- /** @internal */
484
- export const combineConstraints = (
485
- c1: Constraints | undefined,
486
- c2: Constraints | undefined
487
- ): Constraints | undefined => {
488
- if (c1 === undefined) {
489
- return c2
490
- }
491
- if (c2 === undefined) {
492
- return c1
493
- }
494
- switch (c1._tag) {
495
- case "ArrayConstraints": {
496
- switch (c2._tag) {
497
- case "ArrayConstraints":
498
- return new ArrayConstraints({
520
+ function getMax(n1: bigint | undefined, n2: bigint | undefined): bigint | undefined
521
+ function getMax(n1: number | undefined, n2: number | undefined): number | undefined
522
+ function getMax(
523
+ n1: bigint | number | undefined,
524
+ n2: bigint | number | undefined
525
+ ): bigint | number | undefined {
526
+ return n1 === undefined ? n2 : n2 === undefined ? n1 : n1 <= n2 ? n2 : n1
527
+ }
528
+
529
+ function getMin(n1: bigint | undefined, n2: bigint | undefined): bigint | undefined
530
+ function getMin(n1: number | undefined, n2: number | undefined): number | undefined
531
+ function getMin(
532
+ n1: bigint | number | undefined,
533
+ n2: bigint | number | undefined
534
+ ): bigint | number | undefined {
535
+ return n1 === undefined ? n2 : n2 === undefined ? n1 : n1 <= n2 ? n1 : n2
536
+ }
537
+
538
+ const getOr = (a: boolean | undefined, b: boolean | undefined): boolean | undefined => {
539
+ return a === undefined ? b : b === undefined ? a : a || b
540
+ }
541
+
542
+ const merge = (c1: Config, c2: Constraints | undefined): Config => {
543
+ if (c2) {
544
+ switch (c1._tag) {
545
+ case "StringConstraints": {
546
+ if (c2._tag === "StringConstraints") {
547
+ return makeStringConstraints({
499
548
  minLength: getMax(c1.constraints.minLength, c2.constraints.minLength),
500
- maxLength: getMin(c1.constraints.maxLength, c2.constraints.maxLength)
549
+ maxLength: getMin(c1.constraints.maxLength, c2.constraints.maxLength),
550
+ pattern: c1.pattern ?? c2.pattern
501
551
  })
552
+ }
553
+ break
502
554
  }
503
- break
504
- }
505
- case "NumberConstraints": {
506
- switch (c2._tag) {
507
- case "NumberConstraints":
508
- return new NumberConstraints({
555
+ case "NumberConstraints": {
556
+ if (c2._tag === "NumberConstraints") {
557
+ return makeNumberConstraints({
558
+ isInteger: c1.isInteger || c2.isInteger,
509
559
  min: getMax(c1.constraints.min, c2.constraints.min),
560
+ minExcluded: getOr(c1.constraints.minExcluded, c2.constraints.minExcluded),
510
561
  max: getMin(c1.constraints.max, c2.constraints.max),
562
+ maxExcluded: getOr(c1.constraints.maxExcluded, c2.constraints.maxExcluded),
511
563
  noNaN: getOr(c1.constraints.noNaN, c2.constraints.noNaN),
512
564
  noDefaultInfinity: getOr(c1.constraints.noDefaultInfinity, c2.constraints.noDefaultInfinity)
513
565
  })
514
- case "IntegerConstraints":
515
- return new IntegerConstraints({
516
- min: getMax(c1.constraints.min, c2.constraints.min),
517
- max: getMin(c1.constraints.max, c2.constraints.max)
518
- })
566
+ }
567
+ break
519
568
  }
520
- break
521
- }
522
- case "BigIntConstraints": {
523
- switch (c2._tag) {
524
- case "BigIntConstraints":
525
- return new BigIntConstraints({
569
+ case "BigIntConstraints": {
570
+ if (c2._tag === "BigIntConstraints") {
571
+ return makeBigIntConstraints({
526
572
  min: getMax(c1.constraints.min, c2.constraints.min),
527
573
  max: getMin(c1.constraints.max, c2.constraints.max)
528
574
  })
575
+ }
576
+ break
529
577
  }
530
- break
531
- }
532
- case "StringConstraints": {
533
- switch (c2._tag) {
534
- case "StringConstraints":
535
- return new StringConstraints({
578
+ case "ArrayConstraints": {
579
+ if (c2._tag === "ArrayConstraints") {
580
+ return makeArrayConfig({
536
581
  minLength: getMax(c1.constraints.minLength, c2.constraints.minLength),
537
582
  maxLength: getMin(c1.constraints.maxLength, c2.constraints.maxLength)
538
- })
539
- }
540
- break
541
- }
542
- case "IntegerConstraints": {
543
- switch (c2._tag) {
544
- case "NumberConstraints":
545
- case "IntegerConstraints": {
546
- return new IntegerConstraints({
547
- min: getMax(c1.constraints.min, c2.constraints.min),
548
- max: getMin(c1.constraints.max, c2.constraints.max)
549
- })
583
+ }, c1.ast)
550
584
  }
585
+ break
551
586
  }
552
- break
553
587
  }
554
588
  }
589
+ return c1
555
590
  }
556
591
 
557
- const getOr = (a: boolean | undefined, b: boolean | undefined): boolean | undefined => {
558
- return a === undefined ? b : b === undefined ? a : a || b
559
- }
560
-
561
- function getMax(n1: bigint | undefined, n2: bigint | undefined): bigint | undefined
562
- function getMax(n1: number | undefined, n2: number | undefined): number | undefined
563
- function getMax(
564
- n1: bigint | number | undefined,
565
- n2: bigint | number | undefined
566
- ): bigint | number | undefined {
567
- return n1 === undefined ? n2 : n2 === undefined ? n1 : n1 <= n2 ? n2 : n1
592
+ const getSuspendedArray = (
593
+ fc: typeof FastCheck,
594
+ depthIdentifier: string,
595
+ maxDepth: number,
596
+ item: FastCheck.Arbitrary<any>,
597
+ constraints?: FastCheck.ArrayConstraints
598
+ ) => {
599
+ let minLength = 1
600
+ let maxLength = 2
601
+ if (constraints && constraints.minLength !== undefined && constraints.minLength > minLength) {
602
+ minLength = constraints.minLength
603
+ if (minLength > maxLength) {
604
+ maxLength = minLength
605
+ }
606
+ }
607
+ return fc.oneof(
608
+ { maxDepth, depthIdentifier },
609
+ fc.constant([]),
610
+ fc.array(item, { minLength, maxLength })
611
+ )
568
612
  }
569
613
 
570
- function getMin(n1: bigint | undefined, n2: bigint | undefined): bigint | undefined
571
- function getMin(n1: number | undefined, n2: number | undefined): number | undefined
572
- function getMin(
573
- n1: bigint | number | undefined,
574
- n2: bigint | number | undefined
575
- ): bigint | number | undefined {
576
- return n1 === undefined ? n2 : n2 === undefined ? n1 : n1 <= n2 ? n1 : n2
614
+ const getSuspendedContext = (
615
+ ctx: ArbitraryGenerationContext,
616
+ ast: AST.Suspend
617
+ ): ArbitraryGenerationContext => {
618
+ if (ctx.depthIdentifier !== undefined) {
619
+ return ctx
620
+ }
621
+ const depthIdentifier = AST.getIdentifierAnnotation(ast).pipe(
622
+ Option.orElse(() => AST.getIdentifierAnnotation(ast.f())),
623
+ Option.getOrElse(() => "SuspendDefaultDepthIdentifier")
624
+ )
625
+ return { ...ctx, depthIdentifier }
577
626
  }