Description
Bug Report
🔎 Search Terms
generic extends assignable
🕗 Version & Regression Information
- This changed between versions 4.05 and 4.1.5 in terms of the error chain and availability of case manipulation types used as a stand-in for more complicated derived types, and between 3.3.3333 and 3.5.1 for the main error, though the latter change appears to be a difference in handling propagation of the case-manipulation-type-unavailability error
- This is the behavior in every version I tried, and I reviewed the FAQ for entries about generics
- Nightly version at time of test: v5.0.0-dev.20221212
⏯ Playground Link
Playground link with relevant code
💻 Code
type Size = 'Small' | 'Medium' | 'Large';
//Case manipulation is a simplified stand-in for more complex derived types
type CaseMap = {[S in Size]: {lowerCase: Lowercase<S>, upperCase: Uppercase<S>}};
type CaseMapHoverDemo = CaseMap['Medium']; //{lowerCase: "medium"; upperCase: "MEDIUM";}
type ValueOf<T> = T[keyof T]; //https://stackoverflow.com/a/49286056
type ValueOfHoverDemo = ValueOf<CaseMap['Medium']>; //'medium' | 'MEDIUM'
type MyGeneric<S extends Size> = {normalCase: S; caseObj: ValueOf<CaseMap[S]>};
type MyGenericWET<S extends Size> = {normalCase: S; caseObj: ValueOf<{lowerCase: Lowercase<S>, upperCase: Uppercase<S>}>};
type MyGenericHoverDemo = MyGeneric<'Medium'>;
declare function takesAnySizeDirectly (mainParam: Size): any;
declare function takesAnySizeObject (mainParam: MyGeneric<Size>): any;
declare function takesAnySizeObjectWET(mainParam: MyGenericWET<Size>): any;
export const takesOneSize = function<S extends Size>(size: S, mainParam: MyGeneric<S>) {
takesAnySizeDirectly(size); //S, which extends Size, is assignable to Size: that makes sense
takesAnySizeObject(mainParam); //but MyGeneric<S> is not assignable to MyGeneric<Size>: that does not
/* Error ts(2345):
Argument of type 'MyGeneric<S>' is not assignable to parameter of type 'MyGeneric<Size>'.
(It seems like it should be though.)
Types of property 'caseObj' are incompatible.
Type 'ValueOf<CaseMap[S]>' is not assignable to type 'ValueOf<
{ lowerCase: "medium"; upperCase: "MEDIUM"; } |
{ lowerCase: "small"; upperCase: "SMALL"; } |
{ lowerCase: "large"; upperCase: "LARGE"; }
>'. (It seems like it should be though.)
*/
}
export const takesOneSizeWorks = function<S extends Size>(size: S, mainParam: MyGenericWET<S>) {
takesAnySizeDirectly(size); //S, which extends Size, is assignable to Size
takesAnySizeObjectWET(mainParam); //and MyGenericWET<S> is assignable to MyGenericWET<Size>
}
🙁 Actual behavior
Error as shown when reusing the type CaseMap[S]
instead of repeating the type it represents in the definition of MyGeneric.
TS doesn't even allow a direct cast, claiming Conversion of type 'MyGeneric<S>' to type 'MyGeneric<Size>' may be a mistake because neither type sufficiently overlaps with the other.
Note that naming the repeated type doesn't help with the error, but the motivating example doesn't provide a clear way to avoid using the map structure.
🙂 Expected behavior
No errors in the above.
MyGeneric
is actually equivalent to MyGenericWET
, takesAnySizeObject
is actually equivalent to takesAnySizeObjectWET
, and takesOneSize
is equivalent to takesOneSizeWorks
.
Possible cross-links
This might be related to #49505 which seems to focus more on type inference and chained generic functions. That refers to #30215 (a 2019 PR released in v3.4 which enabled “inference of generic function type results for generic functions that operate on other generic functions.”) Here, we don’t have generic functions operating on other generic functions, at least where “functions” is used in the JavaScript sense.