Skip to content

MyGeneric<X> is sometimes not assignable to MyGeneric<Y> though X extends Y #51867

Closed as not planned
@wbt

Description

@wbt

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    DuplicateAn existing issue was already created

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions