Uh oh!
There was an error while loading. Please reload this page.
Add support for abstract constructor types - #36392
Conversation
rbuckton
commented
Jan 24, 2020
CC: @weswigham, @RyanCavanaugh, @DanielRosenwasser (as suggested reviewers is not working). |
ajafff
commented
Jan 24, 2020
Does this also support mixins with abstract classes? Could you add a test for that, too? |
136dbf0 to
757b70fComparerbuckton
commented
Jan 28, 2020
I'm going to revert support for // [source.ts]functionMyMixin<TBaseClassextendsabstractnew(...args: any[])=>any>(baseClass: TBaseClass){abstractclassMyMixinClassextendsbaseClass{abstractabstractMethod(): void;}returnMyMixinClass;}// [output.d.ts]declarefunctionMyMixin<TBaseClassextendsabstractnew(...args: any[])=>any>(baseClass: TBaseClass): abstractclassextendsTBaseClass{abstractabstractMethod(): void;}; |
757b70f to
eb5789aComparerbuckton
commented
Jul 11, 2020
Build failures seem to be due to the fact that eslint isn't handling the construct signatures having modifiers in |
0b9fd25 to
65a49a7Compare
If the constraint for It won't be helpful if we're stuck with either abstract or not-abstract. I think what people really want is that if they pass in an abstract class, then the mixin result is an abstract class (unless the mixin class implements the methods/properties), or if they pass a non-abstract class, then the mixin result is not abstract. Otherwise the usability of mixins would be inflexible. The following code has some type information omitted (like plain JS) so that I can clearly show the intention: abstractclassFoo{abstractfooMethod(): void}functionCoolMixin(Base){returnclassCoolextendsBase{coolMethod(){}}}classMyClassextendsFoo{// Required to implement the abstract fooMethod here.// ...}classMyOtherClassextendsCoolMixin(Foo){// Should also be required to implement the abstract fooMethod here.// ...}As a workaround, we could do the following but it is less ideal: abstractclassFoo{abstractfooMethod(): void}functionCoolMixin(Base){returnclassCoolextendsBase{coolMethod(){}}}classMyClassextendsFoo{// Required to implement the abstract fooMethod here.// ...}class_MyOtherClassextendsFoo{// Required to implement the abstract fooMethod here.}classMyOtherClassextendsCoolMixin(_MyOtherClass){// ...}And if the mixin returns an abstract class, it should inherit abstractedness. So, abstractclassFoo{abstractfooMethod(): void}functionOtherMixin(Base){returnabstractclassOtherextendsBase{otherMethod(){}}}classMyClassextendsFoo{// Required to implement fooMethod here.// ...}classMyOtherClassextendsOtherMixin(Foo){// Required to implement fooMethod and otherMethod here.// ...}classBar{barMethod(){...}}// the previous OtherMixin application accepted an abstract class, and now this application accepts a non-abstract class:classMyNextClassextendsOtherMixin(Bar){// Required to implement otherMethod here, and the barMethod is inherited.// ...}Note that last example in particular accepts both abstract and non-abstract base classes (I didn't show an example of I believe that's what we should aim for in TypeScript, otherwise we're not effectively modeling plain JS in the most useful way. Mixins are suppose to be mixed-and-matched, but if we can not work with I'm merely a TypeScript end user, but I hope some solution can be found so that mixins are as convenient as they are in plain JS but with structural types in place. |
# Conflicts: # src/compiler/parser.ts # src/compiler/types.ts
rbuckton
commented
Sep 8, 2020
@typescript-bot pack this |
typescript-bot
commented
Sep 8, 2020
Hey @rbuckton, I've packed this into an installable tgz. You can install it for testing by referencing it in your and then running |
@trusktr: A functionMixin<TBaseextendsabstractnew(...args: any[])=>{}>(base: TBase){abstractclassMextendsbase{}returnM;}abstractclassAbstractBase{abstractm(): number;}classConcreteBase{m(): number{return1;}}// error: C must implement abstract method `m`classCextendsMixin(AbstractBase){}// no errorclassDextendsMixin(ConcreteBase){} |
rbuckton
commented
Sep 9, 2020
Although it should probably be an error for |
orta
commented
Sep 9, 2020
@typescript-bot pack this |
Hey @orta, I've packed this into an installable tgz. You can install it for testing by referencing it in your and then running There is also a playground for this build. |
DanielRosenwasser
commented
Jan 5, 2021
I think the one thing that I'm still a little bit surprised about is that there's no way to rewrite the following example abstractclassFoo{abstractfooMethod(): void}functionextendFoo(FooCtor: abstractnew()=>Foo){// Error! Must implement 'fooMethod'returnclassextendsFooCtor{}}in such a way that The first reason I think this is weird is because abstractclassFoo{abstractfooMethod(): void}// Homomorphic mapped type over `Foo`typeCopyFoo={[KinkeyofFoo]: Foo[K]}functionextendFoo(FooCtor: abstractnew()=>CopyFoo){// No error now!returnclassextendsFooCtor{}}The second is that in order to model this sort of pattern, you always have to declare a class, even if you won't necessarily be extending from that class (and even if nobody is going to be extending from that class). That on its own feels kind of strange in our mostly-structural type system. But I don't really know the right way to reconcile all of this - would "fixing" these issues mean that interfaces would have to allow |
The issue is that we don't (yet) have a way to represent this type in any other way. #41587 will eventually add syntax that would permit this in type-space: typeTFoo=typeofabstractclass{constructor();abstractfooMethod(): void;};functionextendFoo(FooCtor: TFoo){ ... }However, typeInstanceType<Textendsabstractnew(...args: any)=>any>=Textendsabstractnew(...args: any)=> infer R ? R : any;With a new typeTFooInst=InstanceType<typeofabstractclass{abstractfooMethod(): void;}>;In the end we will need both abstract constructor types and Whether or not we eventually do something else for |
rbuckton
commented
Jan 5, 2021
Actually, the example above isn't the problem, as functionextendFoo(FooCtor: abstractnew()=>Foo){// No errorreturnclassextendsFooCtor{fooMethod(): void{}// implements it just fine.}}While this isn't necessarily sound, it aligns with other inconsistencies in the language, in that we assume interfaceFoo{normalMethod(): void;}declarefunctionFooMixin<Ctorextendsabstractnew(...args: any[])=>any>(base: Ctor): Ctor&(abstractnew(...args: any[])=>Foo);classConcrete{}classSubConcreteextendsMixinFoo(Concrete){}// okabstractclassAbstract{abstractfooMethod(): void;}classSubConcreteAbstractextendsFooMixin(Abstract){// I must implement 'fooMethod' because it's marked 'abstract' in the base class.fooMethod(): void{}} |
rbuckton
commented
Jan 5, 2021
No. We declare a // as a classabstractclassFoo{abstractfooMethod(): void;}// as an interface/constructorinterfaceFoo{abstractfooMethod(): void;}letFoo: abstractnew()=>Foo;I would imagine that if we did introduce |
rbuckton
commented
Jan 7, 2021
I'll be pushing up a minor change to declaration emit/quick info shortly to handle inferred return types that result in an anonymous type with abstract construct signatures. For reference, in this case: // @target: esnext// @declaration: trueinterfaceMixin{mixinMethod(): void;}functionMixin<TBaseextendsabstractnew(...args: any[])=>any>(baseClass: TBase){// must be `abstract` because we cannot know *all* of the possible abstract members that need to be// implemented for this to be concrete.abstractclassMixinClassextendsbaseClassimplementsMixin{mixinMethod(): void{}staticstaticMixinMethod(): void{}}returnMixinClass;}We currently emit this: declarefunctionMixin<TBaseextendsabstractnew(...args: any[])=>any>(baseClass: TBase): {new(...args: any[]): {// does not preserve `abstract`[x: string]: any;mixinMethod(): void;};staticMixinMethod(): void;})&TBase;When we need to emit this instead: declarefunctionMixin<TBaseextendsabstractnew(...args: any[])=>any>(baseClass: TBase): ((abstractnew(...args: any[])=>{[x: string]: any;mixinMethod(): void;})&{staticMixinMethod(): void;})&TBase; |
rbuckton
commented
Jan 7, 2021
@RyanCavanaugh can you take one more look with the update for declaration emit? |
d88027d to
cfec2caCompare* Add support for abstract constructor types * Add backwards-compatible overloads for creating/updating constructor types * Reverting use of 'abstract' in lib/es5.d.ts due to eslint issues * Update baseline due to reverting lib * Add error for failing to mark an mixin class as abstract * Fix declaration/quick info for abstract construct signatures
The Model/Entity being abstract caused compilation errors as TS 4.2 reinforces abstract checks. https://devblogs.microsoft.com/typescript/announcing-typescript-4-2/microsoft/TypeScript#36392 Signed-off-by: Raymond Feng <enjoyjava@gmail.com>
The Model/Entity being abstract caused compilation errors as TS 4.2 reinforces abstract checks. https://devblogs.microsoft.com/typescript/announcing-typescript-4-2/microsoft/TypeScript#36392 Signed-off-by: Raymond Feng <enjoyjava@gmail.com>
The Model/Entity being abstract caused compilation errors as TS 4.2 reinforces abstract checks. https://devblogs.microsoft.com/typescript/announcing-typescript-4-2/microsoft/TypeScript#36392 Signed-off-by: Raymond Feng <enjoyjava@gmail.com>
Sorry to ask, but has this already been released? |
This doesn't appear to actually be present? There is no mention of |
rbuckton
commented
Mar 25, 2021
They couldn't be added at the same time due to a lag between TS releases and the TypeScript ESLint plugin. They will be added by #43380. |
The Model/Entity being abstract caused compilation errors as TS 4.2 reinforces abstract checks. https://devblogs.microsoft.com/typescript/announcing-typescript-4-2/microsoft/TypeScript#36392 Signed-off-by: Raymond Feng <enjoyjava@gmail.com>
jcalz
commented
Jan 31, 2022
I was a little surprised that you can't write |
rbuckton
commented
Jan 31, 2022
It's hinted at a bit here: #36392 (comment), but was something we discussed in a Design Meeting. Adding |
The declaration emit seems to not enforce abstract members... from typeConstructor=abstractnew(...args: any[])=>{};functionmixin<TBaseextendsConstructor>(Base: TBase){abstractclassMyClassextendsBase{abstractabs(): void;}returnMyClass;}I get typeConstructor=abstractnew(...args: any[])=>{};declarefunctionmixin<TBaseextendsConstructor>(Base: TBase): (abstractnew(...args: any[])=>{abs(): void;})&TBase;But if I use that as an ambient declaration (imagine publishing a package based on this). Then an implementation lacking abs doesn't error: classBrokenextendsmixin(class{}){field=5;}Playground (this obviously errors as it's not using ambient) |
lorenzodallavecchia
commented
Apr 22, 2023
I have noticed that abstract constructor types do not accept a class with a
What is the reason for this behavior? |
This adds support for the
abstractkeyword on constructor types and construct signatures, allowing you to indicate the signature isabstract. This also updates the definitions forInstanceTypeandConstructorParametersto use theabstractmodifier:Syntax
Semantics
new(this previously only applied to abstract classes)Examples - Basic Usage
Examples - Mixins
Fixes#26829
Fixes#35576