Skip to content

TypeScript infers a more general type at extending parent #22287

Closed
@WilcoBakker

Description

@WilcoBakker

TypeScript Version: 2.7.0-dev.201xxxxx

Search Terms: inference, generics, static, implemenation

Code

I had encountered a problem with TypeScript which I had a question about on StackOverflow.
This was answered by @jcalz in which he stated he couldn't find any match of the exact same problem, that is why I create this issue.

StackOverflow question: https://stackoverflow.com/questions/49056870/typescript-infers-a-more-general-type-at-static-implementation

The problem is with the code as follows:

enum FruitList {
    APPLE,
    BANANA,
    PEAR,
}

interface FruitInterface {
    fruit: FruitList;
}

abstract class Fruit implements FruitInterface {
    fruit: FruitList;

    constructor(fruit: FruitList) {
        this.fruit = fruit;
    }
}

interface AppleInterface extends Fruit {
    fruit: FruitList.APPLE;
}

class Apple extends Fruit implements AppleInterface {
    fruit: FruitList.APPLE;

    constructor(fruit: FruitList) {
        super(fruit);
    }
}

Here AppleInterface declares the field fruit to be of type FruitList.APPLE while the parent interface FruitInterface declares field 'fruit' to be of type 'FruitList. So far so good. Now Fruithas a constructor with the parameterfruitof typeFruitListwhich becomes the value of the fieldfruitand its child classAppledoes the same however its field is not of typeFruitListbut of typeFruitList.APPLE`. Even though this will is likely to cause issues, it is accepted by TypeScript. The goal of TypeScript is not to be completely sound however the solution to this will quickly become cumbersome.

The solution is as follows:

interface FruitInterface<T extends FruitList> {
  fruit: T;
}

abstract class Fruit<T extends FruitList = FruitList> implements FruitInterface<T> {
  fruit: T;

  constructor(fruit: T) {
      this.fruit = fruit;
  }
}

interface AppleInterface extends Fruit<FruitList.APPLE> {
}

class Apple extends Fruit<FruitList.APPLE> implements AppleInterface {
  constructor(fruit: FruitList) {
      super(fruit); // now error as you expect
  }
}

This seems like a great solution however when there are many fields which's type can be narrowed by its child classes, all those fields must be declared by a generic type extending the base type. This can be really cumbersome. Could this be fixed?

Expected behavior:
TypeScript complains about the child parameter type being of a wider type than the field it gets assign to in its parent constructor because the child narrowed the field its type.

Actual behavior:
TypeScript allows the child parameter type being of a wider type than the field it gets assigned to in the parent constructor when its being narrowed by the child.

Playground Link: https://www.typescriptlang.org/play/index.html#src=enum%20FruitList%20%7B%0D%0A%20%20%20%20APPLE%2C%0D%0A%20%20%20%20BANANA%2C%0D%0A%20%20%20%20PEAR%2C%0D%0A%7D%0D%0A%0D%0Ainterface%20FruitInterface%20%7B%0D%0A%20%20%20%20fruit%3A%20FruitList%3B%0D%0A%7D%0D%0A%0D%0Aabstract%20class%20Fruit%20implements%20FruitInterface%20%7B%0D%0A%20%20%20%20fruit%3A%20FruitList%3B%0D%0A%0D%0A%20%20%20%20constructor(fruit%3A%20FruitList)%20%7B%0D%0A%20%20%20%20%20%20%20%20this.fruit%20%3D%20fruit%3B%0D%0A%20%20%20%20%7D%0D%0A%7D%0D%0A%0D%0Ainterface%20AppleConstructor%20%7B%0D%0A%20%20%20%20new(fruit%3A%20FruitList.APPLE)%3A%20AppleInterface%3B%0D%0A%7D%0D%0A%0D%0Ainterface%20AppleInterface%20extends%20Fruit%20%7B%0D%0A%20%20%20%20fruit%3A%20FruitList.APPLE%3B%0D%0A%7D%0D%0A%0D%0Aclass%20Apple%20extends%20Fruit%20implements%20AppleInterface%20%7B%0D%0A%20%20%20%20fruit%3A%20FruitList.APPLE%3B%0D%0A%0D%0A%20%20%20%20constructor(fruit%3A%20FruitList)%20%7B%0D%0A%20%20%20%20%20%20%20%20super(fruit)%3B%0D%0A%20%20%20%20%7D%0D%0A%7D

Related Issues: #1394

Metadata

Metadata

Assignees

No one assigned

    Labels

    DuplicateAn existing issue was already created

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions