-
Notifications
You must be signed in to change notification settings - Fork 515
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
Implement Reflect built-in (ES6+) #1025
Conversation
The specification seems to require that Incidentally, the same requirement applies (no pun intended) to |
Nice, Reflect should be pretty easy indeed and provides a few very useful things (like For the most part things should be implementable by extending existing functions with a flag or two. For example, It might be good to implement the bindings in several pulls: some things are obvious while others may need some internal shuffling to minimize overlap between helpers etc. |
Does the Reflect object need its own class number? Quickly looking at ES6 I didn't see a Node.js does this which matches my assumption:
|
In any case once Symbol support has been merged to master, I'd like to move all the non-required class numbers into It's also not very modular, and cannot be used by user code (unlike |
So as far as flags are enough to distinguish a Reflect vs. Object function, they should use the same Duktape/C function internally, with a magic value providing the necessary flag(s). |
I'll remove the class number, that's fine. I mostly did it to match Math, which also doesn't really need a class number I think (although pre-ES3 had very weird semantics for things so I very well could be wrong there). Also I didn't realize you were implementing well-known symbols in the Symbol pull, that'll be nice. As for using the same Duktape/C function, yes, that's the intent. In fact I already did that here - just that those functions don't look at the magic value yet so the semantics are wrong. |
Math at least used to need a class number because:
Object.prototype.toString() is the one and only place where classes matter (as far as outward appearances). |
One interesting thing I notice is that ES6 includes |
ES6 updates behavior for Object.prototype.toString() a bit: http://www.ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring. Special handling is required for: undefined, null, Array, String, Arguments, Function, Error, Boolean, Number, Date, and RegExp. Other classes should use So, Math and JSON, for example, should be changed to use a
This should be fixed for Duktape, but doing so depends on the Symbol feature to be completed :) What I could do to make the process a bit easier is to merge the genbuiltins.py Symbol syntax so that |
That ES6 change to So for internals, there's probably a need for two variants: one with side effects, and one guaranteed to have no side effects (and providing "incorrect" result for a getter - which is not likely to be encountered in practice). The former is needed for e.g. |
Trivia: Should the config option be |
I'm fine with In current convention For example, |
ES6 retroactively changes the semantics of several equivalent |
@svaarala As a reminder, if this is merged before Symbol support makes it to master, then |
It's quite convenient that most of the Reflect methods are very simple and specified in terms of algorithms already defined since ES5. That should help keep footprint impact down. |
Interesting question which hasn't really come up before. One solution would be to provide both semantics depending on which version is being configured for. That would mean necessary config options to configure for ES5.1 vs. "latest", or something. In practice that is probably not worth the effort. Ecmascript is a living standard and tracking the latest behavior would make most sense to me. The ES5 profile build could then just use the ES5 built-ins (which is really for footprint control in most cases), with post-ES5 semantics for the built-ins. |
Thank goodness for that. 😄 The wait for ES6 was excruciating, so it's good to see that it's being updated more often now. Although it does give us as implementors more work to keep up with the latest changes... 😉 |
|
So |
I want to try to write testcases for all Reflect methods, so that there will be decent test coverage for this feature in master right from the start. I kind of slacked off in #975 in that the tests weren't really comprehensive. I'll try to do a better job this time. Writing tests isn't nearly as fun as programming new features. ;) @svaarala Quick question: |
Hmm, can't think off hand where it would matter now. Maybe leave unimplemented and now and document it in a Reflect built-in laundry list issue? |
Regarding |
Hmm, how about checking the third argument and if it's present and different from the object, TypeError with "unsupported"? |
I do agree that going forward with a very different operation doesn't pass the "principle of least surprise" test. And further, if you get your code working with that limitation and the limitation later gets fixed, the code will start behaving another way. |
That TypeError solution would be okay with me. We definitely can't just ignore the argument entirely unless we want to see some very confused JS programmers. ;) |
Principle of least surprise, yes :) |
I think I'll open a separate pull for the ES6 |
That's true, but there's no simple way to "implement as tailcalls" though, that's just specification language. The .call() and .apply() functions will need to integrate directly with call setup to ensure the end result behaves like a tailcall but there's no internal support for Duktape/C tailcalls at present (and even if there was, it wouldn't necessarily handle .call() and .apply() correctly). |
I didn't mean to imply it would be a simple change - just pointing out that if a possible a more general solution like #1026 would be preferable to special casing for call/apply. I like generic designs :) I realize sometimes a generic solution isn't always feasible though. |
Ah, you meant that. I agree it'd be great if the internals could use a duk_tailcall() C API without special handling. Let's see if that would be possible - hopefully so :-) Yield/resume are a similar story but they do something completely different so they probably won't benefit from any tailcall handling rework. |
Hm, it seems For now it'd be best I think to update the implementation to throw an error if newTarget !== target, like how the receiver argument is handled in get/set. |
I agree - you wouldn't be able to make that kind of constructor call in ES5 besides Reflect so it's not a big limitation. |
Almost done writing tests. Just need some testcases for @svaarala What are "fltoetc" compile checks? A few of them keep failing but I can't see out what I did wrong. |
They're just different build tests, One example error printout is:
What happens is that the stripped config options remove the The shared built-in functions would need to be:
instead of just:
|
A useful followup work item might be to move the shared Duktape/C helpers for better organization, e.g. into |
For whatever reason Git doesn't seem to have normalized the line endings for the .html files when I committed them, and the ones in my local repo are CRLF so the diff showed the entire file being changed. I fixed it. One of the dangers of doing primary development on Windows I suppose ;) @svaarala This change is nearly done now; just waiting on the webhook tests to see if the stripped builds have been fixed. I'll give this one final going-over tonight, but feel free to add review notes now if you want. |
A |
Ok, will do 👍 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few memory unsafe assumptions (value stack duk_tval
pointer stability). Otherwise looking good 👍.
|
||
(void) duk_require_hobject(ctx, 0); | ||
tv_obj = duk_require_tval(ctx, 0); | ||
(void) duk_to_string(ctx, 1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This duk_to_string()
call may have side effects that invalidate the tv_obj
above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
duk_strict_equals()
is side effect free, but it'd be best to read the tval pointers just before they're used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree, the code also reads much cleaner that way (better separation of concerns).
DUK_ERROR_UNSUPPORTED((duk_hthread *) ctx); | ||
} | ||
|
||
(void) duk_hobject_getprop(thr, tv_obj, tv_key); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about avoiding the tv_obj
and tv_key
pointers entirely by just doing:
duk_set_top(ctx, 2); /* -> [ obj key ] (target, if present, popped off) */
duk_get_prop(ctx);
return 1;
DUK_ERROR_UNSUPPORTED((duk_hthread *) ctx); | ||
} | ||
|
||
ret = duk_hobject_putprop(thr, tv_obj, tv_key, tv_val, 0 /*throw_flag*/); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same duk_tval
stability issue here as above. Also same question here, how about duk_set_top(ctx, 3)
+ duk_put_prop()
+ return 1;
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note the 0 /*throw_flag*/
. The public API calls use strict mode semantics; here I instead need it to return false when it would otherwise throw (for example due to a read-only property). Initial drafts of this pull used duk_{get|put}_prop()
but the semantics were wrong - if I were to make that change here, the tests would break.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is of course another option: A safe call. But that seems like it'd be even messier than the duk_tval
pointers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The concrete problem with duk_put_prop()
is this bit:
If the property write fails, throws an error (strict mode semantics).
Reflect.set()
returns false in that case, but doesn't throw. However errors thrown by setters, etc. will still propagate as usual, so wrapping the access in a safe call won't work either.
} | ||
|
||
try { | ||
applyTest(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For coverage it'd be good to have an apply test for near-maximum number of supported arguments (say 250). This ensures that the implementation's duk_require_stack()
calls are right (they are, but to ensure they keep on being right).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, I'll add something to exercise that. I was honestly not sure what to do with the apply and construct tests; once I tested the basic functionality there didn't seem to be too many corner cases to worry about.
maggie.talk(); | ||
var darkling = Reflect.construct(Person, [ "Darkling" ]); | ||
print(darkling instanceof Person); | ||
darkling.talk(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same argument count comment for .construct().
I have some very small bits here and there which I can fix in a separate pull (takes longer to write them out than to try how they work / affect footprint :-). The memory unsafe bits need fixing, and there's a few testcase coverage issues which would be nice to add. Other than that I can merge whenever you think the pull is done. :) |
All Reflect functions specified in ECMAScript 2016 are implemented, and most share Duktape/C helpers with their ES5 twins from Object. * Reflect.apply() * Reflect.construct() * Reflect.defineProperty() * Reflect.deleteProperty() * Reflect.get() * Reflect.getOwnPropertyDescriptor() * Reflect.getPrototypeOf() * Reflect.has() * Reflect.isExtensible() * Reflect.ownKeys() * Reflect.preventExtensions() * Reflect.set() * Reflect.setPrototypeOf() Presence of the Reflect object is controlled by DUK_USE_REFLECT_BUILTIN and enabled by default in the standard configuration. note: Reflect.enumerate() was retroactively removed in ES7, so it will not be implemented in Duktape.
Low-memory baseline is E5.1.
@svaarala Assuming I haven't broken the tests, this is ready to merge. I reorganized the
|
Testcases cover all functions. There is additionally an argument policy test which checks that all functions will throw for a non-object argument.
The updated testcase for |
The commits are now outdated - re: the putprop throw flag, you're right, I didn't think about that. For internal code what would be useful is an extended So, OK as it was, with the exception of safe usage of |
Oh right, the compiler has a limit for formal arguments but otherwise there can be more arguments. That bytecode limitation of 255 arguments is probably going away next time I touch call opcodes. There will be some higher limit most likely though. |
Looks good now :) |
I may have had a bit too much fun writing the .apply()/.construct() test... :) |
Hehe, well I don't mind as long as the test are useful. |
This pull implements the built-in
Reflect
object, first introduced in Ecmascript 2015 (ES6). Concrete implementation based on the latest Ecmascript standard at the time of writing (2016/ES7).Ecmascript 2016 reference:
http://www.ecma-international.org/ecma-262/7.0/#sec-reflect-object
New Ecmascript calls introduced:
Reflect.apply()
Reflect.construct()
Reflect.defineProperty()
Reflect.deleteProperty()
Reflect.get()
Reflect.getOwnPropertyDescriptor()
Reflect.getPrototypeOf()
Reflect.has()
Reflect.isExtensible()
Reflect.ownKeys()
Reflect.preventExtensions()
Reflect.set()
Reflect.setPrototypeOf()
Reflect.enumerate()
is NOT implemented. It was dropped in ES7 for placing too many constraints on Ecmascript implementors, see this GitHub issue:tc39/ecma262#161
Checklist:
Reflect
, all functions (use ES7 as reference)Reflect
functions