diff --git a/BUILD.gn b/BUILD.gn index 515e969933..da5ff91ee7 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -245,6 +245,7 @@ action("js2c_experimental") { "src/harmony-array.js", "src/harmony-typedarray.js", "src/harmony-classes.js", + "src/harmony-tostring.js" ] outputs = [ diff --git a/include/v8.h b/include/v8.h index 74d0354284..f8519db9bc 100644 --- a/include/v8.h +++ b/include/v8.h @@ -2130,6 +2130,7 @@ class V8_EXPORT Symbol : public Name { // Well-known symbols static Local GetIterator(Isolate* isolate); static Local GetUnscopables(Isolate* isolate); + static Local GetToStringTag(Isolate* isolate); V8_INLINE static Symbol* Cast(v8::Value* obj); diff --git a/src/api.cc b/src/api.cc index b6c7176ef0..1698a3e265 100644 --- a/src/api.cc +++ b/src/api.cc @@ -3409,6 +3409,37 @@ Local v8::Object::GetOwnPropertyNames() { } +static bool GetPredefinedToString(i::Handle tag, + Local* result) { + i::Isolate* i_isolate = tag->GetIsolate(); + Isolate* isolate = reinterpret_cast(i_isolate); + i::Factory* factory = i_isolate->factory(); + + if (i::String::Equals(tag, factory->Arguments_string())) { + *result = v8::String::NewFromUtf8(isolate, "[object ~Arguments]"); + } else if (i::String::Equals(tag, factory->Array_string())) { + *result = v8::String::NewFromUtf8(isolate, "[object ~Array]"); + } else if (i::String::Equals(tag, factory->Boolean_string())) { + *result = v8::String::NewFromUtf8(isolate, "[object ~Boolean]"); + } else if (i::String::Equals(tag, factory->Date_string())) { + *result = v8::String::NewFromUtf8(isolate, "[object ~Date]"); + } else if (i::String::Equals(tag, factory->Error_string())) { + *result = v8::String::NewFromUtf8(isolate, "[object ~Error]"); + } else if (i::String::Equals(tag, factory->Function_string())) { + *result = v8::String::NewFromUtf8(isolate, "[object ~Function]"); + } else if (i::String::Equals(tag, factory->Number_string())) { + *result = v8::String::NewFromUtf8(isolate, "[object ~Number]"); + } else if (i::String::Equals(tag, factory->RegExp_string())) { + *result = v8::String::NewFromUtf8(isolate, "[object ~RegExp]"); + } else if (i::String::Equals(tag, factory->String_string())) { + *result = v8::String::NewFromUtf8(isolate, "[object ~String]"); + } else { + return false; + } + return true; +} + + Local v8::Object::ObjectProtoToString() { i::Isolate* i_isolate = Utils::OpenHandle(this)->GetIsolate(); Isolate* isolate = reinterpret_cast(i_isolate); @@ -3418,6 +3449,7 @@ Local v8::Object::ObjectProtoToString() { i::Handle self = Utils::OpenHandle(this); i::Handle name(self->class_name(), i_isolate); + i::Handle tag; // Native implementation of Object.prototype.toString (v8natives.js): // var c = %_ClassOf(this); @@ -3432,6 +3464,27 @@ Local v8::Object::ObjectProtoToString() { i_isolate->factory()->Arguments_string())) { return v8::String::NewFromUtf8(isolate, "[object Object]"); } else { + if (internal::FLAG_harmony_tostring) { + i::Handle toStringTag = + Utils::OpenHandle(*Symbol::GetToStringTag(isolate)); + EXCEPTION_PREAMBLE(i_isolate); + has_pending_exception = + !i::Runtime::GetObjectProperty(i_isolate, self, toStringTag) + .ToHandle(&tag); + EXCEPTION_BAILOUT_CHECK(i_isolate, Local()); + + if (!tag->IsUndefined()) { + if (!tag->IsString()) + return v8::String::NewFromUtf8(isolate, "[object ???]"); + i::Handle tag_name = i::Handle::cast(tag); + if (!i::String::Equals(class_name, tag_name)) { + Local result; + if (GetPredefinedToString(tag_name, &result)) return result; + + class_name = tag_name; + } + } + } const char* prefix = "[object "; Local str = Utils::ToLocal(class_name); const char* postfix = "]"; @@ -6245,6 +6298,11 @@ Local v8::Symbol::GetUnscopables(Isolate* isolate) { } +Local v8::Symbol::GetToStringTag(Isolate* isolate) { + return GetWellKnownSymbol(isolate, "Symbol.toStringTag"); +} + + Local v8::Private::New(Isolate* isolate, Local name) { i::Isolate* i_isolate = reinterpret_cast(isolate); LOG_API(i_isolate, "Private::New()"); diff --git a/src/array.js b/src/array.js index 35fd54540d..ca3e7bedae 100644 --- a/src/array.js +++ b/src/array.js @@ -349,7 +349,7 @@ function ArrayToString() { func = array.join; } if (!IS_SPEC_FUNCTION(func)) { - return %_CallFunction(array, ObjectToString); + return %_CallFunction(array, NoSideEffectsObjectToString); } return %_CallFunction(array, func); } diff --git a/src/arraybuffer.js b/src/arraybuffer.js index e1c887fdb8..cf00693be7 100644 --- a/src/arraybuffer.js +++ b/src/arraybuffer.js @@ -77,6 +77,9 @@ function SetUpArrayBuffer() { %AddNamedProperty( $ArrayBuffer.prototype, "constructor", $ArrayBuffer, DONT_ENUM); + %AddNamedProperty($ArrayBuffer.prototype, + symbolToStringTag, "ArrayBuffer", DONT_ENUM | READ_ONLY); + InstallGetter($ArrayBuffer.prototype, "byteLength", ArrayBufferGetByteLen); InstallFunctions($ArrayBuffer, DONT_ENUM, $Array( diff --git a/src/bootstrapper.cc b/src/bootstrapper.cc index 029dc52b9e..8fd762c72d 100644 --- a/src/bootstrapper.cc +++ b/src/bootstrapper.cc @@ -1600,6 +1600,7 @@ EMPTY_NATIVE_FUNCTIONS_FOR_FEATURE(harmony_object_literals) EMPTY_NATIVE_FUNCTIONS_FOR_FEATURE(harmony_regexps) EMPTY_NATIVE_FUNCTIONS_FOR_FEATURE(harmony_arrow_functions) EMPTY_NATIVE_FUNCTIONS_FOR_FEATURE(harmony_numeric_literals) +EMPTY_NATIVE_FUNCTIONS_FOR_FEATURE(harmony_tostring) void Genesis::InstallNativeFunctions_harmony_proxies() { @@ -1624,6 +1625,7 @@ EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_classes) EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_object_literals) EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_arrow_functions) EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_numeric_literals) +EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_tostring) void Genesis::InitializeGlobal_harmony_regexps() { Handle builtins(native_context()->builtins()); @@ -2164,6 +2166,8 @@ bool Genesis::InstallExperimentalNatives() { static const char* harmony_regexps_natives[] = {NULL}; static const char* harmony_arrow_functions_natives[] = {NULL}; static const char* harmony_numeric_literals_natives[] = {NULL}; + static const char* harmony_tostring_natives[] = {"native harmony-tostring.js", + NULL}; for (int i = ExperimentalNatives::GetDebuggerCount(); i < ExperimentalNatives::GetBuiltinsCount(); i++) { diff --git a/src/collection.js b/src/collection.js index 7d185d9c34..6a32d698fc 100644 --- a/src/collection.js +++ b/src/collection.js @@ -133,6 +133,8 @@ function SetUpSet() { %SetCode($Set, SetConstructor); %FunctionSetPrototype($Set, new $Object()); %AddNamedProperty($Set.prototype, "constructor", $Set, DONT_ENUM); + %AddNamedProperty( + $Set.prototype, symbolToStringTag, "Set", DONT_ENUM | READ_ONLY); %FunctionSetLength(SetForEach, 1); @@ -282,6 +284,8 @@ function SetUpMap() { %SetCode($Map, MapConstructor); %FunctionSetPrototype($Map, new $Object()); %AddNamedProperty($Map.prototype, "constructor", $Map, DONT_ENUM); + %AddNamedProperty( + $Map.prototype, symbolToStringTag, "Map", DONT_ENUM | READ_ONLY); %FunctionSetLength(MapForEach, 1); diff --git a/src/flag-definitions.h b/src/flag-definitions.h index 4707635094..f5fdb8ebe8 100644 --- a/src/flag-definitions.h +++ b/src/flag-definitions.h @@ -162,7 +162,8 @@ DEFINE_IMPLICATION(harmony, es_staging) V(harmony_classes, "harmony classes") \ V(harmony_object_literals, "harmony object literal extensions") \ V(harmony_regexps, "reg-exp related harmony features") \ - V(harmony_arrow_functions, "harmony arrow functions") + V(harmony_arrow_functions, "harmony arrow functions") \ + V(harmony_tostring, "harmony Symbol.toStringTag") #define STAGED_FEATURES(V) \ V(harmony_numeric_literals, "harmony numeric literals (0o77, 0b11)") diff --git a/src/generator.js b/src/generator.js index 72e64dce6e..b35744a094 100644 --- a/src/generator.js +++ b/src/generator.js @@ -74,6 +74,8 @@ function SetUpGenerators() { GeneratorObjectIterator, DONT_ENUM | DONT_DELETE | READ_ONLY); %AddNamedProperty(GeneratorObjectPrototype, "constructor", GeneratorFunctionPrototype, DONT_ENUM | DONT_DELETE | READ_ONLY); + %AddNamedProperty(GeneratorObjectPrototype, + symbolToStringTag, "Generator", DONT_ENUM | READ_ONLY); %InternalSetPrototype(GeneratorFunctionPrototype, $Function.prototype); %SetCode(GeneratorFunctionPrototype, GeneratorFunctionPrototypeConstructor); %AddNamedProperty(GeneratorFunctionPrototype, "constructor", diff --git a/src/harmony-tostring.js b/src/harmony-tostring.js new file mode 100644 index 0000000000..282b3a0ffb --- /dev/null +++ b/src/harmony-tostring.js @@ -0,0 +1,66 @@ +// Copyright 2013 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +'use strict'; + +// This file relies on the fact that the following declaration has been made +// in runtime.js and symbol.js: +// var $Object = global.Object; +// var $Symbol = global.Symbol; + +var symbolToStringTag = InternalSymbol("Symbol.toStringTag"); + +var kBuiltinStringTags = { + "__proto__": null, + "Arguments": true, + "Array": true, + "Boolean": true, + "Date": true, + "Error": true, + "Function": true, + "Number": true, + "RegExp": true, + "String": true +}; + +DefaultObjectToString = ObjectToStringHarmony; +// ES6 draft 08-24-14, section 19.1.3.6 +function ObjectToStringHarmony() { + if (IS_UNDEFINED(this) && !IS_UNDETECTABLE(this)) return "[object Undefined]"; + if (IS_NULL(this)) return "[object Null]"; + var O = ToObject(this); + var builtinTag = %_ClassOf(O); + var tag = O[symbolToStringTag]; + if (IS_UNDEFINED(tag)) { + tag = builtinTag; + } else if (!IS_STRING(tag)) { + return "[object ???]" + } else if (tag !== builtinTag && kBuiltinStringTags[tag]) { + return "[object ~" + tag + "]"; + } + return "[object " + tag + "]"; +} + +function HarmonyToStringExtendSymbolPrototype() { + %CheckIsBootstrapping(); + + InstallConstants($Symbol, $Array( + // TODO(dslomov, caitp): Move to symbol.js when shipping + "toStringTag", symbolToStringTag + )); +} + +HarmonyToStringExtendSymbolPrototype(); + +function HarmonyToStringExtendObjectPrototype() { + %CheckIsBootstrapping(); + + // Set up the non-enumerable functions on the Array prototype object. + var desc = ToPropertyDescriptor({ + value: ObjectToStringHarmony + }); + DefineOwnProperty($Object.prototype, "toString", desc, false); +} + +HarmonyToStringExtendObjectPrototype(); diff --git a/src/heap/heap.h b/src/heap/heap.h index e844b3ad80..695ad8ae9e 100644 --- a/src/heap/heap.h +++ b/src/heap/heap.h @@ -325,7 +325,10 @@ namespace internal { V(next_string, "next") \ V(byte_length_string, "byteLength") \ V(byte_offset_string, "byteOffset") \ - V(minus_zero_string, "-0") + V(minus_zero_string, "-0") \ + V(Array_string, "Array") \ + V(Error_string, "Error") \ + V(RegExp_string, "RegExp") #define PRIVATE_SYMBOL_LIST(V) \ V(frozen_symbol) \ diff --git a/src/messages.js b/src/messages.js index 2dddffa57a..6578e8dff4 100644 --- a/src/messages.js +++ b/src/messages.js @@ -223,7 +223,8 @@ function NoSideEffectToString(obj) { return str; } if (IS_SYMBOL(obj)) return %_CallFunction(obj, SymbolToString); - if (IS_OBJECT(obj) && %GetDataProperty(obj, "toString") === ObjectToString) { + if (IS_OBJECT(obj) + && %GetDataProperty(obj, "toString") === DefaultObjectToString) { var constructor = %GetDataProperty(obj, "constructor"); if (typeof constructor == "function") { var constructorName = constructor.name; @@ -235,7 +236,8 @@ function NoSideEffectToString(obj) { if (CanBeSafelyTreatedAsAnErrorObject(obj)) { return %_CallFunction(obj, ErrorToString); } - return %_CallFunction(obj, ObjectToString); + + return %_CallFunction(obj, NoSideEffectsObjectToString); } // To determine whether we can safely stringify an object using ErrorToString @@ -274,7 +276,7 @@ function ToStringCheckErrorObject(obj) { function ToDetailString(obj) { - if (obj != null && IS_OBJECT(obj) && obj.toString === ObjectToString) { + if (obj != null && IS_OBJECT(obj) && obj.toString === DefaultObjectToString) { var constructor = obj.constructor; if (typeof constructor == "function") { var constructorName = constructor.name; @@ -1105,12 +1107,12 @@ function GetTypeName(receiver, requireConstructor) { var constructor = receiver.constructor; if (!constructor) { return requireConstructor ? null : - %_CallFunction(receiver, ObjectToString); + %_CallFunction(receiver, NoSideEffectsObjectToString); } var constructorName = constructor.name; if (!constructorName) { return requireConstructor ? null : - %_CallFunction(receiver, ObjectToString); + %_CallFunction(receiver, NoSideEffectsObjectToString); } return constructorName; } diff --git a/src/promise.js b/src/promise.js index e402a18532..443a3b8c01 100644 --- a/src/promise.js +++ b/src/promise.js @@ -365,6 +365,8 @@ var lastMicrotaskId = 0; %CheckIsBootstrapping(); %AddNamedProperty(global, 'Promise', $Promise, DONT_ENUM); + %AddNamedProperty( + $Promise.prototype, symbolToStringTag, "Promise", DONT_ENUM | READ_ONLY); InstallFunctions($Promise, DONT_ENUM, [ "defer", PromiseDeferred, "accept", PromiseResolved, diff --git a/src/symbol.js b/src/symbol.js index e009790190..d6ac5277cc 100644 --- a/src/symbol.js +++ b/src/symbol.js @@ -101,6 +101,8 @@ function SetUpSymbol() { // "isConcatSpreadable", symbolIsConcatSpreadable, // "isRegExp", symbolIsRegExp, "iterator", symbolIterator, + // TODO(dslomov, caitp): Currently defined in harmony-tostring.js --- + // Move here when shipping // "toStringTag", symbolToStringTag, "unscopables", symbolUnscopables )); @@ -110,6 +112,8 @@ function SetUpSymbol() { )); %AddNamedProperty($Symbol.prototype, "constructor", $Symbol, DONT_ENUM); + %AddNamedProperty( + $Symbol.prototype, symbolToStringTag, "Symbol", DONT_ENUM | READ_ONLY); InstallFunctions($Symbol.prototype, DONT_ENUM, $Array( "toString", SymbolToString, "valueOf", SymbolValueOf diff --git a/src/v8natives.js b/src/v8natives.js index 92f377c4ef..7636b702c9 100644 --- a/src/v8natives.js +++ b/src/v8natives.js @@ -215,8 +215,9 @@ SetUpGlobal(); // ---------------------------------------------------------------------------- // Object +var DefaultObjectToString = NoSideEffectsObjectToString; // ECMA-262 - 15.2.4.2 -function ObjectToString() { +function NoSideEffectsObjectToString() { if (IS_UNDEFINED(this) && !IS_UNDETECTABLE(this)) return "[object Undefined]"; if (IS_NULL(this)) return "[object Null]"; return "[object " + %_ClassOf(ToObject(this)) + "]"; @@ -1409,7 +1410,7 @@ function SetUpObject() { // Set up non-enumerable functions on the Object.prototype object. InstallFunctions($Object.prototype, DONT_ENUM, $Array( - "toString", ObjectToString, + "toString", NoSideEffectsObjectToString, "toLocaleString", ObjectToLocaleString, "valueOf", ObjectValueOf, "hasOwnProperty", ObjectHasOwnProperty, diff --git a/src/weak-collection.js b/src/weak-collection.js index 1160176d66..273060adff 100644 --- a/src/weak-collection.js +++ b/src/weak-collection.js @@ -114,6 +114,8 @@ function SetUpWeakMap() { %SetCode($WeakMap, WeakMapConstructor); %FunctionSetPrototype($WeakMap, new $Object()); %AddNamedProperty($WeakMap.prototype, "constructor", $WeakMap, DONT_ENUM); + %AddNamedProperty( + $WeakMap.prototype, symbolToStringTag, "WeakMap", DONT_ENUM | READ_ONLY); // Set up the non-enumerable functions on the WeakMap prototype object. InstallFunctions($WeakMap.prototype, DONT_ENUM, $Array( @@ -214,6 +216,8 @@ function SetUpWeakSet() { %SetCode($WeakSet, WeakSetConstructor); %FunctionSetPrototype($WeakSet, new $Object()); %AddNamedProperty($WeakSet.prototype, "constructor", $WeakSet, DONT_ENUM); + %AddNamedProperty( + $WeakSet.prototype, symbolToStringTag, "WeakSet", DONT_ENUM | READ_ONLY); // Set up the non-enumerable functions on the WeakSet prototype object. InstallFunctions($WeakSet.prototype, DONT_ENUM, $Array( diff --git a/test/cctest/test-api.cc b/test/cctest/test-api.cc index 35a7156e9e..2e8d4ae94e 100644 --- a/test/cctest/test-api.cc +++ b/test/cctest/test-api.cc @@ -2021,6 +2021,19 @@ void SymbolAccessorSetter(Local name, Local value, SimpleAccessorSetter(Local::Cast(sym->Name()), value, info); } +void SymbolAccessorGetterReturnsDefault( + Local name, const v8::PropertyCallbackInfo& info) { + CHECK(name->IsSymbol()); + Local sym = Local::Cast(name); + if (sym->Name()->IsUndefined()) return; + info.GetReturnValue().Set(info.Data()); +} + +static void ThrowingSymbolAccessorGetter( + Local name, const v8::PropertyCallbackInfo& info) { + info.GetReturnValue().Set(info.GetIsolate()->ThrowException(name)); +} + void EmptyInterceptorGetter(Local name, const v8::PropertyCallbackInfo& info) { } @@ -13492,6 +13505,123 @@ THREADED_TEST(ObjectProtoToString) { } +TEST(ObjectProtoToStringES6) { + // TODO(dslomov, caitp): merge into ObjectProtoToString test once shipped. + i::FLAG_harmony_tostring = true; + LocalContext context; + v8::Isolate* isolate = CcTest::isolate(); + v8::HandleScope scope(isolate); + Local templ = v8::FunctionTemplate::New(isolate); + templ->SetClassName(v8_str("MyClass")); + + Local customized_tostring = v8_str("customized toString"); + + // Replace Object.prototype.toString + CompileRun( + "Object.prototype.toString = function() {" + " return 'customized toString';" + "}"); + + // Normal ToString call should call replaced Object.prototype.toString + Local instance = templ->GetFunction()->NewInstance(); + Local value = instance->ToString(); + CHECK(value->IsString() && value->Equals(customized_tostring)); + + // ObjectProtoToString should not call replace toString function. + value = instance->ObjectProtoToString(); + CHECK(value->IsString() && value->Equals(v8_str("[object MyClass]"))); + + // Check global + value = context->Global()->ObjectProtoToString(); + CHECK(value->IsString() && value->Equals(v8_str("[object global]"))); + + // Check ordinary object + Local object = CompileRun("new Object()"); + value = object.As()->ObjectProtoToString(); + CHECK(value->IsString() && value->Equals(v8_str("[object Object]"))); + + // Check that ES6 semantics using @@toStringTag work + Local toStringTag = v8::Symbol::GetToStringTag(isolate); + +#define TEST_TOSTRINGTAG(type, tag, expected) \ + do { \ + object = CompileRun("new " #type "()"); \ + object.As()->Set(toStringTag, v8_str(#tag)); \ + value = object.As()->ObjectProtoToString(); \ + CHECK(value->IsString() && \ + value->Equals(v8_str("[object " #expected "]"))); \ + } while (0) + + TEST_TOSTRINGTAG(Array, Object, Object); + TEST_TOSTRINGTAG(Object, Arguments, ~Arguments); + TEST_TOSTRINGTAG(Object, Array, ~Array); + TEST_TOSTRINGTAG(Object, Boolean, ~Boolean); + TEST_TOSTRINGTAG(Object, Date, ~Date); + TEST_TOSTRINGTAG(Object, Error, ~Error); + TEST_TOSTRINGTAG(Object, Function, ~Function); + TEST_TOSTRINGTAG(Object, Number, ~Number); + TEST_TOSTRINGTAG(Object, RegExp, ~RegExp); + TEST_TOSTRINGTAG(Object, String, ~String); + TEST_TOSTRINGTAG(Object, Foo, Foo); + +#undef TEST_TOSTRINGTAG + + // @@toStringTag getter throws + Local obj = v8::Object::New(isolate); + obj.As()->SetAccessor(toStringTag, ThrowingSymbolAccessorGetter); + { + TryCatch try_catch; + value = obj.As()->ObjectProtoToString(); + CHECK(value.IsEmpty()); + CHECK(try_catch.HasCaught()); + } + + // @@toStringTag getter does not throw + obj = v8::Object::New(isolate); + obj.As()->SetAccessor( + toStringTag, SymbolAccessorGetterReturnsDefault, 0, v8_str("Test")); + { + TryCatch try_catch; + value = obj.As()->ObjectProtoToString(); + CHECK(value->IsString() && value->Equals(v8_str("[object Test]"))); + CHECK(!try_catch.HasCaught()); + } + + // JS @@toStringTag value + obj = CompileRun("obj = {}; obj[Symbol.toStringTag] = 'Test'; obj"); + { + TryCatch try_catch; + value = obj.As()->ObjectProtoToString(); + CHECK(value->IsString() && value->Equals(v8_str("[object Test]"))); + CHECK(!try_catch.HasCaught()); + } + + // JS @@toStringTag getter throws + obj = CompileRun( + "obj = {}; Object.defineProperty(obj, Symbol.toStringTag, {" + " get: function() { throw 'Test'; }" + "}); obj"); + { + TryCatch try_catch; + value = obj.As()->ObjectProtoToString(); + CHECK(value.IsEmpty()); + CHECK(try_catch.HasCaught()); + } + + // JS @@toStringTag getter does not throw + obj = CompileRun( + "obj = {}; Object.defineProperty(obj, Symbol.toStringTag, {" + " get: function() { return 'Test'; }" + "}); obj"); + { + TryCatch try_catch; + value = obj.As()->ObjectProtoToString(); + CHECK(value->IsString() && value->Equals(v8_str("[object Test]"))); + CHECK(!try_catch.HasCaught()); + } +} + + THREADED_TEST(ObjectGetConstructorName) { LocalContext context; v8::HandleScope scope(context->GetIsolate()); diff --git a/test/mjsunit/es6/collections.js b/test/mjsunit/es6/collections.js index 94b2aea12b..60ce46b2e5 100644 --- a/test/mjsunit/es6/collections.js +++ b/test/mjsunit/es6/collections.js @@ -25,7 +25,7 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// Flags: --expose-gc --allow-natives-syntax +// Flags: --expose-gc --allow-natives-syntax --harmony-tostring function assertSize(expected, collection) { @@ -300,7 +300,7 @@ assertEquals("WeakSet", WeakSet.name); function TestPrototype(C) { assertTrue(C.prototype instanceof Object); assertEquals({ - value: {}, + value: C.prototype, writable: false, enumerable: false, configurable: false @@ -1423,3 +1423,12 @@ function TestMapConstructorIterableValue(ctor) { } TestMapConstructorIterableValue(Map); TestMapConstructorIterableValue(WeakMap); + +function TestCollectionToString(C) { + assertEquals("[object " + C.name + "]", + Object.prototype.toString.call(new C())); +} +TestCollectionToString(Map); +TestCollectionToString(Set); +TestCollectionToString(WeakMap); +TestCollectionToString(WeakSet); diff --git a/test/mjsunit/es6/generators-objects.js b/test/mjsunit/es6/generators-objects.js index 8a052ff5e6..25bd0de79d 100644 --- a/test/mjsunit/es6/generators-objects.js +++ b/test/mjsunit/es6/generators-objects.js @@ -25,7 +25,7 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// Flags: --harmony-scoping --allow-natives-syntax +// Flags: --harmony-scoping --allow-natives-syntax --harmony-tostring // Test instantations of generators. @@ -66,6 +66,7 @@ function TestGeneratorObject() { assertTrue(iter instanceof g); assertEquals("Generator", %_ClassOf(iter)); assertEquals("[object Generator]", String(iter)); + assertEquals("[object Generator]", Object.prototype.toString.call(iter)); assertEquals([], Object.getOwnPropertyNames(iter)); assertTrue(iter !== new g()); } diff --git a/test/mjsunit/es6/object-tostring.js b/test/mjsunit/es6/object-tostring.js new file mode 100644 index 0000000000..26dff14b9d --- /dev/null +++ b/test/mjsunit/es6/object-tostring.js @@ -0,0 +1,133 @@ +// Copyright 2014 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Flags: --harmony-tostring + +var global = this; + +var funs = { + Object: [ Object ], + Function: [ Function ], + Array: [ Array ], + String: [ String ], + Boolean: [ Boolean ], + Number: [ Number ], + Date: [ Date ], + RegExp: [ RegExp ], + Error: [ Error, TypeError, RangeError, SyntaxError, ReferenceError, + EvalError, URIError ] +} +for (f in funs) { + for (i in funs[f]) { + assertEquals("[object " + f + "]", + Object.prototype.toString.call(new funs[f][i]), + funs[f][i]); + assertEquals("[object Function]", + Object.prototype.toString.call(funs[f][i]), + funs[f][i]); + } +} + +function testToStringTag(className) { + // Using builtin toStringTags + var obj = {}; + obj[Symbol.toStringTag] = className; + assertEquals("[object ~" + className + "]", + Object.prototype.toString.call(obj)); + + // Getter throws + obj = {}; + Object.defineProperty(obj, Symbol.toStringTag, { + get: function() { throw className; } + }); + assertThrows(function() { + Object.prototype.toString.call(obj); + }, className); + + // Getter does not throw + obj = {}; + Object.defineProperty(obj, Symbol.toStringTag, { + get: function() { return className; } + }); + assertEquals("[object ~" + className + "]", + Object.prototype.toString.call(obj)); + + // Custom, non-builtin toStringTags + obj = {}; + obj[Symbol.toStringTag] = "X" + className; + assertEquals("[object X" + className + "]", + Object.prototype.toString.call(obj)); + + // With getter + obj = {}; + Object.defineProperty(obj, Symbol.toStringTag, { + get: function() { return "X" + className; } + }); + assertEquals("[object X" + className + "]", + Object.prototype.toString.call(obj)); + + // Undefined toStringTag should return [object className] + var obj = className === "Arguments" ? + (function() { return arguments; })() : new global[className]; + obj[Symbol.toStringTag] = undefined; + assertEquals("[object " + className + "]", + Object.prototype.toString.call(obj)); + + // With getter + var obj = className === "Arguments" ? + (function() { return arguments; })() : new global[className]; + Object.defineProperty(obj, Symbol.toStringTag, { + get: function() { return undefined; } + }); + assertEquals("[object " + className + "]", + Object.prototype.toString.call(obj)); +} + +[ + "Arguments", + "Array", + "Boolean", + "Date", + "Error", + "Function", + "Number", + "RegExp", + "String" +].forEach(testToStringTag); + +function testToStringTagNonString(value) { + var obj = {}; + obj[Symbol.toStringTag] = value; + assertEquals("[object ???]", Object.prototype.toString.call(obj)); + + // With getter + obj = {}; + Object.defineProperty(obj, Symbol.toStringTag, { + get: function() { return value; } + }); + assertEquals("[object ???]", Object.prototype.toString.call(obj)); +} + +[ + null, + function() {}, + [], + {}, + /regexp/, + 42, + Symbol("sym"), + new Date(), + (function() { return arguments; })(), + true, + new Error("oops"), + new String("str") +].forEach(testToStringTagNonString); + +function testObjectToStringPropertyDesc() { + var desc = Object.getOwnPropertyDescriptor(Object.prototype, "toString"); + assertTrue(desc.writable); + assertFalse(desc.enumerable); + assertTrue(desc.configurable); +} +testObjectToStringPropertyDesc(); diff --git a/test/mjsunit/es6/promises.js b/test/mjsunit/es6/promises.js index faf154ee0a..04059aa720 100644 --- a/test/mjsunit/es6/promises.js +++ b/test/mjsunit/es6/promises.js @@ -25,13 +25,21 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// Flags: --allow-natives-syntax +// Flags: --allow-natives-syntax --harmony-tostring // Make sure we don't rely on functions patchable by monkeys. var call = Function.prototype.call.call.bind(Function.prototype.call) var observe = Object.observe; -var getOwnPropertyNames = Object.getOwnPropertyNames -var defineProperty = Object.defineProperty +var getOwnPropertyNames = Object.getOwnPropertyNames; +var defineProperty = Object.defineProperty; + + +(function() { + // Test before clearing global (fails otherwise) + assertEquals("[object Promise]", + Object.prototype.toString.call(new Promise(function() {}))); +})(); + function clear(o) { if (o === null || (typeof o !== 'object' && typeof o !== 'function')) return diff --git a/test/mjsunit/es6/symbols.js b/test/mjsunit/es6/symbols.js index d4a968a04c..b9811f509e 100644 --- a/test/mjsunit/es6/symbols.js +++ b/test/mjsunit/es6/symbols.js @@ -25,7 +25,7 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// Flags: --expose-gc --allow-natives-syntax +// Flags: --expose-gc --allow-natives-syntax --harmony-tostring var symbols = [] diff --git a/test/mjsunit/harmony/typedarrays.js b/test/mjsunit/harmony/typedarrays.js index a2f3dccc7a..537fba326c 100644 --- a/test/mjsunit/harmony/typedarrays.js +++ b/test/mjsunit/harmony/typedarrays.js @@ -25,6 +25,8 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// Flags: --harmony-tostring + // ArrayBuffer function TestByteLength(param, expectedByteLength) { @@ -52,6 +54,8 @@ function TestArrayBufferCreation() { var ab = new ArrayBuffer(); assertSame(0, ab.byteLength); + assertEquals("[object ArrayBuffer]", + Object.prototype.toString.call(ab)); } TestArrayBufferCreation(); @@ -123,6 +127,9 @@ function TestTypedArray(constr, elementSize, typicalElement) { var ab = new ArrayBuffer(256*elementSize); var a0 = new constr(30); + assertEquals("[object " + constr.name + "]", + Object.prototype.toString.call(a0)); + assertTrue(ArrayBuffer.isView(a0)); assertSame(elementSize, a0.BYTES_PER_ELEMENT); assertSame(30, a0.length); diff --git a/tools/gyp/v8.gyp b/tools/gyp/v8.gyp index e62ad07794..9bf3a9579a 100644 --- a/tools/gyp/v8.gyp +++ b/tools/gyp/v8.gyp @@ -1608,6 +1608,7 @@ '../../src/generator.js', '../../src/harmony-string.js', '../../src/harmony-array.js', + '../../src/harmony-tostring.js', '../../src/harmony-typedarray.js', '../../src/harmony-classes.js', ],