Răsfoiți Sursa

Implemented Object Spread:
-added Unit tests for core
-disabled by default, use -ES2018ObjectSpread flag to enable

David Yang 7 ani în urmă
părinte
comite
11a1c9982e

+ 3 - 1
lib/Backend/IRBuilder.cpp

@@ -1877,9 +1877,11 @@ IRBuilder::BuildReg2(Js::OpCode newOpcode, uint32 offset, Js::RegSlot R0, Js::Re
 
     switch (newOpcode)
     {
+    case Js::OpCode::SpreadObjectLiteral:
+        // fall through
     case Js::OpCode::SetComputedNameVar:
     {
-        IR::Instr *instr = IR::Instr::New(Js::OpCode::SetComputedNameVar, m_func);
+        IR::Instr *instr = IR::Instr::New(newOpcode, m_func);
         instr->SetSrc1(this->BuildSrcOpnd(R0));
         instr->SetSrc2(src1Opnd);
         this->AddInstr(instr, offset);

+ 2 - 0
lib/Backend/JnHelperMethodList.h

@@ -512,6 +512,8 @@ HELPERCALL(EnsureFunctionProxyDeferredPrototypeType, &Js::FunctionProxy::EnsureF
 HELPERCALL(SpreadArrayLiteral, Js::JavascriptArray::SpreadArrayArgs, 0)
 HELPERCALL(SpreadCall, Js::JavascriptFunction::EntrySpreadCall, 0)
 
+HELPERCALL(SpreadObjectLiteral, Js::JavascriptObject::SpreadObjectLiteral, 0)
+
 HELPERCALLCHK(LdHomeObj,           Js::JavascriptOperators::OP_LdHomeObj, AttrCanNotBeReentrant)
 HELPERCALLCHK(LdFuncObj,           Js::JavascriptOperators::OP_LdFuncObj, AttrCanNotBeReentrant)
 HELPERCALLCHK(LdHomeObjProto,      Js::JavascriptOperators::OP_LdHomeObjProto, AttrCanNotBeReentrant)

+ 6 - 0
lib/Backend/Lower.cpp

@@ -3090,6 +3090,10 @@ Lowerer::LowerRange(IR::Instr *instrStart, IR::Instr *instrEnd, bool defaultDoFa
             break;
         }
 
+        case Js::OpCode::SpreadObjectLiteral:
+            this->LowerBinaryHelperMem(instr, IR::HelperSpreadObjectLiteral);
+            break;
+
         default:
 #ifdef ENABLE_WASM_SIMD
             if (IsSimd128Opcode(instr->m_opcode))
@@ -8613,6 +8617,7 @@ Lowerer::LowerBinaryHelper(IR::Instr *instr, IR::JnHelperMethod helperMethod)
 
     AssertMsg((Js::OpCodeUtil::GetOpCodeLayout(instr->m_opcode) == Js::OpLayoutType::Reg1Unsigned1 && !instr->GetDst()) ||
               Js::OpCodeUtil::GetOpCodeLayout(instr->m_opcode) == Js::OpLayoutType::Reg3 ||
+              Js::OpCodeUtil::GetOpCodeLayout(instr->m_opcode) == Js::OpLayoutType::Reg2 ||
               Js::OpCodeUtil::GetOpCodeLayout(instr->m_opcode) == Js::OpLayoutType::Reg2Int1 ||
               Js::OpCodeUtil::GetOpCodeLayout(instr->m_opcode) == Js::OpLayoutType::ElementU ||
               instr->m_opcode == Js::OpCode::InvalCachedScope, "Expected a binary instruction...");
@@ -8636,6 +8641,7 @@ Lowerer::LowerBinaryHelperMem(IR::Instr *instr, IR::JnHelperMethod helperMethod)
     IR::Instr *instrPrev;
 
     AssertMsg(Js::OpCodeUtil::GetOpCodeLayout(instr->m_opcode) == Js::OpLayoutType::Reg3 ||
+              Js::OpCodeUtil::GetOpCodeLayout(instr->m_opcode) == Js::OpLayoutType::Reg2 ||
               Js::OpCodeUtil::GetOpCodeLayout(instr->m_opcode) == Js::OpLayoutType::Reg2Int1 ||
               Js::OpCodeUtil::GetOpCodeLayout(instr->m_opcode) == Js::OpLayoutType::Reg1Unsigned1, "Expected a binary instruction...");
 

+ 2 - 0
lib/Common/ConfigFlagsList.h

@@ -625,6 +625,7 @@ PHASE(All)
 #define DEFAULT_CONFIG_ES6Spread               (true)
 #define DEFAULT_CONFIG_ES6String               (true)
 #define DEFAULT_CONFIG_ES6StringPrototypeFixes (true)
+#define DEFAULT_CONFIG_ES2018ObjectSpread      (false)
 #ifdef COMPILE_DISABLE_ES6PrototypeChain
     // If ES6PrototypeChain needs to be disabled by compile flag, DEFAULT_CONFIG_ES6PrototypeChain should be false
     #define DEFAULT_CONFIG_ES6PrototypeChain       (false)
@@ -1120,6 +1121,7 @@ FLAGPR           (Boolean, ES6, ES6Rest                , "Enable ES6 Rest parame
 FLAGPR           (Boolean, ES6, ES6Spread              , "Enable ES6 Spread support"                                , DEFAULT_CONFIG_ES6Spread)
 FLAGPR           (Boolean, ES6, ES6String              , "Enable ES6 String extensions"                             , DEFAULT_CONFIG_ES6String)
 FLAGPR           (Boolean, ES6, ES6StringPrototypeFixes, "Enable ES6 String.prototype fixes"                        , DEFAULT_CONFIG_ES6StringPrototypeFixes)
+FLAGPR           (Boolean, ES6, ES2018ObjectSpread     , "Enable ES2018 Object Spread"                              , DEFAULT_CONFIG_ES2018ObjectSpread)
 
 #ifndef COMPILE_DISABLE_ES6PrototypeChain
     #define COMPILE_DISABLE_ES6PrototypeChain 0

+ 46 - 12
lib/Parser/Parse.cpp

@@ -8,6 +8,7 @@
 
 #include "ByteCode/ByteCodeSerializer.h"
 
+
 #if DBG_DUMP
 void PrintPnodeWIndent(ParseNode *pnode, int indentAmt);
 #endif
@@ -4342,6 +4343,7 @@ template<bool buildAST>
 ParseNodePtr Parser::ParseMemberList(LPCOLESTR pNameHint, uint32* pNameHintLength, tokens declarationType)
 {
     ParseNodeBin * pnodeArg = nullptr;
+    ParseNodePtr pnodeSpread = nullptr;
     ParseNodePtr pnodeName = nullptr;
     ParseNodePtr pnodeList = nullptr;
     ParseNodePtr *lastNodeRef = nullptr;
@@ -4416,6 +4418,7 @@ ParseNodePtr Parser::ParseMemberList(LPCOLESTR pNameHint, uint32* pNameHintLengt
         charcount_t idHintIchMin = static_cast<charcount_t>(this->GetScanner()->IecpMinTok());
         charcount_t idHintIchLim = static_cast<charcount_t>(this->GetScanner()->IecpLimTok());
         bool wrapInBrackets = false;
+        bool useSpread = false;
         switch (m_token.tk)
         {
         default:
@@ -4486,6 +4489,17 @@ ParseNodePtr Parser::ParseMemberList(LPCOLESTR pNameHint, uint32* pNameHintLengt
 
             isComputedName = true;
             break;
+
+        case tkEllipsis:
+            if (CONFIG_FLAG(ES2018ObjectSpread))
+            {
+                useSpread = true;
+            }
+            else 
+            {
+                Error(ERRnoMemberIdent);
+            }
+            break;
         }
 
         if (pFullNameHint == nullptr)
@@ -4503,9 +4517,13 @@ ParseNodePtr Parser::ParseMemberList(LPCOLESTR pNameHint, uint32* pNameHintLengt
         }
 
         RestorePoint atPid;
-        this->GetScanner()->Capture(&atPid);
 
-        this->GetScanner()->ScanForcingPid();
+        // Only move to next token if spread op was not seen
+        if (!useSpread)
+        {
+            this->GetScanner()->Capture(&atPid);
+            this->GetScanner()->ScanForcingPid();
+        }
 
         if (isGenerator && m_token.tk != tkLParen)
         {
@@ -4514,7 +4532,7 @@ ParseNodePtr Parser::ParseMemberList(LPCOLESTR pNameHint, uint32* pNameHintLengt
 
         if (tkColon == m_token.tk)
         {
-            // It is a syntax error is the production of the form __proto__ : <> occurs more than once. From B.3.1 in spec.
+            // It is a syntax error if the production of the form __proto__ : <> occurs more than once. From B.3.1 in spec.
             // Note that previous scan is important because only after that we can determine we have a variable.
             if (!isComputedName && pidHint == wellKnownPropertyPids.__proto__)
             {
@@ -4659,7 +4677,15 @@ ParseNodePtr Parser::ParseMemberList(LPCOLESTR pNameHint, uint32* pNameHintLengt
                 pnodeArg = CreateBinNode(knopMember, pnodeName, pnodeFnc);
             }
         }
-        else if (nullptr != pidHint) //Its either tkID/tkStrCon/tkFloatCon/tkIntCon
+        else if (useSpread)
+        {
+            pnodeSpread = ParseExpr<buildAST>(koplCma, nullptr, TRUE, /* fAllowEllipsis */ TRUE);
+            if (buildAST)
+            {
+                this->CheckArguments(pnodeSpread);
+            }
+        }
+        else if (nullptr != pidHint) //It's either tkID/tkStrCon/tkFloatCon/tkIntCon
         {
             Assert(pidHint->Psz() != nullptr);
 
@@ -4765,16 +4791,24 @@ ParseNodePtr Parser::ParseMemberList(LPCOLESTR pNameHint, uint32* pNameHintLengt
 
         if (buildAST)
         {
-            Assert(pnodeArg->pnode2 != nullptr);
-            if (pnodeArg->pnode2->nop == knopFncDecl)
+            if (useSpread)
             {
-                Assert(fullNameHintLength >= shortNameOffset);
-                ParseNodeFnc * pnodeFunc = pnodeArg->pnode2->AsParseNodeFnc();
-                pnodeFunc->hint = pFullNameHint;
-                pnodeFunc->hintLength = fullNameHintLength;
-                pnodeFunc->hintOffset = shortNameOffset;
+                Assert(pnodeSpread != nullptr);
+                AddToNodeListEscapedUse(&pnodeList, &lastNodeRef, pnodeSpread);
+            }
+            else
+            {
+                Assert(pnodeArg->pnode2 != nullptr);
+                if (pnodeArg->pnode2->nop == knopFncDecl)
+                {
+                    Assert(fullNameHintLength >= shortNameOffset);
+                    ParseNodeFnc * pnodeFunc = pnodeArg->pnode2->AsParseNodeFnc();
+                    pnodeFunc->hint = pFullNameHint;
+                    pnodeFunc->hintLength = fullNameHintLength;
+                    pnodeFunc->hintOffset = shortNameOffset;
+                }
+                AddToNodeListEscapedUse(&pnodeList, &lastNodeRef, pnodeArg);
             }
-            AddToNodeListEscapedUse(&pnodeList, &lastNodeRef, pnodeArg);
         }
         pidHint = nullptr;
         pFullNameHint = nullptr;

+ 2 - 2
lib/Runtime/ByteCode/ByteCodeCacheReleaseFileVersion.h

@@ -4,6 +4,6 @@
 //-------------------------------------------------------------------------------------------------------
 // NOTE: If there is a merge conflict the correct fix is to make a new GUID.
 
-// {2E95A003-1442-404F-98D5-D5C973B8A719}
+// {3EB30F58-66E0-404B-BAE8-1D3CD23F866F}
 const GUID byteCodeCacheReleaseFileVersion =
-{ 0x2E95A003, 0x1442, 0x404F, { 0x98, 0xD5, 0xD5, 0xC9, 0x73, 0xB8, 0xA7, 0x19 } };
+{ 0x3EB30F58, 0x66E0, 0x404B, { 0xBA, 0xE8, 0x1D, 0x3C, 0xD2, 0x3F, 0x86, 0x6F } };

+ 48 - 20
lib/Runtime/ByteCode/ByteCodeEmitter.cpp

@@ -8357,6 +8357,16 @@ void EmitMemberNode(ParseNode *memberNode, Js::RegSlot objectLocation, ByteCodeG
     }
 }
 
+void EmitObjectSpreadNode(ParseNode *spreadNode, Js::RegSlot objectLocation, ByteCodeGenerator *byteCodeGenerator, FuncInfo *funcInfo)
+{
+    Js::RegSlot fromObjectLocation;
+    ParseNode *exprNode = spreadNode->AsParseNodeUni()->pnode1;
+    Emit(exprNode, byteCodeGenerator, funcInfo, false);
+    fromObjectLocation = exprNode->location;
+    byteCodeGenerator->Writer()->Reg2(Js::OpCode::SpreadObjectLiteral, fromObjectLocation, objectLocation);
+    funcInfo->ReleaseLoc(exprNode);
+}
+
 void EmitClassInitializers(ParseNode *memberList, Js::RegSlot objectLocation, ByteCodeGenerator *byteCodeGenerator, FuncInfo *funcInfo, ParseNode* parentNode, bool isObjectEmpty)
 {
     if (memberList != nullptr)
@@ -8391,14 +8401,14 @@ void EmitObjectInitializers(ParseNode *memberList, Js::RegSlot objectLocation, B
     typedef JsUtil::BaseHashSet<Js::PropertyId, ArenaAllocator, PowerOf2SizePolicy> PropertyIdSet;
     PropertyIdSet* propertyIds = Anew(byteCodeGenerator->GetAllocator(), PropertyIdSet, byteCodeGenerator->GetAllocator(), 17);
 
-    bool hasComputedName = false;
+    bool hasComputedNameOrSpread = false;
     if (memberList != nullptr)
     {
         while (memberList->nop == knopList)
         {
-            if (memberList->AsParseNodeBin()->pnode1->AsParseNodeBin()->pnode1->nop == knopComputedName)
+            if (memberList->AsParseNodeBin()->pnode1->nop == knopEllipsis || memberList->AsParseNodeBin()->pnode1->AsParseNodeBin()->pnode1->nop == knopComputedName)
             {
-                hasComputedName = true;
+                hasComputedNameOrSpread = true;
                 break;
             }
 
@@ -8411,7 +8421,7 @@ void EmitObjectInitializers(ParseNode *memberList, Js::RegSlot objectLocation, B
             memberList = memberList->AsParseNodeBin()->pnode2;
         }
 
-        if (memberList->AsParseNodeBin()->pnode1->nop != knopComputedName && !hasComputedName)
+        if (memberList->nop != knopEllipsis && memberList->AsParseNodeBin()->pnode1->nop != knopComputedName && !hasComputedNameOrSpread)
         {
             propertyId = memberList->AsParseNodeBin()->pnode1->AsParseNodeStr()->pid->GetPropertyId();
             if (!byteCodeGenerator->GetScriptContext()->IsNumericPropertyId(propertyId, &value))
@@ -8444,7 +8454,7 @@ void EmitObjectInitializers(ParseNode *memberList, Js::RegSlot objectLocation, B
         unsigned int argIndex = 0;
         while (memberList->nop == knopList)
         {
-            if (memberList->AsParseNodeBin()->pnode1->AsParseNodeBin()->pnode1->nop == knopComputedName)
+            if (memberList->AsParseNodeBin()->pnode1->nop == knopEllipsis || memberList->AsParseNodeBin()->pnode1->AsParseNodeBin()->pnode1->nop == knopComputedName)
             {
                 break;
             }
@@ -8457,7 +8467,7 @@ void EmitObjectInitializers(ParseNode *memberList, Js::RegSlot objectLocation, B
             memberList = memberList->AsParseNodeBin()->pnode2;
         }
 
-        if (memberList->AsParseNodeBin()->pnode1->nop != knopComputedName && !hasComputedName)
+        if (memberList->nop != knopEllipsis && memberList->AsParseNodeBin()->pnode1->nop != knopComputedName && !hasComputedNameOrSpread)
         {
             propertyId = memberList->AsParseNodeBin()->pnode1->AsParseNodeStr()->pid->GetPropertyId();
             if (!byteCodeGenerator->GetScriptContext()->IsNumericPropertyId(propertyId, &value) && propertyIds->Remove(propertyId))
@@ -8486,20 +8496,35 @@ void EmitObjectInitializers(ParseNode *memberList, Js::RegSlot objectLocation, B
         while (memberList->nop == knopList)
         {
             ParseNode *memberNode = memberList->AsParseNodeBin()->pnode1;
-
-            if (memberNode->AsParseNodeBin()->pnode1->nop == knopComputedName)
+            if (memberNode->nop == knopEllipsis) 
             {
-                useStore = true;
+                byteCodeGenerator->StartSubexpression(memberNode);
+                EmitObjectSpreadNode(memberNode, objectLocation, byteCodeGenerator, funcInfo);
+                byteCodeGenerator->EndSubexpression(memberNode);
             }
+            else
+            {
+                if (memberNode->AsParseNodeBin()->pnode1->nop == knopComputedName)
+                {
+                    useStore = true;
+                }
 
-            byteCodeGenerator->StartSubexpression(memberNode);
-            EmitMemberNode(memberNode, objectLocation, byteCodeGenerator, funcInfo, nullptr, useStore);
-            byteCodeGenerator->EndSubexpression(memberNode);
+                byteCodeGenerator->StartSubexpression(memberNode);
+                EmitMemberNode(memberNode, objectLocation, byteCodeGenerator, funcInfo, nullptr, useStore);
+                byteCodeGenerator->EndSubexpression(memberNode);
+            }
             memberList = memberList->AsParseNodeBin()->pnode2;
         }
 
         byteCodeGenerator->StartSubexpression(memberList);
-        EmitMemberNode(memberList, objectLocation, byteCodeGenerator, funcInfo, nullptr, useStore);
+        if (memberList->nop == knopEllipsis)
+        {
+            EmitObjectSpreadNode(memberList, objectLocation, byteCodeGenerator, funcInfo);
+        }
+        else
+        {
+            EmitMemberNode(memberList, objectLocation, byteCodeGenerator, funcInfo, nullptr, useStore);
+        }
         byteCodeGenerator->EndSubexpression(memberList);
     }
 }
@@ -10035,13 +10060,16 @@ void TrackMemberNodesInObjectForIntConstants(ByteCodeGenerator *byteCodeGenerato
     while (memberList != nullptr)
     {
         ParseNodePtr memberNode = memberList->nop == knopList ? memberList->AsParseNodeBin()->pnode1 : memberList;
-        ParseNodePtr memberNameNode = memberNode->AsParseNodeBin()->pnode1;
-        ParseNodePtr memberValNode = memberNode->AsParseNodeBin()->pnode2;
-
-        if (memberNameNode->nop != knopComputedName && memberValNode->nop == knopInt)
+        if (memberNode->nop != knopEllipsis)
         {
-            Js::PropertyId propertyId = memberNameNode->AsParseNodeStr()->pid->GetPropertyId();
-            TrackIntConstantsOnGlobalUserObject(byteCodeGenerator, true, propertyId);
+            ParseNodePtr memberNameNode = memberNode->AsParseNodeBin()->pnode1;
+            ParseNodePtr memberValNode = memberNode->AsParseNodeBin()->pnode2;
+
+            if (memberNameNode->nop != knopComputedName && memberValNode->nop == knopInt)
+            {
+                Js::PropertyId propertyId = memberNameNode->AsParseNodeStr()->pid->GetPropertyId();
+                TrackIntConstantsOnGlobalUserObject(byteCodeGenerator, true, propertyId);
+            }
         }
 
         memberList = memberList->nop == knopList ? memberList->AsParseNodeBin()->pnode2 : nullptr;
@@ -10239,7 +10267,7 @@ void Emit(ParseNode *pnode, ByteCodeGenerator *byteCodeGenerator, FuncInfo *func
     case knopEllipsis:
     {
         Emit(pnode->AsParseNodeUni()->pnode1, byteCodeGenerator, funcInfo, false);
-        // Transparently pass the location of the array.
+        // Transparently pass the location of the object or array.
         pnode->location = pnode->AsParseNodeUni()->pnode1->location;
         break;
     }

+ 2 - 0
lib/Runtime/ByteCode/OpCodes.h

@@ -827,6 +827,8 @@ MACRO_BACKEND_ONLY(     TrapIfMinIntOverNegOne, Reg3,       OpSideEffect)
 MACRO_BACKEND_ONLY(     TrapIfZero,         Reg3,           OpSideEffect)
 MACRO_BACKEND_ONLY(     TrapIfUnalignedAccess, Reg3,        OpSideEffect)
 
+MACRO_EXTEND_WMS(       SpreadObjectLiteral, Reg2,          OpSideEffect|OpHasImplicitCall)
+
 // All SIMD ops are backend only for non-asmjs.
 #define MACRO_SIMD(opcode, asmjsLayout, opCodeAttrAsmJs, OpCodeAttr, ...) MACRO_BACKEND_ONLY(opcode, Empty, OpCodeAttr)
 #define MACRO_SIMD_WMS(opcode, asmjsLayout, opCodeAttrAsmJs, OpCodeAttr, ...) MACRO_BACKEND_ONLY(opcode, Empty, OpCodeAttr)

+ 2 - 0
lib/Runtime/Language/InterpreterHandler.inl

@@ -396,6 +396,8 @@ EXDEF3_WMS(CUSTOM,                  ClearAttributes,            OP_ClearAttribut
 EXDEF3_WMS(CUSTOM,                  EmitTmpRegCount,            OP_EmitTmpRegCount, Unsigned1)
 #endif
 EXDEF2    (EMPTY,                   BeginBodyScope,             OP_BeginBodyScope)
+EXDEF2_WMS(A2toXXMem,               SpreadObjectLiteral,        JavascriptObject::SpreadObjectLiteral)
+
 
 #endif
 

+ 10 - 0
lib/Runtime/Language/InterpreterStackFrame.cpp

@@ -389,6 +389,16 @@
 
 #define PROCESS_A2toXXMemNonVar(name, func) PROCESS_A2toXXMemNonVar_COMMON(name, func,)
 
+#define PROCESS_A2toXXMem_COMMON(name, func, suffix) \
+    case OpCode::name: \
+    { \
+        PROCESS_READ_LAYOUT(name, Reg2, suffix); \
+        func(GetReg(playout->R0), GetReg(playout->R1), GetScriptContext()); \
+        break; \
+    }
+
+#define PROCESS_A2toXXMem(name, func) PROCESS_A2toXXMem_COMMON(name, func,)
+
 #define PROCESS_A1NonVarToA1_COMMON(name, func, suffix) \
     case OpCode::name: \
     { \

Fișier diff suprimat deoarece este prea mare
+ 1236 - 1210
lib/Runtime/Library/JavascriptObject.cpp


+ 12 - 4
lib/Runtime/Library/JavascriptObject.h

@@ -111,11 +111,17 @@ namespace Js
         static bool IsPrototypeOf(RecyclableObject* proto, RecyclableObject* obj, ScriptContext* scriptContext);
         static bool IsPrototypeOfStopAtProxy(RecyclableObject* proto, RecyclableObject* obj, ScriptContext* scriptContext);
 
+        static void SpreadObjectLiteral(Var source, Var to, ScriptContext* scriptContext);
+
     private:
-        template <bool tryCopy>
-        static void AssignHelper(Var fromArg, RecyclableObject* to, ScriptContext* scriptContext);
-        static void AssignForGenericObjects(RecyclableObject* from, RecyclableObject* to, ScriptContext* scriptContext);
-        static void AssignForProxyObjects(RecyclableObject* from, RecyclableObject* to, ScriptContext* scriptContext);
+        template <bool tryCopy, bool assign>
+        static void CopyDataPropertiesHelper(Var source, RecyclableObject* to, ScriptContext* scriptContext, PropertyId* excluded = nullptr, uint32 excludedLength = 0);
+        template <bool assign>
+        static void CopyDataPropertiesForGenericObjects(RecyclableObject* from, RecyclableObject* to, PropertyId* excluded, uint32 excludedLength, ScriptContext* scriptContext);
+        template <bool assign>
+        static void CopyDataPropertiesForProxyObjects(RecyclableObject* from, RecyclableObject* to, PropertyId* excluded, uint32 excludedLength, ScriptContext* scriptContext);
+
+        static BOOL CreateDataProperty(RecyclableObject* obj, PropertyId key, Var value, ScriptContext* scriptContext);
         static JavascriptArray* CreateKeysHelper(RecyclableObject* object, ScriptContext* scriptContext, BOOL enumNonEnumerable, bool includeSymbolProperties, bool includeStringProperties, bool includeSpecialProperties);
 
         static void ModifyGetterSetterFuncName(const PropertyRecord * propertyRecord, const PropertyDescriptor& descriptor, ScriptContext* scriptContext);
@@ -125,5 +131,7 @@ namespace Js
         static Var DefinePropertiesHelperForProxyObjects(RecyclableObject* object, RecyclableObject* properties, ScriptContext* scriptContext);
 
         static Var GetToStringTagValue(RecyclableObject *thisArg, ScriptContext *scriptContext);
+
+        
     };
 }

+ 47 - 0
test/Object/ObjectSpread_JIT.js

@@ -0,0 +1,47 @@
+//-------------------------------------------------------------------------------------------------------
+// Copyright (C) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
+//-------------------------------------------------------------------------------------------------------
+
+// Object Spread JIT unit tests
+
+if (this.WScript && this.WScript.LoadScriptFile) { // Check for running in ch
+    this.WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js");
+}
+
+var tests = [
+    {
+        name: "Test JIT basic behavior",
+        body: function() {
+            function f() {
+                return {...{a: 1}};
+            }
+            
+            f();
+            let obj = f();
+
+            assert.areEqual(1, obj.a);
+        }
+    },
+    {
+        name: "Test JIT bailout",
+        body: function() {
+            const obj = { a: 2 };
+            function f(x) {
+                const a = obj.a;
+                const unused = {...x};
+                return a + obj.a;
+            }
+
+            // Train it that ...x is not reentrant, so it emits code that assumes the second obj.a matches the first
+            const result = f({});
+            assert.areEqual(4, result);
+
+            // Now call with a getter and verify that it bails out when the previous assumption is invalidated
+            const reentrantResult = f({ get b() { obj.a = 3; } });
+            assert.areEqual(5, reentrantResult);
+        }
+    },
+];
+
+testRunner.runTests(tests, { verbose: WScript.Arguments[0] != "summary" });

+ 51 - 0
test/Object/ObjectSpread_Limits.js

@@ -0,0 +1,51 @@
+//-------------------------------------------------------------------------------------------------------
+// Copyright (C) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
+//-------------------------------------------------------------------------------------------------------
+
+// Object Spread unit tests
+
+if (this.WScript && this.WScript.LoadScriptFile) { // Check for running in ch
+    this.WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js");
+}
+
+var tests = [
+    // Disabled for now until decision on whether this should be included
+    // {
+    //     name: "Test Maximum Spread size",
+    //     body: function() {
+    //         let maxSize = 243019865;
+    //         let arr = new Array(maxSize);
+    //         for (i = 0; i < maxSize; i++) {
+    //             arr[i] = i;
+    //         }
+    //         let obj = {...arr};
+    //         assert.areEqual(maxSize, Object.keys(obj).length);
+    //         for(var propName in obj) {
+    //             propValue = obj[propName];
+    //             assert.areEqual(propName, propValue.toString());
+    //         }
+    //     }
+    // },
+    {
+        name: "Test Spread near array limits",
+        body: function() {
+            function testRange(start, end) {
+                let arr = [] ;
+                for (i = start; i < end; i++) {
+                    arr[i] = i;
+                }
+                let obj = {...arr};
+                assert.areEqual(end-start, Object.keys(obj).length);
+                for(var propName in obj) {
+                    propValue = obj[propName];
+                    assert.areEqual(propName, propValue.toString());
+                }
+            };
+            testRange(2**31-100, 2**31+100);
+            testRange(2**32-100, 2**32+100);
+        }
+    },
+];
+
+testRunner.runTests(tests, { verbose: WScript.Arguments[0] != "summary" });

+ 339 - 0
test/Object/ObjectSpread_Simple.js

@@ -0,0 +1,339 @@
+//-------------------------------------------------------------------------------------------------------
+// Copyright (C) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
+//-------------------------------------------------------------------------------------------------------
+
+// Object Spread unit tests
+
+if (this.WScript && this.WScript.LoadScriptFile) { // Check for running in ch
+    this.WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js");
+}
+
+function verifyPropertyDesc(obj, prop, desc, propName) {
+    var actualDesc = Object.getOwnPropertyDescriptor(obj, prop);
+    if (typeof propName === "undefined") { propName = prop; }
+    assert.areEqual(desc.configurable, actualDesc.configurable, propName+"'s attribute: configurable");
+    assert.areEqual(desc.enumerable, actualDesc.enumerable, propName+"'s attribute: enumerable");
+    assert.areEqual(desc.writable, actualDesc.writable, propName+"'s attribute: writable");
+}
+
+var a = {i: 1, j: 2};
+var b = {x: 3, y: 4, z: 5};
+var c = {foo: 6};
+
+var tests = [
+    {
+        name: "Test shallow cloning",
+        body: function() {
+            let aClone = {...a};
+            assert.areEqual(1, aClone.i);
+            assert.areEqual(2, aClone.j);
+            assert.areEqual(2, Object.keys(aClone).length);
+        }
+    },
+    {
+        name: "Test Spread Object in parens",
+        body: function() {
+            let aClone = {...(a)};
+            assert.areEqual(1, aClone.i);
+            assert.areEqual(2, aClone.j);
+            assert.areEqual(2, Object.keys(aClone).length);
+        }
+    },
+    {
+        name: "Test merging 2 objects",
+        body: function() {
+            let merged = {...a, ...b};
+            assert.areEqual(1, merged.i);
+            assert.areEqual(2, merged.j);
+            assert.areEqual(3, merged.x);
+            assert.areEqual(4, merged.y);
+            assert.areEqual(5, merged.z);
+            assert.areEqual(5, Object.keys(merged).length);
+        }
+    },
+    {
+        name: "Test merging default properties with another object",
+        body: function() {
+            let merged = {i: 1, ...c};
+            assert.areEqual(1, merged.i);
+            assert.areEqual(6, merged.foo);
+            assert.areEqual(2, Object.keys(merged).length);
+            assert.areEqual(1, Object.keys(c).length);
+
+            // Order should not matter in this case
+            merged = {...c, i: 1};
+            assert.areEqual(1, merged.i);
+            assert.areEqual(6, merged.foo);
+            assert.areEqual(2, Object.keys(merged).length);
+            assert.areEqual(1, Object.keys(c).length);
+        }
+    },
+    {
+        name: "Test overrides",
+        body: function() {
+            let over = {i: 10, j: 11, ...a};
+            assert.areEqual(1, over.i);
+            assert.areEqual(2, over.j);
+            assert.areEqual(2, Object.keys(over).length);
+
+            over = {...a, i: 10, j: 11};
+            assert.areEqual(10, over.i);
+            assert.areEqual(11, over.j);
+            assert.areEqual(2, Object.keys(over).length);
+
+            over = {...a, ...{i: 10, j: 11}};
+            assert.areEqual(10, over.i);
+            assert.areEqual(11, over.j);
+            assert.areEqual(2, Object.keys(over).length);
+
+            let i = 10, j = 11;
+            over = {...a, i, j};
+            assert.areEqual(10, over.i);
+            assert.areEqual(11, over.j);
+            assert.areEqual(2, Object.keys(over).length);
+        }
+    },
+    {
+        name: "Getters in Object Literal should not be evaluated",
+        body: function() {
+            let getterExecuted = false;
+            let objWithGetter = {get i() {getterExecuted = true;}, ...c};
+            assert.areEqual(6, objWithGetter.foo);
+            assert.isFalse(getterExecuted);
+            assert.areEqual(2, Object.keys(objWithGetter).length);
+            assert.isTrue(objWithGetter.hasOwnProperty("i"));
+        }
+    },
+    {
+        name: "Getters in Spread Object should be evaluated",
+        body: function() {
+            let getterExecuted = false;
+            let obj = {i: 1, ...{get j() {getterExecuted = true; return 2;}}};
+            assert.areEqual(1, obj.i);
+            assert.isTrue(getterExecuted);
+            assert.areEqual(2, obj.j);
+            assert.areEqual(2, Object.keys(obj).length);
+        }
+    },
+    {
+        name: "Test Spread Object modifying itself",
+        body: function() {
+            let val = 1;
+            let source = {get i() {val++; return 1;}, get j() {return val;}};
+            let obj = {...source};
+            assert.areEqual(1, obj.i);
+            assert.areEqual(2, obj.j);
+            assert.areEqual(2, Object.keys(obj).length);
+        }
+    },
+    {
+        name: "Test Spread Object modified by other Spread Object",
+        body: function() {
+            let a = {i: 1};
+            let b = {get j() {a.i = 3; return 2;}};
+            let obj = {...b, ...a};
+            assert.areEqual(3, obj.i);
+            assert.areEqual(2, obj.j);
+            assert.areEqual(2, Object.keys(obj).length);
+        }
+    },
+    {
+        name: "Test multiple merges of same object",
+        body: function() {
+            let getterExecutions = 0;
+            let objWithGetter = {get i() {getterExecutions++; return 1;}};
+            let merged = {a: 2, ...objWithGetter, b: 3, ...objWithGetter};
+            assert.areEqual(2, merged.a);
+            assert.areEqual(3, merged.b);
+            assert.areEqual(1, merged.i);
+            assert.areEqual(3, Object.keys(merged).length);
+            assert.areEqual(2, getterExecutions, "Getters should be executed twice, once for each `...`");
+        }
+    },
+    {
+        name: "Setters should not be called in Object Literal",
+        body: function() {
+            let setterExecuted = false;
+            let objWithSetter = {set foo(v) {setterExecuted = true;}, ...c};
+            assert.areEqual(6, objWithSetter.foo);
+            assert.areEqual(1, Object.keys(objWithSetter).length);
+            assert.isFalse(setterExecuted);
+        }
+    },
+    {
+        name: "Null and Undefined should be ignored",
+        body: function() {
+            let empty = {...null};
+            assert.areEqual(0, Object.keys(empty).length);
+
+            empty = {...undefined};
+            assert.areEqual(0, Object.keys(empty).length);
+        }
+    },
+    {
+        name: "Test nesting",
+        body: function() {
+            let base = {name: "foo", prev: {}, num: 5};
+            let derived = {...base, name: "bar", prev: {...base}};
+            assert.areEqual("foo", base.name);
+            assert.areEqual({}, base.prev);
+            assert.areEqual(5, base.num);
+            assert.areEqual(3, Object.keys(base).length);
+            assert.areEqual("bar", derived.name);
+            assert.areEqual("foo", derived.prev.name);
+            assert.areEqual({}, derived.prev.prev);
+            assert.areEqual(5, derived.prev.num);
+            assert.areEqual(5, derived.num);
+            assert.areEqual(3, Object.keys(derived).length);
+        }
+    },
+    {
+        name: "Test Spread with computed property names in Object Literal",
+        body: function() {
+            let obj = {[5]: 5, ["bar"]: 2, ...c};
+            assert.areEqual(5, obj[5]);
+            assert.areEqual(2, obj.bar);
+            assert.areEqual(6, obj.foo);
+            assert.areEqual(3, Object.keys(obj).length);
+        }
+    },
+    {
+        name: "Test Spread with functions in Object Literal",
+        body: function() {
+            let obj = {func() {return true;}, ...c};
+            assert.areEqual(6, obj.foo);
+            assert.isTrue(obj.hasOwnProperty("func"));
+            assert.areEqual(2, Object.keys(obj).length);
+        }
+    },
+    {
+        name: "Property Descriptors from Spread should be default",
+        body: function() {
+            let obj = {...c};
+            let defaultDesc = {
+                configurable: true,
+                enumerable: true,
+                writable: true
+            };
+            verifyPropertyDesc(obj, "foo", defaultDesc);
+        }
+    },
+    {
+        name: "Copy only own properties",
+        body: function() {
+            let parent = {i: 1, j: 2};
+            let child = Object.create(parent);
+            child.i = 3;
+            let obj = {...child};
+
+            assert.areEqual(3, child.i);
+            assert.areEqual(2, child.j);
+            assert.areEqual(3, obj.i);
+            assert.isFalse(obj.hasOwnProperty("j"));
+        }
+    },
+    {
+        name: "Spread includes symbols in properties",
+        body: function() {
+            let sym = Symbol("foo");
+            let a = {};
+            a[sym] = 1;
+            let obj = {...a};
+            assert.areEqual(1, obj[sym], "property with Symbol property name identifier should be copied over");
+            assert.areEqual(1, Object.getOwnPropertySymbols(obj).length);
+        }
+    },
+    {
+        name: "Spread after assignment",
+        body: function() {
+            let temp = {};
+            let obj = {...temp=a};
+            assert.areEqual(2, Object.keys(obj).length);
+            assert.areEqual(1, obj.i);
+            assert.areEqual(2, obj.j);
+
+            obj = {...temp=1};
+            assert.areEqual(0, Object.keys(obj).length);
+        }
+    },
+    {
+        name: "Object Spread interacting with Array Spread",
+        body: function() {
+            let arr = [1, 2];
+            let obj = {...[...arr, 3]};
+            assert.areEqual(3, Object.keys(obj).length);
+            assert.areEqual(1, obj[0]);
+            assert.areEqual(2, obj[1]);
+            assert.areEqual(3, obj[2]);
+        }
+    },
+    {
+        name: "Object Spread interacting with Numbers",
+        body: function() {
+            let obj = {...1}
+            assert.areEqual(0, Object.keys(obj).length);
+        }
+    },
+    {
+        name: "Object Spread interacting with Functions",
+        body: function() {
+            let obj = {...function i() {return 1;}}
+            assert.areEqual(0, Object.keys(obj).length);
+        }
+    },
+    {
+        name: "Object Spread interacting with Strings",
+        body: function() {
+            let obj = {..."edge"};
+            assert.areEqual(4, Object.keys(obj).length);
+            assert.areEqual("e", obj[0]);
+            assert.areEqual("d", obj[1]);
+            assert.areEqual("g", obj[2]);
+            assert.areEqual("e", obj[3]);
+        }
+    },
+    {
+        name: "Test Proxy Object",
+        body: function() {
+            let proxy = new Proxy({i: 1, j: 2}, {});
+            let obj = {...proxy};
+            assert.areEqual(2, Object.keys(obj).length);
+            assert.areEqual(1, obj.i);
+            assert.areEqual(2, obj.j);
+        }
+    },
+    {
+        name: "Test Proxy Object with custom getter",
+        body: function() {
+            let handler = {get: function(obj, prop) {return obj[prop];}};
+            let proxy = new Proxy({i: 1, j: 2}, handler);
+            let obj = {...proxy};
+            assert.areEqual(2, Object.keys(obj).length);
+            assert.areEqual(1, obj.i);
+            assert.areEqual(2, obj.j);
+        }
+    },
+    {
+        name: "Test Proxy Object with custom getter and setter",
+        body: function() {
+            let setterCalled = false;
+            let handler = {
+                get: function(obj, prop) {
+                    return obj[prop];
+                },
+                set: function(obj, prop, value) {
+                    setterCalled = true;
+                }
+            };
+            let proxy = new Proxy({i: 1, j: 2}, handler);
+            let obj = {...proxy};
+            assert.areEqual(2, Object.keys(obj).length);
+            assert.areEqual(1, obj.i);
+            assert.areEqual(2, obj.j);
+            assert.isFalse(setterCalled);
+        }
+    },
+];
+
+testRunner.runTests(tests, { verbose: WScript.Arguments[0] != "summary" });

+ 18 - 0
test/Object/rlexe.xml

@@ -450,4 +450,22 @@
       <baseline>assign.baseline</baseline>
     </default>
   </test>
+  <test>
+    <default>
+      <files>ObjectSpread_Simple.js</files>
+      <compile-flags>-args summary -endargs -NoNative -ES2018ObjectSpread</compile-flags>
+    </default>
+  </test>
+  <test>
+    <default>
+      <files>ObjectSpread_JIT.js</files>
+      <compile-flags>-args summary -endargs -ES2018ObjectSpread -bgjit -maxinterpretcount:1 -off:simplejit</compile-flags>
+    </default>
+  </test>
+  <test>
+    <default>
+      <files>ObjectSpread_Limits.js</files>
+      <compile-flags>-args summary -endargs -ES2018ObjectSpread</compile-flags>
+    </default>
+  </test>
 </regress-exe>

Unele fișiere nu au fost afișate deoarece prea multe fișiere au fost modificate în acest diff