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

Proposal: Introduce a new way to define "inherited" interfaces: likes #18762

Closed
Jack-Works opened this issue Sep 26, 2017 · 8 comments
Closed
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript

Comments

@Jack-Works
Copy link
Contributor

If anyone has interest in this proposal, please let me know, I may do more future work (like try to implement it)

Sometimes, we need a way to introduce an interface that is incompatible with original type but very likes to the original type.

For example, in node-canvas package, they provide a class Canvas that very likes to the HTMLCanvasElement, but it is not compatible with HTMLCanvasElement. If we can have a clear way to "clone" an interface and make some changes, we can just use this way instead of copy-paste everything in the original type.

Proposal: Introduce a new way to define "inherited" interfaces: likes

interface A { name: string, id: number }

interface B likes A { id: typeof uuid }

interface C likes B, A extends X {}

Changes to the grammar

Change InterfaceDeclaration to:

interface ‎BindingIdentifier ‎TypeParameters(opt) ‎InterfaceLikesClause(opt) ‎InterfaceExtendsClause (opt) ‎ObjectType

Add InterfaceLikesClause:
likes ClassOrInterfaceTypeList

Why to introduce this?

  1. It provides a more clear way to write two very similar (but not identical) interface.
  2. ‎Developers can know what different between this interface and what it's liking. (Oh, only property name is incompatible with Person, I can treat it as a little different type of Person)
  3. ‎If a change was made in the original type, it will automatically appear in the liking interface.

Why not to introduce this?

  1. This proposal introduced a new keyword likes
  2. ‎This CFG is some bit of ambiguous about if likes is a variable name or a keyword.
  3. ‎Maybe this scenario is not common enough to introduce a new feature to it.

When to use it

  1. Only a few properties are different from another one, and they have no logically inherited relationship.
  2. ‎If you cannot modify the parent interface (like HTMLCanvasElement) but you really want to "extends" from it.

When not to use it

  1. Only a few properties are needed from another interface. ( Just use ISth['name'] )
  2. You do not want to let other new properties automatically appear. ( Like you do not need new attributes on HTMLElement also appears on your FakeElement, you need a copy-paste )
  3. You can modify both A and B and they have logically inherited(or whatever) relationship. ( You should find something common, make it into C, and let A and B extends from C )
  4. The new interface is compatible with the original type. ( Just use extends )

Way to generate new type

Need to be precise

  1. After we finished dealing with extends, we get interface _extended
  2. ‎Do all the same things just like extends expect one thing: Inherited properties with the same name must be identical (section 3.11.2). (In typescript spec, 7.1), that means this is a conflict friendly version of interface extends.
    Now we get interface extended_liked
  3. Read all types in ObjectType, merge it into extended_liked
  4. ‎We now get the final interface

Deal with confliction

interface _ likes A, B, ... extends C, D, ... { ...E }
  1. If anything with the same name is not compatible in C and D, throw an Error ( Property X in C and D are not compatible )
  2. ‎If anything with the same name is not compatible in A and C (or B and D, and so on), use declaration in C (or D)
    3.a ‎If anything with the same name is not compatible in A and B, check if it is defined in E, if not, throw an Error ( Property X in C and D is not compatible and also not defined in the interface body, cannot determine which one to use )
    3.b If anything with the same name is not compatible in A and B, make this property as type A.X | B.X
  3. 3.a or 3.b, which one is better?
  4. ‎If anything with the same name is not compatible in E and C, throw an Error ( Property X in the interface body are not compatible with X defined in C )
  5. ‎If anything with the same name is not compatible with E and A, use that in E

For example:

interface Foreigner { name: string, id: number }
interface Student { id: typeof uuid }
interface ForeignerStudent likes Foreigner extends Student {  }

// ForeignerStudent is a subtype of Student, but not a sub-type of Foreigner
// ForeignerStudent = { name: string, id: typeof uuid }

3.a

interface Foreigner { name: string, id: number }
interface Student { id: typeof uuid }
interface ForeignerStudent likes Foreigner, Student {  }
// Error: Property id is not compatible
//     typeof uuid is not compatible with number
//          You need to specify a type for id
interface ForeignerStudent2 likes Foreigner, Student { id: typeof uuid }
// This is fine.

3.b

interface Foreigner { name: string, id: number }
interface Student { id: typeof uuid }
interface ForeignerStudent likes Foreigner, Student {  }
// ForeignerStudent = { name: string, id: number | typeof uuid }

Others

What if I want to remove a property inherited by likes?
I have 2 ideas at present

interface N likes M {
    y: never // this way
    ‎delete x // or this way
}

If anyone has interest in this proposal, I will consider it later.

When will a liked interface compatible with the original interface?
Just treat it as an another identical normal interface.

Does it breaks the type system?
I don't think so. It is not a subtype of the original type.

What about generics work with proposal?
No idea, I will think it later.

@kitsonk
Copy link
Contributor

kitsonk commented Sep 26, 2017

Interfaces are not unique to TypeScript. Are there any other similar language features in other languages that do this that you can point to?

@Jack-Works
Copy link
Contributor Author

I have no idea about it.
I almost have no experience on other languages with interface.
So is this cannot be considered? :(

@kitsonk
Copy link
Contributor

kitsonk commented Sep 26, 2017

So is this cannot be considered? :(

I am not a member of the core team. My opinions are my own. I am just raising the question that something like this is far more likely to progress if there is a) established concepts in other languages that can be used as a guideline and b) that there is a syntactical and lexical precedent that can be drawn on.

You clearly feel strong about it. If there are features in other languages that align to this, it maybe worth researching that and referencing that to further your proposal.

@Jack-Works
Copy link
Contributor Author

Jack-Works commented Sep 26, 2017 via email

@RyanCavanaugh RyanCavanaugh added Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript labels Sep 26, 2017
@ghost
Copy link

ghost commented Oct 10, 2017

Possibly this could be done with #4183? Subtract out the properties you don't want, then extend that.

@Jack-Works
Copy link
Contributor Author

Jack-Works commented Oct 25, 2017

Oh I found this #10727

type NewItem = {...Item, notWanted: undefined, overwrite: NewType }

@HerringtonDarkholme
Copy link
Contributor

I think we can already do this with #21316 and #13604

@Jack-Works
Copy link
Contributor Author

See #10727, that proposal is more expressive and more nature than mine. Thanks

@microsoft microsoft locked and limited conversation to collaborators Jul 3, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

4 participants