Implement Object.prototype.{hasOwnProperty, propertyIsEnumerable} for proxies.
authorrossberg@chromium.org <rossberg@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Thu, 21 Jul 2011 11:20:27 +0000 (11:20 +0000)
committerrossberg@chromium.org <rossberg@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Thu, 21 Jul 2011 11:20:27 +0000 (11:20 +0000)
Refactor trap invocation.
Test other Object.prototype functionality for proxies.

R=ager@chromium.org
BUG=v8:1543
TEST=

Review URL: http://codereview.chromium.org/7436004

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

src/messages.js
src/proxy.js
src/v8natives.js
test/mjsunit/harmony/proxies.js

index aaa98ed..c1618e5 100644 (file)
@@ -195,6 +195,7 @@ function FormatMessage(message) {
       non_extensible_proto:         ["%0", " is not extensible"],
       handler_non_object:           ["Proxy.", "%0", " called with non-object as handler"],
       handler_trap_missing:         ["Proxy handler ", "%0", " has no '", "%1", "' trap"],
+      handler_trap_must_be_callable: ["Proxy handler ", "%0", " has non-callable '", "%1", "' trap"],
       handler_returned_false:       ["Proxy handler ", "%0", " returned false for '", "%1", "' trap"],
       handler_returned_undefined:   ["Proxy handler ", "%0", " returned undefined for '", "%1", "' trap"],
       proxy_prop_not_configurable:  ["Trap ", "%1", " of proxy handler ", "%0", " returned non-configurable descriptor for property ", "%2"],
index 27524bd..2839159 100644 (file)
@@ -136,6 +136,10 @@ function DerivedHasTrap(name) {
   return !!this.getPropertyDescriptor(name)
 }
 
+function DerivedHasOwnTrap(name) {
+  return !!this.getOwnPropertyDescriptor(name)
+}
+
 function DerivedKeysTrap() {
   var names = this.getOwnPropertyNames()
   var enumerableNames = []
index 9843635..76c327c 100644 (file)
@@ -232,6 +232,10 @@ function ObjectValueOf() {
 
 // ECMA-262 - 15.2.4.5
 function ObjectHasOwnProperty(V) {
+  if (%IsJSProxy(this)) {
+    var handler = %GetHandler(this);
+    return CallTrap1(handler, "hasOwn", DerivedHasOwnTrap, TO_STRING_INLINE(V));
+  }
   return %HasLocalProperty(TO_OBJECT_INLINE(this), TO_STRING_INLINE(V));
 }
 
@@ -249,7 +253,12 @@ function ObjectIsPrototypeOf(V) {
 
 // ECMA-262 - 15.2.4.6
 function ObjectPropertyIsEnumerable(V) {
-  return %IsPropertyEnumerable(ToObject(this), ToString(V));
+  var P = ToString(V);
+  if (%IsJSProxy(this)) {
+    var desc = GetOwnProperty(this, P);
+    return IS_UNDEFINED(desc) ? false : desc.isEnumerable();
+  }
+  return %IsPropertyEnumerable(ToObject(this), P);
 }
 
 
@@ -310,9 +319,7 @@ function ObjectKeys(obj) {
     throw MakeTypeError("obj_ctor_property_non_object", ["keys"]);
   if (%IsJSProxy(obj)) {
     var handler = %GetHandler(obj);
-    var keys = handler.keys;
-    if (IS_UNDEFINED(keys)) keys = DerivedKeysTrap;
-    var names = %_CallFunction(handler, keys);
+    var names = CallTrap0(handler, "keys", DerivedKeysTrap);
     return ToStringArray(names);
   }
   return %LocalKeys(obj);
@@ -583,16 +590,41 @@ function ConvertDescriptorArrayToDescriptor(desc_array) {
 }
 
 
+// For Harmony proxies.
+function GetTrap(handler, name, defaultTrap) {
+  var trap = handler[name];
+  if (IS_UNDEFINED(trap)) {
+    if (IS_UNDEFINED(defaultTrap)) {
+      throw MakeTypeError("handler_trap_missing", [handler, name]);
+    }
+    trap = defaultTrap;
+  } else if (!IS_FUNCTION(trap)) {
+    throw MakeTypeError("handler_trap_must_be_callable", [handler, name]);
+  }
+  return trap;
+}
+
+
+function CallTrap0(handler, name, defaultTrap) {
+  return %_CallFunction(handler, GetTrap(handler, name, defaultTrap));
+}
+
+
+function CallTrap1(handler, name, defaultTrap, x) {
+  return %_CallFunction(handler, x, GetTrap(handler, name, defaultTrap));
+}
+
+
+function CallTrap2(handler, name, defaultTrap, x, y) {
+  return %_CallFunction(handler, x, y, GetTrap(handler, name, defaultTrap));
+}
+
+
 // ES5 section 8.12.2.
 function GetProperty(obj, p) {
   if (%IsJSProxy(obj)) {
     var handler = %GetHandler(obj);
-    var getProperty = handler.getPropertyDescriptor;
-    if (IS_UNDEFINED(getProperty)) {
-      throw MakeTypeError("handler_trap_missing",
-                          [handler, "getPropertyDescriptor"]);
-    }
-    var descriptor = %_CallFunction(handler, p, getProperty);
+    var descriptor = CallTrap1(obj, "getPropertyDescriptor", void 0, p);
     if (IS_UNDEFINED(descriptor)) return descriptor;
     var desc = ToCompletePropertyDescriptor(descriptor);
     if (!desc.isConfigurable()) {
@@ -613,9 +645,7 @@ function GetProperty(obj, p) {
 function HasProperty(obj, p) {
   if (%IsJSProxy(obj)) {
     var handler = %GetHandler(obj);
-    var has = handler.has;
-    if (IS_UNDEFINED(has)) has = DerivedHasTrap;
-    return ToBoolean(%_CallFunction(handler, obj, p, has));
+    return ToBoolean(CallTrap1(handler, "has", DerivedHasTrap, p));
   }
   var desc = GetProperty(obj, p);
   return IS_UNDEFINED(desc) ? false : true;
@@ -623,15 +653,11 @@ function HasProperty(obj, p) {
 
 
 // ES5 section 8.12.1.
-function GetOwnProperty(obj, p) {
+function GetOwnProperty(obj, v) {
+  var p = ToString(v);
   if (%IsJSProxy(obj)) {
     var handler = %GetHandler(obj);
-    var getOwnProperty = handler.getOwnPropertyDescriptor;
-    if (IS_UNDEFINED(getOwnProperty)) {
-      throw MakeTypeError("handler_trap_missing",
-                          [handler, "getOwnPropertyDescriptor"]);
-    }
-    var descriptor = %_CallFunction(handler, p, getOwnProperty);
+    var descriptor = CallTrap1(handler, "getOwnPropertyDescriptor", void 0, p);
     if (IS_UNDEFINED(descriptor)) return descriptor;
     var desc = ToCompletePropertyDescriptor(descriptor);
     if (!desc.isConfigurable()) {
@@ -644,7 +670,7 @@ function GetOwnProperty(obj, p) {
   // GetOwnProperty returns an array indexed by the constants
   // defined in macros.py.
   // If p is not a property on obj undefined is returned.
-  var props = %GetOwnProperty(ToObject(obj), ToString(p));
+  var props = %GetOwnProperty(ToObject(obj), ToString(v));
 
   // A false value here means that access checks failed.
   if (props === false) return void 0;
@@ -656,11 +682,7 @@ function GetOwnProperty(obj, p) {
 // Harmony proxies.
 function DefineProxyProperty(obj, p, attributes, should_throw) {
   var handler = %GetHandler(obj);
-  var defineProperty = handler.defineProperty;
-  if (IS_UNDEFINED(defineProperty)) {
-    throw MakeTypeError("handler_trap_missing", [handler, "defineProperty"]);
-  }
-  var result = %_CallFunction(handler, p, attributes, defineProperty);
+  var result = CallTrap2(handler, "defineProperty", void 0, p, attributes);
   if (!ToBoolean(result)) {
     if (should_throw) {
       throw MakeTypeError("handler_returned_false",
@@ -889,12 +911,7 @@ function ObjectGetOwnPropertyNames(obj) {
   // Special handling for proxies.
   if (%IsJSProxy(obj)) {
     var handler = %GetHandler(obj);
-    var getOwnPropertyNames = handler.getOwnPropertyNames;
-    if (IS_UNDEFINED(getOwnPropertyNames)) {
-      throw MakeTypeError("handler_trap_missing",
-                          [handler, "getOwnPropertyNames"]);
-    }
-    var names = %_CallFunction(handler, getOwnPropertyNames);
+    var names = CallTrap0(handler, "getOwnPropertyNames", void 0);
     return ToStringArray(names, "getOwnPropertyNames");
   }
 
@@ -1024,11 +1041,7 @@ function ObjectDefineProperties(obj, properties) {
 // Harmony proxies.
 function ProxyFix(obj) {
   var handler = %GetHandler(obj);
-  var fix = handler.fix;
-  if (IS_UNDEFINED(fix)) {
-    throw MakeTypeError("handler_trap_missing", [handler, "fix"]);
-  }
-  var props = %_CallFunction(handler, fix);
+  var props = CallTrap0(handler, "fix", void 0);
   if (IS_UNDEFINED(props)) {
     throw MakeTypeError("handler_returned_undefined", [handler, "fix"]);
   }
index 84641d5..640033d 100644 (file)
@@ -42,22 +42,27 @@ function TestGet(handler) {
 TestGet({
   get: function(r, k) { return 42 }
 })
+
 TestGet({
   get: function(r, k) { return this.get2(r, k) },
   get2: function(r, k) { return 42 }
 })
+
 TestGet({
   getPropertyDescriptor: function(k) { return {value: 42} }
 })
+
 TestGet({
   getPropertyDescriptor: function(k) { return this.getPropertyDescriptor2(k) },
   getPropertyDescriptor2: function(k) { return {value: 42} }
 })
+
 TestGet({
   getPropertyDescriptor: function(k) {
     return {get value() { return 42 }}
   }
 })
+
 TestGet({
   get: undefined,
   getPropertyDescriptor: function(k) { return {value: 42} }
@@ -83,32 +88,38 @@ function TestGetCall(handler) {
 TestGetCall({
   get: function(r, k) { return function() { return 55 } }
 })
+
 TestGetCall({
   get: function(r, k) { return this.get2(r, k) },
   get2: function(r, k) { return function() { return 55 } }
 })
+
 TestGetCall({
   getPropertyDescriptor: function(k) {
     return {value: function() { return 55 }}
   }
 })
+
 TestGetCall({
   getPropertyDescriptor: function(k) { return this.getPropertyDescriptor2(k) },
   getPropertyDescriptor2: function(k) {
     return {value: function() { return 55 }}
   }
 })
+
 TestGetCall({
   getPropertyDescriptor: function(k) {
     return {get value() { return function() { return 55 } }}
   }
 })
+
 TestGetCall({
   get: undefined,
   getPropertyDescriptor: function(k) {
     return {value: function() { return 55 }}
   }
 })
+
 TestGetCall({
   get: function(r, k) {
     if (k == "gg") {
@@ -146,14 +157,17 @@ function TestSet(handler) {
 TestSet({
   set: function(r, k, v) { key = k; val = v; return true }
 })
+
 TestSet({
   set: function(r, k, v) { return this.set2(r, k, v) },
   set2: function(r, k, v) { key = k; val = v; return true }
 })
+
 TestSet({
   getOwnPropertyDescriptor: function(k) { return {writable: true} },
   defineProperty: function(k, desc) { key = k; val = desc.value }
 })
+
 TestSet({
   getOwnPropertyDescriptor: function(k) {
     return this.getOwnPropertyDescriptor2(k)
@@ -162,22 +176,26 @@ TestSet({
   defineProperty: function(k, desc) { this.defineProperty2(k, desc) },
   defineProperty2: function(k, desc) { key = k; val = desc.value }
 })
+
 TestSet({
   getOwnPropertyDescriptor: function(k) {
     return {get writable() { return true }}
   },
   defineProperty: function(k, desc) { key = k; val = desc.value }
 })
+
 TestSet({
   getOwnPropertyDescriptor: function(k) {
     return {set: function(v) { key = k; val = v }}
   }
 })
+
 TestSet({
   getOwnPropertyDescriptor: function(k) { return null },
   getPropertyDescriptor: function(k) { return {writable: true} },
   defineProperty: function(k, desc) { key = k; val = desc.value }
 })
+
 TestSet({
   getOwnPropertyDescriptor: function(k) { return null },
   getPropertyDescriptor: function(k) {
@@ -185,12 +203,14 @@ TestSet({
   },
   defineProperty: function(k, desc) { key = k; val = desc.value }
 })
+
 TestSet({
   getOwnPropertyDescriptor: function(k) { return null },
   getPropertyDescriptor: function(k) {
     return {set: function(v) { key = k; val = v }}
   }
 })
+
 TestSet({
   getOwnPropertyDescriptor: function(k) { return null },
   getPropertyDescriptor: function(k) { return null },
@@ -279,10 +299,12 @@ function TestDefine(handler) {
 TestDefine({
   defineProperty: function(k, d) { key = k; desc = d; return true }
 })
+
 TestDefine({
   defineProperty: function(k, d) { return this.defineProperty2(k, d) },
   defineProperty2: function(k, d) { key = k; desc = d; return true }
 })
+
 TestDefine(Proxy.create({
   get: function(pr, pk) {
     return function(k, d) { key = k; desc = d; return true }
@@ -323,10 +345,12 @@ function TestDelete(handler) {
 TestDelete({
   'delete': function(k) { key = k; return k < "z" }
 })
+
 TestDelete({
   'delete': function(k) { return this.delete2(k) },
   delete2: function(k) { key = k; return k < "z" }
 })
+
 TestDelete(Proxy.create({
   get: function(pr, pk) {
     return function(k) { key = k; return k < "z" }
@@ -363,6 +387,7 @@ TestDescriptor({
   defineProperty: function(k, d) { this["__" + k] = d; return true },
   getOwnPropertyDescriptor: function(k) { return this["__" + k] }
 })
+
 TestDescriptor({
   defineProperty: function(k, d) { this["__" + k] = d; return true },
   getOwnPropertyDescriptor: function(k) {
@@ -404,7 +429,7 @@ assertTrue("object" == typeof Proxy.create({}))
 
 
 
-// Element (in).
+// Membership test (in).
 
 var key
 function TestIn(handler) {
@@ -442,26 +467,31 @@ function TestIn(handler) {
 TestIn({
   has: function(k) { key = k; return k < "z" }
 })
+
 TestIn({
   has: function(k) { return this.has2(k) },
   has2: function(k) { key = k; return k < "z" }
 })
+
 TestIn({
   getPropertyDescriptor: function(k) {
     key = k; return k < "z" ? {value: 42} : void 0
   }
 })
+
 TestIn({
   getPropertyDescriptor: function(k) { return this.getPropertyDescriptor2(k) },
   getPropertyDescriptor2: function(k) {
     key = k; return k < "z" ? {value: 42} : void 0
   }
 })
+
 TestIn({
   getPropertyDescriptor: function(k) {
     key = k; return k < "z" ? {get value() { return 42 }} : void 0
   }
 })
+
 TestIn({
   get: undefined,
   getPropertyDescriptor: function(k) {
@@ -477,7 +507,65 @@ TestIn(Proxy.create({
 
 
 
-// Instanceof (instanceof).
+// Own Properties (Object.prototype.hasOwnProperty).
+
+var key
+function TestHasOwn(handler) {
+  var o = Proxy.create(handler)
+  assertTrue(Object.prototype.hasOwnProperty.call(o, "a"))
+  assertEquals("a", key)
+  assertTrue(Object.prototype.hasOwnProperty.call(o, 99))
+  assertEquals("99", key)
+  assertFalse(Object.prototype.hasOwnProperty.call(o, "z"))
+  assertEquals("z", key)
+}
+
+TestHasOwn({
+  hasOwn: function(k) { key = k; return k < "z" }
+})
+
+TestHasOwn({
+  hasOwn: function(k) { return this.hasOwn2(k) },
+  hasOwn2: function(k) { key = k; return k < "z" }
+})
+
+TestHasOwn({
+  getOwnPropertyDescriptor: function(k) {
+    key = k; return k < "z" ? {value: 42} : void 0
+  }
+})
+
+TestHasOwn({
+  getOwnPropertyDescriptor: function(k) {
+    return this.getOwnPropertyDescriptor2(k)
+  },
+  getOwnPropertyDescriptor2: function(k) {
+    key = k; return k < "z" ? {value: 42} : void 0
+  }
+})
+
+TestHasOwn({
+  getOwnPropertyDescriptor: function(k) {
+    key = k; return k < "z" ? {get value() { return 42 }} : void 0
+  }
+})
+
+TestHasOwn({
+  hasOwn: undefined,
+  getOwnPropertyDescriptor: function(k) {
+    key = k; return k < "z" ? {value: 42} : void 0
+  }
+})
+
+TestHasOwn(Proxy.create({
+  get: function(pr, pk) {
+    return function(k) { key = k; return k < "z" }
+  }
+}))
+
+
+
+// Instanceof (instanceof)
 
 function TestInstanceof() {
   var o = {}
@@ -514,7 +602,7 @@ TestInstanceof()
 
 
 
-// Prototype (Object.getPrototypeOf).
+// Prototype (Object.getPrototypeOf, Object.prototype.isPrototypeOf).
 
 function TestPrototype() {
   var o = {}
@@ -528,6 +616,32 @@ function TestPrototype() {
   assertSame(Object.getPrototypeOf(p2), o)
   assertSame(Object.getPrototypeOf(p3), p2)
   assertSame(Object.getPrototypeOf(p4), null)
+
+  assertTrue(Object.prototype.isPrototypeOf(o))
+  assertFalse(Object.prototype.isPrototypeOf(p1))
+  assertTrue(Object.prototype.isPrototypeOf(p2))
+  assertTrue(Object.prototype.isPrototypeOf(p3))
+  assertFalse(Object.prototype.isPrototypeOf(p4))
+  assertTrue(Object.prototype.isPrototypeOf.call(Object.prototype, o))
+  assertFalse(Object.prototype.isPrototypeOf.call(Object.prototype, p1))
+  assertTrue(Object.prototype.isPrototypeOf.call(Object.prototype, p2))
+  assertTrue(Object.prototype.isPrototypeOf.call(Object.prototype, p3))
+  assertFalse(Object.prototype.isPrototypeOf.call(Object.prototype, p4))
+  assertFalse(Object.prototype.isPrototypeOf.call(o, o))
+  assertFalse(Object.prototype.isPrototypeOf.call(o, p1))
+  assertTrue(Object.prototype.isPrototypeOf.call(o, p2))
+  assertTrue(Object.prototype.isPrototypeOf.call(o, p3))
+  assertFalse(Object.prototype.isPrototypeOf.call(o, p4))
+  assertFalse(Object.prototype.isPrototypeOf.call(p1, p1))
+  assertFalse(Object.prototype.isPrototypeOf.call(p1, o))
+  assertFalse(Object.prototype.isPrototypeOf.call(p1, p2))
+  assertFalse(Object.prototype.isPrototypeOf.call(p1, p3))
+  assertFalse(Object.prototype.isPrototypeOf.call(p1, p4))
+  assertFalse(Object.prototype.isPrototypeOf.call(p2, p1))
+  assertFalse(Object.prototype.isPrototypeOf.call(p2, p2))
+  assertTrue(Object.prototype.isPrototypeOf.call(p2, p3))
+  assertFalse(Object.prototype.isPrototypeOf.call(p2, p4))
+  assertFalse(Object.prototype.isPrototypeOf.call(p3, p2))
 }
 
 TestPrototype()
@@ -544,13 +658,16 @@ function TestPropertyNames(names, handler) {
 TestPropertyNames([], {
   getOwnPropertyNames: function() { return [] }
 })
+
 TestPropertyNames(["a", "zz", " ", "0"], {
   getOwnPropertyNames: function() { return ["a", "zz", " ", 0] }
 })
+
 TestPropertyNames(["throw", "function "], {
   getOwnPropertyNames: function() { return this.getOwnPropertyNames2() },
   getOwnPropertyNames2: function() { return ["throw", "function "] }
 })
+
 TestPropertyNames(["[object Object]"], {
   get getOwnPropertyNames() {
     return function() { return [{}] }
@@ -566,22 +683,27 @@ function TestKeys(names, handler) {
 TestKeys([], {
   keys: function() { return [] }
 })
+
 TestKeys(["a", "zz", " ", "0"], {
   keys: function() { return ["a", "zz", " ", 0] }
 })
+
 TestKeys(["throw", "function "], {
   keys: function() { return this.keys2() },
   keys2: function() { return ["throw", "function "] }
 })
+
 TestKeys(["[object Object]"], {
   get keys() {
     return function() { return [{}] }
   }
 })
+
 TestKeys(["a", "0"], {
   getOwnPropertyNames: function() { return ["a", 23, "zz", "", 0] },
   getOwnPropertyDescriptor: function(k) { return {enumerable: k.length == 1} }
 })
+
 TestKeys(["23", "zz", ""], {
   getOwnPropertyNames: function() { return this.getOwnPropertyNames2() },
   getOwnPropertyNames2: function() { return ["a", 23, "zz", "", 0] },
@@ -590,6 +712,7 @@ TestKeys(["23", "zz", ""], {
   },
   getOwnPropertyDescriptor2: function(k) { return {enumerable: k.length != 1} }
 })
+
 TestKeys(["a", "b", "c", "5"], {
   get getOwnPropertyNames() {
     return function() { return ["0", 4, "a", "b", "c", 5] }
@@ -598,6 +721,7 @@ TestKeys(["a", "b", "c", "5"], {
     return function(k) { return {enumerable: k >= "44"} }
   }
 })
+
 TestKeys([], {
   get getOwnPropertyNames() {
     return function() { return ["a", "b", "c"] }
@@ -661,6 +785,7 @@ function TestFix(names, handler) {
 TestFix([], {
   fix: function() { return {} }
 })
+
 TestFix(["a", "b", "c", "d", "zz"], {
   fix: function() {
     return {
@@ -672,12 +797,14 @@ TestFix(["a", "b", "c", "d", "zz"], {
     }
   }
 })
+
 TestFix(["a"], {
   fix: function() { return this.fix2() },
   fix2: function() {
     return {a: {value: 4, writable: true, configurable: true, enumerable: true}}
   }
 })
+
 TestFix(["b"], {
   get fix() {
     return function() {
@@ -685,3 +812,87 @@ TestFix(["b"], {
     }
   }
 })
+
+
+
+// String conversion (Object.prototype.toString, Object.prototype.toLocaleString)
+
+var key
+function TestToString(handler) {
+  var o = Proxy.create(handler)
+  key = ""
+  assertEquals("[object Object]", Object.prototype.toString.call(o))
+  assertEquals("", key)
+  assertEquals("my_proxy", Object.prototype.toLocaleString.call(o))
+  assertEquals("toString", key)
+}
+
+TestToString({
+  get: function(r, k) { key = k; return function() { return "my_proxy" } }
+})
+
+TestToString({
+  get: function(r, k) { return this.get2(r, k) },
+  get2: function(r, k) { key = k; return function() { return "my_proxy" } }
+})
+
+TestToString(Proxy.create({
+  get: function(pr, pk) {
+    return function(r, k) { key = k; return function() { return "my_proxy" } }
+  }
+}))
+
+
+
+// Value conversion (Object.prototype.toValue)
+
+function TestValueOf(handler) {
+  var o = Proxy.create(handler)
+  assertSame(o, Object.prototype.valueOf.call(o))
+}
+
+TestValueOf({})
+
+
+
+// Enumerability (Object.prototype.propertyIsEnumerable)
+
+var key
+function TestIsEnumerable(handler) {
+  var o = Proxy.create(handler)
+  assertTrue(Object.prototype.propertyIsEnumerable.call(o, "a"))
+  assertEquals("a", key)
+  assertTrue(Object.prototype.propertyIsEnumerable.call(o, 2))
+  assertEquals("2", key)
+  assertFalse(Object.prototype.propertyIsEnumerable.call(o, "z"))
+  assertEquals("z", key)
+}
+
+TestIsEnumerable({
+  getOwnPropertyDescriptor: function(k) {
+    key = k; return {enumerable: k < "z", configurable: true}
+  },
+})
+
+TestIsEnumerable({
+  getOwnPropertyDescriptor: function(k) {
+    return this.getOwnPropertyDescriptor2(k)
+  },
+  getOwnPropertyDescriptor2: function(k) {
+    key = k; return {enumerable: k < "z", configurable: true}
+  },
+})
+
+TestIsEnumerable({
+  getOwnPropertyDescriptor: function(k) {
+    key = k; return {get enumerable() { return k < "z" }, configurable: true}
+  },
+})
+
+TestIsEnumerable(Proxy.create({
+  get: function(pr, pk) {
+    return function(k) {
+      key = k; return {enumerable: k < "z", configurable: true}
+    }
+  }
+}))