microsoft / microsoft/TypeScript

Consider inferring class members types by implemented interface members (continuation of #340)

Open
#32,082 16 comments 34 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

In Discussion Suggestion
Dominant language
Go
Stars
111k
Forks
14.3k
Avg merge
2d 4h
Merged PRs (30d)
132

Description

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 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:

    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

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'
}
interface I {
  kind: 'widget' | 'gadget';
}

class C implements I {
  kind: 'widget' = 'widget'; // ⬅️ Explicit type annotation required to pin as 'widget'
}

Example in #16944:

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:

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:

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Review the prior issues and linked discussion, especially #10570 and the TypeScript PR comment referenced in the proposal, to understand the three contextual-typing options. Compare the listed class and object examples, then identify the compiler tests and implementation entry points that cover contextual typing. Done means the proposed implements cases are specified and covered without changing the stated extends behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
compilers
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.