Comments (4)
First drafts:
type Pretty<T> = { [K in keyof T]: T[K] } & {}
type IsOptional<TType, TKey extends keyof TType> = Pick<TType, TKey> extends Record<TKey, TType[TKey]> ? false : true;
const is1: Pretty<IsOptional<{s?: string}, 's'>> = true;
const is2: Pretty<IsOptional<{s?: string|undefined}, 's'>> = true;
const isNot1: Pretty<IsOptional<{s: string|undefined}, 's'>> = false;
const isNot2: Pretty<IsOptional<{s: string}, 's'>> = false;
const isNot3: Pretty<IsOptional<{s: undefined}, 's'>> = false;
type OptionalKeysOf<TType> = keyof { [K in keyof TType as IsOptional<TType, K> extends true ? K : never]: never }
type RequiredKeysOf<TType> = keyof { [K in keyof TType as IsOptional<TType, K> extends true ? never : K]: never }
type PPP = RequiredKeysOf<{s?: string, n: number | undefined }>
type RecordConstraints<TOut> = {requiredKeys: readonly RequiredKeysOf<TOut>[]}
// "All keys required" matches "Some keys potentially optional"
declare function record<TOut>(
shape:
{ [K in keyof TOut as IsOptional<TOut, K> extends true ? never : K]: Arbitrary<TOut[K]> }
& { [K in keyof TOut as IsOptional<TOut, K> extends true ? K : never]?: Arbitrary<TOut[K]> | Arbitrary<Exclude<TOut[K], undefined>> },
): TOut;
declare function record<TOut, TConstraints extends RecordConstraints<TOut>>(
shape:
{ [K in keyof TOut as IsOptional<TOut, K> extends true ? never : K]: Arbitrary<TOut[K]> }
& { [K in keyof TOut as IsOptional<TOut, K> extends true ? K : never]?: Arbitrary<TOut[K]> | Arbitrary<Exclude<TOut[K], undefined>> },
options: TConstraints
): Pretty<TConstraints>;
const a1 = record<{s: string, n: number }>(
{ s: arbString(), n: arbNat() }
)
const a2 = record<{s?: string, n?: number }>(
{ s: arbString(), n: arbNat() },
)
const a22 = record<{s?: string, n?: number }>( // we have to make sure arbNatOrUndefined would not pass as n? means number when set, not number|undefined
{ s: arbString(), n: arbNatOrUndefined() },
)
const a3 = record<{s?: string, n?: number }>(
{ s: arbString(), n: arbNat() },
{ requiredKeys: [] }
)
const a4 = record<{s?: string, n?: number }>(
{ }
)
const a5 = record<{s: string, n: number }>(
{ s: arbString(), n: arbNat() },
{ requiredKeys: ['s', 'n'] as const }
)
// Should not compile
const b1 = record<{s: string, n: number }>(
{ s: arbString(), n: arbNat() },
{ requiredKeys: [] }
)
const b2 = record<{s?: string, n: number | undefined }>(
{ }
)
from fast-check.
A step forward:
type Arbitrary<T> = {
generate: () => T;
shrink: (value: T) => IterableIterator<T>;
}
declare function arbNat(): Arbitrary<number>;
declare function arbNatOrUndefined(): Arbitrary<number|undefined>;
declare function arbString(): Arbitrary<string>;
declare function arbConstant<T>(v: T): Arbitrary<T>;
type Pretty<T> = { [K in keyof T]: T[K] } & {}
type IsOptional<TType, TKey extends keyof TType> = Pick<TType, TKey> extends Record<TKey, TType[TKey]> ? false : true;
const is1: Pretty<IsOptional<{s?: string}, 's'>> = true;
const is2: Pretty<IsOptional<{s?: string|undefined}, 's'>> = true;
const isNot1: Pretty<IsOptional<{s: string|undefined}, 's'>> = false;
const isNot2: Pretty<IsOptional<{s: string}, 's'>> = false;
const isNot3: Pretty<IsOptional<{s: undefined}, 's'>> = false;
type OptionalKeysOf<TType> = keyof { [K in keyof TType as IsOptional<TType, K> extends true ? K : never]: never }
type RequiredKeysOf<TType> = keyof { [K in keyof TType as IsOptional<TType, K> extends true ? never : K]: never }
type PPP = RequiredKeysOf<{s?: string, n: number | undefined }>
// From https://github.com/Microsoft/TypeScript/issues/13298
type UnionToIntersection<U> = (U extends never ? never : (arg: U) => never) extends (arg: infer I) => void ? I : never;
type UnionToTuple<T, A extends any[] = []> = UnionToIntersection<T extends never ? never : (t: T) => T> extends (_: never) => infer W ? UnionToTuple<Exclude<T, W>, [...A, W]> : A;
type QQQ = UnionToTuple<"a" | "b" | "c">
type RecordShape<TOut> =
{ [K in keyof TOut as IsOptional<TOut, K> extends true ? never : K]: Arbitrary<TOut[K]> }
& { [K in keyof TOut as IsOptional<TOut, K> extends true ? K : never]?: Arbitrary<TOut[K]> | Arbitrary<Exclude<TOut[K], undefined>> };
type RecordConstraints<TOut> = {requiredKeys: UnionToTuple<RequiredKeysOf<TOut>>}
declare function record<TOut>(shape: RecordShape<TOut>): TOut;
declare function record<TOut>(
shape: RecordShape<TOut>,
options: RecordConstraints<TOut>
): TOut;
const a1 = record<{s: string, n: number }>(
{ s: arbString(), n: arbNat() }
)
const a2 = record<{s?: string, n?: number }>(
{ s: arbString(), n: arbNat() },
)
const a22 = record<{s?: string, n?: number }>( // we have to make sure arbNatOrUndefined would not pass as n? means number when set, not number|undefined
{ s: arbString(), n: arbNatOrUndefined() },
)
const a3 = record<{s?: string, n?: number }>(
{ s: arbString(), n: arbNat() },
{ requiredKeys: [] }
)
const a4 = record<{s?: string, n?: number }>(
{ }
)
const a5 = record<{s: string, n: number }>(
{ s: arbString(), n: arbNat() },
{ requiredKeys: ['n', 's'] }
)
const a6 = record<{s: string, n: number }>(
{ s: arbString(), n: arbNat() },
{ requiredKeys: ['s', 'n'] }
)
// Should not compile
const b1 = record<{s: string, n: number }>(
{ s: arbString(), n: arbNat() },
{ requiredKeys: [] }
)
const b2 = record<{s?: string, n: number | undefined }>(
{ }
)
from fast-check.
Here is a Playground to play with to adapt current typings for record: link.
Code of the playground
// Playground to build record
// record in v3+ (v3 with options dropped to be closer to v4)
type RecordValue<T, TConstraints = {}> = TConstraints extends { requiredKeys: (infer TKeys)[] } ? Partial<T> & Pick<T, TKeys & keyof T> : T;
type RecordConstraints<T = unknown> = { requiredKeys?: T[]; noNullPrototype?: boolean; };
declare function record<T>(recordModel: { [K in keyof T]: Arbitrary<T[K]> }): Arbitrary<RecordValue<{ [K in keyof T]: T[K] }>>;
declare function record<T, TConstraints extends RecordConstraints<keyof T>>(recordModel: { [K in keyof T]: Arbitrary<T[K]> }, constraints: TConstraints): Arbitrary<RecordValue<{ [K in keyof T]: T[K] }, TConstraints>>;
// Fake definitions for fast-check
class Arbitrary<T> {
generate(): T { throw new Error("Not implemented!"); }
shrink(value: T): IterableIterator<T> { throw new Error("Not implemented!"); }
}
declare function arbNat(): Arbitrary<number>;
declare function arbOption<T, TNil = null>(arb: Arbitrary<T>, constraints?: {nil?: TNil}): Arbitrary<T | TNil>;
declare function arbString(): Arbitrary<string>;
declare function arbConstant<T>(v: T): Arbitrary<T>;
const fc = { Arbitrary, nat: arbNat, constant: arbConstant, string: arbString, option: arbOption, record };
// New set of tests that we want to make pass for v4
expectType<Arbitrary<{ s: string }>>()(
record<{s: string}>({ s: fc.string() }),
""
);
expectType<Arbitrary<{ s: string }>>()(
record<{s?: string}>({ s: fc.string() }, { requiredKeys: ["s"] }),
""
);
expectType<Arbitrary<{ s?: string }>>()(
record<{s?: string}>({ s: fc.string() }, { requiredKeys: [] }),
""
);
expectType<Arbitrary<{ s?: string | undefined }>>()(
record<{s?: string}>({ s: fc.option(fc.string(), {nil:undefined}) }, { requiredKeys: [] }),
""
);
expectType<Arbitrary<{ a: string, b?: number, c: number | undefined }>>()(
record<{ a: string, b?: number, c: number | undefined }>({ a: fc.string(), b: fc.nat(), c: fc.option(fc.nat(), {nil:undefined}) }, { requiredKeys: ['b', 'c'] }),
""
);
expectType<Arbitrary<{ a: string, b?: number, c: number | undefined }>>()(
record<{ a: string, b?: number, c: number | undefined }>({ a: fc.string(), b: fc.nat(), c: fc.option(fc.nat(), {nil:undefined}) }, { requiredKeys: ['c', 'b'] }),
"same as above but with b and c reversed"
);
// @ts-expect-error - should not pass when keys are missing (missing b)
record<{ a: string, b?: number, c: number | undefined }>({ a: fc.string(), b: fc.nat(), c: fc.option(fc.nat(), {nil:undefined}) }, { requiredKeys: ['c'] });
// @ts-expect-error - should not pass when keys linked to otpional keys are added
record<{ a: string, b?: number, c: number | undefined }>({ a: fc.string(), b: fc.nat(), c: fc.option(fc.nat(), {nil:undefined}) }, { requiredKeys: ['a', 'b', 'c'] });
// @ts-expect-error - should not pass when optional and no requiredKeys specified
record<{ a: string, b?: number, c: number | undefined }>({ a: fc.string(), b: fc.nat(), c: fc.option(fc.nat(), {nil:undefined}) });
// Tests running on the current version
declare const mySymbol1: unique symbol;
declare const mySymbol2: unique symbol;
expectType<Arbitrary<{ a: number; b: string }>>()(
fc.record({ a: fc.nat(), b: fc.string() }),
'"record" can contain multiple types',
);
expectType<Arbitrary<{ [mySymbol1]: number; [mySymbol2]: string }>>()(
fc.record({ [mySymbol1]: fc.nat(), [mySymbol2]: fc.string() }),
'"record" can be indexed using unique symbols as keys',
);
expectType<Arbitrary<{ a: number; b: string }>>()(
fc.record({ a: fc.nat(), b: fc.string() }, {}),
'"record" accepts empty constraints',
);
expectType<Arbitrary<{ a?: number; b?: string }>>()(
fc.record({ a: fc.nat(), b: fc.string() }, { requiredKeys: [] }),
'"record" only applies optional on keys declared within requiredKeys even when empty',
);
expectType<Arbitrary<{ a: number; b?: string }>>()(
fc.record({ a: fc.nat(), b: fc.string() }, { requiredKeys: ['a'] }),
'"record" only applies optional on keys declared within requiredKeys even if unique',
);
expectType<Arbitrary<{ a: number; b?: string; c: string }>>()(
fc.record({ a: fc.nat(), b: fc.string(), c: fc.string() }, { requiredKeys: ['a', 'c'] }),
'"record" only applies optional on keys declared within requiredKeys even if multiple ones specified',
);
expectType<Arbitrary<{ a?: number; b?: string | undefined }>>()(
fc.record({ a: fc.nat(), b: fc.option(fc.string(), { nil: undefined }) }, { requiredKeys: [] }),
'"record" only applies optional on keys declared within requiredKeys and preserves existing |undefined when adding ?',
);
expectType<Arbitrary<{ [mySymbol1]: number; [mySymbol2]?: string }>>()(
fc.record({ [mySymbol1]: fc.nat(), [mySymbol2]: fc.string() }, { requiredKeys: [mySymbol1] as [typeof mySymbol1] }),
'"record" only applies optional on keys declared within requiredKeys even if it contains symbols',
);
expectType<Arbitrary<{ [mySymbol1]: number; [mySymbol2]?: string; a: number; b?: string }>>()(
fc.record(
{ [mySymbol1]: fc.nat(), [mySymbol2]: fc.string(), a: fc.nat(), b: fc.string() },
{ requiredKeys: [mySymbol1, 'a'] as [typeof mySymbol1, 'a'] },
),
'"record" only applies optional on keys declared within requiredKeys even if it contains symbols and normal keys',
);
type Query = { data: { field: 'X' } };
expectType<Arbitrary<Query>>()(
// issue 1453
fc.record<Query>({ data: fc.record({ field: fc.constant('X') }) }),
'"record" can be passed the requested type in <*>',
);
expectType<Arbitrary<Partial<Query>>>()(
// issue 1453
fc.record<Partial<Query>>({ data: fc.record({ field: fc.constant('X') }) }),
'"record" can be passed something assignable to the requested type in <*>',
);
// @ts-expect-error - requiredKeys references an unknown key
fc.record({ a: fc.nat(), b: fc.string() }, { requiredKeys: ['c'] });
// @ts-expect-error - record expects arbitraries not raw values
fc.record({ a: 1 });
// MUST ERROR with exactOptionalPropertyTypes
expectType<Arbitrary<{ a?: number; b?: string | undefined }>>()(fc.record({ a: fc.nat(), b: fc.string() }, { requiredKeys: [] }), '"record" only applies optional on keys declared within requiredKeys by adding ? without |undefined');
// Assertions on types copied from @fast-check/expect-type
declare type Not<T> = T extends true ? false : true;
declare type And<T, U> = T extends true ? (U extends true ? true : false) : false;
declare type Or<T, U> = T extends false ? (U extends false ? false : true) : true;
declare type IsNever<T> = [T] extends [never] ? true : false;
declare type Extends<T, U> = T extends U ? true : false;
declare type ExtendsString<T> = Extends<T, string> extends boolean ? boolean extends Extends<T, string> ? true : false : false; // Extends<T, string> is: false for unknown but boolean for any
declare type IsUnknown<T> = And<And<Not<IsNever<T>>, Extends<T, unknown>>, And<Extends<unknown, T>, Not<ExtendsString<T>>>>;
declare type IsAny<T> = And<And<Not<IsNever<T>>, Not<IsUnknown<T>>>, And<Extends<T, any>, Extends<any, T> extends true ? true : false>>;
declare type DeeperIsSame<T, U> = IsAny<T> extends false ? T extends object ? { [K in keyof (T | U)]: IsSame<T[K], U[K]> } extends { [K in keyof (T | U)]: true } ? true : false : true : false;
declare type IsSame<T, U> = [T, U] extends [U, T] ? Or<Or<Or<And<IsAny<T>, IsAny<U>>, And<IsUnknown<T>, IsUnknown<U>>>, And<IsNever<T>, IsNever<U>>>, And<And<And<And<Not<IsAny<T>>, Not<IsAny<U>>>, And<Not<IsUnknown<T>>, Not<IsUnknown<U>>>>,And<Not<IsNever<T>>, Not<IsNever<U>>>>, DeeperIsSame<T, U>>> : false;
declare function expectType<TExpectedType>(): <TReal>(arg: TReal,...noArgs: IsSame<TExpectedType, TReal> extends true ? [string] : [{ expected: TExpectedType; got: TReal }]) => void;
declare function expectTypeAssignable<TExpectedType>(): <TReal>(arg: TReal,...noArgs: Extends<TReal, TExpectedType> extends true ? [string] : [{ expected: TExpectedType; got: TReal }]) => void;
from fast-check.
This looks good! Feel free to DM me on Discord/Twitter and we can schedule a chat next week and go through the Playground link.
I might have some ideas for handling tuple inputs that could be useful as well as testing + type errors and would be happy to discuss if it would be helpful to an awesome library like this!
from fast-check.
Related Issues (20)
- Add https://github.com/Effect-TS/schema in ecosystem part
- Run knip to detect potential dead code
- Investigate flaky run on double in CI
- Confusing or typo text on Getting Started page HOT 3
- TypeError: Cannot read properties of undefined (reading 'arbitrary') for FrequencyArbitrary.js:83:65 HOT 4
- Connect `filter` to rejection mechanisms like `fc.pre` does
- Add some kind of `waitAtLeastOneScheduled` on scheduler
- Add `waitUntilNextScheduling`
- fc.float unexpectedly yields NaN HOT 2
- Support multiple PRNG when generating values from the Workers
- Add an `fc.double`/`fc.float` variant that strictly produces non-integers HOT 11
- Better interrupt CJS-ESM as we provide both in our bundle (Symbol.for switch) HOT 1
- Modify `fc.webPath` to be able to generate paths starting with `//` HOT 19
- Generate a single value HOT 1
- Running `import { test, fc } from '@fast-check/jest';` results in error reading properties of undefined in jest HOT 6
- Accept readonly versions of the contraints passed to the arbitraries
- Allow record to produce readonly properties when provided readonly
- Duplicate paragraph in docs HOT 3
- Provide a built-in way to generate set of entities having relationships between each others HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from fast-check.