-
-
Notifications
You must be signed in to change notification settings - Fork 565
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
Module handling fixes and improvements #1097
Conversation
lahma
commented
Mar 5, 2022
•
edited
Loading
edited
- tests are no using normal engine execution without extra host
- engine overloads for Evaluate and Execute to allow supplying script type
2cf094c
to
39d2391
Compare
@christianrondeau I made some changes for test running in this PR and it surfaced some problems, 85 failing tests. I was able to fix some and now it's down to 59. I also implemented the dynamic import part. If you have time at some point could you have a quick look at this branch? There seems to be some problems with export resolution logic at least, shown by test case like |
I think there's something fishy going on with module resolution/registration w.r.t. to exported name vs local name, also seems that resolution doesn't follow spec by always using ExportedName but if does some fallbacks... |
I'm not surprised, there were a few things that I was didn't expect to see working without any actual implementation but actually did work. Since a lot of code was already there, I think some code worked "by chance", e.g. since the importing module has access to the exported scope if the name was bound, I didn't actually need to do much in the actual import/export statements. I'll run the tests and review your changes as soon as I can. For now for some reasons the tests just won't start (they spin and won't start, probably something on my end) but I'll reach out when I have both running tests and time to look at them :D |
Great, thank you. No hurry and I can try to contribute too but I think you have already gone through the digesting of the resolution logic so might be easier for you. |
I think |
Aaaah also I see you added engine.Execute() for the module, but you cannot execute a module directly, you have to import it. I'm not sure if/how we can throw a clearer error when trying to export in a non-module context. We can circle back to this once the tests are green. |
I see you added a few methods to Engine.Modules.cs, to allow running Execute directly on a module. Those methods currently break self-referencing and cyclic referencing, and IMHO add confusion between "ImportModule" overloads and "Execute". I wrote a few pages of text explaining why in the previous PR. For example, if you import the same module twice, it should give you the same live bindings, since from the C# code you are actually "importing" the ecmascript module's bindings. Are you OK with me re-renaming some things and removing some of your overloads? Once it's green we can either rename all overloads if you want (Execute will cause conflicts though) or re-open the discussion about mixing modules and scripts. |
Sure, go agenda. Overloads were mostly meant to allow running regular code in module context , allowing imports to work. |
Cool, I'm untangling a few threads (I'm starting with self-evaluation not behaving correctly, I'll reach out once I made sense of this)
|
I think Execute as concept would be nice for end users, that would be their entry point allowing regular JS script code. In hindsight Evaluate makes less sense as it always returns the module namespace. Thanks for looking into this. |
I have a few more green tests, you can track my work here: https://github.com/christianrondeau/jint/tree/module-improvements About Execute, I understand why it seems to make sense at first glance (I wanted to do that first), but it's actually a rabbit hole. Modules do not behave in the same way as scripts at all, and they don't get evaluated in the same way.
It can be done, but I'm afraid it will be much harder to navigate rather than saying, like node does, "you either make a script OR you make a module". If we only talk about semantics (i.e. you want to use .Execute because it's discoverable), the problem I had when I tried was dealing with specifiers. For example, you can Execute(string code), but for a module, you need Execute(string specifier, string code) and it becomes hard to see what is what. In a way, this vision of Execute is like sugar for AddModule + ImportModule, but I'm wondering whether that sugar really is useful, since you won't need modules if you only plan on having one C#-driven module. The big difference with doing The only overload of Execute that would make sense to me would be "Execute(specifier)", but because it conflicts with "Execute(code)" that already exists, you'd need "ExecuteModule(specifier)" which does exactly the same as "ImportModule(specifier)" but without returning the namespace. I'm not sure it's better than simply doing ImportModule. Another alternative that would require more changes but could make sense, would be to separate Engine and have ModuleEngine. That could work because then you'd have a single run of Execute, after which the whole ModuleEngine would be disposed. For example, "var engine = new ModuleEngine(...); engine.Execute(filename);". But I'm not sure if that's actually very useful. Some instances of that discussion about "importing" v.s. "executing":
|
Thank you for the clarifications, I know you have gone back and forth with the design and the problems a great many times more than I have. We should probably hide everything that does not make sense, but also try to allow come use cases that feel sound. I think one of them is to use modules in a script, but I guess that has to wait for another day as it's a can of worms like you've described. In short:
So I think this boils down to the test case I tried to make, but fails to crystalize the idea as it's in module context and cannot return the value I would want to, like const string script = @"
import { formatName } from './modules/format-name.js'
return formatName('John', 'Doe');
";
var result = engine.Evalute(script); If this were module code, I would give it an ID/specifier |
And you can also create a new PR replacing this one if you want to get the test automation results, you can also squash my commits to yours if you want to, I think the most important part is to use the default Test262 testing infrastructure for running. |
This might actually be possible by using dynamic import (the function call), it doesn't need to be in module context when called:
So just allowing current module be null in |
Aaaah indeed dynamic import avoids most of the variable binding problems by returning a promise rather than touching the lexical context. However you wouldn't be able to do the example you gave, it would be something like (afaik we don't have await): const string script = @"
var result;
import('./modules/format-name.js').then(formatter =>
result = formatter.formatName('John', 'Doe');
);
return result;
";
var result = engine.Evalute(script); Without await you would have to set a global variable and run another Evaluate Otherwise, for doing something like you suggested previous, we only need to define a run a module, but that module would still be outside of the context. Henceforth using something like Anyway for now I'll try and make tests pass, and I'll do a new PR. I can do a PR right away if you prefer. |
Let's put |
Will do. By the way, I noticed the tests tend to run quite slow because the file system is fully scanned and every file is read even when running a single test. I'd like to implement lazy loading of file content during the tests listing, so debugging is less painful. Do you have objections if I do that in the PR? I can do it and not commit it if you prefer keeping the PR clean. |
Maybe we should wait for #1039 , it changes the test execution altogether. Maybe there will still be problems, but the PR has the current goal of dynamically loading current test fixtures from GitHub and generating specific test cases. |
I'll keep my changes locally then (it pretty much simply defers loading the code to when the test is actually ran, I'll keep the file locally if eventually you're interested, I'll share the link along with the PR but separately) Edit: I'll just revert, because file content is used to group tests, my change would also modify how tests are listed, I'll just manually return in Test262Test.csm otherwise it sometimes takes almost a minute to just start the tests sometimes :| Edit 2: It would also be nice to have reliable tests ordering (not using ConcurrentBag) so I can always see the "next test to check" being the first of the failed tests list. |
My head hurts :| Edit: Progress! (Sorry ;) ) |
I am making some fairly good progress (mostly figuring out that some of the specifications are in notes rather than in the specs themselves, and that node.js doesn't follow ecmascript specifications to the letter...), down to 31 failing tests :) |
Just to keep you in the loop, some tests fail with recursive dependencies and other weird edge cases, but they seem to match the specifications as far as I can tell... So I am doing a refactor that splits "JsModule" into "ModuleRecord", "CyclicModuleRecord" and "SourceTextModuleRecord" so it's easier to follow specifications methods. Therefore... again another pretty big PR incoming. Up to now except a few edge cases that I did fix, I'm wondering if we have to implement recursive dependencies at all though. For now I'll see how far I can get with at least refactoring into something that can be compared with ecmascript's specs, and if I still don't find the problem, I'll submit a PR and ask for help. |
Sounds good. There's also option to accept "good enough" and utilize skipped.json for items that cause more trouble than it's worth. Until someone reaaaally needs it and is allow willing to create a PR for such edge case 😉 |
There's a pretty good chance that I use this, there are edge cases that are just there to hurt people I think! But I'd like to figure out at least why recursive modules are in the test cases while the specs seem to assert against it. I'm missing a piece of the puzzle. |
don't get me started with my generators PR where there's |
Hahaha they are cruel ;) But hey, recursive cyclic imports work on my branch now! <3 The problem was |
Moved to #1102 (this one can be closed, let me know if you prefer that I make a PR on a branch in this repo instead) |