Skip to content

Instantly share code, notes, and snippets.

@donabrams
Last active November 8, 2018 15:29

Revisions

  1. donabrams revised this gist Nov 8, 2018. 1 changed file with 59 additions and 59 deletions.
    118 changes: 59 additions & 59 deletions Omit.ts
    Original file line number Diff line number Diff line change
    @@ -8,75 +8,75 @@ type SimpleOmit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
    type Foo = { foo: string };
    type FooBar = { foo: string, bar: string };
    type Yay = { yay: true };
    type Before = Foo | FooBar | Yay;
    type NonDiscriminatedUnion = Foo | FooBar | Yay;

    // example union type WITH a discriminator
    type DFoo = { type: 'foo', foo: string };
    type DFooBar = { type: 'foobar', foo: string, bar: string };
    type DYay = { type: 'yay', yay: true };
    type DBefore = DFoo | DFooBar | DYay;
    type DiscriminatedUnion = DFoo | DFooBar | DYay;

    type MappedAddKey<T, K extends string | number | symbol, V> = T & { [P in K]: V };
    type MappedRemoveKey<T, K extends string | number | symbol> = T extends { [P in K]: infer U }
    type MappedAdd<T, K extends string | number | symbol, V> = T & { [P in K]: V };
    type MappedOmit<T, K extends string | number | symbol> = T extends { [P in K]: infer U }
    ? SimpleOmit<T, K> : T;

    // sanity checks on the Before type
    const sanityCheckWorks1: Before = { foo: 'foo'};
    const sanityCheckWorks2: Before = { foo: 'foo', bar: 'bar'};
    const sanityCheckWorks3: Before = { yay: true};
    const sanityCheckFail1: Before = { };
    const sanityCheckFail2: Before = { foo: 'foo', yay: true }; // FIXME: False positive!
    const sanityCheckFail3: Before = { bar: 'bar', yay: true }; // FIXME: False positive!
    const sanityCheckFail4: Before = { foo: 'foo', bar: 'bar', yay: true }; // FIXME: False positive!
    // sanity checks on the NonDiscriminatedUnion type
    const sanityCheckWorks1: NonDiscriminatedUnion = { foo: 'foo'};
    const sanityCheckWorks2: NonDiscriminatedUnion = { foo: 'foo', bar: 'bar'};
    const sanityCheckWorks3: NonDiscriminatedUnion = { yay: true};
    const sanityCheckFail1: NonDiscriminatedUnion = { };
    const sanityCheckFail2: NonDiscriminatedUnion = { foo: 'foo', yay: true }; // FIXME: False positive!
    const sanityCheckFail3: NonDiscriminatedUnion = { bar: 'bar', yay: true }; // FIXME: False positive!
    const sanityCheckFail4: NonDiscriminatedUnion = { foo: 'foo', bar: 'bar', yay: true }; // FIXME: False positive!

    // sanity checks on the Discriminated Before type
    const dsanityCheckWorks1: DBefore = { type: 'foo', foo: 'foo' };
    const dsanityCheckWorks2: DBefore = { type: 'foobar', foo: 'foo', bar: 'bar' };
    const dsanityCheckWorks3: DBefore = { type: 'yay', yay: true };
    const dsanityCheckFail1: DBefore = { type: 'foo' };
    const dsanityCheckFail2: DBefore = { type: 'foobar' };
    const dsanityCheckFail3: DBefore = { type: 'yay' };
    const dsanityCheckFail4: DBefore = { type: 'foo', foo: 'foo', bar: 'bar', yay: true };
    const dsanityCheckFail5: DBefore = { type: 'foobar', foo: 'foo', bar: 'bar', yay: true };
    const dsanityCheckFail6: DBefore = { type: 'yay', foo: 'foo', bar: 'bar', yay: true };
    // sanity checks on the Discriminated NonDiscriminatedUnion type
    const dsanityCheckWorks1: DiscriminatedUnion = { type: 'foo', foo: 'foo' };
    const dsanityCheckWorks2: DiscriminatedUnion = { type: 'foobar', foo: 'foo', bar: 'bar' };
    const dsanityCheckWorks3: DiscriminatedUnion = { type: 'yay', yay: true };
    const dsanityCheckFail1: DiscriminatedUnion = { type: 'foo' };
    const dsanityCheckFail2: DiscriminatedUnion = { type: 'foobar' };
    const dsanityCheckFail3: DiscriminatedUnion = { type: 'yay' };
    const dsanityCheckFail4: DiscriminatedUnion = { type: 'foo', foo: 'foo', bar: 'bar', yay: true };
    const dsanityCheckFail5: DiscriminatedUnion = { type: 'foobar', foo: 'foo', bar: 'bar', yay: true };
    const dsanityCheckFail6: DiscriminatedUnion = { type: 'yay', foo: 'foo', bar: 'bar', yay: true };

    // positive and negative checks on MappedAddKey<Before
    const addKeyWorks1: MappedAddKey<Before, 'beep', string> = { foo: 'foo', beep: 'beep' };
    const addKeyWorks2: MappedAddKey<Before, 'beep', string> = { foo: 'foo', bar: 'bar', beep: 'beep' };
    const addKeyWorks3: MappedAddKey<Before, 'beep', string> = { yay: true, beep: 'beep' };
    const addKeyWorks4: MappedAddKey<Before, 'foo', string> = { foo: 'foo' };
    const addKeyWorks5: MappedAddKey<Before, 'foo', string> = { foo: 'foo', bar: 'bar' };
    const addKeyWorks6: MappedAddKey<Before, 'foo', string> = { foo: 'foo', yay: true };
    const addKeyFails1: MappedAddKey<Before, 'beep', string> = { foo: 'foo' };
    const addKeyFails2: MappedAddKey<Before, 'beep', string> = { foo: 'foo', bar: 'bar' };
    const addKeyFails3: MappedAddKey<Before, 'beep', string> = { yay: true };
    const addKeyFails4: MappedAddKey<Before, 'foo', string> = { yay: true };
    const addKeyFails5: MappedAddKey<Before, 'beep', string> = { foo: 'foo', bar: 'bar', beep: 'beep', yay: true }; // FIXME: False positive!
    // positive and negative checks on MappedAdd<NonDiscriminatedUnion
    const addKeyWorks1: MappedAdd<NonDiscriminatedUnion, 'beep', string> = { foo: 'foo', beep: 'beep' };
    const addKeyWorks2: MappedAdd<NonDiscriminatedUnion, 'beep', string> = { foo: 'foo', bar: 'bar', beep: 'beep' };
    const addKeyWorks3: MappedAdd<NonDiscriminatedUnion, 'beep', string> = { yay: true, beep: 'beep' };
    const addKeyWorks4: MappedAdd<NonDiscriminatedUnion, 'foo', string> = { foo: 'foo' };
    const addKeyWorks5: MappedAdd<NonDiscriminatedUnion, 'foo', string> = { foo: 'foo', bar: 'bar' };
    const addKeyWorks6: MappedAdd<NonDiscriminatedUnion, 'foo', string> = { foo: 'foo', yay: true };
    const addKeyFails1: MappedAdd<NonDiscriminatedUnion, 'beep', string> = { foo: 'foo' };
    const addKeyFails2: MappedAdd<NonDiscriminatedUnion, 'beep', string> = { foo: 'foo', bar: 'bar' };
    const addKeyFails3: MappedAdd<NonDiscriminatedUnion, 'beep', string> = { yay: true };
    const addKeyFails4: MappedAdd<NonDiscriminatedUnion, 'foo', string> = { yay: true };
    const addKeyFails5: MappedAdd<NonDiscriminatedUnion, 'beep', string> = { foo: 'foo', bar: 'bar', beep: 'beep', yay: true }; // FIXME: False positive!

    // positive and negative checks on MappedAddKey<DBefore
    const daddKeyWorks1: MappedAddKey<DBefore, 'beep', string> = { type: 'foo', foo: 'foo', beep: 'beep' };
    const daddKeyWorks2: MappedAddKey<DBefore, 'beep', string> = { type: 'foobar', foo: 'foo', bar: 'bar', beep: 'beep' };
    const daddKeyWorks3: MappedAddKey<DBefore, 'beep', string> = { type: 'yay', yay: true, beep: 'beep' };
    const daddKeyWorks4: MappedAddKey<DBefore, 'foo', string> = { type: 'foo', foo: 'foo' };
    const daddKeyWorks5: MappedAddKey<DBefore, 'foo', string> = { type: 'foobar', foo: 'foo', bar: 'bar' };
    const daddKeyWorks6: MappedAddKey<DBefore, 'foo', string> = { type: 'yay', foo: 'foo', yay: true };
    const daddKeyFails1: MappedAddKey<DBefore, 'beep', string> = { type: 'foo', foo: 'foo' };
    const daddKeyFails2: MappedAddKey<DBefore, 'beep', string> = { type: 'foobar', foo: 'foo', bar: 'bar' };
    const daddKeyFails3: MappedAddKey<DBefore, 'beep', string> = { type: 'yay', yay: true };
    const daddKeyFails4: MappedAddKey<DBefore, 'foo', string> = { type: 'yay', yay: true };
    const daddKeyFails5: MappedAddKey<DBefore, 'beep', string> = { type: 'yay', foo: 'foo', bar: 'bar', beep: 'beep', yay: true };
    // positive and negative checks on MappedAdd<DiscriminatedUnion
    const daddKeyWorks1: MappedAdd<DiscriminatedUnion, 'beep', string> = { type: 'foo', foo: 'foo', beep: 'beep' };
    const daddKeyWorks2: MappedAdd<DiscriminatedUnion, 'beep', string> = { type: 'foobar', foo: 'foo', bar: 'bar', beep: 'beep' };
    const daddKeyWorks3: MappedAdd<DiscriminatedUnion, 'beep', string> = { type: 'yay', yay: true, beep: 'beep' };
    const daddKeyWorks4: MappedAdd<DiscriminatedUnion, 'foo', string> = { type: 'foo', foo: 'foo' };
    const daddKeyWorks5: MappedAdd<DiscriminatedUnion, 'foo', string> = { type: 'foobar', foo: 'foo', bar: 'bar' };
    const daddKeyWorks6: MappedAdd<DiscriminatedUnion, 'foo', string> = { type: 'yay', foo: 'foo', yay: true };
    const daddKeyFails1: MappedAdd<DiscriminatedUnion, 'beep', string> = { type: 'foo', foo: 'foo' };
    const daddKeyFails2: MappedAdd<DiscriminatedUnion, 'beep', string> = { type: 'foobar', foo: 'foo', bar: 'bar' };
    const daddKeyFails3: MappedAdd<DiscriminatedUnion, 'beep', string> = { type: 'yay', yay: true };
    const daddKeyFails4: MappedAdd<DiscriminatedUnion, 'foo', string> = { type: 'yay', yay: true };
    const daddKeyFails5: MappedAdd<DiscriminatedUnion, 'beep', string> = { type: 'yay', foo: 'foo', bar: 'bar', beep: 'beep', yay: true };

    // positive and negative checks on MappedRemoveKey<Before
    const removeKeyWorks1: MappedRemoveKey<Before, 'foo'> = { };
    const removeKeyWorks2: MappedRemoveKey<Before, 'foo'> = { bar: 'bar' };
    const removeKeyWorks3: MappedRemoveKey<Before, 'foo'> = { yay: true };
    const removeKeyFails1: MappedRemoveKey<Before, 'foo'> = { foo: 'foo' }; // FIXME: False positive!
    const removeKeyFails2: MappedRemoveKey<Before, 'foo'> = { foo: 'foo', bar: 'bar' }; // FIXME: False positive!
    // positive and negative checks on MappedOmit<NonDiscriminatedUnion
    const removeKeyWorks1: MappedOmit<NonDiscriminatedUnion, 'foo'> = { };
    const removeKeyWorks2: MappedOmit<NonDiscriminatedUnion, 'foo'> = { bar: 'bar' };
    const removeKeyWorks3: MappedOmit<NonDiscriminatedUnion, 'foo'> = { yay: true };
    const removeKeyFails1: MappedOmit<NonDiscriminatedUnion, 'foo'> = { foo: 'foo' }; // FIXME: False positive!
    const removeKeyFails2: MappedOmit<NonDiscriminatedUnion, 'foo'> = { foo: 'foo', bar: 'bar' }; // FIXME: False positive!

    // positive and negative checks on MappedRemoveKey<DBefore
    const dremoveKeyWorks1: MappedRemoveKey<DBefore, 'foo'> = { type: 'foo' };
    const dremoveKeyWorks2: MappedRemoveKey<DBefore, 'foo'> = { type: 'foobar', bar: 'bar' };
    const dremoveKeyWorks3: MappedRemoveKey<DBefore, 'foo'> = { type: 'yay', yay: true };
    const dremoveKeyFails1: MappedRemoveKey<DBefore, 'foo'> = { type: 'foo', foo: 'foo' };
    const dremoveKeyFails2: MappedRemoveKey<DBefore, 'foo'> = { type: 'foobar', foo: 'foo', bar: 'bar' };
    const dremoveKeyFails3: MappedRemoveKey<DBefore, 'foo'> = { type: 'yay', yay: true, foo: 'foo'};
    // positive and negative checks on MappedOmit<DiscriminatedUnion
    const dremoveKeyWorks1: MappedOmit<DiscriminatedUnion, 'foo'> = { type: 'foo' };
    const dremoveKeyWorks2: MappedOmit<DiscriminatedUnion, 'foo'> = { type: 'foobar', bar: 'bar' };
    const dremoveKeyWorks3: MappedOmit<DiscriminatedUnion, 'foo'> = { type: 'yay', yay: true };
    const dremoveKeyFails1: MappedOmit<DiscriminatedUnion, 'foo'> = { type: 'foo', foo: 'foo' };
    const dremoveKeyFails2: MappedOmit<DiscriminatedUnion, 'foo'> = { type: 'foobar', foo: 'foo', bar: 'bar' };
    const dremoveKeyFails3: MappedOmit<DiscriminatedUnion, 'foo'> = { type: 'yay', yay: true, foo: 'foo'};
  2. donabrams revised this gist Nov 8, 2018. No changes.
  3. donabrams revised this gist Nov 8, 2018. 1 changed file with 11 additions and 0 deletions.
    11 changes: 11 additions & 0 deletions tsconfig.json
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,11 @@
    {
    "compilerOptions": {
    "target": "ES2018", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */
    "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
    "strict": true, /* Enable all strict type-checking options. */
    "noUnusedParameters": true, /* Report errors on unused parameters. */
    "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
    "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
    "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
    }
    }
  4. donabrams revised this gist Nov 8, 2018. 1 changed file with 2 additions and 0 deletions.
    2 changes: 2 additions & 0 deletions Omit.ts
    Original file line number Diff line number Diff line change
    @@ -51,6 +51,7 @@ const addKeyFails1: MappedAddKey<Before, 'beep', string> = { foo: 'foo' };
    const addKeyFails2: MappedAddKey<Before, 'beep', string> = { foo: 'foo', bar: 'bar' };
    const addKeyFails3: MappedAddKey<Before, 'beep', string> = { yay: true };
    const addKeyFails4: MappedAddKey<Before, 'foo', string> = { yay: true };
    const addKeyFails5: MappedAddKey<Before, 'beep', string> = { foo: 'foo', bar: 'bar', beep: 'beep', yay: true }; // FIXME: False positive!

    // positive and negative checks on MappedAddKey<DBefore
    const daddKeyWorks1: MappedAddKey<DBefore, 'beep', string> = { type: 'foo', foo: 'foo', beep: 'beep' };
    @@ -63,6 +64,7 @@ const daddKeyFails1: MappedAddKey<DBefore, 'beep', string> = { type: 'foo', foo:
    const daddKeyFails2: MappedAddKey<DBefore, 'beep', string> = { type: 'foobar', foo: 'foo', bar: 'bar' };
    const daddKeyFails3: MappedAddKey<DBefore, 'beep', string> = { type: 'yay', yay: true };
    const daddKeyFails4: MappedAddKey<DBefore, 'foo', string> = { type: 'yay', yay: true };
    const daddKeyFails5: MappedAddKey<DBefore, 'beep', string> = { type: 'yay', foo: 'foo', bar: 'bar', beep: 'beep', yay: true };

    // positive and negative checks on MappedRemoveKey<Before
    const removeKeyWorks1: MappedRemoveKey<Before, 'foo'> = { };
  5. donabrams revised this gist Nov 8, 2018. 1 changed file with 78 additions and 28 deletions.
    106 changes: 78 additions & 28 deletions Omit.ts
    Original file line number Diff line number Diff line change
    @@ -1,30 +1,80 @@
    // This doesn't support union types, so we have more shannanigans to do
    // This doesn't support map over union types individually
    type SimpleOmit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;

    // example union type for fun
    type Before = { foo: string } | { foo: string, bar: string } | { yay: true };

    type UnaryObject<K extends string | number | symbol, V> = { [P in K]: V; }
    const unaryWorks: UnaryObject<'beep', string> = { beep: 'beep' };
    const unaryFails1: UnaryObject<'beep', string> = { beep: true };
    const unaryFails2: UnaryObject<'beep', string> = { boop: 'beep' };

    type AddKey<T, K extends string | number | symbol, V> = T & { [P in K]: V };
    const addKeyWorks1: AddKey<Before, 'beep', string> = { foo: 'foo', beep: 'beep' };
    const addKeyWorks2: AddKey<Before, 'beep', string> = { foo: 'foo', bar: 'bar', beep: 'beep' };
    const addKeyWorks3: AddKey<Before, 'beep', string> = { yay: true, beep: 'beep' };
    const addKeyWorks4: AddKey<Before, 'foo', string> = { foo: 'foo' };
    const addKeyWorks5: AddKey<Before, 'foo', string> = { foo: 'foo', bar: 'bar' };
    const addKeyWorks6: AddKey<Before, 'foo', string> = { foo: 'foo', yay: true };
    const addKeyFails1: AddKey<Before, 'beep', string> = { foo: 'foo' };
    const addKeyFails2: AddKey<Before, 'beep', string> = { foo: 'foo', bar: 'bar' };
    const addKeyFails3: AddKey<Before, 'beep', string> = { yay: true };
    const addKeyFails4: AddKey<Before, 'foo', string> = { yay: true };

    // I WANT THIS
    type RemoveKey<T, K extends string | number | symbol> = ???
    const removeKeyWorks1: RemoveKey<Before, 'foo'> = { };
    const removeKeyWorks2: RemoveKey<Before, 'foo'> = { bar: 'bar' };
    const removeKeyWorks3: RemoveKey<Before, 'foo'> = { yay: true };
    const removeKeyFails1: RemoveKey<Before, 'foo'> = { foo: 'foo' };
    const removeKeyFails2: RemoveKey<Before, 'foo'> = { foo: 'foo', bar: 'bar' };
    // The below type, MappedRemoveKey, only works with discrimated unions!
    // Otherwise the intersection of the types ALSO passes (submiting a TS bug now-- in typescript-3.1.6)

    // example union type WITHOUT a discriminator
    type Foo = { foo: string };
    type FooBar = { foo: string, bar: string };
    type Yay = { yay: true };
    type Before = Foo | FooBar | Yay;

    // example union type WITH a discriminator
    type DFoo = { type: 'foo', foo: string };
    type DFooBar = { type: 'foobar', foo: string, bar: string };
    type DYay = { type: 'yay', yay: true };
    type DBefore = DFoo | DFooBar | DYay;

    type MappedAddKey<T, K extends string | number | symbol, V> = T & { [P in K]: V };
    type MappedRemoveKey<T, K extends string | number | symbol> = T extends { [P in K]: infer U }
    ? SimpleOmit<T, K> : T;

    // sanity checks on the Before type
    const sanityCheckWorks1: Before = { foo: 'foo'};
    const sanityCheckWorks2: Before = { foo: 'foo', bar: 'bar'};
    const sanityCheckWorks3: Before = { yay: true};
    const sanityCheckFail1: Before = { };
    const sanityCheckFail2: Before = { foo: 'foo', yay: true }; // FIXME: False positive!
    const sanityCheckFail3: Before = { bar: 'bar', yay: true }; // FIXME: False positive!
    const sanityCheckFail4: Before = { foo: 'foo', bar: 'bar', yay: true }; // FIXME: False positive!

    // sanity checks on the Discriminated Before type
    const dsanityCheckWorks1: DBefore = { type: 'foo', foo: 'foo' };
    const dsanityCheckWorks2: DBefore = { type: 'foobar', foo: 'foo', bar: 'bar' };
    const dsanityCheckWorks3: DBefore = { type: 'yay', yay: true };
    const dsanityCheckFail1: DBefore = { type: 'foo' };
    const dsanityCheckFail2: DBefore = { type: 'foobar' };
    const dsanityCheckFail3: DBefore = { type: 'yay' };
    const dsanityCheckFail4: DBefore = { type: 'foo', foo: 'foo', bar: 'bar', yay: true };
    const dsanityCheckFail5: DBefore = { type: 'foobar', foo: 'foo', bar: 'bar', yay: true };
    const dsanityCheckFail6: DBefore = { type: 'yay', foo: 'foo', bar: 'bar', yay: true };

    // positive and negative checks on MappedAddKey<Before
    const addKeyWorks1: MappedAddKey<Before, 'beep', string> = { foo: 'foo', beep: 'beep' };
    const addKeyWorks2: MappedAddKey<Before, 'beep', string> = { foo: 'foo', bar: 'bar', beep: 'beep' };
    const addKeyWorks3: MappedAddKey<Before, 'beep', string> = { yay: true, beep: 'beep' };
    const addKeyWorks4: MappedAddKey<Before, 'foo', string> = { foo: 'foo' };
    const addKeyWorks5: MappedAddKey<Before, 'foo', string> = { foo: 'foo', bar: 'bar' };
    const addKeyWorks6: MappedAddKey<Before, 'foo', string> = { foo: 'foo', yay: true };
    const addKeyFails1: MappedAddKey<Before, 'beep', string> = { foo: 'foo' };
    const addKeyFails2: MappedAddKey<Before, 'beep', string> = { foo: 'foo', bar: 'bar' };
    const addKeyFails3: MappedAddKey<Before, 'beep', string> = { yay: true };
    const addKeyFails4: MappedAddKey<Before, 'foo', string> = { yay: true };

    // positive and negative checks on MappedAddKey<DBefore
    const daddKeyWorks1: MappedAddKey<DBefore, 'beep', string> = { type: 'foo', foo: 'foo', beep: 'beep' };
    const daddKeyWorks2: MappedAddKey<DBefore, 'beep', string> = { type: 'foobar', foo: 'foo', bar: 'bar', beep: 'beep' };
    const daddKeyWorks3: MappedAddKey<DBefore, 'beep', string> = { type: 'yay', yay: true, beep: 'beep' };
    const daddKeyWorks4: MappedAddKey<DBefore, 'foo', string> = { type: 'foo', foo: 'foo' };
    const daddKeyWorks5: MappedAddKey<DBefore, 'foo', string> = { type: 'foobar', foo: 'foo', bar: 'bar' };
    const daddKeyWorks6: MappedAddKey<DBefore, 'foo', string> = { type: 'yay', foo: 'foo', yay: true };
    const daddKeyFails1: MappedAddKey<DBefore, 'beep', string> = { type: 'foo', foo: 'foo' };
    const daddKeyFails2: MappedAddKey<DBefore, 'beep', string> = { type: 'foobar', foo: 'foo', bar: 'bar' };
    const daddKeyFails3: MappedAddKey<DBefore, 'beep', string> = { type: 'yay', yay: true };
    const daddKeyFails4: MappedAddKey<DBefore, 'foo', string> = { type: 'yay', yay: true };

    // positive and negative checks on MappedRemoveKey<Before
    const removeKeyWorks1: MappedRemoveKey<Before, 'foo'> = { };
    const removeKeyWorks2: MappedRemoveKey<Before, 'foo'> = { bar: 'bar' };
    const removeKeyWorks3: MappedRemoveKey<Before, 'foo'> = { yay: true };
    const removeKeyFails1: MappedRemoveKey<Before, 'foo'> = { foo: 'foo' }; // FIXME: False positive!
    const removeKeyFails2: MappedRemoveKey<Before, 'foo'> = { foo: 'foo', bar: 'bar' }; // FIXME: False positive!

    // positive and negative checks on MappedRemoveKey<DBefore
    const dremoveKeyWorks1: MappedRemoveKey<DBefore, 'foo'> = { type: 'foo' };
    const dremoveKeyWorks2: MappedRemoveKey<DBefore, 'foo'> = { type: 'foobar', bar: 'bar' };
    const dremoveKeyWorks3: MappedRemoveKey<DBefore, 'foo'> = { type: 'yay', yay: true };
    const dremoveKeyFails1: MappedRemoveKey<DBefore, 'foo'> = { type: 'foo', foo: 'foo' };
    const dremoveKeyFails2: MappedRemoveKey<DBefore, 'foo'> = { type: 'foobar', foo: 'foo', bar: 'bar' };
    const dremoveKeyFails3: MappedRemoveKey<DBefore, 'foo'> = { type: 'yay', yay: true, foo: 'foo'};
  6. donabrams revised this gist Nov 8, 2018. 1 changed file with 30 additions and 62 deletions.
    92 changes: 30 additions & 62 deletions Omit.ts
    Original file line number Diff line number Diff line change
    @@ -1,62 +1,30 @@
    // type-safe omit
    export function omit<T extends Object, K extends string | number | symbol>(obj: T, k: K): Omit<T, K> {
    const a = { ...(obj as Object) } as T;
    if (k in a) {
    delete (a as any)[k];
    }
    return a as Omit<T, K>;
    }

    type A = {
    foo: string,
    bar: number,
    };

    type B = {
    foo: string,
    beep: string
    };

    type C = {
    bar: number,
    boop: string,
    };

    type D = A | C;

    // correctly works
    function shouldWork(a: A): B {
    const b: { foo: string } = omit(a, 'bar');
    return {
    ...b,
    beep: a.bar.toString(),
    };
    }

    // correctly fails
    function shouldFail1(a: A): B {
    const b: { foo: string, bar: number } = omit(a, 'bar');
    return {
    ...b,
    beep: a.bar.toString(),
    };
    }

    // correctly fails
    function shouldFail2(a: A): B {
    return {
    ...a,
    beep: a.bar.toString(),
    };
    }

    // doesn't work but should :(
    function shouldWork2(d: D): B | undefined {
    const b: { foo: string } | { boop: string } = omit(a, 'bar');
    return 'foo' in b
    ? {
    ...b,
    beep: a.bar,
    }
    : undefined;
    }
    // This doesn't support union types, so we have more shannanigans to do
    type SimpleOmit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;

    // example union type for fun
    type Before = { foo: string } | { foo: string, bar: string } | { yay: true };

    type UnaryObject<K extends string | number | symbol, V> = { [P in K]: V; }
    const unaryWorks: UnaryObject<'beep', string> = { beep: 'beep' };
    const unaryFails1: UnaryObject<'beep', string> = { beep: true };
    const unaryFails2: UnaryObject<'beep', string> = { boop: 'beep' };

    type AddKey<T, K extends string | number | symbol, V> = T & { [P in K]: V };
    const addKeyWorks1: AddKey<Before, 'beep', string> = { foo: 'foo', beep: 'beep' };
    const addKeyWorks2: AddKey<Before, 'beep', string> = { foo: 'foo', bar: 'bar', beep: 'beep' };
    const addKeyWorks3: AddKey<Before, 'beep', string> = { yay: true, beep: 'beep' };
    const addKeyWorks4: AddKey<Before, 'foo', string> = { foo: 'foo' };
    const addKeyWorks5: AddKey<Before, 'foo', string> = { foo: 'foo', bar: 'bar' };
    const addKeyWorks6: AddKey<Before, 'foo', string> = { foo: 'foo', yay: true };
    const addKeyFails1: AddKey<Before, 'beep', string> = { foo: 'foo' };
    const addKeyFails2: AddKey<Before, 'beep', string> = { foo: 'foo', bar: 'bar' };
    const addKeyFails3: AddKey<Before, 'beep', string> = { yay: true };
    const addKeyFails4: AddKey<Before, 'foo', string> = { yay: true };

    // I WANT THIS
    type RemoveKey<T, K extends string | number | symbol> = ???
    const removeKeyWorks1: RemoveKey<Before, 'foo'> = { };
    const removeKeyWorks2: RemoveKey<Before, 'foo'> = { bar: 'bar' };
    const removeKeyWorks3: RemoveKey<Before, 'foo'> = { yay: true };
    const removeKeyFails1: RemoveKey<Before, 'foo'> = { foo: 'foo' };
    const removeKeyFails2: RemoveKey<Before, 'foo'> = { foo: 'foo', bar: 'bar' };
  7. donabrams created this gist Nov 7, 2018.
    62 changes: 62 additions & 0 deletions Omit.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,62 @@
    // type-safe omit
    export function omit<T extends Object, K extends string | number | symbol>(obj: T, k: K): Omit<T, K> {
    const a = { ...(obj as Object) } as T;
    if (k in a) {
    delete (a as any)[k];
    }
    return a as Omit<T, K>;
    }

    type A = {
    foo: string,
    bar: number,
    };

    type B = {
    foo: string,
    beep: string
    };

    type C = {
    bar: number,
    boop: string,
    };

    type D = A | C;

    // correctly works
    function shouldWork(a: A): B {
    const b: { foo: string } = omit(a, 'bar');
    return {
    ...b,
    beep: a.bar.toString(),
    };
    }

    // correctly fails
    function shouldFail1(a: A): B {
    const b: { foo: string, bar: number } = omit(a, 'bar');
    return {
    ...b,
    beep: a.bar.toString(),
    };
    }

    // correctly fails
    function shouldFail2(a: A): B {
    return {
    ...a,
    beep: a.bar.toString(),
    };
    }

    // doesn't work but should :(
    function shouldWork2(d: D): B | undefined {
    const b: { foo: string } | { boop: string } = omit(a, 'bar');
    return 'foo' in b
    ? {
    ...b,
    beep: a.bar,
    }
    : undefined;
    }