typescripttypescript-generics

How to keep autosuggestion with generics when working with keyof?


In following example, I am expecting autosuggestion to suggest a | b | c while allowing me to use any string type value.

In first type used without genericsKeysA<O> it worked as expected, meanwhile if I use KeysB<O, T = string> autosuggestion completely stops working.

const obj = {
  a: 1,
  b: 2,
  c: 3
}


type KeysA<O> = keyof O | (string & {});
const a: KeysA<typeof obj> = 'a';
const b: KeysA<typeof obj> = 'custom';

type KeysB<O, T = string> = keyof O | (T & {});
const c: KeysB<typeof obj> = 'a';
const d: KeysB<typeof obj> = 'custom';

What is the cause of this behaviour, and is there a workaround?


Solution

  • You can set the second generic to never, and it will work.

    type Keys<O, T = never> = keyof O | T | (string & {});
    
    type Example = Keys<{ normal: string }, 'extra'>;
    
    const test = (example: Example) => {};
    
    test('normal');   // works
    test('extra');    // works
    test('random');   // works
    

    Playground

    It won't work in the playground, but the reason is that the playground doesn't show suggestions. Try it in your editor!

    In TypeScript, a union with never simplifies to the remaining types because never is the bottom type.

    To make it more versatile:

    type Keys = 'a' | 'b' | 'c' | (boolean & {}) | (number & {});
    // => suggests 'a' 'b' and 'c' and allows booleans and numbers
    
    const keys = (keys: Keys) => {};
    
    keys('a');
    keys(1);
    keys(true);
    keys({}); // Fails
    
    type KeysReusable<Keys, Extra = never> = Keys | Extra;
    
    const keysReusable = (
      keys: KeysReusable<'a' | 'b' | 'c', (boolean & {}) | (number & {})>
    ) => {};
    
    keysReusable('a');
    keysReusable(1);
    keysReusable(true);
    keysReusable({}); // Fails