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

Cannot call member function of a union of compatible generic types #44215

Closed
ypsah opened this issue May 22, 2021 · 3 comments
Closed

Cannot call member function of a union of compatible generic types #44215

ypsah opened this issue May 22, 2021 · 3 comments
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed

Comments

@ypsah
Copy link

ypsah commented May 22, 2021

Bug Report

🔎 Search Terms

  • union generic intersection
  • union compatible function
  • Cannot call member function of a union of compatible generic types

Looks related to #7294

🕗 Version & Regression Information

This is the behavior in every version I tried, and I reviewed the FAQ for entries about generic and function.

⏯ Playground Link

Playground link with relevant code

💻 Code

interface State<T> {
    value: T,
    setValue: (value: T) => void,
}

function errors<T>(props: State<T> | State<T[]>): void {
    props.setValue(props.value);
}

🙁 Actual behavior

Argument of type 'T | T[]' is not assignable to parameter of type 'T & T[]'.
  Type 'T' is not assignable to type 'T & T[]'.
    Type 'T' is not assignable to type 'T[]'.

🙂 Expected behavior

No compilation error.

props is evaluated to be either:

  • State<T> => props.setValue is of type (value: T) => void and props.value is of type T;
  • State<T[]> => props.setValue is of type (value: T[]) => void and props.value is of type T[].

I understand this was discussed in #7294 and its subsequent PRs/issues, and that it may not be easy to fix.

If it helps, I would be quite happy to just be able to write:

interface State<T> {
    value: T,
    setValue: (value: T) => void,
}

function _setToSelf<T>(props: State<T>): void {
    props.setValue(props.value);
}

function setToSelf<T>(props: State<T> | State<T[]>): void {
    _setToSelf(props);
}

Playground link

@RyanCavanaugh
Copy link
Member

The analysis you've provided here only works if the source of value (props) is the reference-same value as the source of setValue (in this example, also props). Given two utterances, figuring out if they both resolved to exactly the same object is not a trivial task (mutation exists, aliasing exists, etc), and we don't have that kind of tracking anywhere else in the type system. Otherwise, if the two sources possibly came from different values, then the call is unsound.

If you don't possibly have two different values of props, then there's rarely a reason not to rewrite as State<T | T[]> given how our variance rules work. And in fact T[] isn't even doing anything here, it just slightly changes what the inference does without really changing the set of values the function accepts.

So TL;DR the only places this tends to come up are places where either the code isn't really guaranteed to work, or where there was no reason to write the code that way in the first place. Given those restrictions and the amount of work required to make this be analyzed correctly, we're not likely to pursue progress in this area in the near future.

@RyanCavanaugh RyanCavanaugh added the Design Limitation Constraints of the existing architecture prevent this from being fixed label May 27, 2021
@ypsah
Copy link
Author

ypsah commented May 28, 2021

Thank you for taking the time to answer.

Would you rather I close the issue myself, or do you prefer to manage this on your own?

@ErDmKo
Copy link

ErDmKo commented Aug 24, 2022

It look like i have same issue here

type propsT<ValType> = 
   {a: ValType, multiple: 0, c: (a: ValType) => void} | 
   {a: ValType[], multiple: 1, c: (a: ValType[]) => void}

const props = <I,>(p: propsT<I>) => {
    p.c(p.a);  // <- Argument of type 'I | I[]' is not assignable to parameter of type 'I & I[]'.
    if (p.multiple === 0) { // <- redundant if make it works as expected 
       p.c(p.a);
    } else {
       p.c(p.a); 
    }
    
    return 1
}
props({a: ['a'], multiple: 1, c: (a) => console.log(a)})

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed
Projects
None yet
Development

No branches or pull requests

3 participants