Bug 645416, part 5 - Add the Symbol constructor and Symbol wrapper objects. r=efaust.

This exposes a new primitive type to scripts for the first time since
JavaScript first shipped in Netscape 2, over 13 years ago.

The tests focus on identity, equality, and being able to pass a symbol around
as a value. Of course the point of symbols is that they can be property keys,
but that will have to wait for a later patch in this series.

--HG--
extra : rebase_source : c22cf4b774cca8e7c9f6c757079e054a6eb0f307
This commit is contained in:
Jason Orendorff 2014-06-23 10:55:52 -05:00
parent c755dc4354
commit db7aa8370b
23 changed files with 327 additions and 69 deletions

View File

@ -78,6 +78,7 @@ var ecmaGlobals =
{name: "SIMD", nightly: true},
"StopIteration",
"String",
"Symbol",
"SyntaxError",
{name: "TypedObject", nightly: true},
"TypeError",

View File

@ -0,0 +1,109 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sts=4 et sw=4 tw=99:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "builtin/SymbolObject.h"
#include "jsobjinlines.h"
#include "vm/Symbol-inl.h"
using namespace js;
const Class SymbolObject::class_ = {
"Symbol",
JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS) | JSCLASS_HAS_CACHED_PROTO(JSProto_Symbol),
JS_PropertyStub, /* addProperty */
JS_DeletePropertyStub, /* delProperty */
JS_PropertyStub, /* getProperty */
JS_StrictPropertyStub, /* setProperty */
JS_EnumerateStub,
JS_ResolveStub,
JS_ConvertStub
};
SymbolObject *
SymbolObject::create(JSContext *cx, JS::Symbol *symbol)
{
JSObject *obj = NewBuiltinClassInstance(cx, &class_);
if (!obj)
return nullptr;
SymbolObject &symobj = obj->as<SymbolObject>();
symobj.setPrimitiveValue(symbol);
return &symobj;
}
const JSPropertySpec SymbolObject::properties[] = {
JS_PS_END
};
const JSFunctionSpec SymbolObject::methods[] = {
JS_FS_END
};
const JSFunctionSpec SymbolObject::staticMethods[] = {
JS_FS_END
};
JSObject *
SymbolObject::initClass(JSContext *cx, HandleObject obj)
{
Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>());
// This uses &JSObject::class_ because: "The Symbol prototype object is an
// ordinary object. It is not a Symbol instance and does not have a
// [[SymbolData]] internal slot." (ES6 rev 24, 19.4.3)
RootedObject proto(cx, global->createBlankPrototype(cx, &JSObject::class_));
if (!proto)
return nullptr;
RootedFunction ctor(cx, global->createConstructor(cx, construct,
ClassName(JSProto_Symbol, cx), 1));
if (!ctor ||
!LinkConstructorAndPrototype(cx, ctor, proto) ||
!DefinePropertiesAndFunctions(cx, proto, properties, methods) ||
!DefinePropertiesAndFunctions(cx, ctor, nullptr, staticMethods) ||
!GlobalObject::initBuiltinConstructor(cx, global, JSProto_Symbol, ctor, proto))
{
return nullptr;
}
return proto;
}
// ES6 rev 24 (2014 Apr 27) 19.4.1.1 and 19.4.1.2
bool
SymbolObject::construct(JSContext *cx, unsigned argc, Value *vp)
{
// According to a note in the draft standard, "Symbol has ordinary
// [[Construct]] behaviour but the definition of its @@create method causes
// `new Symbol` to throw a TypeError exception." We do not support @@create
// yet, so just throw a TypeError.
CallArgs args = CallArgsFromVp(argc, vp);
if (args.isConstructing()) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_CONSTRUCTOR, "Symbol");
return false;
}
// steps 1-3
RootedString desc(cx);
if (!args.get(0).isUndefined()) {
desc = ToString(cx, args.get(0));
if (!desc)
return false;
}
// step 4
RootedSymbol symbol(cx, JS::Symbol::new_(cx, desc));
if (!symbol)
return false;
args.rval().setSymbol(symbol);
return true;
}
JSObject *
js_InitSymbolClass(JSContext *cx, HandleObject obj)
{
return SymbolObject::initClass(cx, obj);
}

View File

@ -0,0 +1,55 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sts=4 et sw=4 tw=99:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef builtin_SymbolObject_h
#define builtin_SymbolObject_h
#include "jsobj.h"
#include "vm/Symbol.h"
namespace js {
class SymbolObject : public JSObject
{
/* Stores this Symbol object's [[PrimitiveValue]]. */
static const unsigned PRIMITIVE_VALUE_SLOT = 0;
public:
static const unsigned RESERVED_SLOTS = 1;
static const Class class_;
static JSObject *initClass(JSContext *cx, js::HandleObject obj);
/*
* Creates a new Symbol object boxing the given primitive Symbol. The
* object's [[Prototype]] is determined from context.
*/
static SymbolObject *create(JSContext *cx, JS::Symbol *symbol);
JS::Symbol *unbox() const {
return getFixedSlot(PRIMITIVE_VALUE_SLOT).toSymbol();
}
private:
inline void setPrimitiveValue(JS::Symbol *symbol) {
setFixedSlot(PRIMITIVE_VALUE_SLOT, SymbolValue(symbol));
}
static bool construct(JSContext *cx, unsigned argc, Value *vp);
static const JSPropertySpec properties[];
static const JSFunctionSpec methods[];
static const JSFunctionSpec staticMethods[];
};
} /* namespace js */
extern JSObject *
js_InitSymbolClass(JSContext *cx, js::HandleObject obj);
#endif /* builtin_SymbolObject_h */

View File

@ -17,6 +17,7 @@ check(false);
check(1);
check(NaN);
check("ok");
check(Symbol("ok"));
// A Debugger.Object that belongs to a different Debugger object is invalid.
var g = newGlobal();

View File

@ -1,4 +1,4 @@
// env.find() finds noneumerable properties in with statements.
// env.find() finds nonenumerable properties in with statements.
var g = newGlobal();
var dbg = Debugger(g);

View File

@ -237,6 +237,7 @@ enum MIRType
MIRType_Double,
MIRType_Float32,
MIRType_String,
MIRType_Symbol,
MIRType_Object,
MIRType_MagicOptimizedArguments, // JS_OPTIMIZED_ARGUMENTS magic value.
MIRType_MagicOptimizedOut, // JS_OPTIMIZED_OUT magic value.

View File

@ -47,6 +47,7 @@
#include "builtin/Intl.h"
#include "builtin/MapObject.h"
#include "builtin/RegExp.h"
#include "builtin/SymbolObject.h"
#ifdef ENABLE_BINARYDATA
#include "builtin/SIMD.h"
#include "builtin/TypedObject.h"

View File

@ -1971,6 +1971,8 @@ js::ReportIncompatibleMethod(JSContext *cx, CallReceiver call, const Class *clas
JS_ASSERT(clasp != &NumberObject::class_);
} else if (thisv.isBoolean()) {
JS_ASSERT(clasp != &BooleanObject::class_);
} else if (thisv.isSymbol()) {
JS_ASSERT(clasp != &SymbolObject::class_);
} else {
JS_ASSERT(thisv.isUndefined() || thisv.isNull());
}

View File

@ -178,6 +178,8 @@ types::TypeString(Type type)
return "float";
case JSVAL_TYPE_STRING:
return "string";
case JSVAL_TYPE_SYMBOL:
return "symbol";
case JSVAL_TYPE_MAGIC:
return "lazyargs";
default:
@ -332,6 +334,8 @@ TypeSet::mightBeMIRType(jit::MIRType type)
return baseFlags() & TYPE_FLAG_DOUBLE;
case jit::MIRType_String:
return baseFlags() & TYPE_FLAG_STRING;
case jit::MIRType_Symbol:
return baseFlags() & TYPE_FLAG_SYMBOL;
case jit::MIRType_MagicOptimizedArguments:
return baseFlags() & TYPE_FLAG_LAZYARGS;
case jit::MIRType_MagicHole:
@ -1324,6 +1328,8 @@ GetMIRTypeFromTypeFlags(TypeFlags flags)
return jit::MIRType_Double;
case TYPE_FLAG_STRING:
return jit::MIRType_String;
case TYPE_FLAG_SYMBOL:
return jit::MIRType_Symbol;
case TYPE_FLAG_LAZYARGS:
return jit::MIRType_MagicOptimizedArguments;
case TYPE_FLAG_ANYOBJECT:

View File

@ -377,30 +377,32 @@ public:
/* Flags and other state stored in TypeSet::flags */
enum MOZ_ENUM_TYPE(uint32_t) {
TYPE_FLAG_UNDEFINED = 0x1,
TYPE_FLAG_NULL = 0x2,
TYPE_FLAG_BOOLEAN = 0x4,
TYPE_FLAG_INT32 = 0x8,
TYPE_FLAG_DOUBLE = 0x10,
TYPE_FLAG_STRING = 0x20,
TYPE_FLAG_LAZYARGS = 0x40,
TYPE_FLAG_ANYOBJECT = 0x80,
TYPE_FLAG_UNDEFINED = 0x1,
TYPE_FLAG_NULL = 0x2,
TYPE_FLAG_BOOLEAN = 0x4,
TYPE_FLAG_INT32 = 0x8,
TYPE_FLAG_DOUBLE = 0x10,
TYPE_FLAG_STRING = 0x20,
TYPE_FLAG_SYMBOL = 0x40,
TYPE_FLAG_LAZYARGS = 0x80,
TYPE_FLAG_ANYOBJECT = 0x100,
/* Mask containing all primitives */
TYPE_FLAG_PRIMITIVE = TYPE_FLAG_UNDEFINED | TYPE_FLAG_NULL | TYPE_FLAG_BOOLEAN |
TYPE_FLAG_INT32 | TYPE_FLAG_DOUBLE | TYPE_FLAG_STRING,
TYPE_FLAG_INT32 | TYPE_FLAG_DOUBLE | TYPE_FLAG_STRING |
TYPE_FLAG_SYMBOL,
/* Mask/shift for the number of objects in objectSet */
TYPE_FLAG_OBJECT_COUNT_MASK = 0x1f00,
TYPE_FLAG_OBJECT_COUNT_SHIFT = 8,
TYPE_FLAG_OBJECT_COUNT_MASK = 0x3e00,
TYPE_FLAG_OBJECT_COUNT_SHIFT = 9,
TYPE_FLAG_OBJECT_COUNT_LIMIT =
TYPE_FLAG_OBJECT_COUNT_MASK >> TYPE_FLAG_OBJECT_COUNT_SHIFT,
/* Whether the contents of this type set are totally unknown. */
TYPE_FLAG_UNKNOWN = 0x00002000,
TYPE_FLAG_UNKNOWN = 0x00004000,
/* Mask of normal type flags on a type set. */
TYPE_FLAG_BASE_MASK = 0x000020ff,
TYPE_FLAG_BASE_MASK = 0x000041ff,
/* Additional flags for HeapTypeSet sets. */
@ -409,10 +411,10 @@ enum MOZ_ENUM_TYPE(uint32_t) {
* differently from a plain data property, other than making the property
* non-writable.
*/
TYPE_FLAG_NON_DATA_PROPERTY = 0x00004000,
TYPE_FLAG_NON_DATA_PROPERTY = 0x00008000,
/* Whether the property has ever been made non-writable. */
TYPE_FLAG_NON_WRITABLE_PROPERTY = 0x00008000,
TYPE_FLAG_NON_WRITABLE_PROPERTY = 0x00010000,
/*
* Whether the property is definitely in a particular slot on all objects
@ -423,8 +425,8 @@ enum MOZ_ENUM_TYPE(uint32_t) {
* If the property is definite, mask and shift storing the slot + 1.
* Otherwise these bits are clear.
*/
TYPE_FLAG_DEFINITE_MASK = 0xffff0000,
TYPE_FLAG_DEFINITE_SHIFT = 16
TYPE_FLAG_DEFINITE_MASK = 0xfffe0000,
TYPE_FLAG_DEFINITE_SHIFT = 17
};
typedef uint32_t TypeFlags;

View File

@ -13,6 +13,7 @@
#include "mozilla/PodOperations.h"
#include "builtin/SymbolObject.h"
#include "vm/ArrayObject.h"
#include "vm/BooleanObject.h"
#include "vm/NumberObject.h"
@ -162,6 +163,8 @@ PrimitiveTypeFlag(JSValueType type)
return TYPE_FLAG_DOUBLE;
case JSVAL_TYPE_STRING:
return TYPE_FLAG_STRING;
case JSVAL_TYPE_SYMBOL:
return TYPE_FLAG_SYMBOL;
case JSVAL_TYPE_MAGIC:
return TYPE_FLAG_LAZYARGS;
default:
@ -185,6 +188,8 @@ TypeFlagPrimitive(TypeFlags flags)
return JSVAL_TYPE_DOUBLE;
case TYPE_FLAG_STRING:
return JSVAL_TYPE_STRING;
case TYPE_FLAG_SYMBOL:
return JSVAL_TYPE_SYMBOL;
case TYPE_FLAG_LAZYARGS:
return JSVAL_TYPE_MAGIC;
default:
@ -331,6 +336,8 @@ GetClassForProtoKey(JSProtoKey key)
return &BooleanObject::class_;
case JSProto_String:
return &StringObject::class_;
case JSProto_Symbol:
return &SymbolObject::class_;
case JSProto_RegExp:
return &RegExpObject::class_;

View File

@ -38,6 +38,7 @@
#include "builtin/Eval.h"
#include "builtin/Object.h"
#include "builtin/SymbolObject.h"
#include "frontend/BytecodeCompiler.h"
#include "gc/Marking.h"
#include "jit/AsmJSModule.h"
@ -243,6 +244,8 @@ js::InformalValueTypeName(const Value &v)
return v.toObject().getClass()->name;
if (v.isString())
return "string";
if (v.isSymbol())
return "symbol";
if (v.isNumber())
return "number";
if (v.isBoolean())
@ -5642,12 +5645,13 @@ js::PrimitiveToObject(JSContext *cx, const Value &v)
}
if (v.isNumber())
return NumberObject::create(cx, v.toNumber());
JS_ASSERT(v.isBoolean());
return BooleanObject::create(cx, v.toBoolean());
if (v.isBoolean())
return BooleanObject::create(cx, v.toBoolean());
JS_ASSERT(v.isSymbol());
return SymbolObject::create(cx, v.toSymbol());
}
/* Callers must handle the already-object case . */
/* Callers must handle the already-object case. */
JSObject *
js::ToObjectSlow(JSContext *cx, HandleValue val, bool reportScanStack)
{

View File

@ -97,11 +97,12 @@
real(Map, 33, js_InitMapClass, OCLASP(Map)) \
real(Set, 34, js_InitSetClass, OCLASP(Set)) \
real(DataView, 35, js_InitDataViewClass, OCLASP(DataView)) \
IF_SAB(real,imaginary)(SharedArrayBuffer, 36, js_InitSharedArrayBufferClass, &js::SharedArrayBufferObject::protoClass) \
IF_INTL(real,imaginary) (Intl, 37, js_InitIntlClass, CLASP(Intl)) \
IF_BDATA(real,imaginary)(TypedObject, 38, js_InitTypedObjectModuleObject, OCLASP(TypedObjectModule)) \
imaginary(GeneratorFunction, 39, js_InitIteratorClasses, dummy) \
IF_BDATA(real,imaginary)(SIMD, 40, js_InitSIMDClass, OCLASP(SIMD)) \
real(Symbol, 36, js_InitSymbolClass, &js::SymbolObject::class_) \
IF_SAB(real,imaginary)(SharedArrayBuffer, 37, js_InitSharedArrayBufferClass, &js::SharedArrayBufferObject::protoClass) \
IF_INTL(real,imaginary) (Intl, 38, js_InitIntlClass, CLASP(Intl)) \
IF_BDATA(real,imaginary)(TypedObject, 39, js_InitTypedObjectModuleObject, OCLASP(TypedObjectModule)) \
imaginary(GeneratorFunction, 40, js_InitIteratorClasses, dummy) \
IF_BDATA(real,imaginary)(SIMD, 41, js_InitSIMDClass, OCLASP(SIMD)) \
#define JS_FOR_EACH_PROTOTYPE(macro) JS_FOR_PROTOTYPES(macro,macro)

View File

@ -102,6 +102,7 @@ UNIFIED_SOURCES += [
'builtin/Object.cpp',
'builtin/Profilers.cpp',
'builtin/SIMD.cpp',
'builtin/SymbolObject.cpp',
'builtin/TestingFunctions.cpp',
'builtin/TypedObject.cpp',
'devtools/sharkctl.cpp',

View File

View File

@ -0,0 +1,9 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/licenses/publicdomain/ */
// More tests will be added here when Symbol.prototype.toString is added.
assertEq(Object.getPrototypeOf(Symbol.prototype), Object.prototype);
if (typeof reportCompare === "function")
reportCompare(0, 0);

View File

@ -0,0 +1,25 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/licenses/publicdomain/ */
// Several distinct Symbol values.
var symbols = [
Symbol(),
Symbol("Symbol.iterator"),
Symbol("Symbol.iterator") // distinct new symbol with the same description
];
// Distinct symbols are never equal to each other, even if they have the same
// description.
for (var i = 0; i < symbols.length; i++) {
for (var j = i; j < symbols.length; j++) {
var expected = (i === j);
assertEq(symbols[i] == symbols[j], expected);
assertEq(symbols[i] != symbols[j], !expected);
assertEq(symbols[i] === symbols[j], expected);
assertEq(symbols[i] !== symbols[j], !expected);
assertEq(Object.is(symbols[i], symbols[j]), expected);
}
}
if (typeof reportCompare === "function")
reportCompare(0, 0);

View File

@ -0,0 +1,17 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/licenses/publicdomain/ */
// Section numbers cite ES6 rev 24 (2014 April 27).
var sym = Symbol();
// 7.2.2 IsCallable
assertThrowsInstanceOf(() => sym(), TypeError);
assertThrowsInstanceOf(() => Function.prototype.call.call(sym), TypeError);
// 7.2.5 IsConstructor
assertThrowsInstanceOf(() => new sym(), TypeError);
assertThrowsInstanceOf(() => new Symbol(), TypeError);
if (typeof reportCompare === "function")
reportCompare(0, 0);

View File

View File

@ -0,0 +1,25 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/licenses/publicdomain/ */
// Test superficial properties of the Symbol constructor and prototype.
var desc = Object.getOwnPropertyDescriptor(this, "Symbol");
assertEq(desc.configurable, true);
assertEq(desc.enumerable, false);
assertEq(desc.writable, true);
assertEq(typeof Symbol, "function");
assertEq(Symbol.length, 1);
desc = Object.getOwnPropertyDescriptor(Symbol, "prototype");
assertEq(desc.configurable, false);
assertEq(desc.enumerable, false);
assertEq(desc.writable, false);
assertEq(Symbol.prototype.constructor, Symbol);
desc = Object.getOwnPropertyDescriptor(Symbol.prototype, "constructor");
assertEq(desc.configurable, true);
assertEq(desc.enumerable, false);
assertEq(desc.writable, true);
if (typeof reportCompare === "function")
reportCompare(0, 0);

View File

@ -0,0 +1,8 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/licenses/publicdomain/ */
assertEq(typeof Symbol(), "symbol");
assertEq(typeof Symbol("ponies"), "symbol");
if (typeof reportCompare === "function")
reportCompare(0, 0);

View File

@ -23,6 +23,7 @@
#include "builtin/Object.h"
#include "builtin/RegExp.h"
#include "builtin/SIMD.h"
#include "builtin/SymbolObject.h"
#include "builtin/TypedObject.h"
#include "vm/HelperThreads.h"
#include "vm/PIC.h"
@ -317,7 +318,6 @@ GlobalObject::createConstructor(JSContext *cx, Native ctor, JSAtom *nameArg, uns
static JSObject *
CreateBlankProto(JSContext *cx, const Class *clasp, JSObject &proto, GlobalObject &global)
{
JS_ASSERT(clasp != &JSObject::class_);
JS_ASSERT(clasp != &JSFunction::class_);
RootedObject blankProto(cx, NewObjectWithGivenProto(cx, clasp, &proto, &global, SingletonObject));

View File

@ -661,32 +661,31 @@ js::HasInstance(JSContext *cx, HandleObject obj, HandleValue v, bool *bp)
return false;
}
static inline bool
EqualGivenSameType(JSContext *cx, const Value &lval, const Value &rval, bool *equal)
{
MOZ_ASSERT(SameType(lval, rval));
if (lval.isString())
return EqualStrings(cx, lval.toString(), rval.toString(), equal);
if (lval.isDouble()) {
*equal = (lval.toDouble() == rval.toDouble());
return true;
}
if (lval.isGCThing()) { // objects or symbols
*equal = (lval.toGCThing() == rval.toGCThing());
return true;
}
*equal = lval.payloadAsRawUint32() == rval.payloadAsRawUint32();
MOZ_ASSERT_IF(lval.isUndefined(), *equal);
return true;
}
bool
js::LooselyEqual(JSContext *cx, const Value &lval, const Value &rval, bool *result)
{
if (SameType(lval, rval)) {
if (lval.isString()) {
JSString *l = lval.toString();
JSString *r = rval.toString();
return EqualStrings(cx, l, r, result);
}
if (lval.isDouble()) {
double l = lval.toDouble(), r = rval.toDouble();
*result = (l == r);
return true;
}
if (lval.isObject()) {
JSObject *l = &lval.toObject();
JSObject *r = &rval.toObject();
*result = l == r;
return true;
}
*result = lval.payloadAsRawUint32() == rval.payloadAsRawUint32();
return true;
}
if (SameType(lval, rval))
return EqualGivenSameType(cx, lval, rval, result);
if (lval.isNullOrUndefined()) {
*result = rval.isNullOrUndefined() ||
@ -724,24 +723,8 @@ bool
js::StrictlyEqual(JSContext *cx, const Value &lref, const Value &rref, bool *equal)
{
Value lval = lref, rval = rref;
if (SameType(lval, rval)) {
if (lval.isString())
return EqualStrings(cx, lval.toString(), rval.toString(), equal);
if (lval.isDouble()) {
*equal = (lval.toDouble() == rval.toDouble());
return true;
}
if (lval.isObject()) {
*equal = lval.toObject() == rval.toObject();
return true;
}
if (lval.isUndefined()) {
*equal = true;
return true;
}
*equal = lval.payloadAsRawUint32() == rval.payloadAsRawUint32();
return true;
}
if (SameType(lval, rval))
return EqualGivenSameType(cx, lval, rval, equal);
if (lval.isDouble() && rval.isInt32()) {
double ld = lval.toDouble();