Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
wbt opened this issue Dec 12, 2022 · 2 comments
Closed
Labels
Duplicate An existing issue was already created

Comments

@wbt
Copy link

wbt commented Dec 12, 2022

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.

@RyanCavanaugh
Copy link
Member

The problem here is variance measurement -- ValueOf<T> appears to be invariant due to its simultaneous co and contravariant observation on T, but in reality it's just covariant. When instantiated with a concrete object type like in the WET variant, this isn't an issue, because the type is simply instantiated and compared that way. But given an arbitrary deferred type like CaseMap[S], there isn't a procedure we can do (apart from just hardcoding inconsistent behavior that T[keyof T] means something special/magic) which would determine that there isn't a manifestable soundness violation here.

@RyanCavanaugh RyanCavanaugh added the Duplicate An existing issue was already created label Jan 3, 2023
@RyanCavanaugh
Copy link
Member

Tracking at #52083

@RyanCavanaugh RyanCavanaugh closed this as not planned Won't fix, can't repro, duplicate, stale Jan 3, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Duplicate An existing issue was already created
Projects
None yet
Development

No branches or pull requests

2 participants