Forráskód Böngészése

Add support for using and await using (#2139)

Marko Lahma 2 hete
szülő
commit
0b03f8e2d9
32 módosított fájl, 256 hozzáadás és 97 törlés
  1. 3 1
      Jint.Tests.Test262/Test262Harness.settings.json
  2. 64 0
      Jint.Tests/Runtime/InteropDisposeTests.cs
  3. 12 2
      Jint/AstExtensions.cs
  4. 3 1
      Jint/Engine.Defaults.cs
  5. 6 6
      Jint/Engine.cs
  6. 1 1
      Jint/Jint.csproj
  7. 0 7
      Jint/Native/Disposable/DisposeCapability.cs
  8. 1 1
      Jint/Native/Function/ClassDefinition.cs
  9. 3 1
      Jint/Native/Function/ScriptFunction.cs
  10. 0 1
      Jint/Native/Object/ObjectInstance.cs
  11. 3 2
      Jint/ParsingOptions.cs
  12. 26 3
      Jint/Runtime/Environments/DeclarativeEnvironment.cs
  13. 4 1
      Jint/Runtime/Environments/Environment.cs
  14. 2 2
      Jint/Runtime/Environments/GlobalEnvironment.cs
  15. 1 1
      Jint/Runtime/Environments/ModuleEnvironment.cs
  16. 1 1
      Jint/Runtime/Environments/ObjectEnvironment.cs
  17. 24 0
      Jint/Runtime/Interop/ObjectWrapper.cs
  18. 8 0
      Jint/Runtime/Interop/TypeDescriptor.cs
  19. 2 2
      Jint/Runtime/Interpreter/Expressions/DestructuringPatternAssignmentExpression.cs
  20. 4 4
      Jint/Runtime/Interpreter/Expressions/JintFunctionExpression.cs
  21. 1 1
      Jint/Runtime/Interpreter/JintStatementList.cs
  22. 7 1
      Jint/Runtime/Interpreter/Statements/JintBlockStatement.cs
  23. 2 2
      Jint/Runtime/Interpreter/Statements/JintClassDeclarationStatement.cs
  24. 3 3
      Jint/Runtime/Interpreter/Statements/JintExportDefaultDeclaration.cs
  25. 43 37
      Jint/Runtime/Interpreter/Statements/JintForInForOfStatement.cs
  26. 6 4
      Jint/Runtime/Interpreter/Statements/JintForStatement.cs
  27. 2 2
      Jint/Runtime/Interpreter/Statements/JintVariableDeclaration.cs
  28. 1 1
      Jint/Runtime/Modules/BuilderModule.cs
  29. 12 6
      Jint/Runtime/Modules/SourceTextModule.cs
  30. 1 1
      Jint/Runtime/Modules/SyntheticModule.cs
  31. 9 2
      Jint/Runtime/Reference.cs
  32. 1 0
      README.md

+ 3 - 1
Jint.Tests.Test262/Test262Harness.settings.json

@@ -9,7 +9,6 @@
     "async-iteration",
     "Atomics",
     "decorators",
-    "explicit-resource-management",
     "import-defer",
     "iterator-helpers",
     "iterator-sequencing",
@@ -32,6 +31,9 @@
   ],
   "ExcludedFiles": [
 
+    // needs async-iteration
+    "built-ins/AsyncIteratorPrototype/Symbol.asyncDispose/*.js",
+
     // requires rewrite of destructing towards spec
     "language/destructuring/binding/keyed-destructuring-property-reference-target-evaluation-order-with-bindings.js",
     "language/expressions/assignment/destructuring/keyed-destructuring-property-reference-target-evaluation-order-with-bindings.js",

+ 64 - 0
Jint.Tests/Runtime/InteropDisposeTests.cs

@@ -0,0 +1,64 @@
+namespace Jint.Tests.Runtime;
+
+public class InteropDisposeTests
+{
+    private readonly Engine _engine;
+    private readonly SyncDisposable _syncDisposable = new();
+
+#if NETCOREAPP
+    private readonly AsyncDisposable _asyncDisposable = new();
+#endif
+
+    public InteropDisposeTests()
+    {
+        _engine = new Engine();
+        _engine.SetValue("getSync", () => _syncDisposable);
+
+#if NETCOREAPP
+        _engine.SetValue("getAsync", () => _asyncDisposable);
+#endif
+    }
+
+    [Theory]
+    [InlineData("{ using temp = getSync(); }")]
+    [InlineData("(function x() { using temp = getSync(); })()")]
+    [InlineData("class X { constructor() { using temp = getSync(); } } new X();")]
+    [InlineData("class X { static { using temp = getSync(); } } new X();")]
+    [InlineData("for (let i = 0; i < 1; i++) { using temp = getSync(); }")]
+    public void ShouldSyncDispose(string program)
+    {
+        _engine.Execute(program);
+        _syncDisposable.Disposed.Should().BeTrue();
+    }
+
+    private class SyncDisposable : IDisposable
+    {
+        public bool Disposed { get; private set; }
+
+        public void Dispose()
+        {
+            Disposed = true;
+        }
+    }
+
+#if NETCOREAPP
+    [Theory]
+    [InlineData("(async function x() { await using temp = getAsync(); })();")]
+    public void ShouldAsyncDispose(string program)
+    {
+        _engine.Evaluate(program).UnwrapIfPromise();
+        _asyncDisposable.Disposed.Should().BeTrue();
+    }
+
+    private class AsyncDisposable : IAsyncDisposable
+    {
+        public bool Disposed { get; private set; }
+
+        public ValueTask DisposeAsync()
+        {
+            Disposed = true;
+            return default;
+        }
+    }
+#endif
+}

+ 12 - 2
Jint/AstExtensions.cs

@@ -299,7 +299,7 @@ public static class AstExtensions
         if (expression is Identifier identifier)
         {
             var catchEnvRecord = (DeclarativeEnvironment) env;
-            catchEnvRecord.CreateMutableBindingAndInitialize(identifier.Name, canBeDeleted: false, value);
+            catchEnvRecord.CreateMutableBindingAndInitialize(identifier.Name, canBeDeleted: false, value, DisposeHint.Normal);
         }
         else if (expression is DestructuringPattern pattern)
         {
@@ -498,6 +498,16 @@ public static class AstExtensions
         validator.Visit(script);
     }
 
+    internal static DisposeHint GetDisposeHint(this VariableDeclarationKind statement)
+    {
+        return statement switch
+        {
+            VariableDeclarationKind.AwaitUsing => DisposeHint.Async,
+            VariableDeclarationKind.Using => DisposeHint.Sync,
+            _ => DisposeHint.Normal,
+        };
+    }
+
     private sealed class MinimalSyntaxElement : Node
     {
         public MinimalSyntaxElement(in SourceLocation location) : base(NodeType.Unknown)
@@ -545,4 +555,4 @@ public static class AstExtensions
             ExceptionHelper.ThrowSyntaxError(r, $"Private field '#{id.Name}' must be declared in an enclosing class");
         }
     }
-}
+}

+ 3 - 1
Jint/Engine.Defaults.cs

@@ -7,7 +7,9 @@ public partial class Engine
     internal static readonly ParserOptions BaseParserOptions = ParserOptions.Default with
     {
         EcmaVersion = EcmaVersion.ES2023,
-        ExperimentalESFeatures = ExperimentalESFeatures.ImportAttributes | ExperimentalESFeatures.RegExpDuplicateNamedCapturingGroups,
+        ExperimentalESFeatures = ExperimentalESFeatures.ImportAttributes
+                                 | ExperimentalESFeatures.RegExpDuplicateNamedCapturingGroups
+                                 | ExperimentalESFeatures.ExplicitResourceManagement,
         Tolerant = false,
     };
 }

+ 6 - 6
Jint/Engine.cs

@@ -1080,11 +1080,11 @@ public sealed partial class Engine : IDisposable
 
             if (strict)
             {
-                env.CreateImmutableBindingAndInitialize(KnownKeys.Arguments, strict: false, ao);
+                env.CreateImmutableBindingAndInitialize(KnownKeys.Arguments, strict: false, ao, DisposeHint.Normal);
             }
             else
             {
-                env.CreateMutableBindingAndInitialize(KnownKeys.Arguments, canBeDeleted: false, ao);
+                env.CreateMutableBindingAndInitialize(KnownKeys.Arguments, canBeDeleted: false, ao, DisposeHint.Normal);
             }
         }
 
@@ -1108,7 +1108,7 @@ public sealed partial class Engine : IDisposable
             for (var i = 0; i < varsToInitialize.Count; i++)
             {
                 var pair = varsToInitialize[i];
-                env.CreateMutableBindingAndInitialize(pair.Name, canBeDeleted: false, JsValue.Undefined);
+                env.CreateMutableBindingAndInitialize(pair.Name, canBeDeleted: false, JsValue.Undefined, DisposeHint.Normal);
             }
 
             varEnv = env;
@@ -1126,7 +1126,7 @@ public sealed partial class Engine : IDisposable
             {
                 var pair = varsToInitialize[i];
                 var initialValue = pair.InitialValue ?? env.GetBindingValue(pair.Name, strict: false);
-                varEnv.CreateMutableBindingAndInitialize(pair.Name, canBeDeleted: false, initialValue);
+                varEnv.CreateMutableBindingAndInitialize(pair.Name, canBeDeleted: false, initialValue, DisposeHint.Normal);
             }
         }
 
@@ -1367,7 +1367,7 @@ public sealed partial class Engine : IDisposable
                 if (!bindingExists)
                 {
                     varEnvRec.CreateMutableBinding(fn, canBeDeleted: true);
-                    varEnvRec.InitializeBinding(fn, fo);
+                    varEnvRec.InitializeBinding(fn, fo, DisposeHint.Normal);
                 }
                 else
                 {
@@ -1388,7 +1388,7 @@ public sealed partial class Engine : IDisposable
                 if (!bindingExists)
                 {
                     varEnvRec.CreateMutableBinding(vn, canBeDeleted: true);
-                    varEnvRec.InitializeBinding(vn, JsValue.Undefined);
+                    varEnvRec.InitializeBinding(vn, JsValue.Undefined, DisposeHint.Normal);
                 }
             }
         }

+ 1 - 1
Jint/Jint.csproj

@@ -27,7 +27,7 @@
   </PropertyGroup>
 
   <PropertyGroup Condition=" '$(TargetFramework)' != 'net462' and '$(TargetFramework)' != 'netstandard2.0' ">
-    <DefineConstants>$(DefineConstants);SUPPORTS_SPAN_PARSE;SUPPORTS_WEAK_TABLE_ADD_OR_UPDATE;SUPPORTS_WEAK_TABLE_CLEAR</DefineConstants>
+    <DefineConstants>$(DefineConstants);SUPPORTS_SPAN_PARSE;SUPPORTS_WEAK_TABLE_ADD_OR_UPDATE;SUPPORTS_WEAK_TABLE_CLEAR;SUPPORTS_ASYNC_DISPOSE</DefineConstants>
   </PropertyGroup>
 
   <PropertyGroup Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net6.0'))">

+ 0 - 7
Jint/Native/Disposable/DisposeCapability.cs

@@ -2,13 +2,6 @@ using Jint.Runtime;
 
 namespace Jint.Native.Disposable;
 
-internal enum DisposeHint
-{
-    Normal,
-    Sync,
-    Async,
-}
-
 internal sealed class DisposeCapability
 {
     private readonly Engine _engine;

+ 1 - 1
Jint/Native/Function/ClassDefinition.cs

@@ -203,7 +203,7 @@ internal sealed class ClassDefinition
 
             if (_className is not null)
             {
-                classEnv.InitializeBinding(_className, F);
+                classEnv.InitializeBinding(_className, F, DisposeHint.Normal);
             }
 
             F._privateMethods = instancePrivateMethods;

+ 3 - 1
Jint/Native/Function/ScriptFunction.cs

@@ -59,7 +59,7 @@ public sealed class ScriptFunction : Function, IConstructor
     protected internal override JsValue Call(JsValue thisObject, JsCallArguments arguments)
     {
         var strict = _functionDefinition!.Strict || _thisMode == FunctionThisMode.Strict;
-        using (new StrictModeScope(strict, true))
+        using (new StrictModeScope(strict, force: true))
         {
             try
             {
@@ -76,6 +76,7 @@ public sealed class ScriptFunction : Function, IConstructor
                 var context = _engine._activeEvaluationContext ?? new EvaluationContext(_engine);
 
                 var result = _functionDefinition.EvaluateBody(context, this, arguments);
+                result = calleeContext.LexicalEnvironment.DisposeResources(result);
 
                 if (result.Type == CompletionType.Throw)
                 {
@@ -159,6 +160,7 @@ public sealed class ScriptFunction : Function, IConstructor
                 var context = _engine._activeEvaluationContext ?? new EvaluationContext(_engine);
 
                 var result = _functionDefinition!.EvaluateBody(context, this, arguments);
+                result = constructorEnv.DisposeResources(result);
 
                 // The DebugHandler needs the current execution context before the return for stepping through the return point
                 // We exclude the empty constructor generated for classes without an explicit constructor.

+ 0 - 1
Jint/Native/Object/ObjectInstance.cs

@@ -5,7 +5,6 @@ using Jint.Collections;
 using Jint.Native.Array;
 using Jint.Native.BigInt;
 using Jint.Native.Boolean;
-using Jint.Native.Disposable;
 using Jint.Native.Json;
 using Jint.Native.Number;
 using Jint.Native.Promise;

+ 3 - 2
Jint/ParsingOptions.cs

@@ -30,11 +30,12 @@ public interface IParsingOptions
     bool Tolerant { get; init; }
 }
 
-public sealed record class ScriptParsingOptions : IParsingOptions
+public sealed record ScriptParsingOptions : IParsingOptions
 {
     private static readonly ParserOptions _defaultParserOptions = Engine.BaseParserOptions with
     {
         AllowReturnOutsideFunction = true,
+        AllowTopLevelUsing = true,
         RegExpParseMode = RegExpParseMode.AdaptToInterpreted,
     };
 
@@ -106,4 +107,4 @@ public sealed record class ModuleParsingOptions : IParsingOptions
 
     internal ParserOptions GetParserOptions(Options engineOptions)
         => ApplyTo(_defaultParserOptions, _defaultParserOptions.RegExpParseMode, engineOptions.Constraints.RegexTimeout);
-}
+}

+ 26 - 3
Jint/Runtime/Environments/DeclarativeEnvironment.cs

@@ -2,6 +2,7 @@
 using System.Runtime.CompilerServices;
 using Jint.Collections;
 using Jint.Native;
+using Jint.Native.Disposable;
 
 namespace Jint.Runtime.Environments;
 
@@ -13,6 +14,7 @@ internal class DeclarativeEnvironment : Environment
 {
     internal HybridDictionary<Binding>? _dictionary;
     internal readonly bool _catchEnvironment;
+    private DisposeCapability? _disposeCapability;
 
     public DeclarativeEnvironment(Engine engine, bool catchEnvironment = false) : base(engine)
     {
@@ -35,16 +37,24 @@ internal class DeclarativeEnvironment : Environment
         return false;
     }
 
-    internal void CreateMutableBindingAndInitialize(Key name, bool canBeDeleted, JsValue value)
+    internal void CreateMutableBindingAndInitialize(Key name, bool canBeDeleted, JsValue value, DisposeHint hint)
     {
         _dictionary ??= new HybridDictionary<Binding>();
         _dictionary[name] = new Binding(value, canBeDeleted, mutable: true, strict: false);
+        if (hint != DisposeHint.Normal)
+        {
+            HandleDisposal(value, hint);
+        }
     }
 
-    internal void CreateImmutableBindingAndInitialize(Key name, bool strict, JsValue value)
+    internal void CreateImmutableBindingAndInitialize(Key name, bool strict, JsValue value, DisposeHint hint)
     {
         _dictionary ??= new HybridDictionary<Binding>();
         _dictionary[name] = new Binding(value, canBeDeleted: false, mutable: false, strict);
+        if (hint != DisposeHint.Normal)
+        {
+            HandleDisposal(value, hint);
+        }
     }
 
     internal sealed override void CreateMutableBinding(Key name, bool canBeDeleted = false)
@@ -59,10 +69,14 @@ internal class DeclarativeEnvironment : Environment
         _dictionary.CreateImmutableBinding(name, strict);
     }
 
-    internal sealed override void InitializeBinding(Key name, JsValue value)
+    internal sealed override void InitializeBinding(Key name, JsValue value, DisposeHint hint)
     {
         _dictionary ??= new HybridDictionary<Binding>();
         _dictionary.SetOrUpdateValue(name, static (current, value) => current.ChangeValue(value), value);
+        if (hint != DisposeHint.Normal)
+        {
+            HandleDisposal(value, hint);
+        }
     }
 
     internal sealed override void SetMutableBinding(BindingName name, JsValue value, bool strict) => SetMutableBinding(name.Key, value, strict);
@@ -169,6 +183,8 @@ internal class DeclarativeEnvironment : Environment
 
     internal override JsValue GetThisBinding() => Undefined;
 
+    internal sealed override Completion DisposeResources(Completion c) => _disposeCapability?.DisposeResources(c) ?? c;
+
     public void Clear()
     {
         _dictionary = null;
@@ -185,6 +201,13 @@ internal class DeclarativeEnvironment : Environment
             target[bn] = new Binding(lastValue.Value, canBeDeleted: false, mutable: true, strict: false);
         }
     }
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    private void HandleDisposal(JsValue value, DisposeHint hint)
+    {
+        _disposeCapability ??= new DisposeCapability(_engine);
+        _disposeCapability.AddDisposableResource(value, hint);
+    }
 }
 
 internal static class DictionaryExtensions

+ 4 - 1
Jint/Runtime/Environments/Environment.cs

@@ -49,7 +49,8 @@ internal abstract class Environment : JsValue
     /// </summary>
     /// <param name="name">The text of the bound name</param>
     /// <param name="value">The value for the binding.</param>
-    internal abstract void InitializeBinding(Key name, JsValue value);
+    /// <param name="hint">Disposal type hint</param>
+    internal abstract void InitializeBinding(Key name, JsValue value, DisposeHint hint);
 
     /// <summary>
     /// Sets the value of an already existing mutable binding in an environment record.
@@ -106,6 +107,8 @@ internal abstract class Environment : JsValue
 
     internal JsValue? NewTarget { get; set; }
 
+    internal virtual Completion DisposeResources(Completion c) => c;
+
     /// <summary>
     /// Helper to cache JsString/Key when environments use different lookups.
     /// </summary>

+ 2 - 2
Jint/Runtime/Environments/GlobalEnvironment.cs

@@ -146,11 +146,11 @@ internal sealed class GlobalEnvironment : Environment
         ExceptionHelper.ThrowTypeError(_engine.Realm, $"{name} has already been declared");
     }
 
-    internal override void InitializeBinding(Key name, JsValue value)
+    internal override void InitializeBinding(Key name, JsValue value, DisposeHint hint)
     {
         if (_declarativeRecord.HasBinding(name))
         {
-            _declarativeRecord.InitializeBinding(name, value);
+            _declarativeRecord.InitializeBinding(name, value, hint);
         }
         else
         {

+ 1 - 1
Jint/Runtime/Environments/ModuleEnvironment.cs

@@ -31,7 +31,7 @@ internal sealed class ModuleEnvironment : DeclarativeEnvironment
     public void CreateImportBinding(string importName, Module module, string name)
     {
         _importBindings[importName] = new IndirectBinding(module, name);
-        CreateImmutableBindingAndInitialize(importName, true, JsValue.Undefined);
+        CreateImmutableBindingAndInitialize(importName, true, Undefined, DisposeHint.Normal);
     }
 
     /// <summary>

+ 1 - 1
Jint/Runtime/Environments/ObjectEnvironment.cs

@@ -113,7 +113,7 @@ internal sealed class ObjectEnvironment : Environment
     /// <summary>
     /// https://tc39.es/ecma262/#sec-object-environment-records-initializebinding-n-v
     /// </summary>
-    internal override void InitializeBinding(Key name, JsValue value) => SetMutableBinding(name, value, strict: false);
+    internal override void InitializeBinding(Key name, JsValue value, DisposeHint hint) => SetMutableBinding(name, value, strict: false);
 
     internal override void SetMutableBinding(Key name, JsValue value, bool strict)
     {

+ 24 - 0
Jint/Runtime/Interop/ObjectWrapper.cs

@@ -46,6 +46,30 @@ public class ObjectWrapper : ObjectInstance, IObjectWrapper, IEquatable<ObjectWr
                 _prototype = engine.Intrinsics.Array.PrototypeObject;
             }
         }
+
+        if (_typeDescriptor.IsDisposable)
+        {
+            SetProperty(GlobalSymbolRegistry.Dispose, new PropertyDescriptor(new ClrFunction(engine, "dispose", static (thisObject, _) =>
+            {
+                ((thisObject as ObjectWrapper)?.Target as IDisposable)?.Dispose();
+                return Undefined;
+            }), PropertyFlag.NonEnumerable));
+        }
+
+#if SUPPORTS_ASYNC_DISPOSE
+        if (_typeDescriptor.IsAsyncDisposable)
+        {
+            SetProperty(GlobalSymbolRegistry.AsyncDispose, new PropertyDescriptor(new ClrFunction(engine, "asyncDispose", (thisObject, _) =>
+            {
+                var target = ((thisObject as ObjectWrapper)?.Target as IAsyncDisposable)?.DisposeAsync();
+                if (target is not null)
+                {
+                    return ConvertAwaitableToPromise(engine, target);
+                }
+                return Undefined;
+            }), PropertyFlag.NonEnumerable));
+        }
+#endif
     }
 
     /// <summary>

+ 8 - 0
Jint/Runtime/Interop/TypeDescriptor.cs

@@ -48,6 +48,12 @@ internal sealed class TypeDescriptor
 
         IsEnumerable = isEnumerable;
 
+        IsDisposable = type.GetInterface(nameof(IDisposable)) is not null;
+
+#if SUPPORTS_ASYNC_DISPOSE
+        IsAsyncDisposable = type.GetInterface(nameof(IAsyncDisposable)) is not null;
+#endif
+
         if (IsArrayLike)
         {
             LengthProperty = lengthProperty;
@@ -69,6 +75,8 @@ internal sealed class TypeDescriptor
     public bool IsDictionary { get; }
     public bool IsStringKeyedGenericDictionary => _tryGetValueMethod is not null;
     public bool IsEnumerable { get; }
+    public bool IsDisposable { get; }
+    public bool IsAsyncDisposable { get; }
     public PropertyInfo? LengthProperty { get; }
 
     public bool Iterable => IsArrayLike || IsDictionary || IsEnumerable;

+ 2 - 2
Jint/Runtime/Interpreter/Expressions/DestructuringPatternAssignmentExpression.cs

@@ -407,7 +407,7 @@ internal sealed class DestructuringPatternAssignmentExpression : JintExpression
         }
         else
         {
-            lhs.InitializeReferencedBinding(v);
+            lhs.InitializeReferencedBinding(v, DisposeHint.Normal);
         }
         engine._referencePool.Return(lhs);
     }
@@ -434,7 +434,7 @@ internal sealed class DestructuringPatternAssignmentExpression : JintExpression
         var lhs = engine.ResolveBinding(name, environment);
         if (environment is not null)
         {
-            lhs.InitializeReferencedBinding(rval);
+            lhs.InitializeReferencedBinding(rval, DisposeHint.Normal);
         }
         else
         {

+ 4 - 4
Jint/Runtime/Interpreter/Expressions/JintFunctionExpression.cs

@@ -80,7 +80,7 @@ internal sealed class JintFunctionExpression : JintExpression
         }
         closure.MakeConstructor();
 
-        funcEnv?.InitializeBinding(name!, closure);
+        funcEnv?.InitializeBinding(name!, closure, DisposeHint.Normal);
 
         return closure;
     }
@@ -116,7 +116,7 @@ internal sealed class JintFunctionExpression : JintExpression
 
         closure.SetFunctionName(name ?? "");
 
-        funcEnv?.InitializeBinding(name!, closure);
+        funcEnv?.InitializeBinding(name!, closure, DisposeHint.Normal);
 
         return closure;
     }
@@ -158,7 +158,7 @@ internal sealed class JintFunctionExpression : JintExpression
         var prototype = ObjectInstance.OrdinaryObjectCreate(engine, intrinsics.GeneratorFunction.PrototypeObject.PrototypeObject);
         closure.DefinePropertyOrThrow(CommonProperties.Prototype, new PropertyDescriptor(prototype, PropertyFlag.Writable));
 
-        funcEnv?.InitializeBinding(name!, closure);
+        funcEnv?.InitializeBinding(name!, closure, DisposeHint.Normal);
 
         return closure;
     }
@@ -200,7 +200,7 @@ internal sealed class JintFunctionExpression : JintExpression
         var prototype = ObjectInstance.OrdinaryObjectCreate(engine, intrinsics.AsyncGeneratorFunction.PrototypeObject.PrototypeObject);
         closure.DefinePropertyOrThrow(CommonProperties.Prototype, new PropertyDescriptor(prototype, PropertyFlag.Writable));
 
-        funcEnv?.InitializeBinding(name!, closure);
+        funcEnv?.InitializeBinding(name!, closure, DisposeHint.Normal);
 
         return closure;
     }

+ 1 - 1
Jint/Runtime/Interpreter/JintStatementList.cs

@@ -210,7 +210,7 @@ internal sealed class JintStatementList
                 var definition = new JintFunctionDefinition(functionDeclaration);
                 var fn = definition.Name!;
                 var fo = env._engine.Realm.Intrinsics.Function.InstantiateFunctionObject(definition, env, privateEnv);
-                env.InitializeBinding(fn, fo);
+                env.InitializeBinding(fn, fo, DisposeHint.Normal);
             }
         }
 

+ 7 - 1
Jint/Runtime/Interpreter/Statements/JintBlockStatement.cs

@@ -42,12 +42,13 @@ internal sealed class JintBlockStatement : JintStatement<NestedBlockStatement>
             Initialize(context);
         }
 
+        DeclarativeEnvironment? blockEnv = null;
         Environment? oldEnv = null;
         var engine = context.Engine;
         if (_lexicalDeclarations.Declarations.Count > 0)
         {
             oldEnv = engine.ExecutionContext.LexicalEnvironment;
-            var blockEnv = JintEnvironment.NewDeclarativeEnvironment(engine, engine.ExecutionContext.LexicalEnvironment);
+            blockEnv = JintEnvironment.NewDeclarativeEnvironment(engine, engine.ExecutionContext.LexicalEnvironment);
             JintStatementList.BlockDeclarationInstantiation(blockEnv, _lexicalDeclarations);
             engine.UpdateLexicalEnvironment(blockEnv);
         }
@@ -62,6 +63,11 @@ internal sealed class JintBlockStatement : JintStatement<NestedBlockStatement>
             blockValue = _statementList!.Execute(context);
         }
 
+        if (blockEnv != null)
+        {
+            blockValue = blockEnv.DisposeResources(blockValue);
+        }
+
         if (oldEnv is not null)
         {
             engine.UpdateLexicalEnvironment(oldEnv);

+ 2 - 2
Jint/Runtime/Interpreter/Statements/JintClassDeclarationStatement.cs

@@ -26,9 +26,9 @@ internal sealed class JintClassDeclarationStatement : JintStatement<ClassDeclara
         var classBinding = _classDefinition._className;
         if (classBinding != null)
         {
-            env.InitializeBinding(classBinding, value);
+            env.InitializeBinding(classBinding, value, DisposeHint.Normal);
         }
 
         return new Completion(CompletionType.Normal, JsEmpty.Instance, _statement);
     }
-}
+}

+ 3 - 3
Jint/Runtime/Interpreter/Statements/JintExportDefaultDeclaration.cs

@@ -57,7 +57,7 @@ internal sealed class JintExportDefaultDeclaration : JintStatement<ExportDefault
             if (classBinding != null)
             {
                 env.CreateMutableBinding(classBinding);
-                env.InitializeBinding(classBinding, value);
+                env.InitializeBinding(classBinding, value, DisposeHint.Normal);
             }
         }
         else if (_functionDeclaration is not null)
@@ -79,7 +79,7 @@ internal sealed class JintExportDefaultDeclaration : JintStatement<ExportDefault
             functionInstance.SetFunctionName("default");
         }
 
-        env.InitializeBinding("*default*", value);
+        env.InitializeBinding("*default*", value, DisposeHint.Normal);
         return Completion.Empty();
     }
 
@@ -90,7 +90,7 @@ internal sealed class JintExportDefaultDeclaration : JintStatement<ExportDefault
     {
         if (environment is not null)
         {
-            environment.InitializeBinding(name, value);
+            environment.InitializeBinding(name, value, DisposeHint.Normal);
         }
         else
         {

+ 43 - 37
Jint/Runtime/Interpreter/Statements/JintForInForOfStatement.cs

@@ -24,6 +24,7 @@ internal sealed class JintForInForOfStatement : JintStatement<Statement>
     private List<Key>? _tdzNames;
     private bool _destructuring;
     private LhsKind _lhsKind;
+    private DisposeHint _disposeHint;
 
     public JintForInForOfStatement(ForInStatement statement) : base(statement)
     {
@@ -41,47 +42,51 @@ internal sealed class JintForInForOfStatement : JintStatement<Statement>
         _iterationKind = IterationKind.Iterate;
     }
 
-    protected override void Initialize(EvaluationContext context)
+    protected override void Initialize(EvaluationContext context2)
     {
         _lhsKind = LhsKind.Assignment;
-        var engine = context.Engine;
-        if (_leftNode is VariableDeclaration variableDeclaration)
+        _disposeHint = DisposeHint.Normal;
+        switch (_leftNode)
         {
-            _lhsKind = variableDeclaration.Kind == VariableDeclarationKind.Var
-                ? LhsKind.VarBinding
-                : LhsKind.LexicalBinding;
+            case VariableDeclaration variableDeclaration:
+                {
+                    _lhsKind = variableDeclaration.Kind == VariableDeclarationKind.Var
+                        ? LhsKind.VarBinding
+                        : LhsKind.LexicalBinding;
 
-            var variableDeclarationDeclaration = variableDeclaration.Declarations[0];
-            var id = variableDeclarationDeclaration.Id;
-            if (_lhsKind == LhsKind.LexicalBinding)
-            {
-                _tdzNames = new List<Key>(1);
-                id.GetBoundNames(_tdzNames);
-            }
+                    _disposeHint = variableDeclaration.Kind.GetDisposeHint();
 
-            if (id is DestructuringPattern pattern)
-            {
+                    var variableDeclarationDeclaration = variableDeclaration.Declarations[0];
+                    var id = variableDeclarationDeclaration.Id;
+                    if (_lhsKind == LhsKind.LexicalBinding)
+                    {
+                        _tdzNames = new List<Key>(1);
+                        id.GetBoundNames(_tdzNames);
+                    }
+
+                    if (id is DestructuringPattern pattern)
+                    {
+                        _destructuring = true;
+                        _assignmentPattern = pattern;
+                    }
+                    else
+                    {
+                        var identifier = (Identifier) id;
+                        _expr = new JintIdentifierExpression(identifier);
+                    }
+
+                    break;
+                }
+            case DestructuringPattern pattern:
                 _destructuring = true;
                 _assignmentPattern = pattern;
-            }
-            else
-            {
-                var identifier = (Identifier) id;
-                _expr = new JintIdentifierExpression(identifier);
-            }
-        }
-        else if (_leftNode is DestructuringPattern pattern)
-        {
-            _destructuring = true;
-            _assignmentPattern = pattern;
-        }
-        else if (_leftNode is MemberExpression memberExpression)
-        {
-            _expr = new JintMemberExpression(memberExpression);
-        }
-        else
-        {
-            _expr = new JintIdentifierExpression((Identifier) _leftNode);
+                break;
+            case MemberExpression memberExpression:
+                _expr = new JintMemberExpression(memberExpression);
+                break;
+            default:
+                _expr = new JintIdentifierExpression((Identifier) _leftNode);
+                break;
         }
 
         _body = new ProbablyBlockStatement(_forBody);
@@ -163,7 +168,7 @@ internal sealed class JintForInForOfStatement : JintStatement<Statement>
         {
             while (true)
             {
-                Environment? iterationEnv = null;
+                DeclarativeEnvironment? iterationEnv = null;
                 if (!iteratorRecord.TryIteratorStep(out var nextResult))
                 {
                     close = true;
@@ -222,7 +227,7 @@ internal sealed class JintForInForOfStatement : JintStatement<Statement>
                         var reference = (Reference) lhsRef;
                         if (lhsKind == LhsKind.LexicalBinding || _leftNode.Type == NodeType.Identifier && !reference.IsUnresolvableReference)
                         {
-                            reference.InitializeReferencedBinding(nextValue);
+                            reference.InitializeReferencedBinding(nextValue, _disposeHint);
                         }
                         else
                         {
@@ -276,6 +281,7 @@ internal sealed class JintForInForOfStatement : JintStatement<Statement>
                 }
 
                 var result = stmt.Execute(context);
+                result = iterationEnv?.DisposeResources(result) ?? result;
                 engine.UpdateLexicalEnvironment(oldEnv);
 
                 if (!result.Value.IsEmpty)
@@ -374,4 +380,4 @@ internal sealed class JintForInForOfStatement : JintStatement<Statement>
         Iterate,
         AsyncIterate
     }
-}
+}

+ 6 - 4
Jint/Runtime/Interpreter/Statements/JintForStatement.cs

@@ -27,7 +27,6 @@ internal sealed class JintForStatement : JintStatement<ForStatement>
 
     protected override void Initialize(EvaluationContext context)
     {
-        var engine = context.Engine;
         _body = new ProbablyBlockStatement(_statement.Body);
 
         if (_statement.Init != null)
@@ -63,7 +62,7 @@ internal sealed class JintForStatement : JintStatement<ForStatement>
     protected override Completion ExecuteInternal(EvaluationContext context)
     {
         Environment? oldEnv = null;
-        Environment? loopEnv = null;
+        DeclarativeEnvironment? loopEnv = null;
         var engine = context.Engine;
         if (_boundNames != null)
         {
@@ -87,6 +86,7 @@ internal sealed class JintForStatement : JintStatement<ForStatement>
             engine.UpdateLexicalEnvironment(loopEnv);
         }
 
+        var completion = Completion.Empty();
         try
         {
             if (_initExpression != null)
@@ -98,12 +98,14 @@ internal sealed class JintForStatement : JintStatement<ForStatement>
                 _initStatement?.Execute(context);
             }
 
-            return ForBodyEvaluation(context);
+            completion = ForBodyEvaluation(context);
+            return completion;
         }
         finally
         {
             if (oldEnv is not null)
             {
+                loopEnv!.DisposeResources(completion);
                 engine.UpdateLexicalEnvironment(oldEnv);
             }
         }
@@ -177,4 +179,4 @@ internal sealed class JintForStatement : JintStatement<ForStatement>
 
         engine.UpdateLexicalEnvironment(thisIterationEnv);
     }
-}
+}

+ 2 - 2
Jint/Runtime/Interpreter/Statements/JintVariableDeclaration.cs

@@ -76,7 +76,7 @@ internal sealed class JintVariableDeclaration : JintStatement<VariableDeclaratio
                     }
                 }
 
-                lhs.InitializeReferencedBinding(value);
+                lhs.InitializeReferencedBinding(value, _statement.Kind.GetDisposeHint());
                 engine._referencePool.Return(lhs);
             }
             else if (declaration.Init != null)
@@ -122,4 +122,4 @@ internal sealed class JintVariableDeclaration : JintStatement<VariableDeclaratio
 
         return Completion.Empty();
     }
-}
+}

+ 1 - 1
Jint/Runtime/Modules/BuilderModule.cs

@@ -34,7 +34,7 @@ internal sealed class BuilderModule : SourceTextModule
             for (var i = 0; i < _exportBuilderDeclarations.Count; i++)
             {
                 var d = _exportBuilderDeclarations[i];
-                _environment.CreateImmutableBindingAndInitialize(d.Key, true, d.Value);
+                _environment.CreateImmutableBindingAndInitialize(d.Key, true, d.Value, DisposeHint.Normal);
                 _localExportEntries.Add(new ExportEntry(d.Key, null, null, d.Key));
             }
 

+ 12 - 6
Jint/Runtime/Modules/SourceTextModule.cs

@@ -222,7 +222,7 @@ internal class SourceTextModule : CyclicModule
                 {
                     var ns = GetModuleNamespace(importedModule);
                     env.CreateImmutableBinding(ie.LocalName, strict: true);
-                    env.InitializeBinding(ie.LocalName, ns);
+                    env.InitializeBinding(ie.LocalName, ns, DisposeHint.Normal);
                 }
                 else
                 {
@@ -236,7 +236,7 @@ internal class SourceTextModule : CyclicModule
                     {
                         var ns = GetModuleNamespace(resolution.Module);
                         env.CreateImmutableBinding(ie.LocalName, strict: true);
-                        env.InitializeBinding(ie.LocalName, ns);
+                        env.InitializeBinding(ie.LocalName, ns, DisposeHint.Normal);
                     }
                     else
                     {
@@ -269,7 +269,7 @@ internal class SourceTextModule : CyclicModule
                     if (declaredVarNames.Add(dn))
                     {
                         env.CreateMutableBinding(dn);
-                        env.InitializeBinding(dn, Undefined);
+                        env.InitializeBinding(dn, Undefined, d.Kind.GetDisposeHint());
                     }
                 }
             }
@@ -310,7 +310,7 @@ internal class SourceTextModule : CyclicModule
                 {
                     fo.SetFunctionName("default");
                 }
-                env.InitializeBinding(fn, fo);
+                env.InitializeBinding(fn, fo, DisposeHint.Normal);
             }
         }
 
@@ -327,18 +327,24 @@ internal class SourceTextModule : CyclicModule
         {
             using (new StrictModeScope(strict: true, force: true))
             {
+                var result = Completion.Empty();
                 _engine.EnterExecutionContext(moduleContext);
                 try
                 {
                     var statementList = new JintStatementList(statement: null, _source.Body);
+
+                    //Create new evaluation context when called from e.g. module tests
                     var context = _engine._activeEvaluationContext ?? new EvaluationContext(_engine);
-                    var result = statementList.Execute(context); //Create new evaluation context when called from e.g. module tests
-                    return result;
+
+                    result = statementList.Execute(context);
                 }
                 finally
                 {
+                    result = _environment.DisposeResources(result);
                     _engine.LeaveExecutionContext();
                 }
+
+                return result;
             }
         }
         else

+ 1 - 1
Jint/Runtime/Modules/SyntheticModule.cs

@@ -67,7 +67,7 @@ internal sealed class SyntheticModule : Module
         foreach (var exportName in _exportNames)
         {
             _environment.CreateMutableBinding(exportName, canBeDeleted: false);
-            _environment.InitializeBinding(exportName, Undefined);
+            _environment.InitializeBinding(exportName, Undefined, DisposeHint.Normal);
         }
         return index;
     }

+ 9 - 2
Jint/Runtime/Reference.cs

@@ -104,9 +104,9 @@ public sealed class Reference
         }
     }
 
-    internal void InitializeReferencedBinding(JsValue value)
+    internal void InitializeReferencedBinding(JsValue value, DisposeHint hint)
     {
-        ((Environment) _base).InitializeBinding(TypeConverter.ToString(_referencedName), value);
+        ((Environment) _base).InitializeBinding(TypeConverter.ToString(_referencedName), value, hint);
     }
 
     internal void EvaluateAndCachePropertyKey()
@@ -117,3 +117,10 @@ public sealed class Reference
         }
     }
 }
+
+internal enum DisposeHint
+{
+    Normal,
+    Sync,
+    Async,
+}

+ 1 - 0
README.md

@@ -143,6 +143,7 @@ and many more.
 #### ECMAScript Stage 3 or earlier (no version yet)
 
 - ✔ `Error.isError`
+- ✔ Explicit Resource Management (`using` and `await using`)
 - ✔ `Math.sumPrecise`
 - ✔ `ShadowRealm`
 - ✔ `Uint8Array` to/from base64