Repository navigation
Can not extend built in types #1168
Description
Activity
- addedBugA bug in TypeScriptA bug in TypeScriptES6Relates to the ES6 SpecRelates to the ES6 Spec
on Nov 14, 2014 This is a general problem for any js library that exposes functions that are meant to be subclassable. If my library exports the Foo constructor and expects to receive instances of subclasses of Foo (not just objects implementing a Foo-like interface), there's currently no way to allow that.
I would like to be able to say
class Error;There would be no javascript emitted for such a statement, only an update of Typescript's symbol table.
For the general case, I'd like to be able to replace the body of any class declaration with a semicolon, allowing things like
class Foo<X, Y> extends Bar<X> implements Baz<Y>;which would mean that the library already guarantees that Foo.prototype is an instance of Bar.prototype and implements the Baz interface.
saschanaz commented
on Nov 18, 2014 ContributorMore actionsMike Stay (@metaweta) I think it may make some redundancies:
interface Foo { } class Foo; // Foo is now a class! interface Bar extends Foo { } class Bar extends Foo; // Two `extends Foo`s. interface X { } class X extends Foo; // Hmm? interface Y extends Foo { } class Y; // Hmm?
It still have some potentials as the subclassable interfaces will work as open-ended classes. #819
mhegazy commented
on Nov 18, 2014 ContributorAuthorMore actionsMike Stay (@metaweta) your first case should be handled by an ambient class declaration. e.g.
// myLib.d.ts declare class Error { } // no code emitted here for this.
// myfile.ts // Error is subclassable, class MyError extends Error { }
For the second case, I would file a different suggestion issue for it.
mhegazy commented
on Nov 18, 2014 ContributorAuthorMore actionsHere is the full list of "subclassable" objects from the ES6 spec. currently these are all defined as an interface/var pairs.
- Object
- Boolean
- Error
- NativeError
- Number
- Date
- String
- RegExp
- Array
- TypedArray(Int8Array, Uint8Array, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array, Float64Array, and DataView)
- Map
- Set
- WeakMap
- WeakSet
- ArrayBuffer
- DataView
- Promise
Problems:
- Once a type is defined as a class, there is no way to extend the member side (extending the static side can happen through a module).
- Existing extensions to the member side will not work as you can not redefine a class (breaking change)
- No way to model function calls on a class, e.g Object, String, Boolean, Date, Number, RegExp, Error, and Array. (again breaking change)
Possible solutions:
- Allow extending any type with a prototype property and a construct signature
- Allow extending instance side of a class (e.g. module Class.prototype {}, courtesy of Ryan Cavanaugh (@RyanCavanaugh) )
- Allow defining call signatures on a class constructor
Mohamed Hegazy (@mhegazy) Thanks, I was unaware of the ambient class declaration construct.
When I extend the Error class this way (using ambient class definition), throwing the ExtendedError does not produce stack trace nor assign message properly.
declare class Error { constructor(message?: string); } class ExtendedError extends Error { constructor(message?: string) { super(message + " extended"); } } //throw new Error("Test"); throw new ExtendedError("Test");mhegazy commented
on Jun 2, 2015 ContributorAuthorMore actionsHoma Wong (@unional) this looks like an engine issue. as per ES6 spec it should work. Engines should be fixing these now that ES6 has become ratified.
I managed to get it to work. In my js code that was working, I have the Error.captureStackTrace call but I take it out when I implement it in ts because Error declaration doesn't have it.
There is an example code:
It's an C#-like Exception that takes an innerException.declare class Error { public name:string; public message:string; public stack:string; constructor(message?:string); static captureStackTrace(error: Error, constructorOpt: any); } class Exception extends Error { public innerException: Error; constructor(message?: string, innerException?: Error|string) { // Guard against throw Exception(...) usage. if (!(this instanceof Exception)) return new Exception(message, innerException); super(); if (typeof Error.captureStackTrace === 'function') { //noinspection JSUnresolvedFunction Error.captureStackTrace(this, arguments.callee); } this.name = "Exception"; if (innerException) { if (innerException instanceof Error) { this.innerException = innerException; this.message = message + ", innerException: " + this.innerException.message; } else if (typeof innerException === "string") { this.innerException = new Error(innerException); this.message = message + ", innerException: " + this.innerException.message; } else { this.innerException = innerException; this.message = message + ", innerException: " + this.innerException; } } else { this.message = message; } } }- added a commit that references this issue
on Jun 3, 2015 Fixed by #3516. Classes can now extend arbitrary expressions of constructor function types.
- addedFixedA PR has been merged for this issueA PR has been merged for this issue
on Jun 17, 2015 - added a commit that references this issue
on Jul 30, 2015 What release of TypeScript this fix is going to be shipped?
I quickly checked release labels for 1.5.3 and 1.5.4, and seems it hasn't been shipped yet, only in master for now.EDIT:
Sorry, right now have noticed that the bug marked by milestone "TypeScript 1.6"Thanks for your work!
DanielRosenwasser commented
on Aug 24, 2015 MemberMore actionsSergey Kostrukov (@kostrse) you can try our nighty releases of TypeScript 1.6 by running
npm install -g typescript@next.Hi,
I'm using Typescript 1.6.2 and my lib.es6.d.ts show Error (and Array, ...) as Interface not class.
Is this already fixed in 1.6.2?Cheers!
mhegazy commented
on Sep 21, 2015 ContributorAuthorMore actionsJosé Pedro Silva (@jpsfs) the fix, as noted by Anders Hejlsberg (@ahejlsberg) in #1168 (comment) is to allow classes to extend arbitrary expressions of constructor function types.
Even there is ability to extend
Errorclass nowdays, Im still having inconvenience with it on node. For example Im having troubles with "error.stack". When I dothrow new Error(...)I geterror.stack, but when I dothrow new CustomError()- I don't. To solve it I forced to use this trick:export class HttpError extends Error { httpCode: number; message: string; constructor(httpCode: number, message?: string) { super(); if (httpCode) this.httpCode = httpCode; if (message) this.message = message; this.stack = new Error().stack; } }
Reacted by James O'Cull, Sergey Kostrukov, WoongBi Kim, René Verheij, Mo and GP- locked and limited conversation to collaborators
on Jun 18, 2018
The ES6 designates some native objects as subclassable, e.g. Object, Boolean, Error, Map, Promise ..etc. The way these constructs are modeled in lib.d.ts makes is impossible to subclass, as they are defined as a pair of a var and an interface.
From the ES6 spec section19.5.1:
Currently this would result in "A class can only extend another class" error: