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

Argument inference change in 3.4.4 #31297

Closed
zcregan opened this issue May 7, 2019 · 6 comments
Closed

Argument inference change in 3.4.4 #31297

zcregan opened this issue May 7, 2019 · 6 comments
Assignees
Labels
Domain: Type Inference Related to type inference performed during signature resolution or `infer` type resolution Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@zcregan
Copy link

zcregan commented May 7, 2019

TypeScript Version: 3.4.4-dev.20190507

Search Terms: 3.4.4 generic argument inference

Code

function wrap<Self>(
  creator: (self: Self) => Self
) {}

wrap(self => ({
  val: 2,

  func(): number {
    return self.val;
  },
}));

Expected behavior:
Expected to compile without any errors, with the type of self inferred as { val: number, func(): number }. This works as expected from v3.1 -> 3.4.3

Actual behavior:
As of 3.4.4 this no longer works. Looks like self is being inferred as {}. TypeScript emits the following error at return self.val:

9:17 - error TS2339: Property 'val' does not exist on type '{}'.

I tested this with TypeScript@3.5.0-dev.20190507 and it looks like self is being inferred as unknown. It emits the following error:

9:12 - error TS2571: Object is of type 'unknown'.

Playground Link:
works in older version of TypeScript

@weswigham weswigham added Bug A bug in TypeScript Domain: Type Inference Related to type inference performed during signature resolution or `infer` type resolution labels May 8, 2019
@weswigham
Copy link
Member

@ahejlsberg is this an effect of instantiating the func method's return type too soon and not recognizing the non-inferrable type required to calculate it in the first pass?

@weswigham
Copy link
Member

Actually, func is totally annotated, I see no reason why we would fix self too soon. o.O

@ahejlsberg
Copy link
Member

This is working as intended. In the call to wrap, the self parameter is contextually typed by Self. Since there are no other arguments and no contextual return type to make inferences from, we end up fixing Self to type unknown. And once we have committed to a type for Self we can't make any further inferences and therefore infer nothing from the return type of the lambda.

In half-way worked before because of a bug that was fixed in #30856. You can see the inconsistency with some quick info hovering. For example, inside func, statement completion doesn't work on self and hovering shows type { val: number } which certainly isn't correct.

@ahejlsberg ahejlsberg added Working as Intended The behavior described is the intended behavior; this is not a bug and removed Bug A bug in TypeScript labels Jun 12, 2019
@zcregan
Copy link
Author

zcregan commented Jun 12, 2019

If we expand on this example a bit, the behaviour still seems a little strange to me. If the wrap function were to return an instance of Self:

function wrap<Self>(
  creator: (self: Self) => Self
): Self {
  const self = {} as Self;
  Object.assign(self, creator(self));
  return self;
}

Then we get two different return types depending on if we reference self within the lambda:

const knownSelf = wrap(() => ({ val: 2 })); // type of knownSelf is { val: number }

const unknownSelf = wrap(self => ({ val: 2 })); // type of unknownSelf is unknown

From your explanation I vaguely understand why this is the case, but is that behaviour desired?

@zcregan
Copy link
Author

zcregan commented Jun 12, 2019

I was hoping for the following behaviour:

const counter =           // inferred
  wrap(self => ({         // unknown
    val: 0,               // unknown
                          // unknown
    increment(): number { // unknown
      self.val += 1;      // inferred
      return self.val     // inferred
    },                    // unknown
                          // unknown
    decrement(): number { // unknown
      self.val -= 1;      // inferred
      return self.val     // inferred
    },                    // unknown
  }));

@ahejlsberg
Copy link
Member

Did you consider this instead:

const counter = {
    val: 0,
    increment() {
      this.val += 1;
      return this.val;
    },
    decrement() {
      this.val -= 1;
      return this.val;
    },
};

Everything is inferred correctly and no need to double allocate the object.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Domain: Type Inference Related to type inference performed during signature resolution or `infer` type resolution Working as Intended The behavior described is the intended behavior; this is not a bug
Projects
None yet
Development

No branches or pull requests

4 participants