microsoft / microsoft/TypeScript
Allow non-polymorphic ("private") extension of base classes
Dieses Issue hat noch niemand übernommen.
- Vorherrschende Sprache
- Go
- Sterne
- 111k
- Forks
- 14.4k
- Ø Merge
- 1 T. 19 Std.
- Gemergte PRs (30 T.)
- 117
Beschreibung
🔍 Search Terms
private non-polymorphic non-assignable inheritance extension subclass change method signature of base class
✅ Viability Checklist
- 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, new syntax sugar for JS, etc.)
- This isn't a request to add a new utility type: https://github.com/microsoft/TypeScript/wiki/No-New-Utility-Types
- This feature would agree with the rest of our Design Goals: https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals
⭐ Suggestion
I would like to write an ES6 class that does not inherit the typing of its base class (and thus doesn't expose any properties or methods defined in the base class, unless they are overridden in the subclass). In C++ I would call this "private inheritance", but in short: I would like to be able to use the implementation of a class (by using the super keyword in a method) without declaring my instances to be assignment-compatible.
📃 Motivating Example
To the best of my knowledge, there are currently no ways to express this in TypeScript. In pure JavaScript, I can write the following:
export class MyTuple extends Array {
constructor(...args) {
super();
this.push(...args);
Object.freeze(this);
}
}
Instances of the MyTuple class, despite extending the Array prototype, don't conform to the Array contract, as all of the mutation methods will throw a TypeError. Even worse, direct array element assignment will silently fail, as assignment to a read-only property is simply ignored. However, the only way to get TypeScript to output this JS code is to use a TypeScript class declaration, and these always propagate the base class typings.
In this particular case, the class is a better match for the ReadonlyArray interface, so ideally I could write the following in TypeScript:
export class MyTuple<T> extends private Array<T> implements ReadonlyArray<T> {
constructor(...args: readonly T[]) {
super();
this.push(...args);
Object.freeze(this);
}
}
💻 Use Cases
- What do you want to use this for?
In my current project, I'm writing a variable-dimensionality geometry library in which thePointclass is a specialization ofArray<number>. Point is a generic class with the signaturePoint<Dims extends number>, so a 2D point is aPoint<2>, a 3D point is aPoint<3>, etc. Using Array as the base class provides a number of advantages, like being able to use.map()in order to create a new Point with the same dimensionality, based on the existing. However, most of the Array functionality shouldn't be exposed to consumers; in particular, anything that modifies the length of the array or creates an array of different length (push,slice, etc) would cause improper typing. - What shortcomings exist with current approaches?
Well, first off, the return type of.map()is incorrect because of #10886; it and all the other copy-instantiating methods should return aPointbut instead returnnumber[]. I can't usedeclareto override the method signature directly because of #38008 and #48290, and TS won't allow me to override it as a property because the types don't match. - What workarounds are you using in the meantime?
I'm removing the typing entirely by castingArrayto a dummy constructor ofObject:
Previously I'd been casting it to a constructor ofexport class Point<Dims extends number = 2> extends (Array as new () => Object) implements PointLike<Dims> {readonly number[], but that didn't work because, as mentioned above, I need to declaremap()as a method that returns a Point, and the existingnumber[]return signature can't be cast to a Point derived fromreadonly number[]. So now I'm casting it to Object, which comes with a fairly serious drawback: I can no longer use thesuperkeyword in my methods, because TS now thinks thatsuperis type Object, and I can't even correct it - thesuperkeyword can't have a type assertion applied to it, as mentioned in #41034.
Now I go to write a lot of type assertions and // @ts-expect-error comments, because so far as I can tell, there is no other way around this.
Beitragsleitfaden
Erste Schritte
- Lies das ganze Issue und danach den Beitragsleitfaden des Projekts.
- Schreib ins Issue, dass du es übernimmst — das erspart doppelte Arbeit.
- Forke das Repository und arbeite in einem Branch.
- Öffne einen Pull Request, der die Issue-Nummer nennt.
Rechercherichtung
Beginne mit den motivierenden Beispielen MyTuple und Point und prüfe anschließend die verwandten Issues #10886, #38008, #48290 und #41034. Ermittle, ob private oder nicht-polymorphe Basistypisierung spezifiziert werden kann, ohne das erzeugte JavaScript zu ändern; als erledigt gilt die Aufgabe, wenn die Beispiele typgeprüft werden und dabei die Nichtzuweisbarkeit sowie verwendbare super-Aufrufe erhalten bleiben.
Vom Indexierungsmodell aus dem Issue-Text verfasst.
Bewertung
- Tech-Stack
- javascript, typescript
- Bereich
- compilers
- Issue-Typ
- Feature
- Schwierigkeit
- 5/5
- Geschätzter Aufwand
- Über eine Woche
- Aktivitätsstatus
- Veraltet
- Klarheit
- Größtenteils klar
- Anfängerfreundlichkeit
- 25/100