From bf450f44c6420c09ef665be5e266c38371740510 Mon Sep 17 00:00:00 2001 From: Suwei Chen Date: Mon, 25 Jul 2016 14:06:11 -0700 Subject: [PATCH] Enable es6toprimitive under experimental Enable es6toprimitive under experimental flag. Update following areas to be spec-compliant: ToPrimitive (abstract operation) Date.prototype[@@toprimitive]() Symbol.prototype[@@toprimitive]() Update unit tests with coverage on: ToNumber (abstract operation) ToString (abstract operation) ToPropertyKey (abstract operation) Abstract rational comparison (abstract operation) Abstract equality comparison (abstract operation) + operator Date() constructor Date.prototype.toJSON() --- lib/Common/ConfigFlagsList.h | 5 +- lib/Parser/rterrors.h | 3 + lib/Runtime/Language/JavascriptConversion.cpp | 6 +- lib/Runtime/Library/JavascriptDate.cpp | 12 +- lib/Runtime/Library/JavascriptSymbol.cpp | 2 +- test/es6/ES6Symbol.js | 17 +- test/es6/rlexe.xml | 2 +- test/es6/toPrimitive.js | 329 +++++++++++++++++- 8 files changed, 351 insertions(+), 25 deletions(-) diff --git a/lib/Common/ConfigFlagsList.h b/lib/Common/ConfigFlagsList.h index 3bd61e8b111..00233ec6107 100644 --- a/lib/Common/ConfigFlagsList.h +++ b/lib/Common/ConfigFlagsList.h @@ -976,7 +976,10 @@ FLAGPR (Boolean, ES6, ES6StringPrototypeFixes, "Enable ES6 String.prot #define COMPILE_DISABLE_ES6PrototypeChain 0 #endif FLAGPR_REGOVR_EXP(Boolean, ES6, ES6PrototypeChain , "Enable ES6 prototypes (Example: Date prototype is object)", DEFAULT_CONFIG_ES6PrototypeChain) -FLAGPR (Boolean, ES6, ES6ToPrimitive , "Enable ES6 ToPrimitive symbol" , DEFAULT_CONFIG_ES6ToPrimitive) +#ifndef COMPILE_DISABLE_ES6ToPrimitive + #define COMPILE_DISABLE_ES6ToPrimitive 0 +#endif +FLAGPR_REGOVR_EXP(Boolean, ES6, ES6ToPrimitive , "Enable ES6 ToPrimitive symbol" , DEFAULT_CONFIG_ES6ToPrimitive) FLAGPR (Boolean, ES6, ES6ToLength , "Enable ES6 ToLength fixes" , DEFAULT_CONFIG_ES6ToLength) FLAGPR (Boolean, ES6, ES6ToStringTag , "Enable ES6 ToStringTag symbol" , DEFAULT_CONFIG_ES6ToStringTag) FLAGPR (Boolean, ES6, ES6TypedArrayExtensions, "Enable ES6 TypedArray extensions" , DEFAULT_CONFIG_ES6TypedArrayExtensions) diff --git a/lib/Parser/rterrors.h b/lib/Parser/rterrors.h index 8b82e1110e8..2ec9269618b 100644 --- a/lib/Parser/rterrors.h +++ b/lib/Parser/rterrors.h @@ -353,3 +353,6 @@ RT_ERROR_MSG(JSERR_JsonBadNumber, 5654, "JSON.parse Error: Invalid number at pos RT_ERROR_MSG(JSERR_JsonIllegalChar, 5655, "JSON.parse Error: Invalid character at position:%s", "JSON.parse syntax error", kjstTypeError, 0) RT_ERROR_MSG(JSERR_JsonBadHexDigit, 5656, "JSON.parse Error: Expected hexadecimal digit at position:%s", "JSON.parse syntax error", kjstTypeError, 0) RT_ERROR_MSG(JSERR_JsonNoStrEnd, 5657, "JSON.parse Error: Unterminated string constant at position:%s", "JSON.parse syntax error", kjstTypeError, 0) + +// Date.prototype[@@toPrimitive] invalid hint +RT_ERROR_MSG(JSERR_InvalidHint, 5658, "%s: invalid hint", "invalid hint", kjstTypeError, 0) diff --git a/lib/Runtime/Language/JavascriptConversion.cpp b/lib/Runtime/Language/JavascriptConversion.cpp index 4cad33894b3..d9ca8f01905 100644 --- a/lib/Runtime/Language/JavascriptConversion.cpp +++ b/lib/Runtime/Language/JavascriptConversion.cpp @@ -525,17 +525,17 @@ namespace Js return CALL_FUNCTION(exoticToPrim, CallInfo(CallFlags_Value, 2), recyclableObject, hintString); }); - Assert(!CrossSite::NeedMarshalVar(result, requestContext)); - if (!result) { // There was an implicit call and implicit calls are disabled. This would typically cause a bailout. Assert(threadContext->IsDisableImplicitCall()); return requestContext->GetLibrary()->GetNull(); } + + Assert(!CrossSite::NeedMarshalVar(result, requestContext)); } // If result is an ECMAScript language value and Type(result) is not Object, then return result. - if (TaggedInt::Is(result) || JavascriptOperators::IsExposedType(JavascriptOperators::GetTypeId(result))) + if (TaggedInt::Is(result) || !JavascriptOperators::IsObjectType(JavascriptOperators::GetTypeId(result))) { return result; } diff --git a/lib/Runtime/Library/JavascriptDate.cpp b/lib/Runtime/Library/JavascriptDate.cpp index 3f4c9be5f85..c1b713d401b 100644 --- a/lib/Runtime/Library/JavascriptDate.cpp +++ b/lib/Runtime/Library/JavascriptDate.cpp @@ -247,6 +247,11 @@ namespace Js //The allowed values for hint are "default", "number", and "string" if (args.Info.Count == 2) { + if (!JavascriptOperators::IsObjectType(JavascriptOperators::GetTypeId(args[0]))) + { + JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NeedObject, _u("Date[Symbol.toPrimitive]")); + } + if (JavascriptString::Is(args[1])) { JavascriptString* StringObject = JavascriptString::FromVar(args[1]); @@ -267,13 +272,8 @@ namespace Js //anything else should throw a type error } } - else if (args.Info.Count == 1) - { - //7.1.1 ToPrimitive Note: Date objects treat no hint as if the hint were String. - return JavascriptConversion::OrdinaryToPrimitive(args[0], JavascriptHint::HintString/*tryFirst*/, scriptContext); - } - JavascriptError::ThrowTypeError(scriptContext, JSERR_FunctionArgument_Invalid, _u("Date[Symbol.toPrimitive]")); + JavascriptError::ThrowTypeError(scriptContext, JSERR_InvalidHint, _u("Date[Symbol.toPrimitive]")); } Var JavascriptDate::EntryGetDate(RecyclableObject* function, CallInfo callInfo, ...) diff --git a/lib/Runtime/Library/JavascriptSymbol.cpp b/lib/Runtime/Library/JavascriptSymbol.cpp index e8490a6c774..677e2c50abe 100644 --- a/lib/Runtime/Library/JavascriptSymbol.cpp +++ b/lib/Runtime/Library/JavascriptSymbol.cpp @@ -203,7 +203,7 @@ namespace Js } else { - JavascriptError::ThrowTypeError(scriptContext, JSERR_FunctionArgument_Invalid, _u("Symbol[Symbol.toPrimitive]")); + JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NeedSymbol, _u("Symbol[Symbol.toPrimitive]")); } } diff --git a/test/es6/ES6Symbol.js b/test/es6/ES6Symbol.js index cc55b227c17..aa8f7abd3ab 100644 --- a/test/es6/ES6Symbol.js +++ b/test/es6/ES6Symbol.js @@ -137,12 +137,17 @@ var tests = [ assert.areEqual(x, Symbol.prototype[Symbol.toPrimitive].call(x), "x == Symbol.prototype[Symbol.toPrimitive].call(x)"); // TypeError scenarios - assert.throws(function () { x[Symbol.toPrimitive].call("x") }, TypeError, "x[Symbol.toPrimitive].call('x'), toPrimitive throws TypeError for values that does not have SymbolData", "Symbol[Symbol.toPrimitive]: invalid argument"); - assert.throws(function () { Symbol.prototype[Symbol.toPrimitive]() }, TypeError, "toPrimitive throws TypeError if no arguments are passed", "Symbol[Symbol.toPrimitive]: invalid argument"); - assert.throws(function () { Symbol.prototype[Symbol.toPrimitive].call("") }, TypeError, "toPrimitive throws TypeError for values that does not have SymbolData", "Symbol[Symbol.toPrimitive]: invalid argument"); - assert.throws(function () { Symbol.prototype[Symbol.toPrimitive].call(null) }, TypeError, "toPrimitive throws TypeError for null", "Symbol[Symbol.toPrimitive]: invalid argument"); - assert.throws(function () { Symbol.prototype[Symbol.toPrimitive].call(undefined) }, TypeError, "toPrimitive throws TypeError for undefined", "Symbol[Symbol.toPrimitive]: invalid argument"); - assert.throws(function () { Symbol.prototype[Symbol.toPrimitive].call({}) }, TypeError, "toPrimitive throws TypeError for object", "Symbol[Symbol.toPrimitive]: invalid argument"); + assert.throws(function () { x[Symbol.toPrimitive].call("x") }, TypeError, "x[Symbol.toPrimitive].call('x'), toPrimitive throws TypeError for values that does not have SymbolData", "Symbol[Symbol.toPrimitive]: 'this' is not a Symbol object"); + assert.throws(function () { Symbol.prototype[Symbol.toPrimitive]() }, TypeError, "toPrimitive throws TypeError if no arguments are passed", "Symbol[Symbol.toPrimitive]: 'this' is not a Symbol object"); + assert.throws(function () { Symbol.prototype[Symbol.toPrimitive].call(true) }, TypeError, "toPrimitive throws TypeError for boolean true", "Symbol[Symbol.toPrimitive]: 'this' is not a Symbol object"); + assert.throws(function () { Symbol.prototype[Symbol.toPrimitive].call(false) }, TypeError, "toPrimitive throws TypeError for boolean false", "Symbol[Symbol.toPrimitive]: 'this' is not a Symbol object"); + assert.throws(function () { Symbol.prototype[Symbol.toPrimitive].call(0) }, TypeError, "toPrimitive throws TypeError for number", "Symbol[Symbol.toPrimitive]: 'this' is not a Symbol object"); + assert.throws(function () { Symbol.prototype[Symbol.toPrimitive].call(NaN) }, TypeError, "toPrimitive throws TypeError for NaN", "Symbol[Symbol.toPrimitive]: 'this' is not a Symbol object"); + assert.throws(function () { Symbol.prototype[Symbol.toPrimitive].call("") }, TypeError, "toPrimitive throws TypeError for values that does not have SymbolData", "Symbol[Symbol.toPrimitive]: 'this' is not a Symbol object"); + assert.throws(function () { Symbol.prototype[Symbol.toPrimitive].call("abc") }, TypeError, "toPrimitive throws TypeError for values that does not have SymbolData", "Symbol[Symbol.toPrimitive]: 'this' is not a Symbol object"); + assert.throws(function () { Symbol.prototype[Symbol.toPrimitive].call(null) }, TypeError, "toPrimitive throws TypeError for null", "Symbol[Symbol.toPrimitive]: 'this' is not a Symbol object"); + assert.throws(function () { Symbol.prototype[Symbol.toPrimitive].call(undefined) }, TypeError, "toPrimitive throws TypeError for undefined", "Symbol[Symbol.toPrimitive]: 'this' is not a Symbol object"); + assert.throws(function () { Symbol.prototype[Symbol.toPrimitive].call({}) }, TypeError, "toPrimitive throws TypeError for object", "Symbol[Symbol.toPrimitive]: 'this' is not a Symbol object"); var z = Object(y); assert.areEqual(y, Symbol.prototype[Symbol.toPrimitive].call(z), "y == Symbol.prototype[Symbol.toPrimitive].call(z)"); diff --git a/test/es6/rlexe.xml b/test/es6/rlexe.xml index 1e1a5e29d10..ddead35c053 100644 --- a/test/es6/rlexe.xml +++ b/test/es6/rlexe.xml @@ -73,7 +73,7 @@ toPrimitive.js - -es6toprimitive -es6functionname -args summary -endargs + -es6regexsymbols -es6toprimitive -es6hasInstance -es6isconcatspreadable -es6tostringtag -es6functionname -args summary -endargs diff --git a/test/es6/toPrimitive.js b/test/es6/toPrimitive.js index e400aa2fa3a..c0b8a05a0a3 100644 --- a/test/es6/toPrimitive.js +++ b/test/es6/toPrimitive.js @@ -28,10 +28,10 @@ var tests = [ assert.areEqual("a",String(a),"should now call @@toPrimitive and return a"); } }, - { - name: "Symbol Tests", - body: function () - { + { + name: "Symbol Tests", + body: function () + { var o = Object.getOwnPropertyDescriptor(Symbol,"toPrimitive"); assert.isFalse(o.writable, "Symbol @@toPrimitive is not writable"); @@ -44,8 +44,23 @@ var tests = [ assert.isFalse(o.writable, "Symbol @@toPrimitive is not writable"); assert.isFalse(o.enumerable, "Symbol @@toPrimitive is not enumerable"); assert.isTrue(o.configurable, "Symbol @@toPrimitive is configurable"); - assert.throws(function() { Symbol.prototype[Symbol.toPrimitive](); }, TypeError, "Symbol[@@toPrimitive] throws no matter the parameters", "Symbol[Symbol.toPrimitive]: invalid argument"); - } + assert.throws(function() { Symbol.prototype[Symbol.toPrimitive](); }, TypeError, "Symbol[@@toPrimitive] throws no matter the parameters", "Symbol[Symbol.toPrimitive]: 'this' is not a Symbol object"); + + var s = Symbol(); + assert.areEqual(s, Object(s)[Symbol.toPrimitive](), ""); // true + assert.areEqual(s, Symbol.prototype[Symbol.toPrimitive].call(s), ""); // true + + assert.areEqual(Symbol.toPrimitive, Symbol.toPrimitive[Symbol.toPrimitive](), "Symbol.toPrimitive"); + assert.areEqual(Symbol.iterator, Symbol.iterator[Symbol.toPrimitive](), "Symbol.iterator"); + assert.areEqual(Symbol.hasInstance, Symbol.hasInstance[Symbol.toPrimitive](), "Symbol.hasInstance"); + assert.areEqual(Symbol.isConcatSpreadable, Symbol.isConcatSpreadable[Symbol.toPrimitive](), "Symbol.isConcatSpreadable"); + assert.areEqual(Symbol.match, Symbol.match[Symbol.toPrimitive](), "Symbol.match"); + assert.areEqual(Symbol.replace, Symbol.replace[Symbol.toPrimitive](), "Symbol.replace"); + assert.areEqual(Symbol.search, Symbol.search[Symbol.toPrimitive](), "Symbol.search"); + assert.areEqual(Symbol.split, Symbol.split[Symbol.toPrimitive](), "Symbol.split"); + assert.areEqual(Symbol.toStringTag, Symbol.toStringTag[Symbol.toPrimitive](), "Symbol.toStringTag"); + assert.areEqual(Symbol.unscopables, Symbol.unscopables[Symbol.toPrimitive](), "Symbol.unscopables"); + } }, { name: "Date Test", @@ -59,7 +74,6 @@ var tests = [ var d = new Date(2014,5,30,8, 30,45,2); assert.areEqual(d.toString(),d[Symbol.toPrimitive]("string"), "check that the string hint toPrimitive returns toString"); - assert.areEqual(d.toString(),d[Symbol.toPrimitive](),"check that default has the same behaviour as string hint"); assert.areEqual(d.toString(),d[Symbol.toPrimitive]("default"), "check that default has the same behaviour as string hint"); assert.areEqual(d.valueOf(),d[Symbol.toPrimitive]("number"),"check that the number hint toPrimitive returns valueOf"); @@ -73,6 +87,39 @@ var tests = [ assert.isFalse(Date.prototype.hasOwnProperty(Symbol.toPrimitive),"Property is configurable, should be able to delete"); assert.areEqual(d.toString()+10,d+10,"(fall back to OriginalToPrimitive) addition provides no hint resulting default hint which is string"); assert.areEqual(d.valueOf(), (new Number(d)).valueOf(),"(fall back to OriginalToPrimitive) conversion toNumber calls toPrimitive with hint number"); + Object.defineProperty(Date.prototype, Symbol.toPrimitive, o); // restore deleted [@@toPrimitive] property + + assert.throws(()=>(Date.prototype[Symbol.toPrimitive].call(undefined, "default")), TypeError, "this=undefined", "Date[Symbol.toPrimitive]: 'this' is not an Object"); + assert.throws(()=>(Date.prototype[Symbol.toPrimitive].call(null, "default")), TypeError, "this=null", "Date[Symbol.toPrimitive]: 'this' is not an Object"); + assert.throws(()=>(Date.prototype[Symbol.toPrimitive].call(true, "default")), TypeError, "this=true", "Date[Symbol.toPrimitive]: 'this' is not an Object"); + assert.throws(()=>(Date.prototype[Symbol.toPrimitive].call(false, "default")), TypeError, "this=false", "Date[Symbol.toPrimitive]: 'this' is not an Object"); + assert.throws(()=>(Date.prototype[Symbol.toPrimitive].call(0, "default")), TypeError, "this=0", "Date[Symbol.toPrimitive]: 'this' is not an Object"); + assert.throws(()=>(Date.prototype[Symbol.toPrimitive].call(NaN, "default")), TypeError, "this=NaN", "Date[Symbol.toPrimitive]: 'this' is not an Object"); + assert.throws(()=>(Date.prototype[Symbol.toPrimitive].call('', "default")), TypeError, "this=''", "Date[Symbol.toPrimitive]: 'this' is not an Object"); + assert.throws(()=>(Date.prototype[Symbol.toPrimitive].call('abc', "default")), TypeError, "this='abc'", "Date[Symbol.toPrimitive]: 'this' is not an Object"); + + assert.throws(()=>(Date.prototype[Symbol.toPrimitive].call()), TypeError, "this=undefined no hint", "Date[Symbol.toPrimitive]: invalid hint"); + assert.throws(()=>(Date.prototype[Symbol.toPrimitive].call(undefined)), TypeError, "this=undefined hint=undefined", "Date[Symbol.toPrimitive]: invalid hint"); + assert.throws(()=>(Date.prototype[Symbol.toPrimitive].call(null)), TypeError, "this=null hint=undefined", "Date[Symbol.toPrimitive]: invalid hint"); + assert.throws(()=>(Date.prototype[Symbol.toPrimitive].call(true)), TypeError, "this=true hint=undefined", "Date[Symbol.toPrimitive]: invalid hint"); + assert.throws(()=>(Date.prototype[Symbol.toPrimitive].call(false)), TypeError, "this=false hint=undefined", "Date[Symbol.toPrimitive]: invalid hint"); + assert.throws(()=>(Date.prototype[Symbol.toPrimitive].call(0)), TypeError, "this=0 hint=undefined", "Date[Symbol.toPrimitive]: invalid hint"); + assert.throws(()=>(Date.prototype[Symbol.toPrimitive].call(NaN)), TypeError, "this=NaN hint=undefined", "Date[Symbol.toPrimitive]: invalid hint"); + assert.throws(()=>(Date.prototype[Symbol.toPrimitive].call('')), TypeError, "this='' hint=undefined", "Date[Symbol.toPrimitive]: invalid hint"); + assert.throws(()=>(Date.prototype[Symbol.toPrimitive].call('abc')), TypeError, "this='abc' hint=undefined", "Date[Symbol.toPrimitive]: invalid hint"); + assert.throws(()=>(Date.prototype[Symbol.toPrimitive].call(function(){})), TypeError, "this=function(){} hint=undefined", "Date[Symbol.toPrimitive]: invalid hint"); + + assert.throws(()=>(d[Symbol.toPrimitive]()), TypeError, "no hint", "Date[Symbol.toPrimitive]: invalid hint"); + assert.throws(()=>(d[Symbol.toPrimitive](undefined)), TypeError, "hint=undefined", "Date[Symbol.toPrimitive]: invalid hint"); + assert.throws(()=>(d[Symbol.toPrimitive](null)), TypeError, "hint=null", "Date[Symbol.toPrimitive]: invalid hint"); + assert.throws(()=>(d[Symbol.toPrimitive](true)), TypeError, "hint=true", "Date[Symbol.toPrimitive]: invalid hint"); + assert.throws(()=>(d[Symbol.toPrimitive](false)), TypeError, "hint=false", "Date[Symbol.toPrimitive]: invalid hint"); + assert.throws(()=>(d[Symbol.toPrimitive](0)), TypeError, "hint=0", "Date[Symbol.toPrimitive]: invalid hint"); + assert.throws(()=>(d[Symbol.toPrimitive](NaN)), TypeError, "hint=NaN", "Date[Symbol.toPrimitive]: invalid hint"); + assert.throws(()=>(d[Symbol.toPrimitive]('')), TypeError, "hint=''", "Date[Symbol.toPrimitive]: invalid hint"); + assert.throws(()=>(d[Symbol.toPrimitive]('abc')), TypeError, "hint='abc'", "Date[Symbol.toPrimitive]: invalid hint"); + assert.throws(()=>(d[Symbol.toPrimitive](function(){})), TypeError, "hint=function(){}", "Date[Symbol.toPrimitive]: invalid hint"); + assert.throws(()=>(d[Symbol.toPrimitive]({})), TypeError, "hint=={}", "Date[Symbol.toPrimitive]: invalid hint"); } }, { @@ -159,6 +206,274 @@ var tests = [ assert.areEqual(-1,Number(a),"should now call @@toPrimitive and return a"); } }, + { + name: "ToPrimitive calling user-defined [@@toPrimitive] method", + body: function () + { + var o = {}, o1 = {}, o2 = {}; + + var retVal, hint; + + o[Symbol.toPrimitive] = function(h) { hint.push(h); return retVal; } + o1[Symbol.toPrimitive] = function(h) { hint.push("o1:"+h); return retVal; } + o2[Symbol.toPrimitive] = function(h) { hint.push("o2:"+h); return retVal; } + + // Ensuring OrdinaryToPrimitive is not called + Object.defineProperty(o, "toString", { writable:true, configurable:true, enumerable:true, value: function() { throw Error("Unexpected toString() call on o"); } }); + Object.defineProperty(o1, "toString", { writable:true, configurable:true, enumerable:true, value: function() { throw Error("Unexpected toString() call on o1"); } }); + Object.defineProperty(o2, "toString", { writable:true, configurable:true, enumerable:true, value: function() { throw Error("Unexpected toString() call on o2"); } }); + Object.defineProperty(o, "valueOf", { writable:true, configurable:true, enumerable:true, value: function() { throw Error("Unexpected valueOf() call on o"); } }); + Object.defineProperty(o1, "valueOf", { writable:true, configurable:true, enumerable:true, value: function() { throw Error("Unexpected valueOf() call on o1"); } }); + Object.defineProperty(o2, "valueOf", { writable:true, configurable:true, enumerable:true, value: function() { throw Error("Unexpected valueOf() call on o2"); } }); + + var verifyToPrimitive = function(func, expectedResult, expectedHint, description) { + hint = []; + assert.areEqual(expectedResult, func(), "result:" + description); + assert.areEqual(expectedHint, hint, "hint:" + description); + } + + // + // ToNumber calls ToPrimitive(input, 'number') + // + + retVal = NaN; + verifyToPrimitive(()=>Number(o), NaN, ["number"], "Number()"); + verifyToPrimitive(()=>new Uint8Array([o]), new Uint8Array([NaN]), ["number"], "TypedArray constructor"); + verifyToPrimitive(()=>isFinite(o), false, ["number"], "isFinite()"); + verifyToPrimitive(()=>isNaN(o), true, ["number"], "isNaN()"); + + retVal = 100; + verifyToPrimitive(()=>(1-o), -99, ["number"], "1-o"); + verifyToPrimitive(()=>(o-2), 98, ["number"], "o-2"); + verifyToPrimitive(()=>(1*o), 100, ["number"], "1*o"); + verifyToPrimitive(()=>(o*2), 200, ["number"], "o*2"); + verifyToPrimitive(()=>Math.log10(o), 2, ["number"], "Math.log10()"); + verifyToPrimitive(()=>(o1-o2), 0, ["o1:number", "o2:number"], "o1-o2"); + verifyToPrimitive(()=>(o2/o1), 1, ["o2:number", "o1:number"], "o2/o1"); + + retVal = 100; + var n = o; + verifyToPrimitive(()=>(++n), 101, ["number"], "++n"); + n = o; + verifyToPrimitive(()=>(n++), 100, ["number"], "n++"); + n = o; + verifyToPrimitive(()=>(--n), 99, ["number"], "--n"); + n = o; + verifyToPrimitive(()=>(n--), 100, ["number"], "n--"); + + + retVal = "abc"; + verifyToPrimitive(()=>(1-o), NaN, ["number"], "1-o ([@@toPrimitive] returns string)"); + verifyToPrimitive(()=>(o-2), NaN, ["number"], "o-2 ([@@toPrimitive] returns string)"); + + // + // ToString calls ToPrimitive(input, 'string') + // + + retVal = NaN; + verifyToPrimitive(()=>String(o), "NaN", ["string"], "String()"); + verifyToPrimitive(()=>parseFloat(o), NaN, ["string"], "parseFloat()"); + verifyToPrimitive(()=>parseInt(o), NaN, ["string"], "parseInt()"); + verifyToPrimitive(()=>decodeURI(o), "NaN", ["string"], "decodeURI()"); + + // + // ToPropertyKey calls ToPrimitive(input, 'string') + // + + retVal = NaN; + var x = {}; + verifyToPrimitive(()=>Object.defineProperty(x, o, {writable:true, enumerable:true, configurable:true, value: 'abc123'}), x, ["string"], "Object.defineProperty()"); + verifyToPrimitive(()=>x[o], 'abc123', ["string"], "x[o]"); + verifyToPrimitive(()=>x.hasOwnProperty(o), true, ["string"], "Object.prototype.hasOwnProperty()"); + verifyToPrimitive(()=>x.propertyIsEnumerable(o), true, ["string"], "Object.prototype.propertyIsEnumerable()"); + verifyToPrimitive(()=>(o in x), true, ["string"], "o in x"); + + // + // + operator calls ToPrimitive(input, 'default') + // + + retVal = 100; + verifyToPrimitive(()=>(o+1), 101, ["default"], "o+1"); + verifyToPrimitive(()=>(2+o), 102, ["default"], "2+o"); + verifyToPrimitive(()=>(o+'abc'), '100abc', ["default"], "o+'abc'"); + verifyToPrimitive(()=>('abc'+o), 'abc100', ["default"], "'abc'+o"); + verifyToPrimitive(()=>(o1+o2), 200, ["o1:default", "o2:default"], "o1+o2"); + verifyToPrimitive(()=>(o2+o1), 200, ["o2:default", "o1:default"], "o2+o1"); + + retVal = "abc"; + verifyToPrimitive(()=>(o+1), "abc1", ["default"], "o+1 ([@@toPrimitive] returns string)"); + verifyToPrimitive(()=>(2+o), "2abc", ["default"], "2+1 ([@@toPrimitive] returns string)"); + verifyToPrimitive(()=>(o+'def'), 'abcdef', ["default"], "o+'def'"); + verifyToPrimitive(()=>('def'+o), 'defabc', ["default"], "'def'+o"); + verifyToPrimitive(()=>(o1+o2), "abcabc", ["o1:default", "o2:default"], "o1+o2"); + verifyToPrimitive(()=>(o2+o1), "abcabc", ["o2:default", "o1:default"], "o2+o1"); + + // + // abstract relational comparison calls ToPrimitive(input, "number") + // + + retVal = 100; + verifyToPrimitive(()=>(o<1), false, ["number"], "o<1"); + verifyToPrimitive(()=>(1(o<=25), false, ["number"], "o<=25"); + verifyToPrimitive(()=>(-9<=o), true, ["number"], "-9<=o"); + verifyToPrimitive(()=>(o>1), true, ["number"], "o>1"); + verifyToPrimitive(()=>(1>o), false, ["number"], "1>o"); + verifyToPrimitive(()=>(o>=25), true, ["number"], "o>=25"); + verifyToPrimitive(()=>(-9>=o), false, ["number"], "-9>=o"); + verifyToPrimitive(()=>(o1(o2<=o1), true, ["o2:number", "o1:number"], "o2<=o1"); + verifyToPrimitive(()=>(o1>o2), false, ["o1:number", "o2:number"], "o1>o2"); + verifyToPrimitive(()=>(o2>=o1), true, ["o2:number", "o1:number"], "o2>=o1"); + + // + // abstract equality comparison calls ToPrimitive(input, "default") + // + + verifyToPrimitive(()=>(o1==o2), false, [], ""); // 1. If Type(x) is the same of Type(y) return Strict Equality Comparison x === y + + retVal = 100; + verifyToPrimitive(()=>(o==100), true, ["default"], "o==100"); + verifyToPrimitive(()=>(100==o), true, ["default"], "100==o"); + verifyToPrimitive(()=>(o==1), false, ["default"], "o==1"); + verifyToPrimitive(()=>(1==o), false, ["default"], "1==o"); + + retVal = true; + verifyToPrimitive(()=>(o==true), true, ["default"], "o==true"); + verifyToPrimitive(()=>(true==o), true, ["default"], "true==o"); + verifyToPrimitive(()=>(o==false), false, ["default"], "o==false"); + verifyToPrimitive(()=>(false==o), false, ["default"], "false==o"); + + retVal = 'abc'; + verifyToPrimitive(()=>(o=='abc'), true, ["default"], "o=='abc'"); + verifyToPrimitive(()=>('abc'==o), true, ["default"], "'abc'==o"); + verifyToPrimitive(()=>(o=='abc1'), false, ["default"], "o=='abc1'"); + verifyToPrimitive(()=>('abc1'==o), false, ["default"], "'abc1'==o"); + + retVal = Symbol(); + verifyToPrimitive(()=>(o==retVal), true, ["default"], "o==retVal (retVal=Symbol())"); + verifyToPrimitive(()=>(retVal==o), true, ["default"], "retVal==o (retVal=Symbol())"); + verifyToPrimitive(()=>(o==Symbol()), false, ["default"], "o==Symbol()"); + verifyToPrimitive(()=>(Symbol()==o), false, ["default"], "Symbol()==o"); + + // + // Date constructor calls ToPrimitive(input, "default") + // + + retVal = 'Jan 1, 2016'; + verifyToPrimitive(()=>new Date(o).valueOf(), new Date(retVal).valueOf(), ["default"], "Date() constructor"); + + // + // Date.prototype.toJSON calls ToPrimitive(input, "number") + // + + retVal = NaN; + verifyToPrimitive(()=>Date.prototype.toJSON.call(o), null, ["number"], "Date.prototype.toJSON()"); + + // + // Date.prototype[@@toPrimitive] calls ToPrimitive + // + + Object.defineProperty(o, 'toString', { writable:true, configurable:true, enumerable:true, value: function() { return 'abc'; } }); + Object.defineProperty(o, 'valueOf', { writable:true, configurable:true, enumerable:true, value: function() { return 123; } }); + assert.areEqual(123, Date.prototype[Symbol.toPrimitive].call(o, 'number'), "Date.prototype[@@toPrimitive].call(o, 'number')"); + assert.areEqual('abc', Date.prototype[Symbol.toPrimitive].call(o, 'string'), "Date.prototype[@@toPrimitive].call(o, 'string')"); + assert.areEqual('abc', Date.prototype[Symbol.toPrimitive].call(o, 'default'), "Date.prototype[@@toPrimitive].call(o, 'default')"); + + } + }, + { + name: "ToPrimitive calling user-defined [@@toPrimitive] method that returns Object throws TypeError", + body: function () + { + var o = {}; + + [{}, new Date(), Error(), new String(), new Boolean(), new Number()].forEach(function(retVal) { + o[Symbol.toPrimitive] = function(h) { return retVal; } + + // + // ToNumber + // + + assert.throws(()=>(Number(o)), TypeError, "Number()", "[Symbol.toPrimitive]: invalid argument"); + assert.throws(()=>new Uint8Array([o]), TypeError, "TypedArray constructor", "[Symbol.toPrimitive]: invalid argument"); + assert.throws(()=>isFinite(o), TypeError, "isFinite()", "[Symbol.toPrimitive]: invalid argument"); + assert.throws(()=>isNaN(o), TypeError, "isNaN()", "[Symbol.toPrimitive]: invalid argument"); + + assert.throws(()=>(1-o), TypeError, "1-o", "[Symbol.toPrimitive]: invalid argument"); + assert.throws(()=>(o-2), TypeError, "o-2", "[Symbol.toPrimitive]: invalid argument"); + assert.throws(()=>(1*o), TypeError, "1*o", "[Symbol.toPrimitive]: invalid argument"); + assert.throws(()=>(o*2), TypeError, "o*2", "[Symbol.toPrimitive]: invalid argument"); + assert.throws(()=>Math.log10(o), TypeError, "Math.log10()", "[Symbol.toPrimitive]: invalid argument"); + + var n = o; + assert.throws(()=>(++n), TypeError, "++n", "[Symbol.toPrimitive]: invalid argument"); + n = o; + assert.throws(()=>(n++), TypeError, "n++", "[Symbol.toPrimitive]: invalid argument"); + n = o; + assert.throws(()=>(--n), TypeError, "--n", "[Symbol.toPrimitive]: invalid argument"); + n = o; + assert.throws(()=>(n--), TypeError, "n--", "[Symbol.toPrimitive]: invalid argument"); + + // + // ToString + // + + assert.throws(()=>String(o), TypeError, "String()", "[Symbol.toPrimitive]: invalid argument"); + assert.throws(()=>parseFloat(o), TypeError, "parseFloat()", "[Symbol.toPrimitive]: invalid argument"); + assert.throws(()=>parseInt(o), TypeError, "parseInt()", "[Symbol.toPrimitive]: invalid argument"); + assert.throws(()=>decodeURI(o), TypeError, "decodeURI()", "[Symbol.toPrimitive]: invalid argument"); + + // + // ToPropertyKey + // + + var x = {}; + assert.throws(()=>Object.defineProperty(x, o, {}), TypeError, "Object.defineProperty()", "[Symbol.toPrimitive]: invalid argument"); + assert.throws(()=>x[o], TypeError, "x[o]", "[Symbol.toPrimitive]: invalid argument"); + assert.throws(()=>x.hasOwnProperty(o), TypeError, "Object.prototype.hasOwnProperty()", "[Symbol.toPrimitive]: invalid argument"); + assert.throws(()=>x.propertyIsEnumerable(o), TypeError, "Object.prototype.propertyIsEnumerable()", "[Symbol.toPrimitive]: invalid argument"); + assert.throws(()=>(o in x), TypeError, "o in x", "[Symbol.toPrimitive]: invalid argument"); + + // + // + operator + // + + assert.throws(()=>(o+1), TypeError, "o+1", "[Symbol.toPrimitive]: invalid argument"); + assert.throws(()=>(2+o), TypeError, "2+o", "[Symbol.toPrimitive]: invalid argument"); + assert.throws(()=>(o+'abc'), TypeError, "o+'abc'", "[Symbol.toPrimitive]: invalid argument"); + assert.throws(()=>('abc'+o), TypeError, "'abc'+o", "[Symbol.toPrimitive]: invalid argument"); + + // + // abstract relational comparison + // + + assert.throws(()=>(o<1), TypeError, "o<1", "[Symbol.toPrimitive]: invalid argument"); + assert.throws(()=>(1(o<=25), TypeError, "o<=25", "[Symbol.toPrimitive]: invalid argument"); + assert.throws(()=>(-9<=o), TypeError, "-9<=o", "[Symbol.toPrimitive]: invalid argument"); + assert.throws(()=>(o>1), TypeError, "o>1", "[Symbol.toPrimitive]: invalid argument"); + assert.throws(()=>(o>=25), TypeError, "o>=25", "[Symbol.toPrimitive]: invalid argument"); + assert.throws(()=>(-9>=o), TypeError, "-9>=o", "[Symbol.toPrimitive]: invalid argument"); + assert.throws(()=>(o(o<=o), TypeError, "o<=o", "[Symbol.toPrimitive]: invalid argument"); + assert.throws(()=>(o>o), TypeError, "o>o", "[Symbol.toPrimitive]: invalid argument"); + assert.throws(()=>(o>=o), TypeError, "o>=o", "[Symbol.toPrimitive]: invalid argument"); + + // + // abstract equality comparison + // + + assert.isTrue(o==o, "o==o should not call ToPrimitive"); + assert.throws(()=>('abc'==o), TypeError, "'abc'==o", "[Symbol.toPrimitive]: invalid argument"); + assert.throws(()=>(o=='abc'), TypeError, "o=='abc'", "[Symbol.toPrimitive]: invalid argument"); + assert.throws(()=>(100==o), TypeError, "100==o", "[Symbol.toPrimitive]: invalid argument"); + assert.throws(()=>(o==100), TypeError, "o==100", "[Symbol.toPrimitive]: invalid argument"); + assert.throws(()=>(Symbol()==o), TypeError, "Symbol()==o", "[Symbol.toPrimitive]: invalid argument"); + assert.throws(()=>(o==Symbol()), TypeError, "o==Symbol()", "[Symbol.toPrimitive]: invalid argument"); + }); + } + }, ]; testRunner.runTests(tests, { verbose: WScript.Arguments[0] != "summary" });