Non-strict functions use a separate Environment Record for top-level lexical declarations so that a direct eval can determine whether any var scoped declarations introduced by the eval code conflict with pre-existing top-level lexically scoped declarations. This is not needed for strict functions because a strict direct eval always places all declarations into a new Environment Record.
I'm not sure I understand why a separate lexical environment (LE) is needed to determine if this top-level-let / eval-var conflict exists. Why can't we keep the caller function's LE and variable environment (VE) the same and then when it comes time to perform the eval call, use varEnv.HasLexicalDeclaration(name) (as in step 3.a.i.1 of EvalDeclarationInstantiation) to resolve any conflict instead of the loop in step 3.d?
Thanks @bakkot, is there a reason why a similar method isn't available for declarative environment records?
I had read in the ES5 spec that when an execution context is created, both its LE and VE point to the same environment record (as in step 31 of ES2021). So I didn't quite understand why a non-strict function's LE must be put in front of its VE in the scope chain in order to be able to resolve top-level-let / eval-var conflicts. Is it simply because declarative environment records don't have a method like HasLexicalDeclaration to be able to differentiate between var and let declarations?
It wouldn't really make sense given how they're structured. Global environment records are actually a wrapper holding two other environment records, whereas regular environment records hold bindings directly and do not differentiate among the different kind of binding they hold. global.HasLexicalDeclaration works by querying the one of the two associated records which holds lexical bindings.
In other words, the only reason HasLexicalDeclaration can exist on global records is by having two environment records, and that's what's happening here as well. It's just tracked in a different way, by having two environment records directly associated with a function context, rather than (as with global environment records) having a single environment record which wraps two records.
So I didn't quite understand why a non-strict function's LE must be put in front of its VE in the scope chain in order to be able to resolve top-level-let / eval-var conflicts.
It's not specifically in order to be able to resolve top-level-let / eval-var conflicts; it's just how environment records work in general: every environment record must have an outer record (except the global environment record at the top of the chain).
When resolving a binding you first look in the current LexicalEnvironment, and then in its [[OuterEnv]], and then in its [[OuterEnv]], and so on until you reach the top of the chain. So the new lexical environment needs to have the function's variable environment as its outer context to ensure that lookups go through the variable environment.
I see, since program-level var declarations are added to the object portion of the global environment record while program-level let declarations are added to the declarative portion, HasLexicalBinding can identify the latter by just searching the declarative record.
Yes, what I meant was that in the case of non-strict functions, the reason for the divergence between the LE and VE at the context creation phase is to allow us to keep track of top-level lexically scoped declarations and prevent an eval call from hoisting like-named var declarations in the context execution phase.
Prior to this I had incorrectly assumed that the VE and LE could only diverge during the execution phase based on the ES5 description:
The value of the VariableEnvironment component never changes while the value of the LexicalEnvironment component may change during execution of code within an execution context.
That may have been true in ES5 but only because let and const didn't exist back then. Whereas with their introduction in ES6, the value of the LE may change during the context creation phase as well - is this the correct interpretation?