Skip to content

Argument inference change in 3.4.4 #31297

Closed

Description

@zcregan

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

Activity

  1. weswigham commented on May 8, 2019

    @weswigham
    Member

    Anders Hejlsberg (@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?

  2. weswigham commented on May 8, 2019

    @weswigham
    Member

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

  3. ahejlsberg commented on Jun 12, 2019

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

  4. added
    Working as IntendedThe behavior described is the intended behavior; this is not a bug
    and removed
    BugA bug in TypeScript
    on Jun 12, 2019
  5. zcregan commented on Jun 12, 2019

    @zcregan
    Author

    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?

  6. zcregan commented on Jun 12, 2019

    @zcregan
    Author

    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
      }));
  7. ahejlsberg commented on Jun 12, 2019

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

  8. locked as resolved and limited conversation to collaborators on Oct 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Domain: check: Type InferenceRelated to type inference performed during signature resolution or `infer` type resolutionWorking as IntendedThe behavior described is the intended behavior; this is not a bug

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions