Pārlūkot izejas kodu

Implement async/await (#1323)

Marko Lahma 3 gadi atpakaļ
vecāks
revīzija
ffa4260777

+ 9 - 13
Jint.Tests.Test262/Test262Harness.settings.json

@@ -5,7 +5,6 @@
   "Namespace": "Jint.Tests.Test262",
   "Parallel": true,
   "ExcludedFeatures": [
-    "async-functions",
     "async-iteration",
     "Atomics",
     "class-fields-private",
@@ -17,6 +16,7 @@
     "class-static-methods-private",
     "decorators",
     "FinalizationRegistry",
+    "FinalizationRegistry.prototype.cleanupSome",
     "generators",
     "import-assertions",
     "regexp-duplicate-named-groups",
@@ -31,7 +31,6 @@
     "u180e"
   ],
   "ExcludedFlags": [
-    "async"
   ],
   "ExcludedDirectories": [
     "annexB",
@@ -95,9 +94,6 @@
     // Windows line ending differences
     "built-ins/String/raw/special-characters.js",
 
-    // Async flag is missing from test
-    "language/expressions/object/method-definition/object-method-returns-promise.js",
-
     // parsing of large/small years not implemented in .NET (-271821, +271821)
     "built-ins/Date/parse/time-value-maximum-range.js",
 
@@ -140,6 +136,11 @@
 
     // generators not implemented
     "built-ins/Object/prototype/toString/proxy-function.js",
+    "built-ins/ShadowRealm/prototype/evaluate/wrapped-function-from-return-values-share-no-identity.js",
+    "built-ins/ShadowRealm/prototype/evaluate/wrapped-functions-share-no-properties-extended.js",
+    "language/expressions/async-generator/name.js",
+    "language/expressions/dynamic-import/assignment-expression/yield-assign-expr.js",
+    "language/expressions/dynamic-import/assignment-expression/yield-expr.js",
     "language/statements/class/subclass/builtin-objects/GeneratorFunction/*.js",
     "language/**/*-yield-*.js",
 
@@ -153,9 +154,12 @@
 
     "language/literals/regexp/u-surrogate-pairs-atom-escape-decimal.js",
     "language/literals/regexp/u-unicode-esc.js",
+    "language/statements/class/definition/methods-async-super-call-param.js",
+    "built-ins/AsyncFunction/AsyncFunction-construct.js\n",
     "built-ins/String/prototype/split/separator-regexp.js",
 
 
+    "language/expressions/object/method-definition/async-super-call-param.js",
     "language/expressions/object/method-definition/name-super-prop-param.js",
     "language/expressions/optional-chaining/member-expression.js",
     "language/statements/for-of/dstr-obj-id-init-let.js",
@@ -174,14 +178,6 @@
     // Esprima has parsing problems with weirdish unicode identifiers
     "language/identifiers/*-unicode-*.js",
 
-    // async not implemented
-    "built-ins/AsyncFunction/*.js",
-    "language/statements/async-function/*.js",
-    "language/expressions/async-function/*.js",
-    "language/expressions/async-generator/*.js",
-    "built-ins/ShadowRealm/prototype/evaluate/wrapped-functions-share-no-properties-extended.js",
-    "built-ins/ShadowRealm/prototype/evaluate/wrapped-function-from-return-values-share-no-identity.js",
-
     // special casing data
     "built-ins/**/special_casing*.js",
 

+ 5 - 0
Jint.Tests.Test262/Test262Test.cs

@@ -75,6 +75,11 @@ public abstract partial class Test262Test
             engine.Execute(State.Sources[include]);
         }
 
+        if (file.Flags.IndexOf("async") != -1)
+        {
+            engine.Execute(State.Sources["doneprintHandle.js"]);
+        }
+
         return engine;
     }
 

+ 2 - 2
Jint.Tests/Runtime/EngineLimitTests.cs

@@ -10,9 +10,9 @@ public class EngineLimitTests
     public void ShouldAllowReasonableCallStackDepth()
     {
 #if RELEASE
-        const int FunctionNestingCount = 1000;
+        const int FunctionNestingCount = 960;
 #else
-        const int FunctionNestingCount = 570;
+        const int FunctionNestingCount = 520;
 #endif
 
         // generate call tree

+ 42 - 0
Jint/Native/AsyncFunction/AsyncFunctionConstructor.cs

@@ -0,0 +1,42 @@
+using Jint.Native.Function;
+using Jint.Native.Object;
+using Jint.Runtime;
+using Jint.Runtime.Descriptors;
+
+namespace Jint.Native.AsyncFunction;
+
+/// <summary>
+/// https://tc39.es/ecma262/#sec-async-function-constructor
+/// </summary>
+internal sealed class AsyncFunctionConstructor : FunctionInstance, IConstructor
+{
+    private static readonly JsString _functionName = new("AsyncFunction");
+
+    public AsyncFunctionConstructor(Engine engine, Realm realm, FunctionConstructor functionConstructor) : base(engine, realm, _functionName)
+    {
+        PrototypeObject = new AsyncFunctionPrototype(engine, realm, this, functionConstructor.PrototypeObject);
+        _prototype = functionConstructor;
+        _prototypeDescriptor = new PropertyDescriptor(PrototypeObject, PropertyFlag.AllForbidden);
+        _length = new PropertyDescriptor(JsNumber.PositiveOne, PropertyFlag.Configurable);
+    }
+
+    public AsyncFunctionPrototype PrototypeObject { get; }
+
+    protected internal override JsValue Call(JsValue thisObject, JsValue[] arguments)
+    {
+        return Construct(arguments, thisObject);
+    }
+
+    ObjectInstance IConstructor.Construct(JsValue[] arguments, JsValue newTarget) => Construct(arguments, newTarget);
+
+    private ObjectInstance Construct(JsValue[] arguments, JsValue newTarget)
+    {
+        var function = CreateDynamicFunction(
+            this,
+            newTarget,
+            FunctionKind.Async,
+            arguments);
+
+        return function;
+    }
+}

+ 41 - 0
Jint/Native/AsyncFunction/AsyncFunctionPrototype.cs

@@ -0,0 +1,41 @@
+using Jint.Collections;
+using Jint.Native.Function;
+using Jint.Native.Symbol;
+using Jint.Runtime;
+using Jint.Runtime.Descriptors;
+
+namespace Jint.Native.AsyncFunction;
+
+/// <summary>
+/// https://tc39.es/ecma262/#sec-async-function-prototype-properties
+/// </summary>
+internal sealed class AsyncFunctionPrototype : Prototype
+{
+    private readonly AsyncFunctionConstructor _constructor;
+
+    public AsyncFunctionPrototype(
+        Engine engine,
+        Realm realm,
+        AsyncFunctionConstructor constructor,
+        FunctionPrototype objectPrototype) : base(engine, realm)
+    {
+        _constructor = constructor;
+        _prototype = objectPrototype;
+    }
+
+    protected override void Initialize()
+    {
+        var properties = new PropertyDictionary(1, checkExistingKeys: false)
+        {
+            [KnownKeys.Constructor] = new(_constructor, PropertyFlag.NonEnumerable),
+        };
+        SetProperties(properties);
+
+        var symbols = new SymbolDictionary(1)
+        {
+            [GlobalSymbolRegistry.ToStringTag] = new("AsyncFunction", PropertyFlag.Configurable)
+        };
+        SetSymbols(symbols);
+    }
+
+}

+ 2 - 2
Jint/Native/Error/ErrorConstructor.cs

@@ -31,9 +31,9 @@ namespace Jint.Native.Error
             return Construct(arguments, this);
         }
 
-        public ObjectInstance Construct(JsValue[] arguments)
+        public ObjectInstance Construct(string? message = null)
         {
-            return Construct(arguments, this);
+            return Construct(message != null ? new JsValue[]{ message } : System.Array.Empty<JsValue>(), this);
         }
 
         ObjectInstance IConstructor.Construct(JsValue[] arguments, JsValue newTarget) => Construct(arguments, newTarget);

+ 30 - 189
Jint/Native/Function/FunctionConstructor.cs

@@ -1,5 +1,3 @@
-using Esprima;
-using Esprima.Ast;
 using Jint.Native.Object;
 using Jint.Runtime;
 using Jint.Runtime.Descriptors;
@@ -14,9 +12,6 @@ namespace Jint.Native.Function
     public sealed class FunctionConstructor : FunctionInstance, IConstructor
     {
         private static readonly JsString _functionName = new JsString("Function");
-        private static readonly JsString _functionNameAnonymous = new JsString("anonymous");
-
-        private readonly JavaScriptParser _parser = new(new ParserOptions { Tolerant = false });
 
         internal FunctionConstructor(
             Engine engine,
@@ -51,198 +46,44 @@ namespace Jint.Native.Function
         }
 
         /// <summary>
-        /// https://tc39.es/ecma262/#sec-createdynamicfunction
+        /// https://tc39.es/ecma262/#sec-runtime-semantics-instantiatefunctionobject
         /// </summary>
-        internal FunctionInstance CreateDynamicFunction(
-            ObjectInstance constructor,
-            JsValue newTarget,
-            FunctionKind kind,
-            JsValue[] args)
+        internal FunctionInstance InstantiateFunctionObject(
+            JintFunctionDefinition functionDeclaration,
+            EnvironmentRecord scope,
+            PrivateEnvironmentRecord? privateScope)
         {
-            // TODO var callerContext = _engine.GetExecutionContext(1);
-            var callerContext = _engine.ExecutionContext;
-            var callerRealm = callerContext.Realm;
-            var calleeRealm = _engine.ExecutionContext.Realm;
-
-            _engine._host.EnsureCanCompileStrings(callerRealm, calleeRealm);
-
-            if (newTarget.IsUndefined())
-            {
-                newTarget = constructor;
-            }
-
-            Func<Intrinsics, ObjectInstance>? fallbackProto = null;
-            switch (kind)
-            {
-                case FunctionKind.Normal:
-                    fallbackProto = static intrinsics => intrinsics.Function.PrototypeObject;
-                    break;
-                case FunctionKind.Generator:
-                case FunctionKind.AsyncGenerator:
-                case FunctionKind.Async:
-                default:
-                    ExceptionHelper.ThrowArgumentOutOfRangeException(nameof(kind), kind.ToString());
-                    break;
-            }
-
-            var argCount = args.Length;
-            var p = "";
-            var body = "";
-
-            if (argCount == 1)
-            {
-                body = TypeConverter.ToString(args[0]);
-            }
-            else if (argCount > 1)
-            {
-                var firstArg = args[0];
-                p = TypeConverter.ToString(firstArg);
-                for (var k = 1; k < argCount - 1; k++)
-                {
-                    var nextArg = args[k];
-                    p += "," + TypeConverter.ToString(nextArg);
-                }
-
-                body = TypeConverter.ToString(args[argCount - 1]);
-            }
-
-            IFunction? function = null;
-            try
-            {
-                string? functionExpression = null;
-                if (argCount == 0)
-                {
-                    switch (kind)
-                    {
-                        case FunctionKind.Normal:
-                            functionExpression = "function f(){}";
-                            break;
-                        case FunctionKind.Generator:
-                            functionExpression = "function* f(){}";
-                            break;
-                        case FunctionKind.Async:
-                            ExceptionHelper.ThrowNotImplementedException("Async functions not implemented");
-                            break;
-                        case FunctionKind.AsyncGenerator:
-                            ExceptionHelper.ThrowNotImplementedException("Async generators not implemented");
-                            break;
-                        default:
-                            ExceptionHelper.ThrowArgumentOutOfRangeException(nameof(kind), kind.ToString());
-                            break;
-                    }
-                }
-                else
-                {
-                    switch (kind)
-                    {
-                        case FunctionKind.Normal:
-                            functionExpression = "function f(";
-                            break;
-                        case FunctionKind.Generator:
-                            functionExpression = "function* f(";
-                            break;
-                        case FunctionKind.Async:
-                            ExceptionHelper.ThrowNotImplementedException("Async functions not implemented");
-                            break;
-                        case FunctionKind.AsyncGenerator:
-                            ExceptionHelper.ThrowNotImplementedException("Async generators not implemented");
-                            break;
-                        default:
-                            ExceptionHelper.ThrowArgumentOutOfRangeException(nameof(kind), kind.ToString());
-                            break;
-                    }
-
-                    if (p.IndexOf('/') != -1)
-                    {
-                        // ensure comments don't screw up things
-                        functionExpression += "\n" + p + "\n";
-                    }
-                    else
-                    {
-                        functionExpression += p;
-                    }
-
-                    functionExpression += ")";
-
-                    if (body.IndexOf('/') != -1)
-                    {
-                        // ensure comments don't screw up things
-                        functionExpression += "{\n" + body + "\n}";
-                    }
-                    else
-                    {
-                        functionExpression += "{" + body + "}";
-                    }
-                }
-
-                function = (IFunction) _parser.ParseScript(functionExpression).Body[0];
-            }
-            catch (ParserException ex)
-            {
-                ExceptionHelper.ThrowSyntaxError(_engine.ExecutionContext.Realm, ex.Message);
-            }
-
-            var proto = GetPrototypeFromConstructor(newTarget, fallbackProto);
-            var realmF = _realm;
-            var scope = realmF.GlobalEnv;
-            PrivateEnvironmentRecord? privateScope = null;
-
-            var definition = new JintFunctionDefinition(function);
-            FunctionInstance F = OrdinaryFunctionCreate(proto, definition, function.Strict ? FunctionThisMode.Strict : FunctionThisMode.Global, scope, privateScope);
-            F.SetFunctionName(_functionNameAnonymous, force: true);
-
-            if (kind == FunctionKind.Generator)
+            var function = functionDeclaration.Function;
+            if (!function.Generator)
             {
-                ExceptionHelper.ThrowNotImplementedException("generators not implemented");
+                return function.Async
+                    ? InstantiateAsyncFunctionObject(functionDeclaration, scope, privateScope)
+                    : InstantiateOrdinaryFunctionObject(functionDeclaration, scope, privateScope);
             }
-            else if (kind == FunctionKind.AsyncGenerator)
+            else
             {
-                // TODO
-                // Let prototype be ! OrdinaryObjectCreate(%AsyncGeneratorFunction.prototype.prototype%).
-                // Perform DefinePropertyOrThrow(F, "prototype", PropertyDescriptor { [[Value]]: prototype, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }).
-                ExceptionHelper.ThrowNotImplementedException("async generators not implemented");
+                return InstantiateGeneratorFunctionObject(functionDeclaration, scope, privateScope);
             }
-            else if (kind == FunctionKind.Normal)
-            {
-                F.MakeConstructor();
-            }
-
-            return F;
-        }
-
-        /// <summary>
-        /// https://tc39.es/ecma262/#sec-ordinaryfunctioncreate
-        /// </summary>
-        internal ScriptFunctionInstance OrdinaryFunctionCreate(
-            ObjectInstance functionPrototype,
-            JintFunctionDefinition function,
-            FunctionThisMode thisMode,
-            EnvironmentRecord scope,
-            PrivateEnvironmentRecord? privateScope)
-        {
-            return new ScriptFunctionInstance(
-                _engine,
-                function,
-                scope,
-                thisMode,
-                functionPrototype)
-            {
-                _privateEnvironment = privateScope,
-                _realm = _realm
-            };
         }
 
         /// <summary>
-        /// https://tc39.es/ecma262/#sec-runtime-semantics-instantiatefunctionobject
+        /// https://tc39.es/ecma262/#sec-runtime-semantics-instantiateasyncfunctionobject
         /// </summary>
-        internal FunctionInstance InstantiateFunctionObject(
+        private FunctionInstance InstantiateAsyncFunctionObject(
             JintFunctionDefinition functionDeclaration,
-            EnvironmentRecord scope,
-            PrivateEnvironmentRecord? privateScope)
+            EnvironmentRecord env,
+            PrivateEnvironmentRecord? privateEnv)
         {
-            return !functionDeclaration.Function.Generator
-                ? InstantiateOrdinaryFunctionObject(functionDeclaration, scope, privateScope)
-                : InstantiateGeneratorFunctionObject(functionDeclaration, scope, privateScope);
+            var F = OrdinaryFunctionCreate(
+                _realm.Intrinsics.AsyncFunction.PrototypeObject,
+                functionDeclaration,
+                functionDeclaration.ThisMode,
+                env,
+                privateEnv);
+
+            F.SetFunctionName(functionDeclaration.Name ?? "default");
+
+            return F;
         }
 
         /// <summary>
@@ -250,15 +91,15 @@ namespace Jint.Native.Function
         /// </summary>
         private FunctionInstance InstantiateOrdinaryFunctionObject(
             JintFunctionDefinition functionDeclaration,
-            EnvironmentRecord scope,
-            PrivateEnvironmentRecord? privateScope)
+            EnvironmentRecord env,
+            PrivateEnvironmentRecord? privateEnv)
         {
             var F = OrdinaryFunctionCreate(
                 _realm.Intrinsics.Function.PrototypeObject,
                 functionDeclaration,
                 functionDeclaration.ThisMode,
-                scope,
-                privateScope);
+                env,
+                privateEnv);
 
             var name = functionDeclaration.Name ?? "default";
             F.SetFunctionName(name);

+ 194 - 0
Jint/Native/Function/FunctionInstance.Dynamic.cs

@@ -0,0 +1,194 @@
+using Esprima;
+using Esprima.Ast;
+using Jint.Native.Object;
+using Jint.Runtime;
+using Jint.Runtime.Environments;
+using Jint.Runtime.Interpreter;
+
+namespace Jint.Native.Function;
+
+public partial class FunctionInstance
+{
+    private static readonly JsString _functionNameAnonymous = new JsString("anonymous");
+
+    /// <summary>
+    /// https://tc39.es/ecma262/#sec-createdynamicfunction
+    /// </summary>
+    internal FunctionInstance CreateDynamicFunction(
+        ObjectInstance constructor,
+        JsValue newTarget,
+        FunctionKind kind,
+        JsValue[] args)
+    {
+        // TODO var callerContext = _engine.GetExecutionContext(1);
+        var callerContext = _engine.ExecutionContext;
+        var callerRealm = callerContext.Realm;
+        var calleeRealm = _engine.ExecutionContext.Realm;
+
+        _engine._host.EnsureCanCompileStrings(callerRealm, calleeRealm);
+
+        if (newTarget.IsUndefined())
+        {
+            newTarget = constructor;
+        }
+
+        Func<Intrinsics, ObjectInstance>? fallbackProto = null;
+        switch (kind)
+        {
+            case FunctionKind.Normal:
+                fallbackProto = static intrinsics => intrinsics.Function.PrototypeObject;
+                break;
+            case FunctionKind.Async:
+                fallbackProto = static intrinsics => intrinsics.AsyncFunction.PrototypeObject;
+                break;
+            case FunctionKind.Generator:
+            case FunctionKind.AsyncGenerator:
+            default:
+                ExceptionHelper.ThrowArgumentOutOfRangeException(nameof(kind), kind.ToString());
+                break;
+        }
+
+        var argCount = args.Length;
+        var p = "";
+        var body = "";
+
+        if (argCount == 1)
+        {
+            body = TypeConverter.ToString(args[0]);
+        }
+        else if (argCount > 1)
+        {
+            var firstArg = args[0];
+            p = TypeConverter.ToString(firstArg);
+            for (var k = 1; k < argCount - 1; k++)
+            {
+                var nextArg = args[k];
+                p += "," + TypeConverter.ToString(nextArg);
+            }
+
+            body = TypeConverter.ToString(args[argCount - 1]);
+        }
+
+        IFunction? function = null;
+        try
+        {
+            string? functionExpression = null;
+            if (argCount == 0)
+            {
+                switch (kind)
+                {
+                    case FunctionKind.Normal:
+                        functionExpression = "function f(){}";
+                        break;
+                    case FunctionKind.Generator:
+                        functionExpression = "function* f(){}";
+                        break;
+                    case FunctionKind.Async:
+                        functionExpression = "async function f(){}";
+                        break;
+                    case FunctionKind.AsyncGenerator:
+                        ExceptionHelper.ThrowNotImplementedException("Async generators not implemented");
+                        break;
+                    default:
+                        ExceptionHelper.ThrowArgumentOutOfRangeException(nameof(kind), kind.ToString());
+                        break;
+                }
+            }
+            else
+            {
+                switch (kind)
+                {
+                    case FunctionKind.Normal:
+                        functionExpression = "function f(";
+                        break;
+                    case FunctionKind.Async:
+                        functionExpression = "async function f(";
+                        break;
+                    case FunctionKind.Generator:
+                        functionExpression = "function* f(";
+                        break;
+                    case FunctionKind.AsyncGenerator:
+                        ExceptionHelper.ThrowNotImplementedException("Async generators not implemented");
+                        break;
+                    default:
+                        ExceptionHelper.ThrowArgumentOutOfRangeException(nameof(kind), kind.ToString());
+                        break;
+                }
+
+                if (p.IndexOf('/') != -1)
+                {
+                    // ensure comments don't screw up things
+                    functionExpression += "\n" + p + "\n";
+                }
+                else
+                {
+                    functionExpression += p;
+                }
+
+                functionExpression += ")";
+
+                if (body.IndexOf('/') != -1)
+                {
+                    // ensure comments don't screw up things
+                    functionExpression += "{\n" + body + "\n}";
+                }
+                else
+                {
+                    functionExpression += "{" + body + "}";
+                }
+            }
+
+            JavaScriptParser parser = new(new ParserOptions { Tolerant = false });
+            function = (IFunction) parser.ParseScript(functionExpression).Body[0];
+        }
+        catch (ParserException ex)
+        {
+            ExceptionHelper.ThrowSyntaxError(_engine.ExecutionContext.Realm, ex.Message);
+        }
+
+        var proto = GetPrototypeFromConstructor(newTarget, fallbackProto);
+        var realmF = _realm;
+        var scope = realmF.GlobalEnv;
+        PrivateEnvironmentRecord? privateScope = null;
+
+        var definition = new JintFunctionDefinition(function);
+        FunctionInstance F = OrdinaryFunctionCreate(proto, definition, function.Strict ? FunctionThisMode.Strict : FunctionThisMode.Global, scope, privateScope);
+        F.SetFunctionName(_functionNameAnonymous, force: true);
+
+        if (kind == FunctionKind.Generator)
+        {
+            ExceptionHelper.ThrowNotImplementedException("generators not implemented");
+        }
+        else if (kind == FunctionKind.AsyncGenerator)
+        {
+            // TODO
+            // Let prototype be ! OrdinaryObjectCreate(%AsyncGeneratorFunction.prototype.prototype%).
+            // Perform DefinePropertyOrThrow(F, "prototype", PropertyDescriptor { [[Value]]: prototype, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }).
+            ExceptionHelper.ThrowNotImplementedException("async generators not implemented");
+        }
+        else if (kind == FunctionKind.Normal)
+        {
+            F.MakeConstructor();
+        }
+
+        return F;
+    }
+
+    /// <summary>
+    /// https://tc39.es/ecma262/#sec-ordinaryfunctioncreate
+    /// </summary>
+    internal ScriptFunctionInstance OrdinaryFunctionCreate(
+        ObjectInstance functionPrototype,
+        JintFunctionDefinition function,
+        FunctionThisMode thisMode,
+        EnvironmentRecord scope,
+        PrivateEnvironmentRecord? privateScope)
+    {
+        return new ScriptFunctionInstance(
+            _engine,
+            function,
+            scope,
+            thisMode,
+            functionPrototype) { _privateEnvironment = privateScope, _realm = _realm };
+    }
+}

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

@@ -9,7 +9,7 @@ using Jint.Runtime.Interpreter;
 
 namespace Jint.Native.Function
 {
-    public abstract class FunctionInstance : ObjectInstance, ICallable
+    public abstract partial class FunctionInstance : ObjectInstance, ICallable
     {
         protected PropertyDescriptor? _prototypeDescriptor;
 

+ 16 - 4
Jint/Native/Function/ScriptFunctionInstance.cs

@@ -105,10 +105,22 @@ namespace Jint.Native.Function
             }
         }
 
-        internal override bool IsConstructor =>
-            (_homeObject.IsUndefined() || _isClassConstructor)
-            && _functionDefinition?.Function is not ArrowFunctionExpression
-            && _functionDefinition?.Function?.Generator != true;
+        internal override bool IsConstructor
+        {
+            get
+            {
+                if (!_homeObject.IsUndefined() && !_isClassConstructor)
+                {
+                    return false;
+                }
+
+                var function = _functionDefinition?.Function;
+                return function is not null
+                       && function is not ArrowFunctionExpression
+                       && !function.Generator
+                       && !function.Async;
+            }
+        }
 
         /// <summary>
         /// https://tc39.es/ecma262/#sec-ecmascript-function-objects-construct-argumentslist-newtarget

+ 26 - 9
Jint/Native/Promise/PromiseConstructor.cs

@@ -66,6 +66,9 @@ namespace Jint.Native.Promise
             return null;
         }
 
+        /// <summary>
+        /// https://tc39.es/ecma262/#sec-promise-executor
+        /// </summary>
         ObjectInstance IConstructor.Construct(JsValue[] arguments, JsValue newTarget)
         {
             if (newTarget.IsUndefined())
@@ -73,21 +76,28 @@ namespace Jint.Native.Promise
                 ExceptionHelper.ThrowTypeError(_realm, "Constructor Promise requires 'new'");
             }
 
-            var promiseExecutor = arguments.At(0) as ICallable;
-            if (promiseExecutor is null)
+            if (arguments.At(0) is not ICallable executor)
             {
                 ExceptionHelper.ThrowTypeError(_realm, $"Promise executor {(arguments.At(0))} is not a function");
+                return null;
             }
 
-            var instance = OrdinaryCreateFromConstructor(
+            var promise = OrdinaryCreateFromConstructor(
                 newTarget,
                 static intrinsics => intrinsics.Promise.PrototypeObject,
                 static (Engine engine, Realm _, object? _) => new PromiseInstance(engine));
 
-            var (resolve, reject) = instance.CreateResolvingFunctions();
-            promiseExecutor.Call(Undefined, new JsValue[] { resolve, reject });
+            var (resolve, reject) = promise.CreateResolvingFunctions();
+            try
+            {
+                executor.Call(Undefined, new JsValue[] { resolve, reject });
+            }
+            catch (JavaScriptException e)
+            {
+                reject.Call(JsValue.Undefined, new[] { e.Error });
+            }
 
-            return instance;
+            return promise;
         }
 
         // The abstract operation PromiseResolve takes arguments C (a constructor) and x (an ECMAScript language value).
@@ -421,12 +431,14 @@ namespace Jint.Native.Promise
         private JsValue Any(JsValue thisObj, JsValue[] arguments)
         {
             if (!TryGetPromiseCapabilityAndIterator(thisObj, arguments, "Promise.any", out var capability, out var promiseResolve, out var iterator))
+            {
                 return capability.PromiseInstance;
+            }
 
             var (resultingPromise, resolve, reject, resolveObj, _) = capability;
 
             var errors = new List<JsValue>();
-            bool doneIterating = false;
+            var doneIterating = false;
 
             void RejectIfAllRejected()
             {
@@ -446,14 +458,15 @@ namespace Jint.Native.Promise
             // https://tc39.es/ecma262/#sec-performpromiseany
             try
             {
-                int index = 0;
+                var index = 0;
 
                 do
                 {
+                    ObjectInstance? nextItem = null;
                     JsValue value;
                     try
                     {
-                        if (!iterator.TryIteratorStep(out var nextItem))
+                        if (!iterator.TryIteratorStep(out nextItem))
                         {
                             doneIterating = true;
                             RejectIfAllRejected();
@@ -464,6 +477,10 @@ namespace Jint.Native.Promise
                     }
                     catch (JavaScriptException e)
                     {
+                        if (nextItem?.Get("done")?.AsBoolean() == false)
+                        {
+                            throw;
+                        }
                         errors.Add(e.Error);
                         continue;
                     }

+ 2 - 5
Jint/Native/Promise/PromiseInstance.cs

@@ -19,14 +19,12 @@ namespace Jint.Native.Promise
         Reject
     }
 
-
     internal sealed record PromiseReaction(
         ReactionType Type,
         PromiseCapability Capability,
         JsValue Handler
     );
 
-
     internal sealed record ResolvingFunctions(
         FunctionInstance Resolve,
         FunctionInstance Reject
@@ -91,10 +89,9 @@ namespace Jint.Native.Promise
 
             var result = arguments.At(0);
 
-            if (result == this)
+            if (ReferenceEquals(result, this))
             {
-                ExceptionHelper.ThrowTypeError(_engine.Realm, "Cannot resolve Promise with itself");
-                return Undefined;
+                return RejectPromise(_engine.Realm.Intrinsics.TypeError.Construct("Cannot resolve Promise with itself"));
             }
 
             if (result is not ObjectInstance resultObj)

+ 3 - 4
Jint/Native/Promise/PromiseOperations.cs

@@ -78,8 +78,7 @@ namespace Jint.Native.Promise
         //  d. Return Completion(thenCallResult).
         // .....Realm stuff....
         // 6. Return the Record { [[Job]]: job, [[Realm]]: thenRealm }.
-        internal static Action NewPromiseResolveThenableJob(PromiseInstance promise, ObjectInstance thenable,
-            ICallable thenMethod)
+        internal static Action NewPromiseResolveThenableJob(PromiseInstance promise, ObjectInstance thenable, ICallable thenMethod)
         {
             return () =>
             {
@@ -87,11 +86,11 @@ namespace Jint.Native.Promise
 
                 try
                 {
-                    thenMethod.Call(thenable, new[] {resolve as JsValue, reject});
+                    thenMethod.Call(thenable, new[] { resolve as JsValue, reject });
                 }
                 catch (JavaScriptException e)
                 {
-                    reject.Call(JsValue.Undefined, new[] {e.Error});
+                    reject.Call(JsValue.Undefined, new[] { e.Error });
                 }
             };
         }

+ 36 - 0
Jint/Runtime/Interpreter/Expressions/JintAwaitExpression.cs

@@ -0,0 +1,36 @@
+using Esprima.Ast;
+
+namespace Jint.Runtime.Interpreter.Expressions;
+
+internal sealed class JintAwaitExpression : JintExpression
+{
+    private JintExpression _awaitExpression = null!;
+
+    public JintAwaitExpression(AwaitExpression expression) : base(expression)
+    {
+        _initialized = false;
+    }
+
+    protected override void Initialize(EvaluationContext context)
+    {
+        _awaitExpression = Build(((AwaitExpression) _expression).Argument);
+    }
+
+    protected override object EvaluateInternal(EvaluationContext context)
+    {
+        var engine = context.Engine;
+        var asyncContext = engine.ExecutionContext;
+
+        try
+        {
+            var value = _awaitExpression.GetValue(context);
+            engine.RunAvailableContinuations();
+            return value.UnwrapIfPromise();
+        }
+        catch (PromiseRejectedException e)
+        {
+            ExceptionHelper.ThrowJavaScriptException(engine, e.RejectedValue, _expression.Location);
+            return null;
+        }
+    }
+}

+ 1 - 0
Jint/Runtime/Interpreter/Expressions/JintExpression.cs

@@ -138,6 +138,7 @@ namespace Jint.Runtime.Interpreter.Expressions
                 Nodes.ChainExpression => ((ChainExpression) expression).Expression.Type == Nodes.CallExpression
                     ? new JintCallExpression((CallExpression) ((ChainExpression) expression).Expression)
                     : new JintMemberExpression((MemberExpression) ((ChainExpression) expression).Expression),
+                Nodes.AwaitExpression => new JintAwaitExpression((AwaitExpression) expression),
                 _ =>  null
             };
 

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

@@ -21,9 +21,17 @@ namespace Jint.Runtime.Interpreter.Expressions
 
         public override JsValue GetValue(EvaluationContext context)
         {
-            var closure = !_function.Function.Generator
-                ? InstantiateOrdinaryFunctionExpression(context, _function.Name!)
-                : InstantiateGeneratorFunctionExpression(context, _function.Name!);
+            ScriptFunctionInstance closure;
+            if (!_function.Function.Generator)
+            {
+                closure = _function.Function.Async
+                    ? InstantiateAsyncFunctionExpression(context, _function.Name)
+                    : InstantiateOrdinaryFunctionExpression(context, _function.Name);
+            }
+            else
+            {
+                closure = InstantiateGeneratorFunctionExpression(context, _function.Name);
+            }
 
             return closure;
         }
@@ -70,10 +78,49 @@ namespace Jint.Runtime.Interpreter.Expressions
             return closure;
         }
 
+        /// <summary>
+        /// https://tc39.es/ecma262/#sec-runtime-semantics-instantiateasyncfunctionexpression
+        /// </summary>
+        private ScriptFunctionInstance InstantiateAsyncFunctionExpression(EvaluationContext context, string? name = "")
+        {
+            var engine = context.Engine;
+            var runningExecutionContext = engine.ExecutionContext;
+            var scope = runningExecutionContext.LexicalEnvironment;
+
+            DeclarativeEnvironmentRecord? funcEnv = null;
+            if (!string.IsNullOrWhiteSpace(name))
+            {
+                funcEnv = JintEnvironment.NewDeclarativeEnvironment(engine, engine.ExecutionContext.LexicalEnvironment);
+                funcEnv.CreateImmutableBinding(name!, strict: false);
+            }
+
+            var privateScope = runningExecutionContext.PrivateEnvironment;
+
+            var thisMode = _function.Strict || engine._isStrict
+                ? FunctionThisMode.Strict
+                : FunctionThisMode.Global;
+
+            var intrinsics = engine.Realm.Intrinsics;
+            var closure = intrinsics.Function.OrdinaryFunctionCreate(
+                intrinsics.AsyncFunction.PrototypeObject,
+                _function,
+                thisMode,
+                funcEnv ?? scope,
+                privateScope
+            );
+
+            closure.SetFunctionName(name ?? "");
+
+            funcEnv?.InitializeBinding(name!, closure);
+
+            return closure;
+        }
+
+
         /// <summary>
         /// https://tc39.es/ecma262/#sec-runtime-semantics-instantiategeneratorfunctionexpression
         /// </summary>
-        private ScriptFunctionInstance InstantiateGeneratorFunctionExpression(EvaluationContext context, string name = "")
+        private ScriptFunctionInstance InstantiateGeneratorFunctionExpression(EvaluationContext context, string? name)
         {
             // TODO generators
             return InstantiateOrdinaryFunctionExpression(context, name);

+ 13 - 1
Jint/Runtime/Interpreter/Expressions/JintImportExpression.cs

@@ -1,4 +1,5 @@
 using Esprima.Ast;
+using Jint.Native;
 using Jint.Native.Promise;
 
 namespace Jint.Runtime.Interpreter.Expressions;
@@ -28,7 +29,18 @@ internal sealed class JintImportExpression : JintExpression
         var argRef = _importExpression.Evaluate(context);
         var specifier = context.Engine.GetValue(argRef); //.UnwrapIfPromise();
         var promiseCapability = PromiseConstructor.NewPromiseCapability(context.Engine, context.Engine.Realm.Intrinsics.Promise);
-        var specifierString = TypeConverter.ToString(specifier);
+
+        string specifierString;
+        try
+        {
+            specifierString = TypeConverter.ToString(specifier);
+        }
+        catch (JavaScriptException e)
+        {
+            promiseCapability.Reject.Call(JsValue.Undefined, new[] { e.Error });
+            return JsValue.Undefined;
+        }
+
         context.Engine._host.ImportModuleDynamically(referencingScriptOrModule, specifierString, promiseCapability);
         context.Engine.RunAvailableContinuations();
         return promiseCapability.PromiseInstance;

+ 90 - 6
Jint/Runtime/Interpreter/JintFunctionDefinition.cs

@@ -1,7 +1,10 @@
 using System.Runtime.CompilerServices;
 using Esprima.Ast;
 using Jint.Native;
+using Jint.Native.Argument;
 using Jint.Native.Function;
+using Jint.Native.Promise;
+using Jint.Runtime.Environments;
 using Jint.Runtime.Interpreter.Expressions;
 
 namespace Jint.Runtime.Interpreter;
@@ -34,32 +37,113 @@ internal sealed class JintFunctionDefinition
     internal Completion EvaluateBody(EvaluationContext context, FunctionInstance functionObject, JsValue[] argumentsList)
     {
         Completion result;
-        var argumentsInstance = context.Engine.FunctionDeclarationInstantiation(functionObject, argumentsList);
+        ArgumentsInstance? argumentsInstance = null;
         if (Function.Expression)
         {
             // https://tc39.es/ecma262/#sec-runtime-semantics-evaluateconcisebody
             _bodyExpression ??= JintExpression.Build((Expression) Function.Body);
-            var jsValue = _bodyExpression.GetValue(context).Clone();
-            result = new Completion(CompletionType.Return, jsValue, Function.Body);
+            if (Function.Async)
+            {
+                var promiseCapability = PromiseConstructor.NewPromiseCapability(context.Engine, context.Engine.Realm.Intrinsics.Promise);
+                AsyncFunctionStart(context, promiseCapability, context =>
+                {
+                    context.Engine.FunctionDeclarationInstantiation(functionObject, argumentsList);
+                    return new Completion(CompletionType.Normal, _bodyExpression.GetValue(context), _bodyExpression._expression);
+                });
+                result = new Completion(CompletionType.Return, promiseCapability.PromiseInstance, Function.Body);
+            }
+            else
+            {
+                argumentsInstance = context.Engine.FunctionDeclarationInstantiation(functionObject, argumentsList);
+                var jsValue = _bodyExpression.GetValue(context).Clone();
+                result = new Completion(CompletionType.Return, jsValue, Function.Body);
+            }
         }
         else if (Function.Generator)
         {
             // TODO generators
             // result = EvaluateGeneratorBody(functionObject, argumentsList);
+            argumentsInstance = context.Engine.FunctionDeclarationInstantiation(functionObject, argumentsList);
             _bodyStatementList ??= new JintStatementList(Function);
             result = _bodyStatementList.Execute(context);
         }
         else
         {
-            // https://tc39.es/ecma262/#sec-runtime-semantics-evaluatefunctionbody
-            _bodyStatementList ??= new JintStatementList(Function);
-            result = _bodyStatementList.Execute(context);
+            if (Function.Async)
+            {
+                var promiseCapability = PromiseConstructor.NewPromiseCapability(context.Engine, context.Engine.Realm.Intrinsics.Promise);
+                _bodyStatementList ??= new JintStatementList(Function);
+                AsyncFunctionStart(context, promiseCapability, context =>
+                {
+                     context.Engine.FunctionDeclarationInstantiation(functionObject, argumentsList);
+                     return _bodyStatementList.Execute(context);
+                });
+                result = new Completion(CompletionType.Return, promiseCapability.PromiseInstance, Function.Body);
+            }
+            else
+            {
+                // https://tc39.es/ecma262/#sec-runtime-semantics-evaluatefunctionbody
+                argumentsInstance = context.Engine.FunctionDeclarationInstantiation(functionObject, argumentsList);
+                _bodyStatementList ??= new JintStatementList(Function);
+                result = _bodyStatementList.Execute(context);
+            }
         }
 
         argumentsInstance?.FunctionWasCalled();
         return result;
     }
 
+    /// <summary>
+    /// https://tc39.es/ecma262/#sec-async-functions-abstract-operations-async-function-start
+    /// </summary>
+    private static void AsyncFunctionStart(EvaluationContext context, PromiseCapability promiseCapability, Func<EvaluationContext, Completion> asyncFunctionBody)
+    {
+        var runningContext = context.Engine.ExecutionContext;
+        var asyncContext = runningContext;
+        AsyncBlockStart(context, promiseCapability, asyncFunctionBody, asyncContext);
+    }
+
+    /// <summary>
+    /// https://tc39.es/ecma262/#sec-asyncblockstart
+    /// </summary>
+    private static void AsyncBlockStart(EvaluationContext context, PromiseCapability promiseCapability, Func<EvaluationContext, Completion> asyncBody, in ExecutionContext asyncContext)
+    {
+        var runningContext = context.Engine.ExecutionContext;
+        // Set the code evaluation state of asyncContext such that when evaluation is resumed for that execution contxt the following steps will be performed:
+
+        Completion result;
+        try
+        {
+            result = asyncBody(context);
+        }
+        catch (JavaScriptException e)
+        {
+            promiseCapability.Reject.Call(JsValue.Undefined, new[] { e.Error });
+            return;
+        }
+
+        if (result.Type == CompletionType.Normal)
+        {
+            promiseCapability.Resolve.Call(JsValue.Undefined, new[] { JsValue.Undefined });
+        }
+        else if (result.Type == CompletionType.Return)
+        {
+            promiseCapability.Resolve.Call(JsValue.Undefined, new[] { result.Value });
+        }
+        else
+        {
+            promiseCapability.Reject.Call(JsValue.Undefined, new[] { result.Value });
+        }
+
+        /*
+        4. Push asyncContext onto the execution context stack; asyncContext is now the running execution context.
+        5. Resume the suspended evaluation of asyncContext. Let result be the value returned by the resumed computation.
+        6. Assert: When we return here, asyncContext has already been removed from the execution context stack and runningContext is the currently running execution context.
+        7. Assert: result is a normal completion with a value of unused. The possible sources of this value are Await or, if the async function doesn't await anything, step 3.g above.
+        8. Return unused.
+        */
+    }
+
     /// <summary>
     /// https://tc39.es/ecma262/#sec-runtime-semantics-evaluategeneratorbody
     /// </summary>

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

@@ -140,7 +140,7 @@ namespace Jint.Runtime.Interpreter
 
         private static Completion CreateThrowCompletion(ErrorConstructor errorConstructor, Exception e, SyntaxElement s)
         {
-            var error = errorConstructor.Construct(new JsValue[] { e.Message });
+            var error = errorConstructor.Construct(e.Message);
             return new Completion(CompletionType.Throw, error, s);
         }
 

+ 8 - 10
Jint/Runtime/Interpreter/Statements/JintFunctionDeclarationStatement.cs

@@ -1,17 +1,15 @@
 using Esprima.Ast;
-using Jint.Native;
 
-namespace Jint.Runtime.Interpreter.Statements
+namespace Jint.Runtime.Interpreter.Statements;
+
+internal sealed class JintFunctionDeclarationStatement : JintStatement<FunctionDeclaration>
 {
-    internal sealed class JintFunctionDeclarationStatement : JintStatement<FunctionDeclaration>
+    public JintFunctionDeclarationStatement(FunctionDeclaration statement) : base(statement)
     {
-        public JintFunctionDeclarationStatement(FunctionDeclaration statement) : base(statement)
-        {
-        }
+    }
 
-        protected override Completion ExecuteInternal(EvaluationContext context)
-        {
-            return new Completion(CompletionType.Normal, JsValue.Undefined, ((JintStatement) this)._statement);
-        }
+    protected override Completion ExecuteInternal(EvaluationContext context)
+    {
+        return new Completion(CompletionType.Normal, null!, ((JintStatement) this)._statement);
     }
 }

+ 5 - 0
Jint/Runtime/Intrinsics.cs

@@ -2,6 +2,7 @@ using Jint.Native;
 using Jint.Native.AggregateError;
 using Jint.Native.Array;
 using Jint.Native.ArrayBuffer;
+using Jint.Native.AsyncFunction;
 using Jint.Native.BigInt;
 using Jint.Native.Boolean;
 using Jint.Native.DataView;
@@ -79,6 +80,7 @@ namespace Jint.Runtime
         private BooleanConstructor? _boolean;
         private ArrayBufferConstructor? _arrayBufferConstructor;
         private DataViewConstructor? _dataView;
+        private AsyncFunctionConstructor? _asyncFunction;
 
         private IntrinsicTypedArrayConstructor? _typedArray;
         private Int8ArrayConstructor? _int8Array;
@@ -114,6 +116,9 @@ namespace Jint.Runtime
         public ObjectConstructor Object { get; }
         public FunctionConstructor Function { get; }
 
+        internal AsyncFunctionConstructor AsyncFunction =>
+            _asyncFunction ??= new AsyncFunctionConstructor(_engine, _realm, Function);
+
         public ArrayConstructor Array =>
             _array ??= new ArrayConstructor(_engine, _realm, Function.PrototypeObject, Object.PrototypeObject);
 

+ 3 - 3
Jint/Runtime/JavaScriptException.cs

@@ -30,13 +30,13 @@ public class JavaScriptException : JintException
     public JsValue Error => _jsErrorException.Error;
 
     internal JavaScriptException(ErrorConstructor errorConstructor)
-        : base("", new JavaScriptErrorWrapperException(errorConstructor.Construct(Arguments.Empty), ""))
+        : base("", new JavaScriptErrorWrapperException(errorConstructor.Construct(), ""))
     {
         _jsErrorException = (JavaScriptErrorWrapperException) InnerException!;
     }
 
-    public JavaScriptException(ErrorConstructor errorConstructor, string? message)
-        : base(message, new JavaScriptErrorWrapperException(errorConstructor.Construct(new JsValue[] { message }), message))
+    public JavaScriptException(ErrorConstructor errorConstructor, string? message = null)
+        : base(message, new JavaScriptErrorWrapperException(errorConstructor.Construct(message), message))
     {
         _jsErrorException = (JavaScriptErrorWrapperException) InnerException!;
     }

+ 1 - 0
Jint/Runtime/KnownKeys.cs

@@ -9,4 +9,5 @@ internal static class KnownKeys
     internal static readonly Key Done = "done";
     internal static readonly Key Value = "value";
     internal static readonly Key Undefined = "undefined";
+    internal static readonly Key Constructor = "constructor";
 }

+ 7 - 8
Jint/Runtime/PromiseRejectedException.cs

@@ -1,14 +1,13 @@
 using Jint.Native;
 
-namespace Jint.Runtime
+namespace Jint.Runtime;
+
+public sealed class PromiseRejectedException : JintException
 {
-    public sealed class PromiseRejectedException : JintException
+    public PromiseRejectedException(JsValue value) : base($"Promise was rejected with value {value}")
     {
-        public PromiseRejectedException(JsValue value) : base($"Promise was rejected with value {value}")
-        {
-            RejectedValue = value;
-        }
-
-        public JsValue RejectedValue { get; }
+        RejectedValue = value;
     }
+
+    public JsValue RejectedValue { get; }
 }