Repository navigation
Incomplete inference of generic union type over arrayΒ #60893
Description
π Search Terms
generic, array, union, inference
π Version & Regression Information
- This is the behavior in every version I tried, and I reviewed the FAQ for entries about generics
β― Playground Link
π» Code
type Value = number | string;
type BoxedValue<T extends Value> = {
value: T;
};
type Props<T extends Value> = {
boxes?: readonly BoxedValue<T>[];
selected: NoInfer<T>;
}
const onlyNumberBoxes = [
{ value: 1 },
{ value: 2 }
] as const;
const mixedBoxes = [
{ value: 1 },
{ value: "2" }
] as const;
const fn = <T extends Value>({ boxes, selected }: Props<T>) => {
console.log(boxes, selected);
}
fn({ boxes: onlyNumberBoxes, selected: 1 }); // OK, T is `1 | 2`
fn({ boxes: mixedBoxes, selected: 1 }); // Error, Type '"2"' is not assignable to type '1'
fn<(typeof mixedBoxes)[number]["value"]>({ boxes: mixedBoxes, selected: 1 }); // OK, T is `1 | "2"`π Actual behavior
Typescript can't infer the type T in the second call to fn. Instead the following error occurs:
Type 'readonly [{ readonly value: 1; }, { readonly value: "2"; }]' is not assignable to type 'readonly BoxedValue<1>[]'.
Type '{ readonly value: 1; } | { readonly value: "2"; }' is not assignable to type 'BoxedValue<1>'.
Type '{ readonly value: "2"; }' is not assignable to type 'BoxedValue<1>'.
Types of property 'value' are incompatible.
Type '"2"' is not assignable to type '1'.(2322)
Even though T can be either a number or a string, the compiler seems hung up on the base type of the first value it finds. The fact that the compiler can correctly infer 1 | 2 in the first call, and is fine with the explicit annotation of (typeof mixedBoxes)[number]["value"] in the third call, makes me feel like the error for the second call is unexpected behavior. The intended code is valid (e.g. the third call), but compiler can't get there on inference alone. The same issue occurs if you remove the as const, the error just references number and string instead.
π Expected behavior
No error on the second call to fn; the type T is correctly inferred as 1 | "2".
Additional information about the issue
Perhaps I am encountering a limitation on generic inference? I suppose union types could be arbitrarily large.
My goal in the example above is to restrict the accepted type for selected to the actual types given in the boxed values. I came up with a workaround that exhibits the correct behavior, but is not as intuitive: https://www.typescriptlang.org/play/?#code/C4TwDgpgBAaghgGwK7QLxQHZILYCMIBOUAPlAM7AECWGA5gNwCwAUC6JFAEID2AHhABN4yNFADeLKFABuiFAC5YciE2YBfVW3DQe-MlHQEIcAdwwIQXPoOEoA2gF1NzdtACSGAGaEjQ5QB4AFSgIXmAIDAF9XQgyAD4DKEC7LDxCBzsAIlkRTKcWLQ4ABQJuMDIgkLCIqKs9BPQJZilcazIAfkVA1SkyCAQIAGNwgUUPbwJfWwgguNU1AuZBswooMwsAORx8Ahj9dDtJcRllRQBGKDUAGiOxE5FFACZLlgcoOH1ljApnL9XsKj8AR7RKHZrHHIKKAXa63e5QzKPTIvZhvD5QP7AX4rYBQTwYRKVULhSLRNpxAAUd1aeiu5H6QxGl0UJTKFUCcQAlAYEk0pH9uAMAHQIbi0Ck02J0voDYaCTnzRb4qlQSVkRTrEBbNK7NrShly0bQy4KqAAejNUAA8gBpOnBKj6AAGF1IjydLGV1LaigBQL2+tlI3OJvo5stFutdqSUEdUBdJCgiMyHuY+P8FNc3E8UD9gj2nMp3r0vsB+b19KDghDalNkdt9tjztdSaRTqAA
If there is a limitation here around generic inference, maybe the error message can be improved? Again, since T extends number | string, it's not clear why the error occurs.
Activity
jcalz commentedon Dec 31, 2024
It's a feature, not a bug. You're looking for something like #44312.
adriantrunzo commentedon Jan 1, 2025
Thanks for the links, you've addressed my issue.
typescript-bot commentedon Jan 5, 2025
This issue has been marked as "Duplicate" and has seen no recent activity. It has been automatically closed for house-keeping purposes.