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

Rest parameter in callback function using generic tuple types: Forces definition of all parameters #35154

Open
BTOdell opened this issue Nov 17, 2019 · 3 comments
Labels
Needs Investigation This issue needs a team member to investigate its status.

Comments

@BTOdell
Copy link

BTOdell commented Nov 17, 2019

TypeScript Version: 3.8.0-dev.20191116

Search Terms:
rest parameter, spread, tuple type, generics, conditional types

Code
https://github.com/BTOdell/typescript-tuple-rest-spread-bug

This issue was discovered when implementing a type-safe event emitter class. I've produced a minimum reproducible example at the GitHub link above as well as in a Playground link below.

  • index.ts defines an unimplemented EventEmitter that includes 4 functions which should be equivalent (except for the eventName parameter).
  • test.ts consumes the EventEmitter class and calls the various functions to test type-safety.

An Events interface is used to define the supported events by mapping the event name to a tuple type for the parameters of the event listener.
This interface is passed through a generic parameter E in the Test class and then passed through a generic parameter E in the EventEmitter class.

Expected behavior:
A listener function should be able to define 1 parameter even if the caller might pass 2 parameters. The listener will then only have access to the first parameter passed. This is how JavaScript normally behaves.

Actual behavior:
The unexpected behavior occurs on lines 29 and 33 (in test.ts) and only occurs when the Events interface type has to pass through 2 generic class parameters.
If line 14 is uncommented, and line 13 is commented then all unexpected behavior is resolved.

Error:(29, 47) TS2345: Argument of type '(b: boolean) => void' is not assignable to parameter of type '(...args: Args<E["multiArray"]>) => any'.
  Types of parameters 'b' and 'args' are incompatible.
    Type 'Args<E["multiArray"]>' is not assignable to type '[boolean]'.
      Type 'E["multiArray"] | [E["multiArray"]]' is not assignable to type '[boolean]'.
        Type 'any[] & E["multiArray"]' is not assignable to type '[boolean]'.
          Types of property 'length' are incompatible.
            Type '2' is not assignable to type '1'.
              Type '[boolean, boolean]' is not assignable to type '[boolean]'.
                Types of property 'length' are incompatible.
                  Type '2' is not assignable to type '1'.

It seems like the tuple types [boolean, boolean] and [boolean] are not being treated like they're being spread as a rest parameter in a function. Could the context information be getting lost due to jumping through multiple generic parameters?

Playground Link:
Playground link

@jcalz
Copy link
Contributor

jcalz commented Nov 17, 2019

Is this related to or a duplicate of #23132? Or maybe there's some other more canonical issue for discussing issues surrounding the compiler's inability/unwillingness to do much analysis on unresolved generic conditional types?

@BTOdell
Copy link
Author

BTOdell commented Nov 18, 2019

I read #23132 thoroughly and I believe it is related. I wouldn't call it a duplicate though. Ryan Cavanaugh said he was looking for concrete examples and I would say that this is one. I'm not doing anything overly complicated and unusual here... just trying to implement a fully type-safe event emitter.

But the issue definitely seems to be related to using constrained generic parameters along with conditional types.

@andrewbranch
Copy link
Member

It’s not related to conditional types—here’s the same example without the conditional type, using an index signature to guarantee that E[keyof E] is an array type.

The use case here definitely seems reasonable... I think this is doable. No promises 😄

@andrewbranch andrewbranch added the Needs Investigation This issue needs a team member to investigate its status. label Dec 11, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Needs Investigation This issue needs a team member to investigate its status.
Projects
None yet
Development

No branches or pull requests

3 participants