Open dSalieri opened 1 week ago
linking
and evaluating
states are meant to be "internal states" of the link and evaluate algorithm. When those algorithms are not running, all modules are guaranteed to be in one of these states:
new
unlinked
linked
evaluating-async
evaluated
This means that consumers have less states to think about, and that all transitions across the possible states that a consumer can see are easily definable:
new
to unlinked
you need to load the dependenciesunlinked
to linked
you call Link()
linked
to evaluated
or evaluating-async
you call Evaluate()
evaluating-async
to evaluated
you wait on the promiseIntermediate states would be impossible to handle for consumers: for example, what do you do with a module that is linking
? How do you upgrade it to linked
? You cannot just call Link()
on it, because what if you would be causing re-entrancy by doing so?
Recalculate the error result, which we already know from the first calculation, what's the point?
It's conceptually easier to not have a cache than to have one, if the cache is not observable.
Implementations can instead optimize the spec algorithms by adding caches wherever they want, as long as they have the same observable behavior of the spec (i.e. the cached-and-rethrown linking error must not be ===
to the previous one)
@nicolo-ribaudo
Intermediate states would be impossible to handle for consumers: for example, what do you do with a module that is linking? How do you upgrade it to linked? You cannot just call Link() on it, because what if you would be causing re-entrancy by doing so?
You can just leave it as is, but borrow the strategy from the Evaluate() phase:
Add a [[LinkingError]]
field and store throw completion there (or make the [[EvaluatingError]]
field common for both phases, if this does not conflict), and then when calling Link(), check if there was an error and exit the algorithm. This looks more logical and corresponds to the logic from ES6, when only one initialization was performed for the module.
What do you think? Wouldn't it be simpler?
As for the observed behavior and caches that implementations can implement, it is clear.
If you look at the history of modules in the ECMAScript specification, it has been constantly changing. Although some things have remained as they were originally invented in ES6.
My question is about the architecture of modules, and specifically about why the Link() algorithm can change the
[[Status]]
field of a module from linking to unlinked? This happens in the only case when a Syntax Error occurs in the InitializeEnvironment() method of a module, due to a binding resolution error.If you look at ES6, there, if a module received the Module Environment Record value in the
[[Environment]]
field once, then the algorithm noticed this and reset further ModuleDeclarationInstantiation() steps the next time. This happened both in the case of a binding resolution error for a module and in the case when ModuleDeclarationInstantiation() was successful the algorithm simply did not reach the second binding resolution.Now this algorithm ModuleDeclarationInstantiation() is broken down into Link() -> InnerModuleLinking(module, stack, index) -> InitializeEnvironment(). And if before it did not execute the algorithm more than once in any case, now it has a loophole that allows you to execute Link() again in full if an error is detected in resolving bindings.
Why was this done? What was achieved this way? Recalculate the error result, which we already know from the first calculation, what's the point?