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?
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
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