Giter Club home page Giter Club logo

Comments (4)

dubzzz avatar dubzzz commented on June 2, 2024

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.

dubzzz avatar dubzzz commented on June 2, 2024

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.

dubzzz avatar dubzzz commented on June 2, 2024

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.

ssalbdivad avatar ssalbdivad commented on June 2, 2024

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)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.