Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TypeScript infers a more general type at extending parent #22287

Closed
WilcoBakker opened this issue Mar 2, 2018 · 3 comments
Closed

TypeScript infers a more general type at extending parent #22287

WilcoBakker opened this issue Mar 2, 2018 · 3 comments
Labels
Duplicate An existing issue was already created

Comments

@WilcoBakker
Copy link

WilcoBakker commented Mar 2, 2018

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

@jcalz
Copy link
Contributor

jcalz commented Mar 2, 2018

There likely are related issues for the unsoundness that comes with writing to covariant properties. For example, #1394 discusses this. The question here is whether the unsoundness encountered when inheriting a setter/constructor/mutable-property from a superclass is problematic enough in practice to warrant attention. I've never run into it myself, but I can see how one might trip over it.

@WilcoBakker
Copy link
Author

@jcalz Thanks, added the issue to related.

@ghost ghost added the Duplicate An existing issue was already created label Mar 2, 2018
@typescript-bot
Copy link
Collaborator

Automatically closing this issue for housekeeping purposes. The issue labels indicate that it is unactionable at the moment or has already been addressed.

@microsoft microsoft locked and limited conversation to collaborators Jul 25, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Duplicate An existing issue was already created
Projects
None yet
Development

No branches or pull requests

3 participants