-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Editorial: dedupe and isolate modules traversal logic #3635
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
base: main
Are you sure you want to change the base?
Conversation
a4fc0b9 to
e2975da
Compare
c87144b to
1831e10
Compare
1831e10 to
acc3d85
Compare
|
Marking as draft as:
|
64f28a0 to
aa7eab7
Compare
|
The rendered spec for this PR is available at https://tc39.es/ecma262/pr/3635. |
aa7eab7 to
f974a14
Compare
f945022 to
21a4aab
Compare
jmdyck
left a comment
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.
Also, this PR un-defines the following ids:
- sec-InnerModuleLinking
- sec-innermoduleevaluation
- sec-innermoduleinstantiation
which should maybe be retained in oldids attributes.
|
Any preference on whether the oldid for |
892a45f to
309df93
Compare
Me? Maybe a slight pref for BTW, just noticed that the PR leaves lots of dangling references to |
…loop Rather than doing it in the DFS recursion loop. This changes the order in which modules are pushed in [[PendingAsyncDependencies]]: now they automatically are sorted by their [[AsyncEvaluationOrder]]. It does not actually matter though, because GatherAvailableAncestors will merge various [[PendingAsyncDependencies]] lists in a non-order-preserving way so AsyncModuleExecutionFulfilled still needs to explicitly re-sort the resulting list.
This commit deduplicates the logic that is currently both in InnerModuleLinking and InnerModuleEvaluation, clearly splitting what logic is relative to traversing the graph and what is relative to actually linking/evaluating modules.
baae087 to
92f4259
Compare
| <p>An analogous story occurs for the evaluation phase of a cyclic module graph, in the success case.</p> | ||
|
|
||
| <p>Now consider a case where _A_ has a linking error; for example, it tries to import a binding from _C_ that does not exist. In that case, the above steps still occur, including the early return from the second call to InnerModuleLinking on _A_. However, once we unwind back to the original InnerModuleLinking on _A_, it fails during InitializeEnvironment, namely right after _C_.ResolveExport(). The thrown *SyntaxError* exception propagates up to _A_.Link, which resets all modules that are currently on its _stack_ (these are always exactly the modules that are still ~linking~). Hence both _A_ and _B_ become ~unlinked~. Note that _C_ is left as ~linked~.</p> | ||
| <p>Now consider a case where _A_ has a linking error; for example, it tries to import a binding from _C_ that does not exist. In that case, the above steps still occur, including the early return from the second call to InnerModuleGraphDFS on _A_. However, once we unwind back to the original InnerModuleGraphDFS on _A_, it fails during _action_'s _A_.InitializeEnvironment, namely right after _C_.ResolveExport(). The thrown *SyntaxError* exception propagates up to _A_.Link, which resets all modules that are currently on its _stack_ (these are always exactly the modules that are still ~linking~) thourh ModuleGraphDFS's _postErrorCleanup_. Hence both _A_ and _B_ become ~unlinked~. Note that _C_ is left as ~linked~.</p> |
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.
| <p>Now consider a case where _A_ has a linking error; for example, it tries to import a binding from _C_ that does not exist. In that case, the above steps still occur, including the early return from the second call to InnerModuleGraphDFS on _A_. However, once we unwind back to the original InnerModuleGraphDFS on _A_, it fails during _action_'s _A_.InitializeEnvironment, namely right after _C_.ResolveExport(). The thrown *SyntaxError* exception propagates up to _A_.Link, which resets all modules that are currently on its _stack_ (these are always exactly the modules that are still ~linking~) thourh ModuleGraphDFS's _postErrorCleanup_. Hence both _A_ and _B_ become ~unlinked~. Note that _C_ is left as ~linked~.</p> | |
| <p>Now consider a case where _A_ has a linking error; for example, it tries to import a binding from _C_ that does not exist. In that case, the above steps still occur, including the early return from the second call to InnerModuleGraphDFS on _A_. However, once we unwind back to the original InnerModuleGraphDFS on _A_, it fails during _action_'s _A_.InitializeEnvironment, namely right after _C_.ResolveExport(). The thrown *SyntaxError* exception propagates up to _A_.Link, which resets all modules that are currently on its _stack_ (these are always exactly the modules that are still ~linking~) through ModuleGraphDFS's _postErrorCleanup_. Hence both _A_ and _B_ become ~unlinked~. Note that _C_ is left as ~linked~.</p> |
| 1. NOTE: _module_.[[AsyncEvaluationOrder]] is ~done~ if and only if _module_ had already been evaluated and that evaluation was asynchronous. | ||
| 1. Perform ! Call(_capability_.[[Resolve]], *undefined*, « *undefined* »). | ||
| 1. Assert: _stack_ is empty. | ||
| 1. Let _result_ be Completion(ModuleGraphDFS(_module_, ~linked~, ~evaluating~, EvaluateDFSAction, EvaluateDFSCompleteSCC, EvaluateDFSPostErrorCleanup)). |
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.
These Evaluate* AOs should be ACs.
| 1. Let _dfsPostErrorCleanup_ be a new Abstract Closure with parameters (_cyclicModule_, _errorCompletion_) that captures nothing and performs the following steps when called: | ||
| 1. Set _cyclicModule_.[[Status]] to ~unlinked~. | ||
| 1. Return ~unused~. | ||
| 1. Perform ? ModuleGraphDFS(_module_, ~unlinked~, ~linking~, _dfsAction_, _dfsSCCcompletion_, _dfsPostErrorCleanup_). |
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.
ModuleGraphDFS is only called in 2 places: here, where it passes only ACs that capture nothing, and in Evaluate below where it passes AOs (which obviously also capture nothing). That feels like it should just be a flag (a 2-state spec enum) to me.
This PR separates the logic to perform a depth-first traversal on module graph out of InnerModuleLinking/InnerModuleEvaluation. It has two advantages:
Whether this is an improvement or not is subjective (it adds a layer of indirection), so I'm
curious to read the editors opinion about it.
From an editorial/formatting point of view, ModuleGraphDFS is an AO that takes a bunch of callbacks. I've declared them inline in the
Link()/Evaluate()concrete methods as abstract closures, even though they don't actually close over anything. I think having them inline rather than as separate AOs makes the reading flow simpler. Ideally, my preferred approach would be something that lets us writeI suggest reading this commit-by-commit. The first two commits just move logic around so that what would go in the same AC of the third commit is already all in the same place.
This PR preserves the bugs that would be fixed by #3613 and #3583, but by implementing this PR + those two in engine262 all the tests I have still pass.
The
import deferproposal would add a new callback toModuleGraphDFS,_getModuleDescendants_, that for linking just returns the RequestedModules while for Evaluation it uses import defer's step 11 of https://tc39.es/proposal-defer-import-eval/#sec-innermoduleevaluation (with GatherAsynchronousTransitiveDependencies).