-
-
Notifications
You must be signed in to change notification settings - Fork 30
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
Type conversion overhaul #33
Conversation
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.
Only major issue is the dictionary conversion is more subtle than done here.
This is overall really awesome; thanks for working on it. Would you be able to show some sample outputs? Eventually I guess we should get better about writing tests for webidl2js, but for now just being able to spot-check the output would be helpful.
Are there particular features you're planning on adding to jsdom using these?
lib/constructs/dictionary.js
Outdated
@@ -1,6 +1,6 @@ | |||
"use strict"; | |||
|
|||
const conversions = require("webidl-conversions"); | |||
const Types = require("../types.js"); |
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.
Since this is not a class, it should not be capitalized. Also, in this project it seems like we're not including .js
in the requires.
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.
I'm mirroring how require('../parameters')
is usually called Parameters
, even though Parameters
is not a class. See e.g. constructs/interface.js. Will fix the .js
.
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.
Oh, I see. I guess good to stay consistent, even though that's a weird style choice from @Sebmaster :)
lib/types.js
Outdated
|
||
const utils = require("./utils"); | ||
|
||
module.exports.generateTypeConversion = function (name, idlType, argAttrs, customTypes, opts) { |
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 function is very long. I think it would benefit from calling out to a few non-exported factored-out functions for the two cases of sequence and IDL type. What do you think?
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.
Agreed, will split.
lib/types.js
Outdated
}; | ||
|
||
module.exports.canHandleType = function (idlType, customTypes) { | ||
return idlType.generic === "sequence" || conversions[idlType.idlType] || customTypes.has(idlType.idlType); |
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.
idlType.idlType in conversions
seems a bit nicer since it guarantees the return value will always be a boolean.
lib/types.js
Outdated
if (typeof ${name} !== "object") { | ||
throw new TypeError("The value provided is not an object"); | ||
} else { | ||
const result = new Map; |
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.
Nit: parens after constructor
lib/types.js
Outdated
throw new TypeError("The value provided is not an object"); | ||
} else { | ||
const result = new Map; | ||
const keys = Object.keys(${name}); |
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.
Per spec this should be Object.getOwnPropertyKeys, and then you should use getOwnPropertyDescriptor, and check enumerability, and so on. This is important as it impacts how many side effects occur before an exception happens in exceptional cases.
lib/transformer.js
Outdated
yield fs.writeFile(path.join(outputDir, this._dictionaries[key].name + ".js"), ""); | ||
} else { |
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.
I guess while here it would be good to change this to else if (this._typedefs[key])
as that seems a bit less likely to blow up in the future.
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.
TBH this entire thing should be changed to use Map
anyway...
Any specific cases you have in mind?
Yeah of course. I already tested this against JSDOM to make sure it still works.
Haven't decided yet, though I want to eventually implement the Fetch API and spec. Might also implement |
Oops, I meant records, not dictionaries, as per the comments you already saw.
That would be awesome! +1 to URLSearchParams as a first step; as seen in jsdom/whatwg-url#37 people seem to definitely want this. |
@domenic, okay, updated. I made some example I/Os in https://github.com/TimothyGu/webidl2js/tree/34c9ae6c1356d7a6ec0a898a774a2479414acc26/test. |
OK, awesome! Are you planning to commit those in this PR? Reviewing the output:
|
I just pushed a patch that fixes arg unwrapping (and hence #5) but it's fairly big I'll need more time to make sure things like variadic arguments still work, so DO NOT MERGE. I've tested it against jsdom though and other than a few small fixes here and there in jsdom, its tests all pass. Another thing is that I changed my mind regarding typedef. They should not generate interface files, but rather be inlined and flattened when they are used elsewhere. This is necessary because union type handling requires fully flattened member types. I have not fixed this yet, however. Do you want me to include the test cases? And if so, do you want to just put them in test/ or write some actual tests with them? Updated outputs are in https://github.com/TimothyGu/webidl2js/tree/354eb7acaa5c2911c895cd858da1f5c8df68fc64/test (compare with before. Here's what's changed (so far):
|
+1 for |
I'm not sure what our best strategy is here personally. What do you think? In my opinion it's useful to know when things change, so maybe trying to use baseline-tester would do the trick? We could at some point try to write semantic tests, e.g. actually pass in various inputs to a generated file, and check how they get converted to a sequence by the time the test impl sees them. That sounds like a lot of work though :-/. Maybe we can add them as we spot particular bugs we want to guard against regressions on. Those fixes are all really wonderful <3. Looking forward to being able to merge this.
This still seems to do a loop to copy |
@TimothyGu thoughts on moving this forward? I'd love to merge it :) |
I'm having some problems with overloaded rest arguments. I haven't had a lot of time to work on this recently, but I'll try to get back at it over this weekend. |
@TimothyGu hi 😄 |
Guess what… the HD that contained my WIP crashed a few weeks ago, so the commits I didn't push to GitHub are now gone. I'll see what I can do about it, but I'll have to change the implementation for typedef anyway so I might just consider breaking up this PR into smaller chunks… |
💥 🖥 💥 |
013432e
to
12b95ee
Compare
Rebased. I removed the experimental (i.e. buggy) fix for #5 as well as incorrect typedef and union support. Those are bigger projects that will need to be tackled at a later time. After those changes, this PR now only contains:
The different handling for sequences and parameter unwrapping means that this PR is an incompatible set of changes. In JSDOM, jsdom/jsdom@master...TimothyGu:f9bbb2b is necessary to keep the tests green. Optionally, certain explicit error handling in implementation files can also be removed (TimothyGu/jsdom@2ab95dd) since the logic is now a part of the generated files. |
Awesome! I guess I'm most able to review this by trying to figure out its impact on jsdom; apart from that, each commit looks reasonable. You say that all the tests are green with that diff? That's awesome and a little surprising :D.
What are these changes about? In general our philosphy was that impl code should only know about or deal with impls, if possible.
What does uncommenting these help with? It still seems like we're handling overloads manually.
Why are we still testing HTMLElement.isImpl? |
Yep 😄
This is indeed pretty tricky, so I'm going to try to explain it the best I can. The function this piece of diff is located in, reportAnError(), is always called with Now this does mean that instead of passing a wrapper function around in the guts, we can specifically unwrap diff --git a/lib/jsdom/living/helpers/runtime-script-errors.js b/lib/jsdom/living/helpers/runtime-script-errors.js
index 5f334e87..71f91477 100644
--- a/lib/jsdom/living/helpers/runtime-script-errors.js
+++ b/lib/jsdom/living/helpers/runtime-script-errors.js
@@ -1,5 +1,6 @@
"use strict";
const util = require("util");
+const idlUtils = require("../generated/utils");
const ErrorEvent = require("../generated/ErrorEvent");
const errorReportingMode = Symbol("error reporting mode");
@@ -15,7 +16,7 @@ function reportAnError(line, col, target, errorObject, message, location) {
target[errorReportingMode] = true;
// TODO Events: use constructor directly, once they are no longer tied to a window.
- const event = ErrorEvent.create(["error", {
+ const event = ErrorEvent.createImpl(["error", {
bubbles: false,
cancelable: true,
message,
@@ -51,7 +52,7 @@ module.exports = function reportException(window, error, filenameHint) {
const lineNumber = pieces && parseInt(pieces[3]) || 0;
const columnNumber = pieces && parseInt(pieces[4]) || 0;
- const handled = reportAnError(lineNumber, columnNumber, window, error, error.message, fileName);
+ const handled = reportAnError(lineNumber, columnNumber, idlUtils.tryImplForWrapper(window), error, error.message, fileName);
if (!handled) {
const errorString = shouldBeDisplayedAsError(error) ? `[${error.name}: ${error.message}]` : util.inspect(error); I can do that instead if that's what you prefer. I'll explain the other changes later today. |
Yep we are (for now), but if the first line is not commented webidl2js will think Blob is the only possible type for the second parameter, and therefore throw an error if it is not a Blob.
The old code is actually not spec-compliant, since by spec value can legally be either |
Thanks for the explanation. Yeah, I think passing the "Window impl" (actually an EventTarget impl, if I understand correctly) would be the way to go here. That also has the benefit that we don't get borked when some on-page script overwrites It might be worth adding a comment to
Hmm. Looking at the generated output though, I don't see it working great... URLSearchParams.prototype.append = function append(name, value) {
if (!this || !module.exports.is(this)) {
throw new TypeError("Illegal invocation");
}
if (arguments.length < 2) {
throw new TypeError("Failed to execute 'append' on 'URLSearchParams': 2 arguments required, but only " + arguments.length + " present.");
}
const args = [];
for (let i = 0; i < arguments.length && i < 2; ++i) {
args[i] = arguments[i];
}
args[0] = conversions["USVString"](args[0], { context: "Failed to execute 'append' on 'URLSearchParams': parameter 1" });
args[1] = conversions["USVString"](args[1], { context: "Failed to execute 'append' on 'URLSearchParams': parameter 2" });
return this[impl].append(...args);
}; Should we instead be creating our own IDL, like
? In any case this warrants commenting in the IDL file. I guess this also explains the impl -> wrapper change
You removed all the tryWrapperToImpl stuff, so now we need to assume unknown inputs are wrappers. But again it seems like we should be using
Can we just test for null then? Overall it sounds like the webidl2js side of all this is pretty solid, so probably not many changes needed in this PR, but I thought it'd be good to be sure before merging. |
Agreed, done.
What you were initially referring to was actually --- orig.js 2017-05-06 14:33:21.436963019 -0700
+++ after-pr.js 2017-05-06 14:33:16.084943368 -0700
@@ -8,11 +8,12 @@
const args = [];
for (let i = 0; i < arguments.length && i < 3; ++i) {
- args[i] = utils.tryImplForWrapper(arguments[i]);
- }
- args[0] = conversions["USVString"](args[0]);
- if (args[2] !== undefined) {
- args[2] = conversions["USVString"](args[2]);
+ args[i] = arguments[i];
}
+ args[0] = conversions["USVString"](args[0], { context: "Failed to execute 'append' on 'FormData': parameter 1" });
+ args[1] = convertBlob(args[1], { context: "Failed to execute 'append' on 'FormData': parameter 2" });
+if (args[2] !== undefined) {
+ args[2] = conversions["USVString"](args[2], { context: "Failed to execute 'append' on 'FormData': parameter 3" });
+}
return this[impl].append(...args);
}; The important thing here is that
Or yeah, this works too. Changed.
Again, agreed.
Yep, though I added back
Sure. I've created a PR at jsdom with the jsdom changes to ease merging: jsdom/jsdom#1841 |
Throw a TypeError when the object that is supposed to implement an interface does not.
Also eliminate outdated dictionary error for Date and RegExp.
All right, let's give this a shot... |
Implementing union type conversion is next, followed by overload resolution algorithm (if need be).