You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Support an abstract keyword for classes and their methods
Examples:
abstractclassBase{abstractgetThing(): string;getOtherThing(){return'hello';}}varx=newBase();// Error, 'Base' is abstract// Error, must either be 'abstract' or implement concrete 'getThing'classDerived1extendsBase{}classDerived2extendsBase{getThing(){return'hello';}foo(){super.getThing();}// Error: cannot invoke abstract members through 'super'}varx=newDerived2();// OKvary: Base=newDerived2();// Also OKy.getThing();// OKy.getOtherThing();// OKabstractclassEmpty{}// OK
Adeel Mujahid (@am11) given that methods in JS get called by traversing the prototype chain, all methods are already "virtual" in the traditional sense that C++, C#, Java, and similar languages define it.
Actually, I was thinking about the conventional OO keywords virtual and overrirde, so its easy to clearly differentiate between various inheritance behaviors. The TS compiler might just throw warning if those keywords are missing in applicable scenarios and fallback to standard JS convention.
new is infeasible given JavaScript's runtime semantics; given that we've already shipped the language this way, virtual would be the default in the absence of any other modifier. Let's take discussion of those modifiers to a different suggestion if needed.
Seems like the example above has enough information to discuss. Some open questions:
Can I have an abstract class with zero abstract methods?
Is the abstract modifier implicit in a class that doesn't implement all abstract methods?
Can I have an abstract class with zero abstract methods?
Since abstract class -- in principle -- serves a purpose of grouping commonalities together such that its objects alone don't make sense; I think we should rule no_abstract_method as a legit case and let the TypeScript linter take care of recommending best practices, code-styling et el.
Is the abstract modifier implicit in a class that doesn't implement all abstract methods?
Given an abstract class would be able to inherit another abstract class, implicit inference like this would be confusing? I guess (at least C#/Java) developers would expect compiler to throw error and editor to help them correct it, if the first non-abstract / concrete implementer is missing any expected override. Well as a Ruby guy, I'd probably appreciate this kind of crypto-magic :`)
From these more descriptive keywords, it can be seen immediately that the overridable keyword is unnecessary as methods in JavaScript are overridable by default as opposed to in C# and is less likely to lead to confusion demonstrated by Adeel Mujahid (@am11). I believe the same gain in clarity is achieved with the other keywords listed.
We're trying to clear out our design backlog and I think this should come up soon once ES6 features are wrapped up. We discussed abstract in a design meeting a few months ago and there didn't seem to be any big open questions.
The open question here is how we prevent you from trying to instantiate an abstract class. Consider some code:
abstractclassAbs{constructor(){/* some general init here */}}varx=newAbs();// Desired: error
This case is easy -- when you invoke new on a symbol, we see if the symbol is a class with abstract, and error if so.
Next:
vara=Abs;varx=newa();// Not a good thing to do
This is less obvious. You probably want an error? But then consider this case:
classBextendsAbs{/* ... */}classCextendsAbs{/* ... */}vart: typeofAbs;if(/*... */){t=B;}else{t=C;}varj=newt();// This is fine
There's nothing distinguishing this case from the previous one (in terms of the type system), but the latter is perfectly valid code to write (and would probably even be common code to write in a factory method). We will probably have to not error on the new a(); case, both because it mimics a valid pattern and because attempting to prevent it would require a lot of extra type system mechanics.
abstract class Abs {
constructor() { /* some general init here */ }
}
Because the class is abstract, we cannot instanciate it, you said you wanted to check if the class was abstract or not to do so. What if having a abstract class would generate somehow hidden field ___abstract___ = true. If the class isn't abstract like with class B extends Abs { /* ... */ } then we would have the ___abstract___ = false, so to check if a class is abstract or not we could just check that hidden field.
I don't know if it's a proper solution, or even if it's possible, but the point is to check if the closest class is abstract or not, whatever if it extends and abstract class or not.
For the record, I'm currently (trying) to implement this.
edit What is the best way for this? First some basic stuff and create a PR for review? Or implement it with full test-suite and stuff? Because when I'm moving in total wrong direction I might waste a lot of my time and maybe yours too.
Indirect invocations, e.g. var x = MyAbstractClass; var y = new x(); are allowed, and the static side of abstract classes have the same construct signatures they would have if they weren't abstract
I think this should be reconsidered. Special-casing only a direct "new MyAbstractClass" (rather than removing abstract classes' new() signatures) is hacky, and makes it easy to accidentally pass an abstract class to a function that instantiates it (function constructAndDoOtherStuff(clazz: new() => Foo) { ... })
Earlier, this code was given as an example in favor of abstract classes having a new() signature:
class B extends Abs { /* ... */ }
class C extends Abs { /* ... */ }
var t: typeof Abs;
if(/*... */) {
t = B;
} else {
t = C;
}
var j = new t(); // error here if `typeof Abs` is not instantiable
But t could be declared as new() => Abs instead, which better describes what is expected of a valid t value anyway. (There is no backward compatibility to worry about, since there are no abstract classes in existing TypeScript code.)
In fact, using typeof Abs here may already be an error even withoutabstract! Suppose that Abs has an extra constructor parameter, which B and C supply in their super(...) calls (This is a pattern I use a lot - currently, it's the only way for a class to have an abstract-like "hole" that must be supplied by subclasses):
class Abs { constructor(public typeName: string) {} }
class B extends Abs { constructor() { super("Class B"); } }
class C extends Abs { constructor() { super("Class C"); } }
var t: typeof Abs;
t = B;
new t(); // Error here - wrong number of parameters
Because Abs has the wrong constructor signature, we are already required to use new() => Abs instead of typeof Abs in this case. It doesn't seem an undue burden to require the same if Abs's "holes" take the form of abstract methods rather than constructor parameters.
jeffreymorlan there was a suggestion that users can program their abstract classes to error in the constructor if they wanted it to (it's above) like this:
classclassa{constructor(){if(Object.getPrototypeOf&&(Object.getPrototypeOf(this)===classa.prototype){thrownewerror("classa is abstract and should not be directly instantiated");}}}
This specifically checks of classa is 'bottom' of the prototype chain.
Yeah, thanks Arthur Ozga (@aozgaa)! Lately, I've been adding many, many throw new Error('this should not have been called, it's abstract') across my codebase with the intention of replacing it all with proper abstract support.
Activity
am11 commentedon Jul 27, 2014
👍
The example would be more awesome if you incorporate
virtualas well. 😄DanielRosenwasser commentedon Jul 27, 2014
Adeel Mujahid (@am11) given that methods in JS get called by traversing the prototype chain, all methods are already "virtual" in the traditional sense that C++, C#, Java, and similar languages define it.
For instance:
will alert "F".
Did you have something else in mind?
am11 commentedon Jul 27, 2014
Daniel Rosenwasser (@DanielRosenwasser), that is so true. Thanks!
Actually, I was thinking about the conventional OO keywords
virtualandoverrirde, so its easy to clearly differentiate between various inheritance behaviors. The TS compiler might just throw warning if those keywords are missing in applicable scenarios and fallback to standard JS convention.I believe this way we can bring C#'s new modifier as well:
there may exist A2, such that:
to explicitly hide super's implementation (without
newkeyword there, CS compiler gives warning).RyanCavanaugh commentedon Jul 28, 2014
newis infeasible given JavaScript's runtime semantics; given that we've already shipped the language this way,virtualwould be the default in the absence of any other modifier. Let's take discussion of those modifiers to a different suggestion if needed.Seems like the example above has enough information to discuss. Some open questions:
abstractmodifier implicit in a class that doesn't implement allabstractmethods?am11 commentedon Jul 29, 2014
Since abstract class -- in principle -- serves a purpose of grouping commonalities together such that its objects alone don't make sense; I think we should rule no_abstract_method as a legit case and let the TypeScript linter take care of recommending best practices, code-styling et el.
Given an abstract class would be able to inherit another abstract class, implicit inference like this would be confusing? I guess (at least C#/Java) developers would expect compiler to throw error and editor to help them correct it, if the first non-abstract / concrete implementer is missing any expected
override. Well as a Ruby guy, I'd probably appreciate this kind of crypto-magic :`)RamIdeas commentedon Oct 23, 2014
Even before
abstractis implemented, I'm still hoping you'd allow intellisense kick in (in Visual Studio) when overriding base class functions:My only option is to open BaseClass.ts to check I'm spelling it correctly.
RamIdeas commentedon Oct 23, 2014
I'd still prefer to see more descriptive keywords, as I mentioned on https://typescript.codeplex.com/discussions/449920
abstractMustInheritMustOverridevirtualOverridableOverridablesealedNotInheritableNotOverridableFrom these more descriptive keywords, it can be seen immediately that the
overridablekeyword is unnecessary as methods in JavaScript are overridable by default as opposed to in C# and is less likely to lead to confusion demonstrated by Adeel Mujahid (@am11). I believe the same gain in clarity is achieved with the other keywords listed.RyanCavanaugh commentedon Dec 3, 2014
We're trying to clear out our design backlog and I think this should come up soon once ES6 features are wrapped up. We discussed
abstractin a design meeting a few months ago and there didn't seem to be any big open questions.metaweta commentedon Dec 12, 2014
The standard design pattern in JS is to throw an exception in the abstract class:
The benefit TypeScript could provide is reducing boilerplate and a compile-time error.
RyanCavanaugh commentedon Dec 12, 2014
Discussed today in the design meeting.
The open question here is how we prevent you from trying to instantiate an abstract class. Consider some code:
This case is easy -- when you invoke
newon a symbol, we see if the symbol is a class withabstract, and error if so.Next:
This is less obvious. You probably want an error? But then consider this case:
There's nothing distinguishing this case from the previous one (in terms of the type system), but the latter is perfectly valid code to write (and would probably even be common code to write in a factory method). We will probably have to not error on the
new a();case, both because it mimics a valid pattern and because attempting to prevent it would require a lot of extra type system mechanics.Vadorequest commentedon Dec 12, 2014
What about an hidden field?
Let's say we have a class
Because the class is
abstract, we cannot instanciate it, you said you wanted to check if the class wasabstractor not to do so. What if having aabstractclass would generate somehow hidden field___abstract___ = true. If the class isn't abstract like withclass B extends Abs { /* ... */ }then we would have the___abstract___ = false, so to check if a class is abstract or not we could just check that hidden field.I don't know if it's a proper solution, or even if it's possible, but the point is to check if the closest class is abstract or not, whatever if it extends and abstract class or not.
35 remaining items
DickvdBrink commentedon Apr 28, 2015
For the record, I'm currently (trying) to implement this.
edit What is the best way for this? First some basic stuff and create a PR for review? Or implement it with full test-suite and stuff? Because when I'm moving in total wrong direction I might waste a lot of my time and maybe yours too.
mhegazy commentedon Apr 28, 2015
We are open to looking at incremental changes. Just make sure the first iteration is substantial enough to warrant feedback.
I would recommend sharing your approach and design on this issue before jumping into implementation. this way we can help save you time early on.
jeffreymorlan commentedon Apr 29, 2015
I think this should be reconsidered. Special-casing only a direct "new MyAbstractClass" (rather than removing abstract classes'
new()signatures) is hacky, and makes it easy to accidentally pass an abstract class to a function that instantiates it (function constructAndDoOtherStuff(clazz: new() => Foo) { ... })Earlier, this code was given as an example in favor of abstract classes having a
new()signature:But
tcould be declared asnew() => Absinstead, which better describes what is expected of a validtvalue anyway. (There is no backward compatibility to worry about, since there are noabstractclasses in existing TypeScript code.)In fact, using
typeof Abshere may already be an error even withoutabstract! Suppose thatAbshas an extra constructor parameter, whichBandCsupply in theirsuper(...)calls (This is a pattern I use a lot - currently, it's the only way for a class to have an abstract-like "hole" that must be supplied by subclasses):Because
Abshas the wrong constructor signature, we are already required to usenew() => Absinstead oftypeof Absin this case. It doesn't seem an undue burden to require the same ifAbs's "holes" take the form ofabstractmethods rather than constructor parameters.Griffork commentedon Apr 29, 2015
jeffreymorlan there was a suggestion that users can program their abstract classes to error in the constructor if they wanted it to (it's above) like this:
This specifically checks of classa is 'bottom' of the prototype chain.
RyanCavanaugh commentedon Jul 1, 2015
Merged into
masterand will be available in our 1.6 release. Thanks Arthur Ozga (@aozgaa) !ddotlic commentedon Jul 2, 2015
Yeah, thanks Arthur Ozga (@aozgaa)! Lately, I've been adding many, many
throw new Error('this should not have been called, it's abstract')across my codebase with the intention of replacing it all with properabstractsupport.Beta/Alpha 1.6 cannot come soon enough 😉
aozgaa commentedon Jul 2, 2015
My apologies about not offering the back-reference. Thanks @jasonwilliams200OK for linking above!