TypeScript Version: 4.2.1
Search Terms: discriminated union optional property
Code
type DiscriminatorTrue = {
disc: true;
cb: (x: string) => void;
}
type DiscriminatorFalse = {
disc?: false;
cb: (x: number) => void;
}
type Props = DiscriminatorTrue | DiscriminatorFalse;
declare function f(options: DiscriminatorTrue | DiscriminatorFalse): any;
f({
disc: true,
cb: s => parseInt(s) // Inference works 馃憤
});
f({
disc: false,
cb: n => n.toFixed() // Inference works 馃憤
});
f({
cb: n => n.toFixed() // Implicit any error 馃憥
});
f({
cb: (n: string) => parseInt(n) // But errors correctly with incorrect type annotation 馃憤
});
Expected behavior:
No implicit any error on the third call site
Actual behavior:
Implicit any error on the callback parameter at the third call site
Related Issues: https://github.com/microsoft/TypeScript/issues/31404#issuecomment-596841068 is this issue, but the OP there is different.
Notes:
This pattern sometimes appears in React component props, where convention is to make boolean properties optional and only pass them as true (usually with the shorthand <MyComponent boolProp />). I actually suggested using discriminated union props for React components in an old blog post, and noted this issue in a footnote, calling it a possible bug, but didn鈥檛 file it at the time because I had low confidence it wasn鈥檛 a duplicate or design limitation. It was later mentioned in https://github.com/microsoft/TypeScript/issues/31404#issuecomment-596841068, but was probably ignored because it was assumed to be an instance of the OP鈥檚 issue, which was determined to be a design limitation. I dove into this again because someone tweeted at me asking about that footnote after reading the post.
In this case, I think this'd be fixable by adjusting discriminateContextualTypeByObjectMembers and discriminateContextualTypeByJSXAttributes to include undefined'able discriminant props which don't appear in the node's properties. (They both currently filter the props which _do_ exist on the input object down to the discriminant ones right now, so the possibly undefined discriminants from the contextual type would need to be appended to those)
Tagging backlog since this is a longstanding shortcoming, but we can take it earlier if someone's eager to take a stab.
Most helpful comment
In this case, I think this'd be fixable by adjusting
discriminateContextualTypeByObjectMembersanddiscriminateContextualTypeByJSXAttributesto includeundefined'able discriminant props which don't appear in thenode's properties. (They both currently filter the props which _do_ exist on the input object down to the discriminant ones right now, so the possiblyundefineddiscriminants from the contextual type would need to be appended to those)