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

Generic type argument cannot be inferred by a callback parameter return type if the same type was used for the callback's parameter #9659

Closed
Igorbek opened this issue Jul 13, 2016 · 8 comments
Labels
Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented. Suggestion An idea for TypeScript

Comments

@Igorbek
Copy link
Contributor

Igorbek commented Jul 13, 2016

TypeScript Version: 1.8.10

Code

declare function a<T>(f: (x: T) => T): T;       // callback takes and returns the same generic type
declare function b<T>(f: (x: number) => T): T;  // callback returns generic type, but doesn't take it 

interface X { x: number; }

a<X>(x => ({ x: 1 }));      // ok, inferring isn't needed
a((x: X) => ({ x: 1 }));    // ok, inferred by argument type
a(x => ({ x: 1 }));         // infers T as {}

// on the other hand
b(x => ({ x: 1 }));         // infers T as X

Expected behavior:
In a(x => ({ x: 1 })), T is inferred as { x: number }

Actual behavior:
In a(x => ({ x: 1 })), T is inferred as { }

@RyanCavanaugh RyanCavanaugh added Suggestion An idea for TypeScript Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented. labels Jul 13, 2016
@RyanCavanaugh
Copy link
Member

I believe the problem here is we have two inference sites for T -- the return type of the function ({ x: number }), and the type of the parameter (which ends up being { } for lack of better inference), and the most general type among them is then { }.

There might be some fix here without other side effects but I don't know what it would be.

@Igorbek
Copy link
Contributor Author

Igorbek commented Jul 13, 2016

So should we create a proposal to fix that?

@tsofist
Copy link

tsofist commented Oct 15, 2016

It would be very helpful!

@tsofist
Copy link

tsofist commented Oct 19, 2016

@RyanCavanaugh

Just imagine how convenient it was to write this (for example - Promise executor):

const promise = new Promise((resolve, reject) => {
    resolve(123);
});
promise.then((res) => {
    console.log('I get called:', res.toFixed());
});

Also disappear errors like this:

const promise = new Promise((resolve, reject) => {
    resolve(123);
    resolve("!");
    resolve(true);
});

But now we have...

const promise = new Promise<number>((resolve, reject) => {
    resolve(123);
});
//or
const promise: Promise<number> = new Promise((resolve, reject) => {
    resolve(123);
});

// more example
function doSome(): Promise<number> {  // let's say that do Some logic is very complicated and we want to explicitly specify the results
    return new Promise<number>((resolve, reject) => { // <number> for greater security logic inside Promise executor
        resolve(123);
    });
}

Many similar problems will go into oblivion if this is done
#11701
#11537
#3038
#10785
#10609
#11058
#9660
#11537
#11877
...and more

@Igorbek
Copy link
Contributor Author

Igorbek commented Nov 4, 2016

@tsofist I cannot agree regarding #10717, it is not the case for sure

@tsofist
Copy link

tsofist commented Nov 4, 2016

@Igorbek my bad

@k8w
Copy link

k8w commented Aug 22, 2017

See this.

type AABB = 'AA' | 'BB';
let arr: any[];
let arr2: AABB[] = arr.map(v => 'AA'); 
let arr3: AABB[] = arr.map(v => 'BB');

If I want it work, I could only use this:

let arr2: AABB[] = arr.map(v => 'AA' as 'AA'); 

'AA' as 'AA' is really unnecessary...

@RyanCavanaugh
Copy link
Member

I think the behavior today is correct. If you write

a(() => ({ x: 1 }));

Then T is inferred to { x: number } as expected.

If the callback does take a parameter, then this is a contravariant-only inference case, and unknown is the sound choice here because there's no way for a to know what to call f with, thus any reference to the parameter inside the callback body should be an "infectious unknown".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented. Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

4 participants