diff --git a/Jint.Tests/Runtime/DestructuringTests.cs b/Jint.Tests/Runtime/DestructuringTests.cs index f62264cc7b..86540029b5 100644 --- a/Jint.Tests/Runtime/DestructuringTests.cs +++ b/Jint.Tests/Runtime/DestructuringTests.cs @@ -68,4 +68,54 @@ public void EmptyRest() { _engine.Execute("function test({ ...props }){}; test({});"); } + + [Fact] + public void VarDestructuringInForOfShouldHoistInStrictMode() + { + // Nested destructuring var names must be hoisted to function scope. + // Previously only Identifier bindings were collected; patterns like [[x]] were skipped, + // causing ReferenceError in strict mode. + var result = _engine.Evaluate(""" + 'use strict'; + (function() { + var results = []; + for (var [[x, y, z] = [4, 5, 6]] of [[]]) { + results.push(x, y, z); + } + return results.join(','); + })() + """); + + Assert.Equal("4,5,6", result.AsString()); + } + + [Fact] + public void VarObjectDestructuringInForOfShouldHoistInStrictMode() + { + var result = _engine.Evaluate(""" + 'use strict'; + (function() { + for (var { a, b } of [{ a: 1, b: 2 }]) {} + return a + ',' + b; + })() + """); + + Assert.Equal("1,2", result.AsString()); + } + + [Fact] + public void VarDestructuringInForAwaitOfShouldHoistInStrictMode() + { + var engine = new Engine(); + var result = engine.Evaluate(""" + 'use strict'; + (async function() { + for await (var [[x] = [1]] of [[]]) {} + return x; + })() + """); + + result = result.UnwrapIfPromise(); + Assert.Equal(1, result.AsInteger()); + } } diff --git a/Jint/HoistingScope.cs b/Jint/HoistingScope.cs index d077341dcb..eb61fa7d42 100644 --- a/Jint/HoistingScope.cs +++ b/Jint/HoistingScope.cs @@ -10,7 +10,7 @@ internal sealed class HoistingScope internal readonly List? _varNames; internal readonly List? _lexicalDeclarations; - internal readonly List? _lexicalNames; + internal readonly List? _lexicalNames; /// /// B.3.2/B.3.3: Block-level function declarations that need AnnexB var hoisting in sloppy mode. @@ -22,7 +22,7 @@ private HoistingScope( List? varNames, List? variableDeclarations, List? lexicalDeclarations, - List? lexicalNames, + List? lexicalNames, List? annexBFunctionDeclarations = null) { _functionDeclarations = functionDeclarations; @@ -171,7 +171,7 @@ private sealed class ScriptWalker private readonly bool _collectLexicalNames; internal List? _lexicalDeclarations; - internal List? _lexicalNames; + internal List? _lexicalNames; /// /// B.3.2/B.3.3: Function declarations inside blocks/switch cases in sloppy mode. @@ -271,10 +271,7 @@ private void VisitCore(Node node, Node? parent, HashSet? enclosingLexica ref readonly var nodeList = ref variableDeclaration.Declarations; foreach (var declaration in nodeList) { - if (declaration.Id is Identifier identifier) - { - _varNames.Add(identifier.Name); - } + declaration.Id.GetBoundNames(_varNames); } } } @@ -289,10 +286,7 @@ private void VisitCore(Node node, Node? parent, HashSet? enclosingLexica ref readonly var nodeList = ref variableDeclaration.Declarations; foreach (var declaration in nodeList) { - if (declaration.Id is Identifier identifier) - { - _lexicalNames.Add(identifier.Name); - } + declaration.Id.GetBoundNames(_lexicalNames); } } } diff --git a/Jint/Runtime/Interpreter/JintFunctionDefinition.cs b/Jint/Runtime/Interpreter/JintFunctionDefinition.cs index 289eba7d02..92277ad085 100644 --- a/Jint/Runtime/Interpreter/JintFunctionDefinition.cs +++ b/Jint/Runtime/Interpreter/JintFunctionDefinition.cs @@ -344,7 +344,7 @@ internal static State BuildState(IFunction function) } else if (!state.HasParameterExpressions) { - if (state.FunctionNames.Contains(KnownKeys.Arguments) || lexicalNames?.Contains(KnownKeys.Arguments.Name) == true) + if (state.FunctionNames.Contains(KnownKeys.Arguments) || lexicalNames?.Contains(KnownKeys.Arguments) == true) { state.ArgumentsObjectNeeded = false; } @@ -468,7 +468,7 @@ internal static State BuildState(IFunction function) continue; } - if (lexicalNames?.Contains(fn.Name) == true) + if (lexicalNames?.Contains(fn) == true) { continue; }