Repository: TypeScript
Issue: Consider inferring class members types by implemented interface members (continuation of #340)
Labels: Suggestion,In Discussion

Continuation of #340, #1373, #5749, #6118, #10570, #16944, #23911.

## Why a new issue?

Many previous issues discussed about having contextual types based on **both** extended base class members **and** implemented interface members. This proposal only applies to `implements`, not `extends`.

Discussions in #6118 [ends](https://github.com/microsoft/TypeScript/pull/6118#issuecomment-216595207) with an edge case when a class extends a base class and implements another interface. I think this problem would not occur if the scope is limited to `implements` keyword only, and I believe would be a step forward from having parameters inferred as `any`.

Previous issues have been locked, making it impossible to continue the discussion as time passes.

## Proposal

Using terminology “option 1”, “option 2”, “option 3” referring to this comment: https://github.com/microsoft/TypeScript/issues/10570#issuecomment-296860943

- For members from `extends` keep option 1.
- For members from `implements`:
    - Use option 3 for non-function properties that don’t have type annotation.
    - Use option 2 for function properties and methods.

## Examples

Code example in the [linked comment](https://github.com/microsoft/TypeScript/pull/6118#issuecomment-216595207):

```ts
    class C extends Base.Base implements Contract {
        item = createSubitem(); // ⬅️ No type annotation -- inferred as `Subitem`
    }
```

Since only the `implements` keyword is considered, `item` will be inferred as `Subitem`.

Example in https://github.com/microsoft/TypeScript/issues/10570#issuecomment-296860943

```ts
interface I {
  kind: 'widget' | 'gadget';
}

class C implements I {
  kind = 'widget'; // ⬅️ No type annotation -- inferred as 'widget' | 'gadget'
}

// Above behavior is consistent with:
const c: I = {
  kind: 'widget' // ⬅️ c.kind is also 'widget' | 'gadget'
}
```

```ts
interface I {
  kind: 'widget' | 'gadget';
}

class C implements I {
  kind: 'widget' = 'widget'; // ⬅️ Explicit type annotation required to pin as 'widget'
}
```

Example in #16944:

```ts
interface IComponentLifecycle<P> {
  componentWillReceiveProps?(nextProps: Readonly<P>, nextContext: any): void;
}

interface IProps {
  hello: string;
}

class X implements IComponentLifecycle<IProps> {
  componentWillReceiveProps(nextProps) {
    //                      ^ Contextually typed as Readonly<IProps>
  }
}
```

Example in #340:

```ts
interface SomeInterface1 {
    getThing(x: string): Element;
}

interface SomeInterface2 {
    getThing(x: number): HTMLElement;   
}

declare class SomeClass implements SomeInterface1, SomeInterface2 {
    getThing(x) {
        //   ^ Contextually inferred from
        //     (SomeInterface1 & SomeInterface2)['getThing']
        //     As of TS 3.5, it is an implicit any.
    }
}

// Above behavior is consistent with:
const c: SomeInterface1 & SomeInterface2 = {
    getThing(x) {
        //   ^ Implicit any, as of TS 3.5
    },
}
```

## Checklist

My suggestion meets these guidelines:

* [x] This wouldn't be a breaking change in existing TypeScript/JavaScript code
* [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).