Skip to content

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

Closed

Description

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.

Activity

  1. zpdDG4gta8XKpMCd commented on Dec 2, 2015

    @zpdDG4gta8XKpMCd

    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 {}.

  2. hesselink commented on Dec 2, 2015

    @hesselink
    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.

  3. zpdDG4gta8XKpMCd commented on Dec 2, 2015

    @zpdDG4gta8XKpMCd

    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

  4. hesselink commented on Dec 2, 2015

    @hesselink
    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.

  5. felixfbecker commented on Dec 21, 2015

    @felixfbecker
    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);
  6. weswigham commented on Dec 21, 2015

    @weswigham
    Member

    Felix Becker (@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.

  7. felixfbecker commented on Dec 21, 2015

    @felixfbecker
    Contributor

    Wesley Wigham (@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.

  8. RyanCavanaugh commented on Dec 21, 2015

    @RyanCavanaugh
    Member

    Felix Becker (@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.

  9. DanielRosenwasser commented on Dec 28, 2015

    @DanielRosenwasser
    Member

    Discussion should either continue here or on #5254.

  10. mhegazy commented on Jan 7, 2016

    @mhegazy
    Contributor

    closing this in favor of #5254.

  11. locked and limited conversation to collaborators on Jun 19, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    QuestionAn issue which isn't directly actionable in code

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions