-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Extending from Error doesn't work when emitting ES5 #10166
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
Comments
It's late and I need to go to sleep, but after having experimented with this, I believe that this issue is related to or a duplicate of #7574. |
Basically the reason I say it's a duplicate is because if we simply capture the value of the call to var __extends = (this && this.__extends) || function (d, b) {
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
var GenConstructFail = (function (_super) {
__extends(GenConstructFail, _super);
function GenConstructFail() {
var SUPER_RESULT = _super.apply(this, arguments) || this;
// substitute any use of 'this' with 'SUPER_RESULT'
return SUPER_RESULT;
}
return GenConstructFail;
}(Error));
var x = new GenConstructFail("Test one two");
console.log(x.message); |
In any case, thanks for filing because it shows that this is another good use-case. |
Daniel-- I don't think this is a duplicate of #7574, although it is clearly a related problem. At least, your sample code does not work, so if this is the code that a fix for #7574 would produce then it would not resolve this bug. If I run your code ("code snippet 5"), I find that
On both Chrome and Edge, The reason your sample code does not work is that code snippet 5, just as much as code snippet 2, depends on undefined behavior of I believe that the actual bug in this particular issue is that |
Having gotten the opportunity to revisit this in investigating #7574, I now see the issue. However, I'm not sure what we can do here. I think we'd have to do a special check to correctly set the prototypes on certain classes. |
I'll bring this up with @bterlson at some point soon. |
At the moment, I am working around this by never inheriting from Error, and instead exclusively inheriting from this class:
This does seem to demonstrate that working around fake-constructor entities is easy, assuming you have the mechanism in place to identify the situations where you need to work around... |
@DanielRosenwasser @mcclure How is this? var __extends = (this && this.__extends) || function (d, b) {
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
var C = (function (_super) {
__extends(C, _super);
function C() {
var _that = _super.call(this, 'error');
if (_that) {
_that.__proto__ = this;
}
var _this = _that || this;
return _this;
}
C.prototype.m = function () {
return 'm';
};
return C;
}(Error));
console.log(new C().message); // 'error'
console.log(new C().m()); // 'm'
console.log(new C() instanceof Error); // true
console.log(new C() instanceof C); // true This is included in #12123 |
Is there no progress or solution on this topic? It's blocking some of my code like #12097. |
Technically this will be fixed in TypeScript 2.1; however, a different part of your code may no longer work. |
This workaround worked for me: interface IErrorConstructor {
new (message:string):Error;
}
const CustomError = function(message:string) {
Error.call(this, message);
Error.captureStackTrace(this);
this.message = message;
this.name = this.constructor.name;
} as any as IErrorConstructor;
CustomError.prototype = Object.create(Error.prototype);
class MyError extends CustomError {}
const err = new MyError('something bad happened!');
err instanceof MyError; // true
err.message; // 'something bad happened'
err.name; // 'MyError' |
I wonder why compiler cannot generate the code like this... |
@eschwartz Will your workaround work when targeting ES5? Edit: As far as I can tell, it does! |
What's the status of this? This took us a while to find out what was going when switching our targets from ES2015 to ES5. |
Hi! Why don't just make the solution like in babel-plugin-transform-builtin-extend? |
I am using Typescript 1.8.10. I am using Node v4.4.7 as my primary means of executing Javascript.
Consider this very simple code (I will refer to this later as "code snippet 1"):
I run this with
tsc test.ts && node test.js
. My expected behavior is that this will print "Test one two". My observed behavior is that it prints only a blank line.There have been issues filed about this before, the ones I found seem to all link back to #5069 where project member @mhegazy closed the issue and explained "The issue is with how these built in types are hand[led] by the engine. They are extensible as per the es6 spec, but do not think the engines are there yet." However, I believe this is not the correct way to think about the issue.
If I look at the ES2015 spec, I find:
The last paragraph here is important. The ES2015 spec requires that Error be subclassable using extends and super(). However, it does not specify Error should be subclassable by other means. Here is what
tsc
emits when run with the default target of ES5 (I will refer to this later as "code snippet 2"):I do not fully understand what the Typescript
__extends
function is doing, but the generated code forGenConstructCall
appears to revolve around expecting it can invokecall
on the_super
object, in this caseError
. However,call
is a method onFunction.prototype
andError
does not haveFunction.prototype
in its prototype chain.Error
is an intrinsic object, its prototype is also an intrinsic object (defined in section 19.5.2.1 of the spec), and the operations supported onError
, its constructor, and its prototype are all explicitly enumerated.call
is not among them.So in other words, if the code Typescript generates in ES5 mode does not behave correctly with regard to constructor parameters, it is not because the engines have not yet caught up to ES6, it is because Typescript has generated code which is neither correct ES5 nor correct ES6.
In fact, in my testing, I find that ES6 engines that fully support "code snippet 1" above when simply evaluated as code, do not support "code snippet 2" (the Typescript-transpiled-to-ES5 version of the same code). I tested with Chrome version "51.0.2704.106 m" (up to date) and also with "Microsoft Edge 25.10586.0.0 / Microsoft EdgeHTML 13.10586"; in both cases, code snippet 1 printed "Test one two" and code snippet 2 printed a blank line. (A person I talked to on Twitter tested with Node 5.4.1 and saw these same results.) I also found that in both Chrome and Edge,
Error("test")
produced an Error object with the message "test" (this is a special behavior guaranteed by section 19.5.1.1 of the ES2015 spec) butError.call("test")
produced an Error object with a blank message.So, to summarize: Typescript's ES5 target generates an invalid constructor when
Error
is extended, it is doing this predictably and 100% of the time, and it is invalid because of the Javascript spec and not because of any particular engine limitation. This should be considered a bug.Moreover, it seems Typescript could be easily modified to fix this. The problem is that
.call
invocation. Consider this alternate version of code snippet 1 (let's call this "code snippet 3"):Here is the Javascript
tsc
emits for code snippet 3 (let's call this "code snippet 4"):The problem with "code snippet 2", at least with V8 and Edge, is that
call()
is swallowing the arguments to the constructor (which is after all not a real constructor). Assigning the fields after thecall()
avoids this problem and the code prints "TEST THREE FOUR" even on my old copy of Node 4.4.7.My suggested fix is that Typescript should detect when it is subclassing
Error
or one of the built-inError
subclasses (all intrinsics) while in ES5 mode, and in this case generate a constructor which instead of assumingcall()
works simply takes the arguments tosuper()
and assigns them tothis
aftercall()
. (And maybe it would be better to find a way to avoidcall()
altogether if you can, since I see nothing in the spec to guarantee it even exists onError
.)The text was updated successfully, but these errors were encountered: