-
Notifications
You must be signed in to change notification settings - Fork 13.3k
Description
π Search Terms
function generic type narrowing
generic inference
π Version & Regression Information
This is the behaviour in every version I tried in the playground back to 3.3
β― Playground Link
π» Code
// START TYPES
type Ret = any;
type Name = 'Foo' | 'Bar';
type Args<T extends Name> = T extends 'Foo'
? {
foo: string;
}
: T extends 'Bar'
? {
bar: string;
}
: never;
declare function fnWithOneDefault<
R extends Ret,
N extends Name = Name,
>(
name: N,
variables: Args<N>,
): R;
declare function fnWithTwoDefault<
R extends Ret = Ret,
N extends Name = Name,
>(
name: N,
variables: Args<N>,
): R;
// END TYPES
const oneGeneric = fnWithOneDefault<123>('Foo', {
bar: 'bar', // should only allow `{ foo: string }` but allows `{ foo: string }` OR `{ bar: string }` OR `{ foo: string; bar: string }`
// foo: 'foo'
});
const twoGeneric = fnWithOneDefault<123, 'Foo'>('Foo', {
bar: 'bar' // should only allow `{ foo: string }` and is correctly marked as incorrect in TS
});
const noGeneric = fnWithTwoDefault('Foo', {
bar: 'bar' // should only allow `{ foo: string }` and is correctly marked as incorrect in TS
});π Actual behavior
As per the comments in the code, the oneGeneric function call allows me to provide the args from the 'Bar' name type, or the union of both.
I understand this is due to the 2nd generic N extends Name = Name and when the 2nd generic is omitted then it uses the union of 'Foo' and 'Bar' so both args types are applicable.
ChatGPT revealed:
The key behavior is:
- You explicitly provide the first generic (R).
- When a generic is explicitly provided, omitted later generics use their defaults rather than being inferred from arguments.
- So N becomes its default Name (the union), not 'Foo'.
- Args therefore becomes a union of both variable shapes, so the object for Bar is accepted.
Is this "When a generic is explicitly provided, omitted later generics use their defaults rather than being inferred from arguments" intentional or documented?
π Expected behavior
I would expect the union type of N to be narrowed to 'Foo' when the argument of name is provided as 'Foo' even when the 2nd generic is omitted. It works as expected when both generics have defaults and are both omitted from the calling function as can be seen in the noGeneric function call.
Additional information about the issue
No response