microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
100.17k stars 12.38k forks source link

Generators with extended unions, null and undefined produces compiler errors #40044

Open LeviticusMB opened 4 years ago

LeviticusMB commented 4 years ago

I've run into a weird problem in a project that uses async generators. I've reduced the problematic code to the example below, which also fails in a similar way that my actual code does.

There seem to be several conditions that must be fulfilled:

  1. Type T is a union.
  2. The argument generator extends T (with WithID in the example).
  3. The argument generator also accepts both null and undefiened.
  4. The provided generator has a return signature.
  5. The provided generator's return signature yields T and either null or undefined (but not both).

Change just any of these conditions in the playground, and the error goes away ...

TypeScript Version: 4.0.0-beta (and below)

Search Terms: generator null undefined

Expected behavior: No errors.

Actual behavior: A strange error message when doSomething() is called.

Related Issues: No

Code

interface WithID {
    id?: number;
}

type MyEvent = { type: "number", value: number } |  { type: "string", value: string };
//interface MyEvent { type: "number" | "string", value: number | string };

function doSomething<T extends object>(gen: Generator<T & WithID | undefined | null>) {
    // ...
}

function* readEvents(): Generator<MyEvent | undefined> {
    yield undefined;
}

doSomething(readEvents());
Output ```ts "use strict"; //interface MyEvent { type: "number" | "string", value: number | string }; function doSomething(gen) { // ... } function* readEvents() { yield undefined; } doSomething(readEvents()); ```
Compiler Options ```json { "compilerOptions": { "noImplicitAny": true, "strictNullChecks": true, "strictFunctionTypes": true, "strictPropertyInitialization": true, "strictBindCallApply": true, "noImplicitThis": true, "noImplicitReturns": true, "alwaysStrict": true, "esModuleInterop": true, "declaration": true, "experimentalDecorators": true, "emitDecoratorMetadata": true, "moduleResolution": 2, "target": "ES2019", "jsx": "React", "module": "ESNext" } } ```

Playground Link: Provided

rbuckton commented 10 months ago

The error may stem from doSomething inferring the wrong type for T, as doSomething(readEvents()) seems to infer T to { type: "number"; value: number; } and not MyEvent. The error also goes away if you write doSomething<MyEvent>(readEvents()); instead. I will need to investigate further to determine if this is something we can improve on.