ES6: Add Object.getOwnPropertySymbols
authorrossberg@chromium.org <rossberg@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Thu, 9 Jan 2014 15:57:30 +0000 (15:57 +0000)
committerrossberg@chromium.org <rossberg@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Thu, 9 Jan 2014 15:57:30 +0000 (15:57 +0000)
http://people.mozilla.org/~jorendorff/es6-draft.html#sec-object.getownpropertysymbols

This allows you to get the symbols used as property keys for an object.

  var object = {};
  var sym = Symbol();
  object[sym] = 42;
  assert(Object.getOwnPropertySymbols(object)[0] === sym);

This is only available with --harmony-symbols

BUG=v8:3049
R=rossberg@chromium.org, rossberg
LOG=Y

Review URL: https://codereview.chromium.org/108083005

Patch from Erik Arvidsson <arv@chromium.org>.

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@18520 ce2b1a6d-e550-0410-aec6-3dcde31c8c00

src/macros.py
src/mirror-debugger.js
src/objects.cc
src/property-details.h
src/runtime.cc
src/symbol.js
src/v8natives.js
test/cctest/test-api.cc
test/mjsunit/harmony/symbols.js

index 7bad23b..0ba5427 100644 (file)
@@ -159,6 +159,7 @@ macro JSON_NUMBER_TO_STRING(arg) = ((%_IsSmi(%IS_VAR(arg)) || arg - arg == 0) ?
 
 # Private names.
 macro NEW_PRIVATE(name) = (%CreatePrivateSymbol(name));
+macro IS_PRIVATE(sym) = (%SymbolIsPrivate(sym));
 macro HAS_PRIVATE(obj, sym) = (sym in obj);
 macro GET_PRIVATE(obj, sym) = (obj[sym]);
 macro SET_PRIVATE(obj, sym, val) = (obj[sym] = val);
@@ -260,3 +261,9 @@ const COMPILATION_TYPE_JSON = 2;
 
 # Matches Messages::kNoLineNumberInfo from v8.h
 const kNoLineNumberInfo = 0;
+
+# Matches PropertyAttributes from property-details.h
+const PROPERTY_ATTRIBUTES_NONE = 0;
+const PROPERTY_ATTRIBUTES_STRING = 8;
+const PROPERTY_ATTRIBUTES_SYMBOLIC = 16;
+const PROPERTY_ATTRIBUTES_PRIVATE_SYMBOL = 32;
index 4277136..212bb0b 100644 (file)
@@ -637,8 +637,9 @@ ObjectMirror.prototype.propertyNames = function(kind, limit) {
 
   // Find all the named properties.
   if (kind & PropertyKind.Named) {
-    // Get the local property names.
-    propertyNames = %GetLocalPropertyNames(this.value_, true);
+    // Get all the local property names.
+    propertyNames =
+        %GetLocalPropertyNames(this.value_, PROPERTY_ATTRIBUTES_NONE);
     total += propertyNames.length;
 
     // Get names for named interceptor properties if any.
index c484eab..dcbe211 100644 (file)
@@ -5924,6 +5924,24 @@ bool JSReceiver::IsSimpleEnum() {
 }
 
 
+static bool FilterKey(Object* key, PropertyAttributes filter) {
+  if ((filter & SYMBOLIC) && key->IsSymbol()) {
+    return true;
+  }
+
+  if ((filter & PRIVATE_SYMBOL) &&
+      key->IsSymbol() && Symbol::cast(key)->is_private()) {
+    return true;
+  }
+
+  if ((filter & STRING) && !key->IsSymbol()) {
+    return true;
+  }
+
+  return false;
+}
+
+
 int Map::NumberOfDescribedProperties(DescriptorFlag which,
                                      PropertyAttributes filter) {
   int result = 0;
@@ -5933,7 +5951,7 @@ int Map::NumberOfDescribedProperties(DescriptorFlag which,
       : NumberOfOwnDescriptors();
   for (int i = 0; i < limit; i++) {
     if ((descs->GetDetails(i).attributes() & filter) == 0 &&
-        ((filter & SYMBOLIC) == 0 || !descs->GetKey(i)->IsSymbol())) {
+        !FilterKey(descs->GetKey(i), filter)) {
       result++;
     }
   }
@@ -13540,7 +13558,7 @@ void JSObject::GetLocalPropertyNames(
     DescriptorArray* descs = map()->instance_descriptors();
     for (int i = 0; i < real_size; i++) {
       if ((descs->GetDetails(i).attributes() & filter) == 0 &&
-          ((filter & SYMBOLIC) == 0 || !descs->GetKey(i)->IsSymbol())) {
+          !FilterKey(descs->GetKey(i), filter)) {
         storage->set(index++, descs->GetKey(i));
       }
     }
@@ -15642,7 +15660,7 @@ int Dictionary<Shape, Key>::NumberOfElementsFilterAttributes(
   for (int i = 0; i < capacity; i++) {
     Object* k = HashTable<Shape, Key>::KeyAt(i);
     if (HashTable<Shape, Key>::IsKey(k) &&
-        ((filter & SYMBOLIC) == 0 || !k->IsSymbol())) {
+        !FilterKey(k, filter)) {
       PropertyDetails details = DetailsAt(i);
       if (details.IsDeleted()) continue;
       PropertyAttributes attr = details.attributes();
index 753eeee..71ac8bb 100644 (file)
@@ -42,9 +42,12 @@ enum PropertyAttributes {
   SEALED            = DONT_DELETE,
   FROZEN            = SEALED | READ_ONLY,
 
-  SYMBOLIC          = 8,  // Used to filter symbol names
-  DONT_SHOW         = DONT_ENUM | SYMBOLIC,
-  ABSENT            = 16  // Used in runtime to indicate a property is absent.
+  STRING            = 8,  // Used to filter symbols and string names
+  SYMBOLIC          = 16,
+  PRIVATE_SYMBOL    = 32,
+
+  DONT_SHOW         = DONT_ENUM | SYMBOLIC | PRIVATE_SYMBOL,
+  ABSENT            = 64  // Used in runtime to indicate a property is absent.
   // ABSENT can never be stored in or returned from a descriptor's attributes
   // bitfield.  It is only used as a return value meaning the attributes of
   // a non-existent property.
index ba9da22..7a2d46c 100644 (file)
@@ -5736,6 +5736,7 @@ static int LocalPrototypeChainLength(JSObject* obj) {
 
 // Return the names of the local named properties.
 // args[0]: object
+// args[1]: PropertyAttributes as int
 RUNTIME_FUNCTION(MaybeObject*, Runtime_GetLocalPropertyNames) {
   HandleScope scope(isolate);
   ASSERT(args.length() == 2);
@@ -5743,8 +5744,8 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_GetLocalPropertyNames) {
     return isolate->heap()->undefined_value();
   }
   CONVERT_ARG_HANDLE_CHECKED(JSObject, obj, 0);
-  CONVERT_BOOLEAN_ARG_CHECKED(include_symbols, 1);
-  PropertyAttributes filter = include_symbols ? NONE : SYMBOLIC;
+  CONVERT_SMI_ARG_CHECKED(filter_value, 1);
+  PropertyAttributes filter = static_cast<PropertyAttributes>(filter_value);
 
   // Skip the global proxy as it has no properties and always delegates to the
   // real global object.
index 050e7d9..be308d9 100644 (file)
@@ -68,6 +68,20 @@ function SymbolValueOf() {
   return %_ValueOf(this);
 }
 
+
+// ES6 19.1.2.8
+function ObjectGetOwnPropertySymbols(obj) {
+  if (!IS_SPEC_OBJECT(obj)) {
+    throw MakeTypeError("called_on_non_object",
+                        ["Object.getOwnPropertySymbols"]);
+  }
+
+  // TODO(arv): Proxies use a shared trap for String and Symbol keys.
+
+  return ObjectGetOwnPropertyKeys(obj, true);
+}
+
+
 //-------------------------------------------------------------------
 
 function SetUpSymbol() {
@@ -85,3 +99,14 @@ function SetUpSymbol() {
 }
 
 SetUpSymbol();
+
+
+function ExtendObject() {
+  %CheckIsBootstrapping();
+
+  InstallFunctions($Object, DONT_ENUM, $Array(
+    "getOwnPropertySymbols", ObjectGetOwnPropertySymbols
+  ));
+}
+
+ExtendObject();
index b2b2ec5..e987baf 100644 (file)
@@ -1052,46 +1052,41 @@ function ToNameArray(obj, trap, includeSymbols) {
 }
 
 
-// ES5 section 15.2.3.4.
-function ObjectGetOwnPropertyNames(obj) {
-  if (!IS_SPEC_OBJECT(obj)) {
-    throw MakeTypeError("called_on_non_object", ["Object.getOwnPropertyNames"]);
-  }
-  // Special handling for proxies.
-  if (%IsJSProxy(obj)) {
-    var handler = %GetHandler(obj);
-    var names = CallTrap0(handler, "getOwnPropertyNames", UNDEFINED);
-    return ToNameArray(names, "getOwnPropertyNames", false);
-  }
-
+function ObjectGetOwnPropertyKeys(obj, symbolsOnly) {
   var nameArrays = new InternalArray();
+  var filter = symbolsOnly ?
+      PROPERTY_ATTRIBUTES_STRING | PROPERTY_ATTRIBUTES_PRIVATE_SYMBOL :
+      PROPERTY_ATTRIBUTES_SYMBOLIC;
 
   // Find all the indexed properties.
 
-  // Get the local element names.
-  var localElementNames = %GetLocalElementNames(obj);
-  for (var i = 0; i < localElementNames.length; ++i) {
-    localElementNames[i] = %_NumberToString(localElementNames[i]);
-  }
-  nameArrays.push(localElementNames);
-
-  // Get names for indexed interceptor properties.
-  var interceptorInfo = %GetInterceptorInfo(obj);
-  if ((interceptorInfo & 1) != 0) {
-    var indexedInterceptorNames = %GetIndexedInterceptorElementNames(obj);
-    if (!IS_UNDEFINED(indexedInterceptorNames)) {
-      nameArrays.push(indexedInterceptorNames);
+  // Only get the local element names if we want to include string keys.
+  if (!symbolsOnly) {
+    var localElementNames = %GetLocalElementNames(obj);
+    for (var i = 0; i < localElementNames.length; ++i) {
+      localElementNames[i] = %_NumberToString(localElementNames[i]);
+    }
+    nameArrays.push(localElementNames);
+
+    // Get names for indexed interceptor properties.
+    var interceptorInfo = %GetInterceptorInfo(obj);
+    if ((interceptorInfo & 1) != 0) {
+      var indexedInterceptorNames = %GetIndexedInterceptorElementNames(obj);
+      if (!IS_UNDEFINED(indexedInterceptorNames)) {
+        nameArrays.push(indexedInterceptorNames);
+      }
     }
   }
 
   // Find all the named properties.
 
   // Get the local property names.
-  nameArrays.push(%GetLocalPropertyNames(obj, false));
+  nameArrays.push(%GetLocalPropertyNames(obj, filter));
 
   // Get names for named interceptor properties if any.
   if ((interceptorInfo & 2) != 0) {
-    var namedInterceptorNames = %GetNamedInterceptorPropertyNames(obj);
+    var namedInterceptorNames =
+        %GetNamedInterceptorPropertyNames(obj);
     if (!IS_UNDEFINED(namedInterceptorNames)) {
       nameArrays.push(namedInterceptorNames);
     }
@@ -1104,18 +1099,18 @@ function ObjectGetOwnPropertyNames(obj) {
   // Property names are expected to be unique strings,
   // but interceptors can interfere with that assumption.
   if (interceptorInfo != 0) {
-    var propertySet = { __proto__: null };
+    var seenKeys = { __proto__: null };
     var j = 0;
     for (var i = 0; i < propertyNames.length; ++i) {
-      if (IS_SYMBOL(propertyNames[i])) continue;
-      var name = ToString(propertyNames[i]);
-      // We need to check for the exact property value since for intrinsic
-      // properties like toString if(propertySet["toString"]) will always
-      // succeed.
-      if (propertySet[name] === true) {
-        continue;
+      var name = propertyNames[i];
+      if (symbolsOnly) {
+        if (!IS_SYMBOL(name) || IS_PRIVATE(name)) continue;
+      } else {
+        if (IS_SYMBOL(name)) continue;
+        name = ToString(name);
       }
-      propertySet[name] = true;
+      if (seenKeys[name]) continue;
+      seenKeys[name] = true;
       propertyNames[j++] = name;
     }
     propertyNames.length = j;
@@ -1125,6 +1120,22 @@ function ObjectGetOwnPropertyNames(obj) {
 }
 
 
+// ES5 section 15.2.3.4.
+function ObjectGetOwnPropertyNames(obj) {
+  if (!IS_SPEC_OBJECT(obj)) {
+    throw MakeTypeError("called_on_non_object", ["Object.getOwnPropertyNames"]);
+  }
+  // Special handling for proxies.
+  if (%IsJSProxy(obj)) {
+    var handler = %GetHandler(obj);
+    var names = CallTrap0(handler, "getOwnPropertyNames", UNDEFINED);
+    return ToNameArray(names, "getOwnPropertyNames", false);
+  }
+
+  return ObjectGetOwnPropertyKeys(obj, false);
+}
+
+
 // ES5 section 15.2.3.5.
 function ObjectCreate(proto, properties) {
   if (!IS_SPEC_OBJECT(proto) && proto !== null) {
@@ -1434,12 +1445,15 @@ function SetUpObject() {
     "getPrototypeOf", ObjectGetPrototypeOf,
     "getOwnPropertyDescriptor", ObjectGetOwnPropertyDescriptor,
     "getOwnPropertyNames", ObjectGetOwnPropertyNames,
+    // getOwnPropertySymbols is added in symbol.js.
     "is", ObjectIs,
     "isExtensible", ObjectIsExtensible,
     "isFrozen", ObjectIsFrozen,
     "isSealed", ObjectIsSealed,
     "preventExtensions", ObjectPreventExtension,
     "seal", ObjectSeal
+    // deliverChangeRecords, getNotifier, observe and unobserve are added
+    // in object-observe.js.
   ));
 }
 
index 3e3afee..00465e3 100644 (file)
@@ -10195,7 +10195,8 @@ THREADED_TEST(Regress91517) {
   // Call the runtime version of GetLocalPropertyNames() on the natively
   // created object through JavaScript.
   context->Global()->Set(v8_str("obj"), o4);
-  CompileRun("var names = %GetLocalPropertyNames(obj, true);");
+  // PROPERTY_ATTRIBUTES_NONE = 0
+  CompileRun("var names = %GetLocalPropertyNames(obj, 0);");
 
   ExpectInt32("names.length", 1006);
   ExpectTrue("names.indexOf(\"baz\") >= 0");
@@ -10249,7 +10250,8 @@ THREADED_TEST(Regress269562) {
   // the natively created object through JavaScript.
   context->Global()->Set(v8_str("obj"), o2);
   context->Global()->Set(v8_str("sym"), sym);
-  CompileRun("var names = %GetLocalPropertyNames(obj, true);");
+  // PROPERTY_ATTRIBUTES_NONE = 0
+  CompileRun("var names = %GetLocalPropertyNames(obj, 0);");
 
   ExpectInt32("names.length", 7);
   ExpectTrue("names.indexOf(\"foo\") >= 0");
@@ -21228,7 +21230,8 @@ TEST(AccessCheckThrows) {
   CheckCorrectThrow("%HasElement(other, 1)");
   CheckCorrectThrow("%IsPropertyEnumerable(other, 'x')");
   CheckCorrectThrow("%GetPropertyNames(other)");
-  CheckCorrectThrow("%GetLocalPropertyNames(other, true)");
+  // PROPERTY_ATTRIBUTES_NONE = 0
+  CheckCorrectThrow("%GetLocalPropertyNames(other, 0)");
   CheckCorrectThrow("%DefineOrRedefineAccessorProperty("
                         "other, 'x', null, null, 1)");
 
index 3fcd06d..ce02a05 100644 (file)
@@ -273,7 +273,7 @@ function TestKeyGet(obj) {
 }
 
 
-function TestKeyHas() {
+function TestKeyHas(obj) {
   for (var i in symbols) {
     assertTrue(symbols[i] in obj)
     assertTrue(Object.hasOwnProperty.call(obj, symbols[i]))
@@ -298,6 +298,15 @@ function TestKeyNames(obj) {
 }
 
 
+function TestGetOwnPropertySymbols(obj) {
+  var syms = Object.getOwnPropertySymbols(obj)
+  assertEquals(syms.length, symbols.length)
+  for (var i in syms) {
+    assertEquals("symbol", typeof syms[i])
+  }
+}
+
+
 function TestKeyDescriptor(obj) {
   for (var i in symbols) {
     var desc = Object.getOwnPropertyDescriptor(obj, symbols[i]);
@@ -331,6 +340,7 @@ for (var i in objs) {
   TestKeyHas(obj)
   TestKeyEnum(obj)
   TestKeyNames(obj)
+  TestGetOwnPropertySymbols(obj)
   TestKeyDescriptor(obj)
   TestKeyDelete(obj)
 }
@@ -350,3 +360,44 @@ function TestCachedKeyAfterScavenge() {
   }
 }
 TestCachedKeyAfterScavenge();
+
+
+function TestGetOwnPropertySymbolsWithProto() {
+  // We need to be have fast properties to have insertion order for property
+  // keys. The current limit is currently 30 properties.
+  var syms = symbols.slice(0, 30);
+  var proto = {}
+  var object = Object.create(proto)
+  for (var i = 0; i < syms.length; i++) {
+    // Even on object, odd on proto.
+    if (i % 2) {
+      proto[syms[i]] = i
+    } else {
+      object[syms[i]] = i
+    }
+  }
+
+  assertTrue(%HasFastProperties(object));
+
+  var objectOwnSymbols = Object.getOwnPropertySymbols(object)
+  assertEquals(objectOwnSymbols.length, syms.length / 2)
+
+  for (var i = 0; i < objectOwnSymbols.length; i++) {
+    assertEquals(objectOwnSymbols[i], syms[i * 2])
+  }
+}
+TestGetOwnPropertySymbolsWithProto()
+
+
+function TestGetOwnPropertySymbolsWithPrivateSymbols() {
+  var privateSymbol = %CreatePrivateSymbol("private")
+  var publicSymbol = Symbol()
+  var publicSymbol2 = Symbol()
+  var obj = {}
+  obj[publicSymbol] = 1
+  obj[privateSymbol] = 2
+  obj[publicSymbol2] = 3
+  var syms = Object.getOwnPropertySymbols(obj)
+  assertEquals(syms, [publicSymbol, publicSymbol2])
+}
+TestGetOwnPropertySymbolsWithPrivateSymbols()