Repository: TypeScript
Issue: Make it possible to infer superclass type parameters / constructor overload from the `super` call
Labels: Suggestion,Awaiting More Feedback

## Search Terms
superclass base class generic type infer contextual infer parent generic super constructor

## Suggestion

Make it possible to infer superclass type parameters / constructor overload from the `super` call

## Use Cases

I'm working on an intermediate library with typings for a legacy codebase. That intermediate library is used in third party to use the functionality from the main codebase (called adapters). There are several supported ways to access this functionality:
1. `const foo1 = Adapter(options);`
2. `const foo2 = new Adapter(options);`
3. `class Foo3 extends Adapter { ... }` - The constructor must call the superclass constructor with the options.

Now the problem is that the `Adapter` "class" (actually an ES5-style "function" class) has some properties that only exist if specific options are passed to its constructor. I'm able to model this behavior for case 1 and 2, but not 3 - at least not without manually specifying the type.

Here's what I have so far. The code is split into three parts: main codebase, intermediate library, 3rd party / user code. ([Playground link](https://www.typescriptlang.org/play/?ts=3.8.0-dev.20200125&ssl=84&ssc=2&pln=79&pc=1#code/PTAEBsFMHMEMGMCep4HsAmkA0LYDs9UAXFAC32kgC4BYAKE3nFgCdIVmBnT0ASTyKQWeWOACC6WAAdBLUAG96oZaBCgERAK6jwyfKACiAZQCsAWk5FEUDrG6gAZprzwiAS1R4c8fQCN2PuBQ6KAA7m5EpOp4IeGRqJokeJChAIRKKpyoALaQAAosqFIAjFTRiADcGcpZuQVFAExl+JX01apgvGH4JESooKgAbkIsbpigkW48VlKQ7T7wpJAA8r4AVmUASpBoLOgAPJajeNA4LQB8oAA+oM6YDm7J6FV0AL5tdGoAvD+-f-8A34fNQAWWQaEw3n0iwo1Hoj1kDgQ7Ak0lkyxkHjwPEUdBUoBEuTKR0e0Be+IWSwA-GUiCxNJAXu86PQZijJDIhPxLPh4JB9gAVUCQAAeghiPFRnJYGPcnk4ly+oCFovF6Bx9AAkJTqBN6Yz6MzNVTQMtshF9vxZCJxBzZDgAEQ61ZrB2XABkCi1mu1CCWLrKBhFTE0mEtAiENqlsgA2k6-St1g6ALo4O6QB5Pc4vTXKI1lM0Wq2R0TRoSO51J7MfBFCJF80BllgAYXldM0rlQclxmuSoVAAApYHahAA5WBE0Akk4ASjKTe5RF5-PkhN10+gr2rdF7KVAguFYsgEsbI5lmPl5yHZ9lWM4ZQFc9PaK52KXLn5Au3+K115fLHHScNyfBc32XfZVwndc6VJLccwPVVj3VZ9pVvS8-1Qi9sQfECz0XcCvyZD5GGYNgUDbFDZHnM9WzfelOxYF56G+QFWNY4EwAAVU4IRyMhKdSAScAQn8dQeE4NxsikGw7FAKRUG4NxfCgeg0DfAkoNAJUHQcVBUF8VgHReNTLAGLCeCVeQNNyUBXiYugTN6SBLGKLTKKEAc1xnF5BBcgA6Ss1gqDpQHQVBnIJYhDymIgcFFWZXEgdAPkciZnKIBo3L7dyWE8qDvNZdKGgChMXWCtQwoiwgSFFGK4pFBLBGSlkHIo3yiAAZjcpsByKOVsQKuh2o6krFkTIKQsqnhquiyx6sapKUra9KABYsr3Hq+rvQb2pW0b-XWcqwCmyKapFOrDwW5rVOWywTHW-seqsvyXq2+UoTG2l9Vsnb0pMfbxqO2aiE4eadialKuB4AV0oANkPNVJTPfYmzQ7FLlxCk23ovpcre7CcrRzgZy9PF8UyTRZjx8zBvJlRJk4AGysm8Lpqi2q5su8HFrJvNDUhuxofSgB2BGkKR-8UZvcyMfmbGO1x3rzOo-8iZJzG6anSmPOe17zI+pYvoZH7yU1hmmcOlmqvZ87OeceLuea-FmWZVSoeVdKAA4xZPJspdV8zQE9eQdSNyAt1JrG6IVrslf6+9CZpyPNc4bXct1vz8dB3BPr1Y3Xlps3SCmC2JrUDmQZwXxEj4UByGGCZ+nQSnwDcHxBAmJYJkQWZ2hdgX7BhywAE4feQptVAAKm72Z1BYaBNFyARQEeBwRiSxvQFE1HA+D0O8-D0BJ7ADXlEcnHY6zlXMPj9X2nxVOqYHDOs4N3V23YAvTbp83AqBzhBKaGEsDPuhogA))
```ts
// legacy code, cannot change:
declare class InternalAdapter {
    // actually an ES5-style class function, can be called with and without new!
    someProp1: any;
    someProp2: any;

    // I want to override this type
    cacheObj: Record<string, any> | undefined;
}

// ========================

// My code, can change:
interface AdapterOptions {
    name: string;
    cache?: boolean;
}

type AdapterInstance<T extends AdapterOptions> = T extends {
	cache: true;
}
	? Omit<InternalAdapter, "cacheObj"> & {
			cacheObj: Exclude<InternalAdapter["cacheObj"], undefined>;
	  }
	: Omit<InternalAdapter, "cacheObj">;

interface AdapterConstructor {
	new (adapterName: string): AdapterInstance<{name: string}>;
	new <T extends AdapterOptions>(adapterOptions: T): AdapterInstance<T>;
    
	(adapterName: string): AdapterInstance<{name: string}>;
	<T extends AdapterOptions>(adapterOptions: T): AdapterInstance<T>;
}

declare const Adapter: AdapterConstructor;

// ========================

// User code, should be as simple as possible
const name = "foobar";
const options = { name };

const test1 = Adapter(name);
test1.cacheObj; // does not exist, expected

const test2 = new Adapter(name);
test2.cacheObj; // does not exist, expected

const test3 = Adapter(options);
test3.cacheObj; // does not exist, expected

const test4 = new Adapter(options);
test4.cacheObj; // does not exist, expected

const test5 = new Adapter({ ...options, cache: true });
test5.cacheObj; // exists, expected

// Here, the problems start:

class Test6 extends Adapter<AdapterOptions> {
    constructor(options: AdapterOptions) {
        super(options);
        this.cacheObj; // does not exist, expected
    }
}

class Test7 extends Adapter<AdapterOptions> {
    constructor(options: AdapterOptions) {
        super({ ...options, cache: true });
        this.cacheObj; // does not exist, unexpected
    }
}

class Test8 extends Adapter<AdapterOptions & {cache: true}> {
    constructor(options: AdapterOptions) {
        super({ ...options, cache: true });
        this.cacheObj; // exists, but I have to duplicate the type
    }
}
```

Notice how the class definitions are all awkward. In `Test6` I have to duplicate the generic type `AdapterOptions` or the super call will default to the string constructor.
In `Test7` this is actually wrong, because the generic type overrides the conditional behavior for the `cacheObj`. This can be fixed like in `Test8`, but that is really ugly. IMO, TypeScript should be able to infer the type arguments from a constructor call if that type argument matches the class' type argument

## Examples

Show how this would be used and what the behavior would be - Ideally it should be like this:
```ts
class Test9 extends Adapter {
    constructor(options: AdapterOptions) {
        super({ ...options, cache: true });
        this.cacheObj; // should exist
    }
}
```
The `super` call would be used to infer that the 2nd constructor overload is the correct one:
```ts
new <T extends AdapterOptions>(adapterOptions: T): AdapterInstance<T>;
```
and therefore the instance type would be `AdapterInstance<AdapterOptions & {cache: true}>`.

## Checklist

My suggestion meets these guidelines:

* [ ] This wouldn't be a breaking change in existing TypeScript/JavaScript code **(not sure actually. I'd guess not?)**
* [x] This wouldn't change the runtime behavior of existing JavaScript code
* [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).