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

Incorrect inferred return type when returning object literal from lambda bound to generic parameter #5487

Closed
russpowers opened this issue Nov 1, 2015 · 6 comments
Assignees
Labels
Bug A bug in TypeScript Fixed A PR has been merged for this issue

Comments

@russpowers
Copy link

When passing a function as a generic type T, the compiler correctly infers the return type when passing a previously declared function, but not when the same function is passed as an inline lambda. This seems to only happens with object literals. Classes and builtin return types are inferred properly.

function wrapper<T>(fn: T): T { return fn };

var fn = function(x: number) { return { x } };

// Correct: type of fnWrapper is (x: number) => { x: number }
var fnWrapper = wrapper(fn);

// Incorrect: type of inlineWrapper is (x: number) => any 
var inlineWrapper = wrapper(function(x: number) { return { x } });
@DanielRosenwasser DanielRosenwasser added the Bug A bug in TypeScript label Nov 1, 2015
@mhegazy mhegazy added this to the TypeScript 2.0 milestone Dec 8, 2015
@sandersn
Copy link
Member

sandersn commented Mar 2, 2016

I guess that function return type inference (maybe kicked off by contextual typing of the object literal?) interferes with fixing the type parameters.

@sandersn
Copy link
Member

sandersn commented Mar 4, 2016

Unfortunately, this is a real circularity in the contextual typing algorithm:

  1. To find the return type of the inline function in the call to wrapper, the compiler looks at the type of each return statement.
  2. To find the type of the return { x } statement, the compiler first checks the contextual type of { x }.
  3. To find the contextual type of { x }, the compiler checks the contextual type of the containing statement, then the contextual return type of the containing function.
  4. To find the contextual return type of the containing function, the compiler checks its return type. At this point, circularity detection notices that we started checking the return type of the function at (1) and gives up on finding the contextual type.
  5. It returns unknownType to (3) and records the circularity for when the compiler gets back to (1).

The funny part is that after the circularity is detected, the compiler can get the type of the object literal in checkObjectLiteral without a contextual type -- that's why var fn = function ... works. But the circularity detection back at (1) throws this result away and returns any instead.

I'm not sure if the circularity detection that throws away the result is correct. I'll look at that next.

@sandersn
Copy link
Member

sandersn commented Mar 4, 2016

@ahejlsberg, in getReturnTypeOfSignature, if popTypeResolution returns false, then it always ignores the type found earlier in the function and just uses any, even if the previous type turns out to be correct as in this case. Is this correct when the circularity arises from contextual typing like it does in this case?

@kitsonk
Copy link
Contributor

kitsonk commented Aug 28, 2017

I came across what I believe this issue is today, where literals could be contextually inferred, but don't seem to be... Even more surprising, is where they appear to be inferred contextually they suddenly widen without implicit types. The real world use case was using Promise.resolve() to return a literal value. Here is the example in the playground

function truePromise(): Promise<true> {
    return Promise.resolve(true); // Type 'boolean' is not assignable to type 'true'.
}

interface Wrap<T> {
    value: T;
}

function wrap<T>(value: T): Wrap<T> {
    return { value };
}

function wrappedFoo(): Wrap<'foo'> {
    return wrap('foo'); // Type 'string' is not assignable to type '"foo"'.
}

function wrapBar(value: 'bar'): Wrap<'bar'> {
    return { value };
}

function wrappedBar(): Wrap<'bar'> {
    const value = 'bar'; // const value: "bar"
    const inferred = wrapBar(value); // const inferred: Wrap<"bar">
    const literal = wrapBar('bar'); // no error!
    const value2: string = 'bar';
    const literal2 = wrapBar(value2); // Argument of type 'string' is not assignable to parameter of type '"bar"'.
    return wrap(value); // Type 'string' is not assignable to type '"bar"'. ?!?!?!
}

function wrappedBaz(): Wrap<'baz'> {
    const value: 'baz' = 'baz';
    return wrap(value);
}

The surprising one was wrappedBar() where the contextually inferred type gets widened when passed as an argument. It only seems to widen when checking assignability to generics, in other situations, where the value could be assignable to the literal, it is treated as such.

@aleclarson
Copy link

@kitsonk This issue might interest you: #10195

Here's the workaround for your example: (playground)

The error for wrapBar(value2) is intended.

@ahejlsberg
Copy link
Member

Closing as the bug no longer reproduces. We now infer the correct type.

@ahejlsberg ahejlsberg removed this from the TypeScript 3.4.0 milestone Jan 30, 2019
@ahejlsberg ahejlsberg added the Fixed A PR has been merged for this issue label Jan 30, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug A bug in TypeScript Fixed A PR has been merged for this issue
Projects
None yet
Development

No branches or pull requests

7 participants