Skip to content

Generic type arguments not inferred, default to {} #5884

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

Closed
hesselink opened this issue Dec 2, 2015 · 10 comments
Closed

Generic type arguments not inferred, default to {} #5884

hesselink opened this issue Dec 2, 2015 · 10 comments
Labels
Question An issue which isn't directly actionable in code

Comments

@hesselink
Copy link

I sometimes run into scenarios where it's clear to me what a generic type argument should be, but typescript fails to infer it. I think this is because inference only looks at the call site, and not the context. The example I just ran into was this:

function comparing <A,B> (f : (x : A) => B) : (x : A, y : A) => number {
    return function (x : A, y : A) : number {
        return f(x) < f(y) ? -1 : x == y ? 0 : 1;
    }
}

var x : Array<{ foo : string }> = [{ foo : "hello" }, { foo : "world"}];

// Works, explicit types
function getFoo (o : { foo : string }) : string { return o.foo }
x.sort(comparing(getFoo))

// Works, inferred any everywhere
function getFoo2 (o) { return o.foo }
x.sort(comparing(getFoo2))

// Doesn't work, infers type parameters to {}
x.sort(comparing(o => o.foo))

In the last case it infers type parameter A to {}, causing it to fail since {} has no property foo. But from the context (comparing should return a function (x : { foo : string }, y : { foo : string }) : number) because it is used as an argument to sort on x) it should be clear that A should be { foo : string }.

Is there any way this kind of inference could be added, or is this too complicated due to overloads/subtyping/etc.

P.S. There seem to be several issues about things like this, but they all seem slightly different. If they're not, my apologies. I'd like to have a place where I can track this issue, so I would appreciate any pointers to another ticket, a FAQ, etc.

@zpdDG4gta8XKpMCd
Copy link

TypeScript doesn't crave up type arguments from the context. Type parameters of compare can only be resolved from its argument (function f). In the last case there is nothing said about the types of the o => o.foo lambda used as an argument f, so compare has nothing to deduce its type arguments from, so the inference fails.

The question remains why a function declaration defaults its parameters to implicit any whereas parameters of a lambda default to the empty type {}.

@hesselink
Copy link
Author

Yes, that's what I gathered, and why I asked if that could be changed so the context influences the type inference. This is what I'm used to from e.g. Haskell but of course typescript has many features that make this more complicated.

@zpdDG4gta8XKpMCd
Copy link

Hindley-Milner type system as in Haskell doesn't work very well with subtyping and inheritance, also the union type is problematic to work with, so it seems it was a trade-off that TypeScript design team had to make

http://stackoverflow.com/questions/7234095/why-is-scalas-type-inference-not-as-powerful-as-haskells

@hesselink
Copy link
Author

Yes, that's what I was afraid of. I'm still hoping there is a way to do something local (but less local than now) to get cases like this working.

@mhegazy mhegazy added the Question An issue which isn't directly actionable in code label Dec 3, 2015
@felixfbecker
Copy link
Contributor

I have that problem frequently with promises. If you write

const mypromise: Promise<void> = Promise.resolve();

TypeScript errors with Type Promise<void> is not compatible with type Promise<{}>.
Where does it get the {} from? Definition:

function resolve<R>(value?: R | Thenable<R>): Promise<R>;

I don't pass anything to Promise.resolve(), therefore the type argument should be the type of undefined, which in typescript is void. But Typescript defaults it to {}. Doing this however works:

const mypromise: Promise<void> = Promise.resolve(undefined);

@weswigham
Copy link
Member

@felixfbecker undefined is widened to the type any on inference, which is compatible with void. Passing no arguments means we have no inference sites to guess the generic argument, so we infer to {} - the empty object, which is not compatible with void.

That's how and why it works as it does in your case.

const mypromise: Promise<void> = Promise.resolve<void>();

to manually specify the type argument, rather than relying on inference may be more along the lines of what you want.

@felixfbecker
Copy link
Contributor

@weswigham thanks for your answer, but I think this is wrong behaviour... Why the empty object? If I don't pass an argument to a function, the value of that argument will in fact be undefined in the scope of the function, not an empty object. So TS should infer void.

@RyanCavanaugh
Copy link
Member

@felixfbecker the behavior of inferring {} occurs whenever there are zero inference sites. That can happen for a number of reasons, not just because there aren't any arguments being passed.

@DanielRosenwasser
Copy link
Member

Discussion should either continue here or on #5254.

@mhegazy
Copy link
Contributor

mhegazy commented Jan 7, 2016

closing this in favor of #5254.

@mhegazy mhegazy closed this as completed Jan 7, 2016
BarneyStratford added a commit to BarneyStratford/online-go.com that referenced this issue Jul 13, 2017
…e type Promise<string> itself and needs a little help. See the following for more discussion:

microsoft/TypeScript#5884
microsoft/TypeScript#5254
@microsoft microsoft locked and limited conversation to collaborators Jun 19, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Question An issue which isn't directly actionable in code
Projects
None yet
Development

No branches or pull requests

7 participants