Skip to content

Commit

Permalink
feat: choose best number fn (#3)
Browse files Browse the repository at this point in the history
  • Loading branch information
TomerAberbach authored Dec 7, 2024
1 parent c43e975 commit de99015
Show file tree
Hide file tree
Showing 13 changed files with 267 additions and 145 deletions.
27 changes: 7 additions & 20 deletions src/arbitrary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,8 @@ export type Arbitrary = BaseArbitrary &
| UnionArbitrary
| EnumArbitrary
| BooleanArbitrary
| IntegerArbitrary
| BigIntegerArbitrary
| FloatArbitrary
| DoubleArbitrary
| NumberArbitrary
| BigIntArbitrary
| StringArbitrary
)

Expand Down Expand Up @@ -54,30 +52,19 @@ export type BooleanArbitrary = {
type: `boolean`
}

export type IntegerArbitrary = {
type: `integer`
export type NumberArbitrary = {
type: `number`
min: number
max: number
isInteger: boolean
}

export type BigIntegerArbitrary = {
type: `big-integer`
export type BigIntArbitrary = {
type: `bigint`
min?: bigint
max?: bigint
}

export type FloatArbitrary = {
type: `float`
min?: number
max?: number
}

export type DoubleArbitrary = {
type: `double`
min?: number
max?: number
}

export type StringArbitrary = {
type: `string`
minLength?: number
Expand Down
111 changes: 69 additions & 42 deletions src/components.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
/* eslint-disable new-cap */
import { entries, filter, map, pipe, reduce, toArray, toMap } from 'lfi'
import {
entries,
filter,
get,
map,
minBy,
pipe,
reduce,
toArray,
toMap,
} from 'lfi'
import * as ay from '@alloy-js/core/stc'
import * as ts from '@alloy-js/typescript/stc'
import { join as ayJoin, code, refkey } from '@alloy-js/core'
Expand All @@ -8,16 +18,15 @@ import type {
Arbitrary,
ArbitraryNamespace,
ArrayArbitrary,
BigIntegerArbitrary,
BigIntArbitrary,
DictionaryArbitrary,
DoubleArbitrary,
EnumArbitrary,
FloatArbitrary,
IntegerArbitrary,
NumberArbitrary,
RecordArbitrary,
StringArbitrary,
UnionArbitrary,
} from './arbitrary.ts'
import { fastCheckNumerics } from './numerics.ts'

const ArbitraryFile = ({
namespace,
Expand Down Expand Up @@ -140,14 +149,10 @@ const ArbitraryDefinition = ({
return UnionArbitrary({ arbitrary, sharedArbitraries })
case `enum`:
return EnumArbitrary({ arbitrary })
case `integer`:
return IntegerArbitrary({ arbitrary })
case `big-integer`:
return BigIntegerArbitrary({ arbitrary })
case `float`:
return FloatArbitrary({ arbitrary })
case `double`:
return DoubleArbitrary({ arbitrary })
case `number`:
return NumberArbitrary({ arbitrary })
case `bigint`:
return BigIntArbitrary({ arbitrary })
case `string`:
return StringArbitrary({ arbitrary })
}
Expand Down Expand Up @@ -214,22 +219,57 @@ const UnionArbitrary = ({
const EnumArbitrary = ({ arbitrary }: { arbitrary: EnumArbitrary }): Child =>
code`fc.constantFrom(${arbitrary.values.join(`, `)})`

const IntegerArbitrary = ({
const NumberArbitrary = ({
arbitrary,
}: {
arbitrary: IntegerArbitrary
}): Child =>
code`fc.integer(${Options({
arbitrary: NumberArbitrary
}): Child => {
const [name, { min, max }] = pipe(
entries(fastCheckNumerics),
filter(
([, { isInteger, min, max }]) =>
arbitrary.isInteger === isInteger &&
(arbitrary.min === min.value ||
min.configurable === true ||
(min.configurable === `higher` && arbitrary.min >= min.value)) &&
(arbitrary.max === max.value ||
max.configurable === true ||
(max.configurable === `lower` && arbitrary.max <= max.value)),
),
minBy(([, a], [, b]) => {
const matchCount1 =
Number(arbitrary.min === a.min.value) +
Number(arbitrary.max === a.max.value)
const matchCount2 =
Number(arbitrary.min === b.min.value) +
Number(arbitrary.max === b.max.value)
if (matchCount1 !== matchCount2) {
return matchCount2 - matchCount1
}

const delta1 =
Math.abs(arbitrary.min - a.min.value) +
Math.abs(arbitrary.max - a.max.value)
const delta2 =
Math.abs(arbitrary.min - b.min.value) +
Math.abs(arbitrary.max - b.max.value)
return delta1 - delta2
}),
get,
)

return code`fc.${name}(${Options({
properties: new Map([
[`min`, arbitrary.min],
[`max`, arbitrary.max],
[`min`, arbitrary.min === min.value ? null : arbitrary.min],
[`max`, arbitrary.max === max.value ? null : arbitrary.max],
]),
})})`
}

const BigIntegerArbitrary = ({
const BigIntArbitrary = ({
arbitrary,
}: {
arbitrary: BigIntegerArbitrary
arbitrary: BigIntArbitrary
}): Child =>
code`fc.bigInt(${Options({
properties: new Map([
Expand All @@ -238,26 +278,6 @@ const BigIntegerArbitrary = ({
]),
})})`

const FloatArbitrary = ({ arbitrary }: { arbitrary: FloatArbitrary }): Child =>
code`fc.float(${Options({
properties: new Map([
[`min`, arbitrary.min],
[`max`, arbitrary.max],
]),
})})`

const DoubleArbitrary = ({
arbitrary,
}: {
arbitrary: DoubleArbitrary
}): Child =>
code`fc.double(${Options({
properties: new Map([
[`min`, arbitrary.min],
[`max`, arbitrary.max],
]),
})})`

const StringArbitrary = ({
arbitrary,
}: {
Expand Down Expand Up @@ -290,7 +310,14 @@ const Options = ({
ayJoin(
pipe(
filteredProperties,
map(([name, value]) => code`${ts.ObjectProperty({ name, value })},\n`),
map(
([name, value]) =>
code`${ts.ObjectProperty({
name,
// https://github.com/alloy-framework/alloy/issues/42
value: typeof value === `number` ? String(value) : value,
})},\n`,
),
reduce(toArray()),
),
),
Expand Down
88 changes: 19 additions & 69 deletions src/convert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import pascalcase from 'pascalcase'
import keyalesce from 'keyalesce'
import toposort from 'toposort'
import type { Arbitrary, ArbitraryNamespace } from './arbitrary.ts'
import { numerics } from './numerics.ts'

const convertProgram = (
program: Program,
Expand Down Expand Up @@ -174,29 +175,21 @@ const convertScalar = (
case `boolean`:
return memoize({ type: `boolean`, name })
case `int8`:
return convertInteger(scalar, options, { min: -128, max: 127 })
case `int16`:
return convertInteger(scalar, options, { min: -32_768, max: 32_767 })
case `int32`:
return convertInteger(scalar, options, {
min: -2_147_483_648,
max: 2_147_483_647,
})
case `int64`:
return convertBigint(scalar, options, {
min: -9_223_372_036_854_775_808n,
max: 9_223_372_036_854_775_807n,
})
case `integer`:
return convertBigint(scalar, options)
case `safeint`:
case `float32`:
return convertFloat(scalar, options)
case `float64`:
return convertNumber(scalar, options, numerics[scalar.name])
case `float`:
case `decimal128`:
case `decimal`:
case `numeric`:
return convertDouble(scalar, options)
return convertNumber(scalar, options, numerics.float64)
case `int64`:
return convertBigint(scalar, options, numerics.int64)
case `integer`:
return convertBigint(scalar, options)
case `string`:
return convertString(scalar, options)
default:
Expand All @@ -214,16 +207,17 @@ const convertScalar = (
throw new Error(`Unhandled Scalar: ${scalar.name}`)
}

const convertInteger = (
integer: Scalar,
const convertNumber = (
number: Scalar,
{ constraints }: ConvertTypeOptions,
{ min, max }: { min: number; max: number },
{ min, max, isInteger }: { min: number; max: number; isInteger: boolean },
): Arbitrary =>
memoize({
type: `integer`,
name: integer.name,
type: `number`,
name: number.name,
min: maxOrUndefined(constraints.min?.asNumber() ?? undefined, min),
max: minOrUndefined(constraints.max?.asNumber() ?? undefined, max),
isInteger,
})

const convertBigint = (
Expand All @@ -232,40 +226,12 @@ const convertBigint = (
{ min, max }: { min?: bigint; max?: bigint } = {},
): Arbitrary =>
memoize({
type: `big-integer`,
type: `bigint`,
name: integer.name,
min: maxOrUndefined(constraints.min?.asBigInt() ?? undefined, min),
max: minOrUndefined(constraints.max?.asBigInt() ?? undefined, max),
})

const convertFloat = (
float: Scalar,
{ constraints }: ConvertTypeOptions,
): Arbitrary => {
let min = constraints.min?.asNumber() ?? undefined
if (min !== undefined && min < -3.4e38) {
min = undefined
}

let max = constraints.max?.asNumber() ?? undefined
if (max !== undefined && max > 3.4e38) {
max = undefined
}

return memoize({ type: `float`, name: float.name, min, max })
}

const convertDouble = (
double: Scalar,
{ constraints }: ConvertTypeOptions,
): Arbitrary =>
memoize({
type: `double`,
name: double.name,
min: constraints.min?.asNumber() ?? undefined,
max: constraints.max?.asNumber() ?? undefined,
})

const convertString = (
string: Scalar,
{ constraints }: ConvertTypeOptions,
Expand Down Expand Up @@ -328,28 +294,14 @@ const getArbitraryKey = (arbitrary: Arbitrary): ArbitraryKey => {
return keyalesce([arbitrary.type, arbitrary.name, ...arbitrary.variants])
case `enum`:
return keyalesce([arbitrary.type, arbitrary.name, ...arbitrary.values])
case `integer`:
return keyalesce([
arbitrary.type,
arbitrary.name,
arbitrary.min,
arbitrary.max,
])
case `big-integer`:
case `number`:
return keyalesce([
arbitrary.type,
arbitrary.name,
arbitrary.min,
arbitrary.max,
])
case `float`:
return keyalesce([
arbitrary.type,
arbitrary.name,
arbitrary.min,
arbitrary.max,
])
case `double`:
case `bigint`:
return keyalesce([
arbitrary.type,
arbitrary.name,
Expand Down Expand Up @@ -445,10 +397,8 @@ const getDirectArbitraryDependencies = (
return new Set(arbitrary.variants)
case `enum`:
case `boolean`:
case `integer`:
case `big-integer`:
case `float`:
case `double`:
case `number`:
case `bigint`:
case `string`:
return new Set()
}
Expand Down
Loading

0 comments on commit de99015

Please sign in to comment.