зеркало из https://github.com/mozilla/rhino.git
Implement `Symbol.prototype.description`
Refactored `NativeSymbol` to use `LambdaConstructor` instead of `IdScriptableObject`.
This commit is contained in:
Родитель
3577b83557
Коммит
6fee4ce0ae
|
@ -35,7 +35,7 @@ public class LambdaConstructor extends LambdaFunction {
|
|||
public static final int CONSTRUCTOR_DEFAULT = CONSTRUCTOR_FUNCTION | CONSTRUCTOR_NEW;
|
||||
|
||||
// Lambdas should not be serialized.
|
||||
private final transient Constructable targetConstructor;
|
||||
protected final transient Constructable targetConstructor;
|
||||
private final int flags;
|
||||
|
||||
/**
|
||||
|
@ -112,6 +112,23 @@ public class LambdaConstructor extends LambdaFunction {
|
|||
proto.defineProperty(name, f, attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a function property on the prototype of the constructor using a LambdaFunction under
|
||||
* the covers.
|
||||
*/
|
||||
public void definePrototypeMethod(
|
||||
Scriptable scope,
|
||||
SymbolKey name,
|
||||
int length,
|
||||
Callable target,
|
||||
int attributes,
|
||||
int propertyAttributes) {
|
||||
LambdaFunction f = new LambdaFunction(scope, "[" + name.getName() + "]", length, target);
|
||||
f.setStandardPropertyAttributes(propertyAttributes);
|
||||
ScriptableObject proto = getPrototypeScriptable();
|
||||
proto.defineProperty(name, f, attributes);
|
||||
}
|
||||
|
||||
/** Define a property that may be of any type on the prototype of this constructor. */
|
||||
public void definePrototypeProperty(String name, Object value, int attributes) {
|
||||
ScriptableObject proto = getPrototypeScriptable();
|
||||
|
|
|
@ -14,7 +14,7 @@ import java.util.Map;
|
|||
* properties. One of them is that some objects can have an "internal data slot" that makes them a
|
||||
* Symbol and others cannot.
|
||||
*/
|
||||
public class NativeSymbol extends IdScriptableObject implements Symbol {
|
||||
public class NativeSymbol extends ScriptableObject implements Symbol {
|
||||
private static final long serialVersionUID = -589539749749830003L;
|
||||
|
||||
public static final String CLASS_NAME = "Symbol";
|
||||
|
@ -27,8 +27,60 @@ public class NativeSymbol extends IdScriptableObject implements Symbol {
|
|||
private final NativeSymbol symbolData;
|
||||
|
||||
public static void init(Context cx, Scriptable scope, boolean sealed) {
|
||||
NativeSymbol obj = new NativeSymbol("");
|
||||
ScriptableObject ctor = obj.exportAsJSClass(MAX_PROTOTYPE_ID, scope, false);
|
||||
LambdaConstructor ctor =
|
||||
new LambdaConstructor(
|
||||
scope,
|
||||
CLASS_NAME,
|
||||
0,
|
||||
0, // Unused
|
||||
NativeSymbol::js_constructor) {
|
||||
// Calling new Symbol('foo') is not allowed. However, we need to be able to
|
||||
// create
|
||||
// a new symbol instance to register built-in ones and for the implementation of
|
||||
// "for",
|
||||
// so we have this trick of a thread-local variable to allow it.
|
||||
@Override
|
||||
public Scriptable construct(Context cx, Scriptable scope, Object[] args) {
|
||||
if (cx.getThreadLocal(CONSTRUCTOR_SLOT) == null) {
|
||||
throw ScriptRuntime.typeErrorById("msg.no.new", getFunctionName());
|
||||
}
|
||||
return (Scriptable) call(cx, scope, null, args);
|
||||
}
|
||||
|
||||
// Calling Symbol('foo') should act like a constructor call
|
||||
@Override
|
||||
public Object call(
|
||||
Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
|
||||
Scriptable obj = targetConstructor.construct(cx, scope, args);
|
||||
obj.setPrototype(getClassPrototype());
|
||||
obj.setParentScope(scope);
|
||||
return obj;
|
||||
}
|
||||
};
|
||||
|
||||
ctor.setPrototypePropertyAttributes(DONTENUM | READONLY | PERMANENT);
|
||||
|
||||
ctor.defineConstructorMethod(
|
||||
scope, "for", 1, NativeSymbol::js_for, DONTENUM, DONTENUM | READONLY);
|
||||
ctor.defineConstructorMethod(
|
||||
scope, "keyFor", 1, NativeSymbol::js_keyFor, DONTENUM, DONTENUM | READONLY);
|
||||
|
||||
ctor.definePrototypeMethod(
|
||||
scope, "toString", 0, NativeSymbol::js_toString, DONTENUM, DONTENUM | READONLY);
|
||||
ctor.definePrototypeMethod(
|
||||
scope, "valueOf", 0, NativeSymbol::js_valueOf, DONTENUM, DONTENUM | READONLY);
|
||||
ctor.definePrototypeMethod(
|
||||
scope,
|
||||
SymbolKey.TO_PRIMITIVE,
|
||||
1,
|
||||
NativeSymbol::js_valueOf,
|
||||
DONTENUM | READONLY,
|
||||
DONTENUM | READONLY);
|
||||
ctor.definePrototypeProperty(SymbolKey.TO_STRING_TAG, CLASS_NAME, DONTENUM | READONLY);
|
||||
ctor.definePrototypeProperty(
|
||||
cx, "description", NativeSymbol::js_description, DONTENUM | READONLY);
|
||||
|
||||
ScriptableObject.defineProperty(scope, CLASS_NAME, ctor, DONTENUM);
|
||||
|
||||
cx.putThreadLocal(CONSTRUCTOR_SLOT, Boolean.TRUE);
|
||||
try {
|
||||
|
@ -45,7 +97,6 @@ public class NativeSymbol extends IdScriptableObject implements Symbol {
|
|||
createStandardSymbol(cx, scope, ctor, "search", SymbolKey.SEARCH);
|
||||
createStandardSymbol(cx, scope, ctor, "split", SymbolKey.SPLIT);
|
||||
createStandardSymbol(cx, scope, ctor, "unscopables", SymbolKey.UNSCOPABLES);
|
||||
|
||||
} finally {
|
||||
cx.removeThreadLocal(CONSTRUCTOR_SLOT);
|
||||
}
|
||||
|
@ -95,117 +146,12 @@ public class NativeSymbol extends IdScriptableObject implements Symbol {
|
|||
return CLASS_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillConstructorProperties(IdFunctionObject ctor) {
|
||||
super.fillConstructorProperties(ctor);
|
||||
addIdFunctionProperty(ctor, CLASS_NAME, ConstructorId_for, "for", 1);
|
||||
addIdFunctionProperty(ctor, CLASS_NAME, ConstructorId_keyFor, "keyFor", 1);
|
||||
}
|
||||
|
||||
private static void createStandardSymbol(
|
||||
Context cx, Scriptable scope, ScriptableObject ctor, String name, SymbolKey key) {
|
||||
Scriptable sym = cx.newObject(scope, CLASS_NAME, new Object[] {name, key});
|
||||
Context cx, Scriptable scope, LambdaConstructor ctor, String name, SymbolKey key) {
|
||||
Scriptable sym = (Scriptable) ctor.call(cx, scope, scope, new Object[] {name, key});
|
||||
ctor.defineProperty(name, sym, DONTENUM | READONLY | PERMANENT);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int findPrototypeId(String s) {
|
||||
int id = 0;
|
||||
switch (s) {
|
||||
case "constructor":
|
||||
id = Id_constructor;
|
||||
break;
|
||||
case "toString":
|
||||
id = Id_toString;
|
||||
break;
|
||||
case "valueOf":
|
||||
id = Id_valueOf;
|
||||
break;
|
||||
default:
|
||||
id = 0;
|
||||
break;
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int findPrototypeId(Symbol key) {
|
||||
if (SymbolKey.TO_STRING_TAG.equals(key)) {
|
||||
return SymbolId_toStringTag;
|
||||
} else if (SymbolKey.TO_PRIMITIVE.equals(key)) {
|
||||
return SymbolId_toPrimitive;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static final int ConstructorId_keyFor = -2,
|
||||
ConstructorId_for = -1,
|
||||
Id_constructor = 1,
|
||||
Id_toString = 2,
|
||||
Id_valueOf = 4,
|
||||
SymbolId_toStringTag = 3,
|
||||
SymbolId_toPrimitive = 5,
|
||||
MAX_PROTOTYPE_ID = SymbolId_toPrimitive;
|
||||
|
||||
@Override
|
||||
protected void initPrototypeId(int id) {
|
||||
switch (id) {
|
||||
case Id_constructor:
|
||||
initPrototypeMethod(CLASS_NAME, id, "constructor", 0);
|
||||
break;
|
||||
case Id_toString:
|
||||
initPrototypeMethod(CLASS_NAME, id, "toString", 0);
|
||||
break;
|
||||
case Id_valueOf:
|
||||
initPrototypeMethod(CLASS_NAME, id, "valueOf", 0);
|
||||
break;
|
||||
case SymbolId_toStringTag:
|
||||
initPrototypeValue(id, SymbolKey.TO_STRING_TAG, CLASS_NAME, DONTENUM | READONLY);
|
||||
break;
|
||||
case SymbolId_toPrimitive:
|
||||
initPrototypeMethod(
|
||||
CLASS_NAME, id, SymbolKey.TO_PRIMITIVE, "Symbol.toPrimitive", 1);
|
||||
break;
|
||||
default:
|
||||
super.initPrototypeId(id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object execIdCall(
|
||||
IdFunctionObject f, Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
|
||||
if (!f.hasTag(CLASS_NAME)) {
|
||||
return super.execIdCall(f, cx, scope, thisObj, args);
|
||||
}
|
||||
int id = f.methodId();
|
||||
switch (id) {
|
||||
case ConstructorId_for:
|
||||
return js_for(cx, scope, args);
|
||||
case ConstructorId_keyFor:
|
||||
return js_keyFor(cx, scope, args);
|
||||
|
||||
case Id_constructor:
|
||||
if (thisObj == null) {
|
||||
if (cx.getThreadLocal(CONSTRUCTOR_SLOT) == null) {
|
||||
// We should never get to this via "new".
|
||||
throw ScriptRuntime.typeErrorById("msg.no.symbol.new");
|
||||
}
|
||||
// Unless we are being called by our own internal "new"
|
||||
return js_constructor(args);
|
||||
}
|
||||
return construct(cx, scope, args);
|
||||
|
||||
case Id_toString:
|
||||
return getSelf(cx, scope, thisObj).toString();
|
||||
case Id_valueOf:
|
||||
case SymbolId_toPrimitive:
|
||||
return getSelf(cx, scope, thisObj).js_valueOf();
|
||||
default:
|
||||
return super.execIdCall(f, cx, scope, thisObj, args);
|
||||
}
|
||||
}
|
||||
|
||||
private static NativeSymbol getSelf(Context cx, Scriptable scope, Object thisObj) {
|
||||
try {
|
||||
return (NativeSymbol) ScriptRuntime.toObject(cx, scope, thisObj);
|
||||
|
@ -214,16 +160,10 @@ public class NativeSymbol extends IdScriptableObject implements Symbol {
|
|||
}
|
||||
}
|
||||
|
||||
private static NativeSymbol js_constructor(Object[] args) {
|
||||
String desc;
|
||||
if (args.length > 0) {
|
||||
if (Undefined.instance.equals(args[0])) {
|
||||
desc = "";
|
||||
} else {
|
||||
desc = ScriptRuntime.toString(args[0]);
|
||||
}
|
||||
} else {
|
||||
desc = "";
|
||||
private static NativeSymbol js_constructor(Context cx, Scriptable scope, Object[] args) {
|
||||
String desc = null;
|
||||
if (args.length > 0 && !Undefined.isUndefined(args[0])) {
|
||||
desc = ScriptRuntime.toString(args[0]);
|
||||
}
|
||||
|
||||
if (args.length > 1) {
|
||||
|
@ -233,18 +173,29 @@ public class NativeSymbol extends IdScriptableObject implements Symbol {
|
|||
return new NativeSymbol(new SymbolKey(desc));
|
||||
}
|
||||
|
||||
private Object js_valueOf() {
|
||||
// In the case that "Object()" was called we actually have a different "internal slot"
|
||||
return symbolData;
|
||||
private static String js_toString(
|
||||
Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
|
||||
return getSelf(cx, scope, thisObj).toString();
|
||||
}
|
||||
|
||||
private Object js_for(Context cx, Scriptable scope, Object[] args) {
|
||||
private static Object js_valueOf(
|
||||
Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
|
||||
// In the case that "Object()" was called we actually have a different "internal slot"
|
||||
return getSelf(cx, scope, thisObj).symbolData;
|
||||
}
|
||||
|
||||
private static Object js_description(Scriptable thisObj) {
|
||||
NativeSymbol self = LambdaConstructor.convertThisObject(thisObj, NativeSymbol.class);
|
||||
return self.getKey().getDescription();
|
||||
}
|
||||
|
||||
private static Object js_for(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
|
||||
String name =
|
||||
(args.length > 0
|
||||
? ScriptRuntime.toString(args[0])
|
||||
: ScriptRuntime.toString(Undefined.instance));
|
||||
|
||||
Map<String, NativeSymbol> table = getGlobalMap();
|
||||
Map<String, NativeSymbol> table = getGlobalMap(scope);
|
||||
NativeSymbol ret = table.get(name);
|
||||
|
||||
if (ret == null) {
|
||||
|
@ -255,14 +206,15 @@ public class NativeSymbol extends IdScriptableObject implements Symbol {
|
|||
}
|
||||
|
||||
@SuppressWarnings("ReferenceEquality")
|
||||
private Object js_keyFor(Context cx, Scriptable scope, Object[] args) {
|
||||
private static Object js_keyFor(
|
||||
Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
|
||||
Object s = (args.length > 0 ? args[0] : Undefined.instance);
|
||||
if (!(s instanceof NativeSymbol)) {
|
||||
throw ScriptRuntime.throwCustomError(cx, scope, "TypeError", "Not a Symbol");
|
||||
}
|
||||
NativeSymbol sym = (NativeSymbol) s;
|
||||
|
||||
Map<String, NativeSymbol> table = getGlobalMap();
|
||||
Map<String, NativeSymbol> table = getGlobalMap(scope);
|
||||
for (Map.Entry<String, NativeSymbol> e : table.entrySet()) {
|
||||
if (e.getValue().key == sym.key) {
|
||||
return e.getKey();
|
||||
|
@ -341,8 +293,8 @@ public class NativeSymbol extends IdScriptableObject implements Symbol {
|
|||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Map<String, NativeSymbol> getGlobalMap() {
|
||||
ScriptableObject top = (ScriptableObject) getTopLevelScope(this);
|
||||
private static Map<String, NativeSymbol> getGlobalMap(Scriptable scope) {
|
||||
ScriptableObject top = (ScriptableObject) getTopLevelScope(scope);
|
||||
Map<String, NativeSymbol> map =
|
||||
(Map<String, NativeSymbol>) top.getAssociatedValue(GLOBAL_TABLE_KEY);
|
||||
if (map == null) {
|
||||
|
|
|
@ -25,14 +25,27 @@ public class SymbolKey implements Symbol, Serializable {
|
|||
public static final SymbolKey SPLIT = new SymbolKey("Symbol.split");
|
||||
public static final SymbolKey UNSCOPABLES = new SymbolKey("Symbol.unscopables");
|
||||
|
||||
private String name;
|
||||
// If passed a javascript undefined, this will be a (java) null
|
||||
private final String name;
|
||||
|
||||
public SymbolKey(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the symbol's name. Returns empty string for anonymous symbol (i.e. something created
|
||||
* with <code>Symbol()</code>).
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
return name != null ? name : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the symbol's description - will return {@link Undefined#instance} if we have an
|
||||
* anonymous symbol (i.e. something created with <code>Symbol()</code>).
|
||||
*/
|
||||
public Object getDescription() {
|
||||
return name != null ? name : Undefined.instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -571,4 +571,19 @@ TestStringify('{}', symbol_wrapper);
|
|||
symbol_wrapper.a = 1;
|
||||
TestStringify('{"a":1}', symbol_wrapper);
|
||||
|
||||
|
||||
function TestDescription() {
|
||||
assertEquals(Symbol("abc").description, "abc");
|
||||
assertEquals(Symbol.iterator.description, "Symbol.iterator");
|
||||
assertEquals(Symbol().description, undefined);
|
||||
|
||||
assertFalse(Symbol("abc").hasOwnProperty("description"));
|
||||
assertTrue(Symbol.prototype.hasOwnProperty("description"));
|
||||
assertTrue(Object.getOwnPropertyDescriptor(Symbol.prototype, "description").configurable);
|
||||
assertFalse(Object.getOwnPropertyDescriptor(Symbol.prototype, "description").enumerable);
|
||||
assertTrue(Object.getOwnPropertyDescriptor(Symbol.prototype, "description").get !== null);
|
||||
}
|
||||
TestDescription();
|
||||
|
||||
|
||||
"success";
|
||||
|
|
|
@ -2299,27 +2299,17 @@ built-ins/String 140/1182 (11.84%)
|
|||
|
||||
built-ins/StringIteratorPrototype 0/7 (0.0%)
|
||||
|
||||
built-ins/Symbol 34/92 (36.96%)
|
||||
built-ins/Symbol 24/92 (26.09%)
|
||||
asyncIterator/prop-desc.js
|
||||
for/cross-realm.js
|
||||
for/description.js
|
||||
for/not-a-constructor.js {unsupported: [Reflect.construct]}
|
||||
hasInstance/cross-realm.js
|
||||
isConcatSpreadable/cross-realm.js
|
||||
iterator/cross-realm.js
|
||||
keyFor/arg-non-symbol.js
|
||||
keyFor/cross-realm.js
|
||||
keyFor/not-a-constructor.js {unsupported: [Reflect.construct]}
|
||||
matchAll 2/2 (100.0%)
|
||||
match/cross-realm.js
|
||||
prototype/description/description-symboldescriptivestring.js
|
||||
prototype/description/descriptor.js
|
||||
prototype/description/get.js
|
||||
prototype/description/this-val-non-symbol.js
|
||||
prototype/description/this-val-symbol.js
|
||||
prototype/description/wrapper.js
|
||||
prototype/Symbol.toPrimitive/name.js
|
||||
prototype/Symbol.toPrimitive/prop-desc.js
|
||||
prototype/Symbol.toPrimitive/redefined-symbol-wrapper-ordinary-toprimitive.js
|
||||
prototype/Symbol.toPrimitive/removed-symbol-wrapper-ordinary-toprimitive.js
|
||||
prototype/toString/not-a-constructor.js {unsupported: [Reflect.construct]}
|
||||
|
|
Загрузка…
Ссылка в новой задаче