Repository: TypeScript
Issue: instanceof narrowing should preserve generic types from super to child type
Labels: Suggestion,In Discussion,Domain: Control Flow

## Search Terms

* instanceof generic 
*  narrow instanceof

## Suggestion

The following code has a rather unexpected behavior today:

```ts
class Parent<T> {
    x: T;
}
class Child<S> extends Parent<S> {
    y: S;
}

function example(obj: Parent<number>) {
    if (obj instanceof Child) {
        const child = obj;
    }
}
```

The narrowed type that `child` gets is `Child<any>`. 

I'm requesting that the type instead gets narrowed to `Child<number>`, which is what I originally assumed would happen.

My proposal is, in general, to infer the type arguments to the narrowed-to class type whenever they can be determined from the original type.

## Precise Behavior

Consider the following code today:

```ts
const x: P = ...;
if (x instanceof C) {
    x;
}
```

Today, if `C` is a generic class, `x` gets the narrowed type `C<any, any, any, ...>`; otherwise it just gets the type `C`.

My proposal is to consider the following piece of code, and use it to inform 

Imagine that `C` has a no-argument constructor. Then the following piece of code is valid today:

```ts
function onlyP(c: P) { ... }

onlyC(new C());
```
in order to make it valid, the compiler infers type arguments for `C` that make it into a subtype of `P`. My proposal is to use the same strategy to infer the type arguments for `C` in an `instanceof` narrowing.

Consider the following examples today, and their corresponding `instanceof` narrowings:

```ts
function ex1(x: Parent<string>) { }
ex1(new Child()); // inferred arguments: <string>

function ex2(x: Parent<number | string>) { }
ex2(new Child()); // inferred arguments: <number | string>

function ex3(x: Parent<number> | string) { }
ex3(new Child()); // inferred arguments: <number>

function ex4(x: Parent<number> | Parent<string>) { }
ex4(new Child()); // inferred arguments: Child<number | string>
// Note: the above errors, because it Child<number|string> actually fails to be a subtype of
// Parent<number> | Parent<string>.
// We can either choose to infer Child<any> in this case, or use the (incorrect, but more-precise)
// inference Child<number | string>.
```

## Examples

The original use-case I had in mind was roughly the following:

```ts
abstract class Obtainer<T> {
  __phantom: T = null as T;
}

abstract class Fetcher<T> extends Obtainer<T> {
  public abstract fetch(): Promise<T>;
}

abstract class Dependency<T, D> extends Obtainer<T> {
  public abstract dependencies(): Obtainer<D>;
  public abstract compute(got: D): T;
}

async function obtain<T>(obtainer: Obtainer<T>): Promise<T> {
  if (obtainer instanceof Fetcher) {
    // obtainer: Fetcher<T>
    // currently, it's a Fetcher<any>
    return await obtainer.fetch();
  }
  if (obtainer instanceof Dependency) {
    // obtainer: Dependency<T, any>
    // currently, it's a Dependency<any, any>
    const dependencies = obtainer.dependencies();
    return obtainer.compute(await obtain(dependencies));
  }
  throw new Error("not implemented");
}
```

(note: there's still one extraneous `any` in the above, since the `D` parameter cannot be inferred)

## Checklist

My suggestion meets these guidelines:

* [ ] This wouldn't be a breaking change in existing TypeScript/JavaScript code
    * This *is* a breaking change. However, I *think* it could only really break code that was already broken. If really necessary, it could be put behind a new `--strictGenericNarrowing` flag. 
* [x] This wouldn't change the runtime behavior of existing JavaScript code
    * No change to runtime; just inferred generic types.
* [x] This could be implemented without emitting different JS based on the types of the expressions
* [ x This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
* [x] This feature would agree with the rest of [TypeScript's Design Goals](https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals).
    * I think this is an easy win for a more sound type system without any negative impact


Also, I am interested in contributing this change if it's approved!