Skip to content

Commit

Permalink
Implement Object.fromEntries
Browse files Browse the repository at this point in the history
  • Loading branch information
rhuanjl committed Aug 23, 2018
1 parent a21da6b commit 1ffeec2
Show file tree
Hide file tree
Showing 15 changed files with 23,877 additions and 23,258 deletions.
3 changes: 1 addition & 2 deletions lib/Runtime/Base/JnDirectFields.h
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,6 @@ ENTRY(every)
ENTRY(exec)
ENTRY2(false_, _u("false")) // "false" cannot be an identifier in C++ so using "false_" instead
ENTRY(flags)
ENTRY(flat)
ENTRY(flatMap)
ENTRY(fill)
ENTRY(filter)
ENTRY(finally)
Expand Down Expand Up @@ -605,6 +603,7 @@ ENTRY(InitInternalProperties)
ENTRY(methodName)
ENTRY(registerChakraLibraryFunction)
ENTRY(registerFunction)
ENTRY(staticMethod)
ENTRY(toLength)
ENTRY(toInteger)
ENTRY(arraySpeciesCreate)
Expand Down
11,002 changes: 5,501 additions & 5,501 deletions lib/Runtime/Library/InJavascript/Intl.js.bc.32b.h

Large diffs are not rendered by default.

11,005 changes: 5,502 additions & 5,503 deletions lib/Runtime/Library/InJavascript/Intl.js.bc.64b.h

Large diffs are not rendered by default.

9,640 changes: 4,820 additions & 4,820 deletions lib/Runtime/Library/InJavascript/Intl.js.nojit.bc.32b.h

Large diffs are not rendered by default.

9,633 changes: 4,816 additions & 4,817 deletions lib/Runtime/Library/InJavascript/Intl.js.nojit.bc.64b.h

Large diffs are not rendered by default.

9 changes: 8 additions & 1 deletion lib/Runtime/Library/JavascriptLibrary.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3610,7 +3610,7 @@ namespace Js
// so that the update is in sync with profiler
JavascriptLibrary* library = objectConstructor->GetLibrary();
ScriptContext* scriptContext = objectConstructor->GetScriptContext();
int propertyCount = 18;
int propertyCount = 19;
if (scriptContext->GetConfig()->IsES6ObjectExtensionsEnabled())
{
propertyCount += 2;
Expand Down Expand Up @@ -3677,6 +3677,13 @@ namespace Js
library->AddFunctionToLibraryObject(objectConstructor, PropertyIds::entries, &JavascriptObject::EntryInfo::Entries, 1));
}

#ifdef ENABLE_JS_BUILTINS
if (scriptContext->IsJsBuiltInEnabled())
{
library->EnsureBuiltInEngineIsReady();
}
#endif

objectConstructor->SetHasNoEnumerableProperties(true);

return true;
Expand Down
30 changes: 30 additions & 0 deletions lib/Runtime/Library/JsBuiltIn/JsBuiltIn.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
ArrayFilter: { className: "Array", methodName: "filter", argumentsCount: 1, forceInline: true /*optional*/ },
ArrayFlat: { className: "Array", methodName: "flat", argumentsCount: 0, forceInline: true /*optional*/ },
ArrayFlatMap: { className: "Array", methodName: "flatMap", argumentsCount: 1, forceInline: true /*optional*/ },
ObjectFromEntries: { className: "Object", staticMethod: true, methodName: "fromEntries", argumentsCount: 1, forceInline: true /*optional*/ },
};

var setPrototype = platform.builtInSetPrototype;
Expand All @@ -38,10 +39,12 @@
__chakraLibrary.ArrayIterator.prototype = CreateObject(iteratorPrototype);
__chakraLibrary.raiseNeedObjectOfType = platform.raiseNeedObjectOfType;
__chakraLibrary.raiseThis_NullOrUndefined = platform.raiseThis_NullOrUndefined;
__chakraLibrary.raiseNeedObject = platform.raiseNeedObject;
__chakraLibrary.raiseLengthIsTooBig = platform.raiseLengthIsTooBig;
__chakraLibrary.raiseFunctionArgument_NeedFunction = platform.raiseFunctionArgument_NeedFunction;
__chakraLibrary.callInstanceFunc = platform.builtInCallInstanceFunction;
__chakraLibrary.functionBind = platform.builtInJavascriptFunctionEntryBind;
__chakraLibrary.objectDefineProperty = _objectDefineProperty;

_objectDefineProperty(__chakraLibrary.ArrayIterator.prototype, 'next',
// Object's getter and setter can get overriden on the prototype, in that case while setting the value attributes, we will end up with TypeError
Expand Down Expand Up @@ -415,4 +418,31 @@
return A;
});

platform.registerFunction(FunctionsEnum.ObjectFromEntries, function (iterable) {
// #sec-object.fromentries
"use strict";
if (this === null || this === undefined) {
__chakraLibrary.raiseNeedObject("Object.prototype.fromEntries");
}

const o = {};
const propDescriptor = {
enumerable : true,
configurable : true,
writable : true,
value : undefined
};

let key;
for (const entry of iterable) {
if (typeof entry !== "object") {
__chakraLibrary.raiseNeedObject("Object.prototype.fromEntries");
}

key = entry[0];
propDescriptor.value = entry[1];
__chakraLibrary.objectDefineProperty(o, key, propDescriptor);
}
return o;
});
});
1,463 changes: 785 additions & 678 deletions lib/Runtime/Library/JsBuiltIn/JsBuiltIn.js.bc.32b.h

Large diffs are not rendered by default.

1,463 changes: 785 additions & 678 deletions lib/Runtime/Library/JsBuiltIn/JsBuiltIn.js.bc.64b.h

Large diffs are not rendered by default.

1,357 changes: 730 additions & 627 deletions lib/Runtime/Library/JsBuiltIn/JsBuiltIn.js.nojit.bc.32b.h

Large diffs are not rendered by default.

1,357 changes: 730 additions & 627 deletions lib/Runtime/Library/JsBuiltIn/JsBuiltIn.js.nojit.bc.64b.h

Large diffs are not rendered by default.

29 changes: 26 additions & 3 deletions lib/Runtime/Library/JsBuiltInEngineInterfaceExtensionObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -224,14 +224,35 @@ namespace Js
}
}

DynamicObject* JsBuiltInEngineInterfaceExtensionObject::GetPrototypeFromName(Js::PropertyIds propertyId, ScriptContext* scriptContext)
DynamicObject* JsBuiltInEngineInterfaceExtensionObject::GetPrototypeFromName(Js::PropertyIds propertyId, BOOL staticMethod, ScriptContext* scriptContext)
{
JavascriptLibrary* library = scriptContext->GetLibrary();

if (staticMethod)
{
switch (propertyId) {
case PropertyIds::Array:
return library->arrayConstructor;

case PropertyIds::Object:
return library->objectConstructor;

case PropertyIds::String:
return library->stringConstructor;

default:
AssertMsg(false, "Unable to find a constructor that match with this className.");
return nullptr;
}
}

switch (propertyId) {
case PropertyIds::Array:
return library->arrayPrototype;

case PropertyIds::Object:
return library->objectPrototype;

case PropertyIds::String:
return library->stringPrototype;

Expand Down Expand Up @@ -297,7 +318,7 @@ namespace Js
func->GetFunctionProxy()->SetIsPublicLibraryCode();
func->GetFunctionProxy()->EnsureDeserialized()->SetDisplayName(methodName->GetString(), methodName->GetLength(), 0);

DynamicObject* chakraLibraryObject = GetPrototypeFromName(PropertyIds::__chakraLibrary, scriptContext);
DynamicObject* chakraLibraryObject = GetPrototypeFromName(PropertyIds::__chakraLibrary, false, scriptContext);
PropertyIds functionIdentifier = JavascriptOperators::GetPropertyId(methodName, scriptContext);

// Link the function to __chakraLibrary.
Expand Down Expand Up @@ -335,6 +356,7 @@ namespace Js
Var argumentsCountProperty = JavascriptOperators::OP_GetProperty(funcInfo, Js::PropertyIds::argumentsCount, scriptContext);
Var forceInlineProperty = JavascriptOperators::OP_GetProperty(funcInfo, Js::PropertyIds::forceInline, scriptContext);
Var aliasProperty = JavascriptOperators::OP_GetProperty(funcInfo, Js::PropertyIds::alias, scriptContext);
Var staticMethodProperty = JavascriptOperators::OP_GetProperty(funcInfo, Js::PropertyIds::staticMethod, scriptContext);

Assert(JavascriptString::Is(classNameProperty));
Assert(JavascriptString::Is(methodNameProperty));
Expand All @@ -344,6 +366,7 @@ namespace Js
JavascriptString* methodName = JavascriptString::FromVar(methodNameProperty);
int argumentsCount = TaggedInt::ToInt32(argumentsCountProperty);

BOOL staticMethod = JavascriptConversion::ToBoolean(staticMethodProperty, scriptContext);
BOOL forceInline = JavascriptConversion::ToBoolean(forceInlineProperty, scriptContext);

JavascriptFunction* func = JavascriptFunction::FromVar(args.Values[2]);
Expand All @@ -352,7 +375,7 @@ namespace Js
func->GetFunctionProxy()->SetIsPublicLibraryCode();
func->GetFunctionProxy()->EnsureDeserialized()->SetDisplayName(methodName->GetString(), methodName->GetLength(), 0);

DynamicObject* prototype = GetPrototypeFromName(JavascriptOperators::GetPropertyId(className, scriptContext), scriptContext);
DynamicObject* prototype = GetPrototypeFromName(JavascriptOperators::GetPropertyId(className, scriptContext), staticMethod, scriptContext);
PropertyIds functionIdentifier = methodName->BufferEquals(_u("Symbol.iterator"), 15)? PropertyIds::_symbolIterator :
JavascriptOperators::GetPropertyId(methodName, scriptContext);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ namespace Js

void EnsureJsBuiltInByteCode(ScriptContext * scriptContext);

static DynamicObject* GetPrototypeFromName(Js::PropertyIds propertyId, ScriptContext* scriptContext);
static DynamicObject* GetPrototypeFromName(Js::PropertyIds propertyId, BOOL staticMethod, ScriptContext* scriptContext);
static void RecordDefaultIteratorFunctions(Js::PropertyIds propertyId, ScriptContext * scriptContext, JavascriptFunction* iteratorFunc);
static void RecordCommonNativeInterfaceBuiltIns(Js::PropertyIds propertyId, ScriptContext * scriptContext, JavascriptFunction * scriptFunction);
static Var EntryJsBuiltIn_RegisterChakraLibraryFunction(RecyclableObject* function, CallInfo callInfo, ...);
Expand Down
136 changes: 136 additions & 0 deletions test/Object/fromEntries.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------

WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js");

function verifyProperties(obj, property, value)
{
const descriptor = Object.getOwnPropertyDescriptor(obj, property);
assert.areEqual(value, obj[property], "Object.fromEntries should set correct valued");
assert.isTrue(descriptor.enumerable, "Object.fromEntries should create enumerable properties");
assert.isTrue(descriptor.configurable, "Object.fromEntries should create configurable properties");
assert.isTrue(descriptor.writable, "Object.fromEntries should create writable properties");
obj[property] = "other value";
assert.areEqual("other value", obj[property], "should actually be able to write to properties created by Object.fromEntries");
assert.doesNotThrow(()=>{"use strict"; delete obj[property];}, "deleting properties created by Object.fromEntries should not throw");
assert.isUndefined(obj[property], "deleting properties created by Object.fromEntries should succeed");
}

function verifyObject(expected, actual)
{
for (let i in expected)
{
assert.isTrue(actual.hasOwnProperty(i), "Object.fromEntries shouldn't create unexpected properties");
}
for (let i in expected)
{
verifyProperties(actual, i, expected[i]);
}
}


const tests = [
{
name : "Object.fromEntries invalid parameters",
body : function () {
assert.throws(()=>{Object.fromEntries(null);}, TypeError, "Object.fromEntries throws when called with null parameter");
assert.throws(()=>{Object.fromEntries(undefined);}, TypeError, "Object.fromEntries throws when called with undefined parameter");
assert.throws(()=>{Object.fromEntries("something");}, TypeError, "Object.fromEntries throws when called with string literal parameter");
assert.throws(()=>{Object.fromEntries(456);}, TypeError, "Object.fromEntries throws when called with number literal parameter");
assert.throws(()=>{Object.fromEntries(Number());}, TypeError, "Object.fromEntries throws when called with Number Object parameter");
assert.doesNotThrow(()=>{Object.fromEntries(String());}, "Object.fromEntries does not throw when called with String Object parameter with length 0");
assert.throws(()=>{Object.fromEntries(String("anything"));}, TypeError, "Object.fromEntries throws when called with String Object parameter with length > 0");
assert.throws(()=>{Object.fromEntries({});}, TypeError, "Object.fromEntries throws when called with Object literal");
assert.throws(()=>{Object.fromEntries({a : "5", b : "10"});}, TypeError, "Object.fromEntries throws when called with Object literal");
}
},
{
name : "Object.fromEntries basic cases",
body : function () {
const obj1 = Object.fromEntries([["first", 50], ["second", 30], ["third", 60], ["fourth", 70]]);
verifyObject({first : 50, second : 30, third : 60, fourth : 70}, obj1);
const obj2 = Object.fromEntries([Object("a12234"),Object("b2kls"),Object("c3deg")]);
verifyObject({a : "1", b : "2", c : "3"}, obj2);
}
},
{
name : "Object.fromEntries does not call setters",
body : function () {
let calledSet = false;
Object.defineProperty(Object.prototype, "prop", {
set : function () { calledSet = true; }
});
const obj = Object.fromEntries([["prop", 10]]);
verifyProperties(obj, "prop", 10);
assert.isFalse(calledSet, "Object.fromEntries should not call setters");
}
},
{
name : "Object.fromEntries iterates over generators",
body : function () {
function* gen1 ()
{
yield ["val1", 10];
yield ["val2", 50];
yield ["val3", 60, "other stuff"];
}
const obj = Object.fromEntries(gen1());
verifyObject({val1 : 10, val2 : 50, val3: 60}, obj);
let unreachable = false;
function* gen2 ()
{
yield ["val1", 10];
yield "val2";
unreachable = true;
yield ["val3", 60, "other stuff"];
}
assert.throws(()=>{Object.fromEntries(gen2())}, TypeError, "When generator provides invalid case Object.fromEntries should throw");
assert.isFalse(unreachable, "Object.fromEntries does not continue after invalid case provided");
}
},
{
name : "Object.fromEntries access properties in correct order",
body : function () {
const accessedProps = [];
const handler = {
get : function (target, prop, receiver) {
accessedProps.push(prop + Reflect.get(target, prop));
return Reflect.get(target, prop);
}
}

function* gen () {
yield new Proxy(["a", "b", "c"], handler);
yield new Proxy(["e", "g", "h", "j"], handler);
}
const obj = Object.fromEntries(gen());
verifyObject({a : "b", e : "g"}, obj);
const expected = ["0a", "1b", "0e", "1g"];
const len = accessedProps.length;
assert.areEqual(4, len, "Object.fromEntries accesses correct number of properties");
for (let i = 0; i < len; ++i)
{
assert.areEqual(expected[i], accessedProps[i], "Object.fromEntries accesses the correct properties");
}
}
},
{
name : "Object.fromEntries properties",
body : function () {
assert.areEqual("fromEntries", Object.fromEntries.name, "Object.fromEntries.name should be 'fromEntries'");
assert.areEqual(1, Object.fromEntries.length, "Object.fromEntries.length should be 1");
const descriptor = Object.getOwnPropertyDescriptor(Object, "fromEntries");
assert.isFalse(descriptor.enumerable, "Object.fromEntries should be enumerable");
assert.isTrue(descriptor.writable, "Object.fromEntries should be writable");
assert.isTrue(descriptor.configurable, "Object.fromEntries should be configurable");
assert.doesNotThrow(()=>{"use strict"; delete Object.fromEntries.length;}, "Deleting Object.fromEntries.length should succeed");
assert.areEqual(0, Object.fromEntries.length, "After deletion Object.fromEntries.length should be 0");
assert.doesNotThrow(()=>{"use strict"; delete Object.fromEntries;}, "Deleting Object.fromEntries should succeed");
assert.isUndefined(Object.fromEntries, "After deletion Object.fromEntries should be undefined");
}
}
];

testRunner.runTests(tests, { verbose: WScript.Arguments[0] != "summary" });
6 changes: 6 additions & 0 deletions test/Object/rlexe.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@
<baseline>hasOwnProperty.baseline</baseline>
</default>
</test>
<test>
<default>
<files>fromEntries.js</files>
<compile-flags>-args summary -endargs</compile-flags>
</default>
</test>
<test>
<default>
<files>isEnumerable.js</files>
Expand Down

0 comments on commit 1ffeec2

Please sign in to comment.