Skip to content

tuple inference in array vs rest #34865

Closed

Description

@jackmellis

TypeScript Version: typescript@3.8.0-dev.20191101

Search Terms: generic, tuple, rest

Code

const pipe = <A, B, C, D>(items: [ (a: A) => B, (b: B) => C, (c: C) => D ]): D => {
  return items.reduce((acc, fn) => fn(acc), {} as any) as D;
};

const result = pipe([
  // a: {}
  (a: {}) => ({
    ...a,
    age: 4,
  }),
  // b: { age: number }
  (b) => ({
    ...b,
    color: 'blue',
  }),
  // c: unknown
  (c) => ({
    ...c,
    height: 100,
  }),
]);

Expected behavior:
type is:

{ age: number, color: string, height: number }

Actual behavior:
Parameter c is unknown

In v3.3.x which I was working with (before testing against latest) c was inferred as {}, so the return type would be { height: 100 } (i.e. all other properties were dropped).

If you do the exact same code with a rest parameter instead of an array, it works absolutely fine:

const pipe2 = <A, B, C, D>(...items: [ (a: A) => B, (b: B) => C, (c: C) => D ]): D => {
  return items.reduce((acc, fn) => fn(acc), {} as any) as D;
};

Playground Link: https://codesandbox.io/embed/typescript-playground-export-kbre9

Activity

  1. AnyhowStep commented on Nov 1, 2019

    @AnyhowStep
    Contributor

    I don't know why this happens but my personal experience aligns with yours.

    TS infers more stuff, and is more accurate with inferences when it comes to tuple rest parameters.
    It's not so good with "regular" tuple parameters.

    It's why I tend to avoid "regular" tuple parameters nowadays.

    Also, 3.6.3 TS Playground

  2. RyanCavanaugh commented on Nov 1, 2019

    @RyanCavanaugh
    Member

    This is basically a design limitation for the same reason that this wouldn't work with an explicit parameter list if the parameters were in the opposite order - given multiple context-sensitive things that need inference, we proceed in left-to-right order of parameters, including for expansion of rest tuples.

    When the inference sites are all part of a single argument tuple, there's no mechanism or heuristic to do multiple rounds of inference - it's the same as if we were trying to infer to multiple properties of the same object (in fact, it's exactly that, since a tuple in a non-rest position is just an object as far as inference is concerned).

    See #30134 for general discussion of an algorithm that would be able to handle this.

  3. AnyhowStep commented on Nov 2, 2019

    @AnyhowStep
    Contributor

    Is it because the function with rest params is converted to a function without rest params first?

    So, (...args : [A, B, C, D]) becomes (a : A, b : B, c : C, d : D).
    Then, it does inference for A, B, C, D.
    And they're all considered different objects?

  4. jackmellis commented on Nov 4, 2019

    @jackmellis
    Author

    Ryan Cavanaugh (@RyanCavanaugh) is there any sort of workaround I can employ?

    I can't use the rest parameter approach as my actual code looks more like this: <A, B, C>(opts: { inferredStuff: [ (a: A) => B, (b: B) => C ] }) => Wrapper<C> where this is just one of several optional properties

  5. RyanCavanaugh commented on Nov 4, 2019

    @RyanCavanaugh
    Member

    AnyhowStep correct

    Jack (@jackmellis) I can't think of a workaround that doesn't have effects on how the function would be called. TS just can't do multiple rounds of inference against the same object, so the inference sites need to be split up across either multiple parameters or multiple calls

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

    Design LimitationConstraints of the existing architecture prevent this from being fixed

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions