Generator Functions are not assignable to type GeneratorFunction
Bug Report
🔎 Search Terms
GeneratorFunction Symbol.toStringTag
🕗 Version & Regression Information
The type GeneratorFunction seems to be completely useless as generator functions are not assignable to it.
This exists at least since v3.3 (probably earlier with the introduction of Generators) and can still be reproduced with the newest nightly in the playground.
⏯ Playground Link
Playground link with relevant code
💻 Code
let foo: GeneratorFunction = function * () { return 1}
// Property '[Symbol.toStringTag]' is missing in type '() => IterableIterator<number>' but required in type 'GeneratorFunction'.
🙁 Actual behavior
The types are not assignable to each other because Symbol.toStringTag is available on GeneratorFunction though not on Function. That's quite weird as it should actually be usable on all objects. I'm wondering why it was not added to the Object interface, but only to the GeneratorFunction.
What's even more strange is that the function* gets inferred to () => Generator which is accurate, though it makes the GeneratorFunction interface pretty useless.
🙂 Expected behavior
I would expect that Function and GeneratorFunction are compatible (Symbol.toStringTag is available on all Objects or at least on Functions too). I would also expect that function* gets inferred as GeneratorFunction.
Is it a bug? I'm not sure ... it could also be the result of various constraints that I am unaware of or that I could not find. In that case I'd be very grateful for an explanation :)
Thanks in advance, Jonas
Have same issue with function that expect argument with type GeneratorFunction
Playground: https://www.typescriptlang.org/play?#code/CYUwxgNghgTiAEAzArgOzAFwJYHtXxjXgApFUAueAcRFRBigxxgDE1NdUBKSgNxyzAA3ACgRhVKXbY8AKhJd4AbxHx4ATywgIweAEZRazdt0AmQxq074AZlEBfLkKA
I ran into this problem as well. I came up with a workaround by just defining my own interface for generator functions:
interface PatchedGeneratorFunction {
/**
* Creates a new Generator object.
* @param args A list of arguments the function accepts.
*/
(...args: any[]): Generator;
/**
* The length of the arguments.
*/
readonly length: number;
/**
* Returns the name of the function.
*/
readonly name: string;
/**
* A reference to the prototype.
*/
readonly prototype: Generator;
}
This omits both the [Symbol.toStringTag] and the new (...args: any[]): Generator; that are present in the GeneratorFunction interface that ships with TypeScript.
Can we fix that in TypeScript 5.3?
(
Symbol.toStringTagis available on allObjects or at least onFunctions too)
Unfortunately, Function.prototype does not define [Symbol.toStringTag] since that would affect the toString() output of all class instances.
Currently, all function types have an effective supertype of Function. Unfortunately, we cannot give generator functions and methods a different effective supertype as that wouldn't carry over to declaration emit:
declare let foo: () => Generator;
let bar: GeneratorFunction = foo; // error
The only way we could make this consistent in declaration emit would be to introduce a "generator function type" syntax, i.e., declare let foo: * () => Generator, or something like it. However, aside from making it fit with GeneratorFunction, there's no other real value to be gained by introducing new syntax for this.
Barring that, we could just remove the construct signature and [Symbol.toStringTag] from GeneratorFunction. However, doing so could break existing users if anyone is using new with GeneratorFunction. In addition, by stripping those elements from the type what you would essentially have is no better than (...args: any) => Generator.
Instead, I would argue that you really shouldn't be using GeneratorFunction in this way as it is not an interface to conform to. Rather, it is the superclass of a given generator function. In that way, it has the same behavior as any other attempt to assign a subtype constructor to a supertype constructor that does not conform to the same set of signatures:
class A {
constructor(public x: number) {}
}
class B extends A {
constructor(x: number, public y: number) {
super(x);
}
}
let foo: typeof A = B; // error: Type 'typeof B' is not assignable to type 'typeof A'.