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

tuple inference in array vs rest #34865

Closed
jackmellis opened this issue Nov 1, 2019 · 5 comments
Closed

tuple inference in array vs rest #34865

jackmellis opened this issue Nov 1, 2019 · 5 comments
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed

Comments

@jackmellis
Copy link

jackmellis commented Nov 1, 2019

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

@AnyhowStep
Copy link
Contributor

AnyhowStep commented Nov 1, 2019

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

@RyanCavanaugh
Copy link
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.

@RyanCavanaugh RyanCavanaugh added the Design Limitation Constraints of the existing architecture prevent this from being fixed label Nov 1, 2019
@AnyhowStep
Copy link
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?

@jackmellis
Copy link
Author

@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

@RyanCavanaugh
Copy link
Member

@AnyhowStep correct

@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
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed
Projects
None yet
Development

No branches or pull requests

3 participants