2
0
Эх сурвалжийг харах

Spread, rest and default (#563)

Marko Lahma 6 жил өмнө
parent
commit
2eddfd8d5a

+ 39 - 0
Jint.Tests.Test262/LanguageTests.cs

@@ -0,0 +1,39 @@
+using Xunit;
+
+namespace Jint.Tests.Test262
+{
+    public class LanguageTests : Test262Test
+    {
+        [Theory(DisplayName = "language\\rest-parameters")]
+        [MemberData(nameof(SourceFiles), "language\\rest-parameters", false)]
+        [MemberData(nameof(SourceFiles), "language\\rest-parameters", true, Skip = "Skipped")]
+        protected void RestParameters(SourceFile sourceFile)
+        {
+            RunTestInternal(sourceFile);
+        }
+
+        [Theory(DisplayName = "language\\expressions\\array")]
+        [MemberData(nameof(SourceFiles), "language\\expressions\\array", false)]
+        [MemberData(nameof(SourceFiles), "language\\expressions\\array", true, Skip = "Skipped")]
+        protected void ExpressionsArray(SourceFile sourceFile)
+        {
+            RunTestInternal(sourceFile);
+        }
+
+        [Theory(DisplayName = "language\\expressions\\call")]
+        [MemberData(nameof(SourceFiles), "language\\expressions\\call", false)]
+        [MemberData(nameof(SourceFiles), "language\\expressions\\call", true, Skip = "Skipped")]
+        protected void ExpressionsCall(SourceFile sourceFile)
+        {
+            RunTestInternal(sourceFile);
+        }
+
+        [Theory(DisplayName = "language\\expressions\\new")]
+        [MemberData(nameof(SourceFiles), "language\\expressions\\new", false)]
+        [MemberData(nameof(SourceFiles), "language\\expressions\\new", true, Skip = "Skipped")]
+        protected void ExpressionsNew(SourceFile sourceFile)
+        {
+            RunTestInternal(sourceFile);
+        }
+    }
+}

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

@@ -4,6 +4,7 @@ using System.IO;
 using System.Linq;
 using System.Reflection;
 using System.Text.RegularExpressions;
+using Esprima;
 using Jint.Runtime;
 using Newtonsoft.Json.Linq;
 using Xunit;

+ 17 - 1
Jint.Tests.Test262/test/skipped.json

@@ -336,10 +336,26 @@
   },
   {
     "source": "language/statements/for/head-lhs-let.js",
-    "reason": "Esprima edge case"
+    "reason": "Esprima problem"
   },
   {
     "source": "language/statements/for-in/head-var-bound-names-dup.js",
     "reason": "destructing not implemented"
+  },
+  {
+    "source": "language/rest-parameters/with-new-target.js",
+    "reason": "classes not implemented"
+  },
+  {
+    "source": "language/rest-parameters/arrow-function.js",
+    "reason": "arrow functions not implemented"
+  },
+  {
+    "source": "language/rest-parameters/object-pattern.js",
+    "reason": "destructing not implemented"
+  },
+  {
+    "source": "language/expressions/call/trailing-comma.js",
+    "reason": "Esprima problem"
   }
 ]

+ 48 - 0
Jint.Tests/Runtime/EngineTests.cs

@@ -2613,5 +2613,53 @@ function output(x) {
             Assert.Equal(2, TypeConverter.ToNumber(result2.Get("TestDictionaryAverage2")));
             Assert.Equal(2, TypeConverter.ToNumber(result2.Get("TestDictionaryAverage3")));
         }
+        [Fact]
+        public void ShouldBeAbleToSpreadArrayLiteralsAndFunctionParameters()
+        {
+            RunTest(@"
+                function concat(x, a, b) {
+                    x += a;
+                    x += b;
+                    return x;
+                }
+                var s = [...'abc'];
+                var c = concat(1, ...'ab');
+                var arr1 = [1, 2];
+                var arr2 = [3, 4 ];
+                var r = [...arr2, ...arr1];
+            ");
+
+            var arrayInstance = (ArrayInstance) _engine.GetValue("r");
+            Assert.Equal(arrayInstance[0], 3);
+            Assert.Equal(arrayInstance[1], 4);
+            Assert.Equal(arrayInstance[2], 1);
+            Assert.Equal(arrayInstance[3], 2);
+
+            arrayInstance = (ArrayInstance) _engine.GetValue("s");
+            Assert.Equal(arrayInstance[0], 'a');
+            Assert.Equal(arrayInstance[1], 'b');
+            Assert.Equal(arrayInstance[2], 'c');
+
+            var c = _engine.GetValue("c").ToString();
+            Assert.Equal("1ab", c);
+        }
+
+        [Fact]
+        public void ShouldSupportDefaultsInFunctionParameters()
+        {
+            RunTest(@"
+                function f(x, y=12) {
+                  // y is 12 if not passed (or passed as undefined)
+                  return x + y;
+                }
+            ");
+
+            var function = _engine.GetValue("f");
+            var result = function.Invoke(3).ToString();
+            Assert.Equal("15", result);
+
+            result = function.Invoke(3, JsValue.Undefined).ToString();
+            Assert.Equal("15", result);
+        }
     }
 }

+ 11 - 2
Jint/Engine.cs

@@ -427,7 +427,12 @@ namespace Jint
 
             using (new StrictModeScope(_isStrict || program.Strict))
             {
-                DeclarationBindingInstantiation(DeclarationBindingType.GlobalCode, program.HoistingScope.FunctionDeclarations, program.HoistingScope.VariableDeclarations, null, null);
+                DeclarationBindingInstantiation(
+                    DeclarationBindingType.GlobalCode,
+                    program.HoistingScope.FunctionDeclarations,
+                    program.HoistingScope.VariableDeclarations,
+                    functionInstance: null,
+                    arguments: null);
 
                 var list = new JintStatementList(this, null, program.Body);
                 var result = list.Execute();
@@ -773,9 +778,11 @@ namespace Jint
                 var argsObj = _argumentsInstancePool.Rent(functionInstance, functionInstance._formalParameters, arguments, env, strict);
                 canReleaseArgumentsInstance = true;
 
+                var functionDeclaration = (functionInstance as ScriptFunctionInstance)?.FunctionDeclaration;
+
                 if (!ReferenceEquals(der, null))
                 {
-                    der.AddFunctionParameters(functionInstance, arguments, argsObj);
+                    der.AddFunctionParameters(functionInstance, arguments, argsObj, functionDeclaration);
                 }
                 else
                 {
@@ -785,6 +792,8 @@ namespace Jint
                     {
                         var argName = parameters[i];
                         var v = i + 1 > arguments.Length ? Undefined.Instance : arguments[i];
+                        v = DeclarativeEnvironmentRecord.HandleAssignmentPatternIfNeeded(functionDeclaration, v, i);
+
                         var argAlreadyDeclared = env.HasBinding(argName);
                         if (!argAlreadyDeclared)
                         {

+ 6 - 0
Jint/Native/Argument/ArgumentsInstance.cs

@@ -173,6 +173,12 @@ namespace Jint.Native.Argument
 
         public override bool DefineOwnProperty(string propertyName, PropertyDescriptor desc, bool throwOnError)
         {
+            if (_func is ScriptFunctionInstance scriptFunctionInstance && scriptFunctionInstance._function._hasRestParameter)
+            {
+                // immutable
+                return false;
+            }
+
             EnsureInitialized();
 
             if (!_strict && !ReferenceEquals(ParameterMap, null))

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

@@ -60,7 +60,7 @@ namespace Jint.Native.Function
                                 DeclarationBindingType.EvalCode,
                                 program.HoistingScope.FunctionDeclarations,
                                 program.HoistingScope.VariableDeclarations,
-                                this, 
+                                functionInstance: this, 
                                 arguments);
 
                             var statement = JintStatement.Build(_engine, program);

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

@@ -40,7 +40,7 @@ namespace Jint.Native.Function
             string[] parameters,
             LexicalEnvironment scope,
             bool strict,
-            in string objectClass)
+            string objectClass)
             : base(engine, objectClass)
         {
             _name = new PropertyDescriptor(name, PropertyFlag.Configurable);

+ 23 - 31
Jint/Native/Function/ScriptFunctionInstance.cs

@@ -5,14 +5,13 @@ using Jint.Runtime;
 using Jint.Runtime.Descriptors;
 using Jint.Runtime.Descriptors.Specialized;
 using Jint.Runtime.Environments;
-using Jint.Runtime.Interpreter.Statements;
+using Jint.Runtime.Interpreter;
 
 namespace Jint.Native.Function
 {
     public sealed class ScriptFunctionInstance : FunctionInstance, IConstructor
     {
-        private readonly IFunction _functionDeclaration;
-        private readonly JintStatement _functionBody;
+        internal readonly JintFunctionDefinition _function;
 
         /// <summary>
         /// http://www.ecma-international.org/ecma-262/5.1/#sec-13.2
@@ -22,15 +21,24 @@ namespace Jint.Native.Function
             IFunction functionDeclaration,
             LexicalEnvironment scope,
             bool strict)
-            : base(engine, functionDeclaration.Id?.Name ?? "", GetParameterNames(functionDeclaration), scope, strict)
+            : this(engine, new JintFunctionDefinition(engine, functionDeclaration), scope, strict)
         {
-            _functionDeclaration = functionDeclaration;
-            _functionBody = JintStatement.Build(engine, functionDeclaration.Body);
+        }
+
+        internal ScriptFunctionInstance(
+            Engine engine,
+            JintFunctionDefinition function,
+            LexicalEnvironment scope,
+            bool strict)
+            : base(engine, function._name ?? "", function._parameterNames, scope, strict)
+        {
+            _function = function;
 
             Extensible = true;
             Prototype = _engine.Function.PrototypeObject;
 
-            _length = new PropertyDescriptor(JsNumber.Create(_formalParameters.Length), PropertyFlag.AllForbidden);
+            var length = function._hasRestParameter ? _formalParameters.Length - 1 : _formalParameters.Length;
+            _length = new PropertyDescriptor(JsNumber.Create(length), PropertyFlag.AllForbidden);
 
             var proto = new ObjectInstanceWithConstructor(engine, this)
             {
@@ -40,9 +48,9 @@ namespace Jint.Native.Function
 
             _prototype = new PropertyDescriptor(proto, PropertyFlag.OnlyWritable);
 
-            if (_functionDeclaration.Id != null)
+            if (_function._name != null)
             {
-                DefineOwnProperty("name", new PropertyDescriptor(_functionDeclaration.Id.Name, PropertyFlag.None), false);
+                DefineOwnProperty("name", new PropertyDescriptor(_function._name, PropertyFlag.None), false);
             }
 
             if (strict)
@@ -54,24 +62,8 @@ namespace Jint.Native.Function
             }
         }
 
-        private static string[] GetParameterNames(IFunction functionDeclaration)
-        {
-            var list = functionDeclaration.Params;
-            var count = list.Count;
-
-            if (count == 0)
-            {
-                return System.ArrayExt.Empty<string>();
-            }
-
-            var names = new string[count];
-            for (var i = 0; i < count; ++i)
-            {
-                names[i] = ((Identifier) list[i]).Name;
-            }
-
-            return names;
-        }
+        // for example RavenDB wants to inspect this
+        public IFunction FunctionDeclaration => _function._function;
 
         /// <summary>
         /// http://www.ecma-international.org/ecma-262/5.1/#sec-13.2.1
@@ -111,12 +103,12 @@ namespace Jint.Native.Function
                 {
                     var argumentInstanceRented = _engine.DeclarationBindingInstantiation(
                         DeclarationBindingType.FunctionCode,
-                        _functionDeclaration.HoistingScope.FunctionDeclarations,
-                        _functionDeclaration.HoistingScope.VariableDeclarations,
-                        this,
+                        _function._hoistingScope.FunctionDeclarations,
+                        _function._hoistingScope.VariableDeclarations,
+                        functionInstance: this,
                         arguments);
 
-                    var result = _functionBody.Execute();
+                    var result = _function._body.Execute();
                     
                     var value = result.GetValueOrDefault();
                     

+ 6 - 3
Jint/Native/JsValue.cs

@@ -168,15 +168,18 @@ namespace Jint.Native
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         internal bool TryGetIterator(Engine engine, out IIterator iterator)
         {
-            if (!(this is ObjectInstance oi)
-                || !oi.TryGetValue(GlobalSymbolRegistry.Iterator._value, out var value)
+            var objectInstance = TypeConverter.ToObject(engine, this);
+
+            if (!objectInstance.TryGetValue(GlobalSymbolRegistry.Iterator._value, out var value)
                 || !(value is ICallable callable))
             {
                 iterator = null;
                 return false;
             }
 
-            var obj = (ObjectInstance) callable.Call(this, Arguments.Empty);
+            var obj = callable.Call(this, Arguments.Empty) as ObjectInstance
+                      ?? ExceptionHelper.ThrowTypeError<ObjectInstance>(engine, "Result of the Symbol.iterator method is not an object");
+
             if (obj is IIterator i)
             {
                 iterator = i;

+ 54 - 4
Jint/Runtime/Environments/DeclarativeEnvironmentRecord.cs

@@ -6,8 +6,9 @@ using Jint.Collections;
 using Jint.Native;
 using Jint.Native.Argument;
 using Jint.Native.Function;
+ using Jint.Runtime.Interpreter.Expressions;
 
-namespace Jint.Runtime.Environments
+ namespace Jint.Runtime.Environments
 {
     /// <summary>
     /// Represents a declarative environment record
@@ -260,13 +261,17 @@ namespace Jint.Runtime.Environments
         internal void AddFunctionParameters(
             FunctionInstance functionInstance,
             JsValue[] arguments,
-            ArgumentsInstance argumentsInstance)
+            ArgumentsInstance argumentsInstance,
+            IFunction functionDeclaration)
         {
             var parameters = functionInstance._formalParameters;
             bool empty = _dictionary == null && !_set;
             if (empty && parameters.Length == 1 && parameters[0].Length != BindingNameArguments.Length)
             {
                 var jsValue = arguments.Length == 0 ? Undefined : arguments[0];
+                jsValue = HandleAssignmentPatternIfNeeded(functionDeclaration, jsValue, 0);
+                jsValue = HandleRestPatternIfNeeded(_engine, functionDeclaration, arguments, 0, jsValue);
+
                 var binding = new Binding(jsValue, false, true);
                 _set = true;
                 _key = parameters[0];
@@ -274,7 +279,7 @@ namespace Jint.Runtime.Environments
             }
             else
             {
-                AddMultipleParameters(arguments, parameters);
+                AddMultipleParameters(arguments, parameters, functionDeclaration);
             }
 
             if (ReferenceEquals(_argumentsBinding.Value, null))
@@ -283,7 +288,7 @@ namespace Jint.Runtime.Environments
             }
         }
 
-        private void AddMultipleParameters(JsValue[] arguments, string[] parameters)
+        private void AddMultipleParameters(JsValue[] arguments, string[] parameters, IFunction functionDeclaration)
         {
             bool empty = _dictionary == null && !_set;
             for (var i = 0; i < parameters.Length; i++)
@@ -291,6 +296,12 @@ namespace Jint.Runtime.Environments
                 var argName = parameters[i];
                 var jsValue = i + 1 > arguments.Length ? Undefined : arguments[i];
 
+                jsValue = HandleAssignmentPatternIfNeeded(functionDeclaration, jsValue, i);
+                if (i == parameters.Length - 1)
+                {
+                    jsValue = HandleRestPatternIfNeeded(_engine, functionDeclaration, arguments, i, jsValue);
+                }
+
                 if (empty || !TryGetValue(argName, out var existing))
                 {
                     var binding = new Binding(jsValue, false, true);
@@ -318,6 +329,45 @@ namespace Jint.Runtime.Environments
             }
         }
 
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal static JsValue HandleAssignmentPatternIfNeeded(IFunction functionDeclaration, JsValue jsValue, int index)
+        {
+            if (jsValue.IsUndefined()
+                && index < functionDeclaration?.Params.Count
+                && functionDeclaration.Params[index] is AssignmentPattern ap
+                && ap.Right is Literal l)
+            {
+                return JintLiteralExpression.ConvertToJsValue(l);
+            }
+
+            return jsValue;
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private static JsValue HandleRestPatternIfNeeded(
+            Engine engine,
+            IFunction functionDeclaration,
+            JsValue[] arguments,
+            int index,
+            JsValue defaultValue)
+        {
+            if (index < functionDeclaration?.Params.Count
+                && functionDeclaration.Params[index] is RestElement)
+            {
+                var count = (uint) (arguments.Length - functionDeclaration.Params.Count + 1);
+                var rest = engine.Array.ConstructFast(count);
+
+                uint targetIndex = 0;
+                for (var i = index; i < arguments.Length; ++i)
+                {
+                    rest.SetIndexValue(targetIndex++, arguments[i], updateLength: false);
+                }
+                return rest;
+            }
+
+            return defaultValue;
+        }
+
         internal void AddVariableDeclarations(List<VariableDeclaration> variableDeclarations)
         {
             var variableDeclarationsCount = variableDeclarations.Count;

+ 5 - 0
Jint/Runtime/Environments/ObjectEnvironmentRecord.cs

@@ -97,6 +97,11 @@ namespace Jint.Runtime.Environments
             return ArrayExt.Empty<string>();
         }
 
+        public override bool Equals(JsValue other)
+        {
+            return ReferenceEquals(_bindingObject, other);
+        }
+
         internal override void FunctionWasCalled()
         {
         }

+ 70 - 3
Jint/Runtime/Interpreter/Expressions/JintArrayExpression.cs

@@ -1,10 +1,14 @@
 using Esprima.Ast;
+using Jint.Native;
+using Jint.Native.Array;
+using Jint.Native.Iterator;
 
 namespace Jint.Runtime.Interpreter.Expressions
 {
     internal sealed class JintArrayExpression : JintExpression
     {
         private JintExpression[] _expressions;
+        private bool _hasSpreads;
 
         public JintArrayExpression(Engine engine, ArrayExpression expression) : base(engine, expression)
         {
@@ -22,25 +26,88 @@ namespace Jint.Runtime.Interpreter.Expressions
                 {
                     var expression = Build(_engine, (Expression) expr);
                     _expressions[n] = expression;
+                    _hasSpreads |= expression is JintSpreadExpression;
                 }
             }
+
+            // we get called from nested spread expansion in call
+            _initialized = true;
         }
 
         protected override object EvaluateInternal()
         {
-            var a = _engine.Array.ConstructFast((uint) _expressions.Length);
+            var a = _engine.Array.ConstructFast(_hasSpreads ? 0 : (uint) _expressions.Length);
             var expressions = _expressions;
+
+            uint arrayIndexCounter = 0;
             for (uint i = 0; i < (uint) expressions.Length; i++)
             {
                 var expr = expressions[i];
-                if (expr != null)
+                if (expr == null)
+                {
+                    arrayIndexCounter++;
+                    continue;
+                }
+
+                if (_hasSpreads && expr is JintSpreadExpression jse)
+                {
+                    jse.GetValueAndCheckIterator(out var objectInstance, out var iterator);
+                    // optimize for array
+                    if (objectInstance is ArrayInstance ai)
+                    {
+                        var length = ai.GetLength();
+                        var newLength = arrayIndexCounter + length;
+                        a.EnsureCapacity(newLength);
+                        a.CopyValues(ai, sourceStartIndex: 0, targetStartIndex: arrayIndexCounter, length);
+                        arrayIndexCounter += length;
+                        a.SetLength(newLength);
+                    }
+                    else
+                    {
+                        var protocol = new ArraySpreadProtocol(_engine, a, iterator, arrayIndexCounter);
+                        protocol.Execute();
+                        arrayIndexCounter += protocol._addedCount;
+                    }
+                }
+                else
                 {
                     var value = expr.GetValue();
-                    a.SetIndexValue(i, value, updateLength: false);
+                    a.SetIndexValue(arrayIndexCounter++, value, updateLength: false);
                 }
             }
 
+            if (_hasSpreads)
+            {
+                a.SetLength(arrayIndexCounter);
+            }
+
             return a;
         }
+
+        private sealed class ArraySpreadProtocol : IteratorProtocol
+        {
+            private readonly ArrayInstance _instance;
+            internal long _index;
+            internal uint _addedCount = 0;
+
+            public ArraySpreadProtocol(
+                Engine engine,
+                ArrayInstance instance,
+                IIterator iterator,
+                long startIndex) : base(engine, iterator, 0)
+            {
+                _instance = instance;
+                _index = startIndex - 1;
+            }
+
+            protected override void ProcessItem(JsValue[] args, JsValue currentValue)
+            {
+                _index++;
+                _addedCount++;
+                var jsValue = ExtractValueFromIteratorInstance(currentValue);
+
+                _instance.SetIndexValue((uint) _index, jsValue, updateLength: false);
+            }
+        }
     }
 }

+ 25 - 3
Jint/Runtime/Interpreter/Expressions/JintCallExpression.cs

@@ -13,6 +13,7 @@ namespace Jint.Runtime.Interpreter.Expressions
         private readonly int _maxRecursionDepth;
 
         private readonly JintExpression _calleeExpression;
+        private bool _hasSpreads;
 
         public JintCallExpression(Engine engine, CallExpression expression) : base(engine, expression)
         {
@@ -30,12 +31,26 @@ namespace Jint.Runtime.Interpreter.Expressions
                 JintArguments = new JintExpression[expression.Arguments.Count]
             };
 
+            bool CanSpread(INode e)
+            {
+                return e?.Type == Nodes.SpreadElement
+                    || e is AssignmentExpression ae && ae.Right?.Type == Nodes.SpreadElement;
+            }
+
             bool cacheable = true;
             for (var i = 0; i < expression.Arguments.Count; i++)
             {
                 var expressionArgument = (Expression) expression.Arguments[i];
                 cachedArgumentsHolder.JintArguments[i] = Build(_engine, expressionArgument);
                 cacheable &= expressionArgument is Literal;
+                _hasSpreads |= CanSpread(expressionArgument);
+                if (expressionArgument is ArrayExpression ae)
+                {
+                    for (var elementIndex = 0; elementIndex < ae.Elements.Count; elementIndex++)
+                    {
+                        _hasSpreads |= CanSpread(ae.Elements[elementIndex]);
+                    }
+                }
             }
 
             if (cacheable)
@@ -44,7 +59,7 @@ namespace Jint.Runtime.Interpreter.Expressions
                 var arguments = ArrayExt.Empty<JsValue>();
                 if (cachedArgumentsHolder.JintArguments.Length > 0)
                 {
-                    arguments = _engine._jsValueArrayPool.RentArray(cachedArgumentsHolder.JintArguments.Length);
+                    arguments = new JsValue[cachedArgumentsHolder.JintArguments.Length];
                     BuildArguments(cachedArgumentsHolder.JintArguments, arguments);
                 }
 
@@ -76,8 +91,15 @@ namespace Jint.Runtime.Interpreter.Expressions
             {
                 if (cachedArguments.JintArguments.Length > 0)
                 {
-                    arguments = _engine._jsValueArrayPool.RentArray(cachedArguments.JintArguments.Length);
-                    BuildArguments(cachedArguments.JintArguments, arguments);
+                    if (_hasSpreads)
+                    {
+                        arguments = BuildArgumentsWithSpreads(cachedArguments.JintArguments);
+                    }
+                    else
+                    {
+                        arguments = _engine._jsValueArrayPool.RentArray(cachedArguments.JintArguments.Length);
+                        BuildArguments(cachedArguments.JintArguments, arguments);
+                    }
                 }
             }
 

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

@@ -1,6 +1,9 @@
 using System.Runtime.CompilerServices;
+using System.Collections.Generic;
 using Esprima.Ast;
 using Jint.Native;
+using Jint.Native.Array;
+using Jint.Native.Iterator;
 using Jint.Native.Number;
 using Jint.Runtime.Environments;
 
@@ -111,6 +114,9 @@ namespace Jint.Runtime.Interpreter.Expressions
                 case Nodes.UnaryExpression:
                     return new JintUnaryExpression(engine, (UnaryExpression) expression);
 
+                case Nodes.SpreadElement:
+                    return new JintSpreadExpression(engine, (SpreadElement) expression);
+
                 default:
                     ExceptionHelper.ThrowArgumentOutOfRangeException();
                     return null;
@@ -330,6 +336,61 @@ namespace Jint.Runtime.Interpreter.Expressions
             }
         }
 
+        protected JsValue[] BuildArgumentsWithSpreads(JintExpression[] jintExpressions)
+        {
+            var args = new List<JsValue>(jintExpressions.Length);
+            for (var i = 0; i < jintExpressions.Length; i++)
+            {
+                var jintExpression = jintExpressions[i];
+                if (jintExpression is JintSpreadExpression jse)
+                {
+                    jse.GetValueAndCheckIterator(out var objectInstance, out var iterator);
+                    // optimize for array
+                    if (objectInstance is ArrayInstance ai)
+                    {
+                        var length = ai.GetLength();
+                        for (uint j = 0; j < length; ++j)
+                        {
+                            if (ai.TryGetValue(j, out var value))
+                            {
+                                args.Add(value);
+                            }
+                        }
+                    }
+                    else
+                    {
+                        var protocol = new ArraySpreadProtocol(_engine, args, iterator);
+                        protocol.Execute();
+                    }
+                }
+                else
+                {
+                    args.Add(jintExpression.GetValue());
+                }
+            }
+
+            return args.ToArray();
+        }
+
+        private sealed class ArraySpreadProtocol : IteratorProtocol
+        {
+            private readonly List<JsValue> _instance;
+
+            public ArraySpreadProtocol(
+                Engine engine,
+                List<JsValue> instance,
+                IIterator iterator) : base(engine, iterator, 0)
+            {
+                _instance = instance;
+            }
+
+            protected override void ProcessItem(JsValue[] args, JsValue currentValue)
+            {
+                var jsValue = ExtractValueFromIteratorInstance(currentValue);
+                _instance.Add(jsValue);
+            }
+        }
+
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         protected bool TryGetIdentifierEnvironmentWithBindingValue(
             string expressionName,

+ 7 - 8
Jint/Runtime/Interpreter/Expressions/JintFunctionExpression.cs

@@ -6,13 +6,12 @@ namespace Jint.Runtime.Interpreter.Expressions
 {
     internal sealed class JintFunctionExpression : JintExpression
     {
-        private readonly IFunction _function;
-        private readonly string _name;
+        private readonly JintFunctionDefinition _function;
 
-        public JintFunctionExpression(Engine engine, IFunction function) : base(engine, ArrowParameterPlaceHolder.Empty)
+        public JintFunctionExpression(Engine engine, IFunction function)
+            : base(engine, ArrowParameterPlaceHolder.Empty)
         {
-            _function = function;
-            _name = !string.IsNullOrEmpty(function.Id?.Name) ? function.Id.Name : null;
+            _function = new JintFunctionDefinition(engine, function);
         }
 
         protected override object EvaluateInternal()
@@ -24,11 +23,11 @@ namespace Jint.Runtime.Interpreter.Expressions
                 _engine,
                 _function,
                 funcEnv,
-                _function.Strict);
+                _function._strict);
 
-            if (_name != null)
+            if (_function._name != null)
             {
-                envRec.CreateMutableBinding(_name, closure);
+                envRec.CreateMutableBinding(_function._name, closure);
             }
 
             return closure;

+ 12 - 3
Jint/Runtime/Interpreter/Expressions/JintNewExpression.cs

@@ -7,6 +7,7 @@ namespace Jint.Runtime.Interpreter.Expressions
     {
         private readonly JintExpression _calleeExpression;
         private JintExpression[] _jintArguments;
+        private bool _hasSpreads;
 
         public JintNewExpression(Engine engine, NewExpression expression) : base(engine, expression)
         {
@@ -21,14 +22,22 @@ namespace Jint.Runtime.Interpreter.Expressions
             for (var i = 0; i < _jintArguments.Length; i++)
             {
                 _jintArguments[i] = Build(_engine, (Expression) expression.Arguments[i]);
+                _hasSpreads |= _jintArguments[i] is JintSpreadExpression;
             }
         }
 
         protected override object EvaluateInternal()
         {
-            var arguments = _engine._jsValueArrayPool.RentArray(_jintArguments.Length);
-
-            BuildArguments(_jintArguments, arguments);
+            JsValue[] arguments;
+            if (_hasSpreads)
+            {
+                arguments = BuildArgumentsWithSpreads(_jintArguments);
+            }
+            else
+            {
+                arguments = _engine._jsValueArrayPool.RentArray(_jintArguments.Length);
+                BuildArguments(_jintArguments, arguments);
+            }
 
             // todo: optimize by defining a common abstract class or interface
             if (!(_calleeExpression.GetValue() is IConstructor callee))

+ 43 - 0
Jint/Runtime/Interpreter/Expressions/JintSpreadExpression.cs

@@ -0,0 +1,43 @@
+using Esprima.Ast;
+using Jint.Native;
+using Jint.Native.Iterator;
+
+namespace Jint.Runtime.Interpreter.Expressions
+{
+    internal sealed class JintSpreadExpression : JintExpression
+    {
+        private readonly JintExpression _argument;
+        private readonly string _argumentName;
+
+        public JintSpreadExpression(Engine engine, SpreadElement expression) : base(engine, expression)
+        {
+            _argument = Build(engine, expression.Argument);
+            _argumentName = (expression.Argument as Identifier)?.Name;
+        }
+
+        protected override object EvaluateInternal()
+        {
+            GetValueAndCheckIterator(out var objectInstance, out var iterator);
+            return objectInstance;
+        }
+
+        public override JsValue GetValue()
+        {
+            // need to notify correct node when taking shortcut
+            _engine._lastSyntaxNode = _expression;
+
+            GetValueAndCheckIterator(out var objectInstance, out var iterator);
+            return objectInstance;
+        }
+
+        internal void GetValueAndCheckIterator(out JsValue instance, out IIterator iterator)
+        {
+            instance = _argument.GetValue();
+            if (instance is null || !instance.TryGetIterator(_engine, out iterator))
+            {
+                iterator = null;
+                ExceptionHelper.ThrowTypeError(_engine, _argumentName + " is not iterable");
+            }
+        }
+    }
+}

+ 68 - 0
Jint/Runtime/Interpreter/JintFunctionDefinition.cs

@@ -0,0 +1,68 @@
+using Esprima;
+using Esprima.Ast;
+using Jint.Runtime.Interpreter.Statements;
+
+namespace Jint.Runtime.Interpreter
+{
+    internal sealed class JintFunctionDefinition
+    {
+        internal readonly IFunction _function;
+        internal readonly string _name;
+        internal readonly bool _strict;
+        internal readonly string[] _parameterNames;
+        internal readonly JintStatement _body;
+        internal bool _hasRestParameter;
+
+        public readonly HoistingScope _hoistingScope;
+
+        public JintFunctionDefinition(Engine engine, IFunction function)
+        {
+            _function = function;
+            _hoistingScope = function.HoistingScope;
+            _name = !string.IsNullOrEmpty(function.Id?.Name) ? function.Id.Name : null;
+            _strict = function.Strict;
+            _parameterNames = GetParameterNames(function);
+            _body = JintStatement.Build(engine, function.Body);
+        }
+
+        private string[] GetParameterNames(IFunction functionDeclaration)
+        {
+            var list = functionDeclaration.Params;
+            var count = list.Count;
+
+            if (count == 0)
+            {
+                return System.ArrayExt.Empty<string>();
+            }
+
+            var names = new string[count];
+            for (var i = 0; i < count; ++i)
+            {
+                var node = list[i];
+                if (node is Identifier identifier)
+                {
+                    names[i] = identifier.Name;
+                }
+                else if (node is AssignmentPattern ap)
+                {
+                    names[i] = ((Identifier) ap.Left).Name;
+                }
+                else if (node is RestElement re)
+                {
+                    if (re.Argument is Identifier id)
+                    {
+                        names[i] = id.Name;
+                    }
+                    else
+                    {
+                        names[i] = "";
+                    }
+                    _hasRestParameter = true;
+                }
+            }
+
+            return names;
+        }
+
+    }
+}

+ 1 - 1
README.md

@@ -157,7 +157,7 @@ ES6 features which are being implemented:
 - [ ] [enhanced object literals](https://github.com/lukehoban/es6features/blob/master/README.md#enhanced-object-literals)
 - [ ] [template strings](https://github.com/lukehoban/es6features/blob/master/README.md#template-strings)
 - [ ] [destructuring](https://github.com/lukehoban/es6features/blob/master/README.md#destructuring)
-- [ ] [default + rest + spread](https://github.com/lukehoban/es6features/blob/master/README.md#default--rest--spread)
+- [x] [default + rest + spread](https://github.com/lukehoban/es6features/blob/master/README.md#default--rest--spread)
 - [ ] [let + const](https://github.com/lukehoban/es6features/blob/master/README.md#let--const)
 - [x] [iterators + for..of](https://github.com/lukehoban/es6features/blob/master/README.md#iterators--forof)
 - [ ] [generators](https://github.com/lukehoban/es6features/blob/master/README.md#generators)