TypeScript icon indicating copy to clipboard operation
TypeScript copied to clipboard

Generator Functions are not assignable to type GeneratorFunction

Open Jonasdoubleyou opened this issue 4 years ago • 2 comments

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

This question originated from StackOverflow

Jonasdoubleyou avatar Jul 22 '21 14:07 Jonasdoubleyou

Have same issue with function that expect argument with type GeneratorFunction

Playground: https://www.typescriptlang.org/play?#code/CYUwxgNghgTiAEAzArgOzAFwJYHtXxjXgApFUAueAcRFRBigxxgDE1NdUBKSgNxyzAA3ACgRhVKXbY8AKhJd4AbxHx4ATywgIweAEZRazdt0AmQxq074AZlEBfLkKA

krutoo avatar Aug 31 '21 07:08 krutoo

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.

lachlanhunt avatar May 03 '23 15:05 lachlanhunt

Can we fix that in TypeScript 5.3?

krutoo avatar Oct 16 '23 14:10 krutoo

(Symbol.toStringTag is available on all Objects or at least on Functions 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'.

rbuckton avatar Oct 23 '23 19:10 rbuckton