Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Symbol.prototype and other related Symbol and type coersion changes #1611

Draft
wants to merge 12 commits into
base: master
Choose a base branch
from
Draft
3 changes: 2 additions & 1 deletion rhino/src/main/java/org/mozilla/javascript/NativeDate.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Optional;

/**
* This class implements the Date native object. See ECMA 15.9.
Expand Down Expand Up @@ -293,7 +294,7 @@ public Object execIdCall(
final String toISOString = "toISOString";

Scriptable o = ScriptRuntime.toObject(cx, scope, thisObj);
Object tv = ScriptRuntime.toPrimitive(o, ScriptRuntime.NumberClass);
Object tv = ScriptRuntime.toPrimitive(o, Optional.of(ScriptRuntime.NumberClass));
if (tv instanceof Number) {
double d = ((Number) tv).doubleValue();
if (Double.isNaN(d) || Double.isInfinite(d)) {
Expand Down
10 changes: 6 additions & 4 deletions rhino/src/main/java/org/mozilla/javascript/NativeSymbol.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,20 @@ public static void init(Context cx, Scriptable scope, boolean sealed) {

cx.putThreadLocal(CONSTRUCTOR_SLOT, Boolean.TRUE);
try {
createStandardSymbol(cx, scope, ctor, "iterator", SymbolKey.ITERATOR);
createStandardSymbol(cx, scope, ctor, "species", SymbolKey.SPECIES);
createStandardSymbol(cx, scope, ctor, "toStringTag", SymbolKey.TO_STRING_TAG);
createStandardSymbol(cx, scope, ctor, "asyncIterator", SymbolKey.ASYNC_ITERATOR);
createStandardSymbol(cx, scope, ctor, "hasInstance", SymbolKey.HAS_INSTANCE);
createStandardSymbol(
cx, scope, ctor, "isConcatSpreadable", SymbolKey.IS_CONCAT_SPREADABLE);
createStandardSymbol(cx, scope, ctor, "isRegExp", SymbolKey.IS_REGEXP);
createStandardSymbol(cx, scope, ctor, "toPrimitive", SymbolKey.TO_PRIMITIVE);
createStandardSymbol(cx, scope, ctor, "iterator", SymbolKey.ITERATOR);
createStandardSymbol(cx, scope, ctor, "match", SymbolKey.MATCH);
createStandardSymbol(cx, scope, ctor, "matchAll", SymbolKey.MATCH_ALL);
createStandardSymbol(cx, scope, ctor, "replace", SymbolKey.REPLACE);
createStandardSymbol(cx, scope, ctor, "search", SymbolKey.SEARCH);
createStandardSymbol(cx, scope, ctor, "species", SymbolKey.SPECIES);
createStandardSymbol(cx, scope, ctor, "split", SymbolKey.SPLIT);
createStandardSymbol(cx, scope, ctor, "toPrimitive", SymbolKey.TO_PRIMITIVE);
createStandardSymbol(cx, scope, ctor, "toStringTag", SymbolKey.TO_STRING_TAG);
createStandardSymbol(cx, scope, ctor, "unscopables", SymbolKey.UNSCOPABLES);

} finally {
Expand Down
90 changes: 76 additions & 14 deletions rhino/src/main/java/org/mozilla/javascript/ScriptRuntime.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.function.BiConsumer;

import org.mozilla.javascript.ast.FunctionNode;
import org.mozilla.javascript.v8dtoa.DoubleConversion;
import org.mozilla.javascript.v8dtoa.FastDtoa;
Expand Down Expand Up @@ -3474,16 +3475,57 @@ public static Number negate(Number val) {
return -val.doubleValue();
}

public static Object toPrimitive(Object val) {
return toPrimitive(val, null);
public static Object toPrimitive(Object input) {
return toPrimitive(input, Optional.empty());
}

public static Object toPrimitive(Object val, Class<?> typeHint) {
if (!(val instanceof Scriptable)) {
return val;
/**
* 1. If input is an Object, then
* a. Let exoticToPrim be ? GetMethod(input, @@toPrimitive).
* b. If exoticToPrim is not undefined, then
* i. If preferredType is not present, then
* 1. Let hint be "default".
* ii. Else if preferredType is string, then
* 1. Let hint be "string".
* iii. Else,
* 1. Assert: preferredType is number.
* 2. Let hint be "number".
* iv. Let result be ? Call(exoticToPrim, input, « hint »).
* v. If result is not an Object, return result.
* vi. Throw a TypeError exception.
* c. If preferredType is not present, let preferredType be number.
* d. Return ? OrdinaryToPrimitive(input, preferredType).
* 2. Return input.
* @param input
* @param preferredType
* @return
* @see <a href="https://262.ecma-international.org/15.0/index.html#sec-toprimitive"></a>
*/
public static Object toPrimitive(Object input, Optional<Class<?>> preferredType) {
if (!isObject(input)) {
return input;
}
final Scriptable s = (Scriptable) input;
final Object exoticToPrim = ScriptableObject.getProperty(s, SymbolKey.TO_PRIMITIVE);
if (exoticToPrim instanceof Function) {
final Function func = (Function) exoticToPrim;
final Context cx = Context.getCurrentContext();
final Scriptable scope = func.getParentScope();
final String hint = preferredType
.map(type -> type == StringClass ? "string" : "number")
.orElse("default");
final Object[] args = new Object[] {hint};
final Object result = func.call(cx, scope, s, args);
if (isObject(result)) {
throw typeErrorById("msg.cant.convert.to.primitive");
}
return result;
}
Scriptable s = (Scriptable) val;
Object result = s.getDefaultValue(typeHint);
if (!Undefined.isUndefined(exoticToPrim) && exoticToPrim != Scriptable.NOT_FOUND) {
throw notFunctionError(exoticToPrim);
}
final Class<?> defaultValueHint = preferredType.orElse(NumberClass);
final Object result = s.getDefaultValue(defaultValueHint);
if ((result instanceof Scriptable) && !isSymbol(result))
throw typeErrorById("msg.bad.default.value");
return result;
Expand Down Expand Up @@ -3526,6 +3568,8 @@ public static boolean eq(Object x, Object y) {
}
}
return eqNumber(b ? 1.0 : 0.0, y);
} else if (isSymbol(x) && isObject(y)) {
return eq(x, toPrimitive(y));
} else if (x instanceof Scriptable) {
if (x instanceof Delegator) {
x = ((Delegator) x).getDelegee();
Expand All @@ -3540,6 +3584,10 @@ public static boolean eq(Object x, Object y) {
return true;
}

if (isSymbol(y) && isObject(x)) {
return eq(toPrimitive(x), y);
}

if (y instanceof Scriptable) {
if (x instanceof ScriptableObject) {
Object test = ((ScriptableObject) x).equivalentValues(y);
Expand Down Expand Up @@ -3878,18 +3926,32 @@ public static boolean compare(Object val1, Object val2, int op) {
if (val1 instanceof Number && val2 instanceof Number) {
return compare((Number) val1, (Number) val2, op);
} else {
if ((val1 instanceof Symbol) || (val2 instanceof Symbol)) {
if ((isSymbol(val1)) || (isSymbol(val2))) {
throw typeErrorById("msg.compare.symbol");
}
if (val1 instanceof Scriptable) {
val1 = ((Scriptable) val1).getDefaultValue(NumberClass);
}
if (val2 instanceof Scriptable) {
val2 = ((Scriptable) val2).getDefaultValue(NumberClass);
}
val1 = toPrimitive(val1, Optional.of(NumberClass));
val2 = toPrimitive(val2, Optional.of(NumberClass));
if (val1 instanceof CharSequence && val2 instanceof CharSequence) {
return compareTo(val1.toString(), val2.toString(), op);
}
if (val1 instanceof BigInteger && val2 instanceof CharSequence) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can have one 'val1 instanceof CharSequence' base if with another if inside....

final BigInteger ny;
try {
ny = toBigInt(val2.toString());
} catch (EcmaError e) {
return false;
}
return compareTo((BigInteger) val1, ny, op);
}
if (val1 instanceof CharSequence && val2 instanceof BigInteger) {
final BigInteger nx;
try {
nx = toBigInt(val1.toString());
} catch (EcmaError e) {
return false;
}
return compareTo(nx, (BigInteger) val2, op);
}
return compare(toNumeric(val1), toNumeric(val2), op);
}
}
Expand Down
10 changes: 6 additions & 4 deletions rhino/src/main/java/org/mozilla/javascript/SymbolKey.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,19 @@ public class SymbolKey implements Symbol, Serializable {

// These are common SymbolKeys that are equivalent to well-known symbols
// defined in ECMAScript.
public static final SymbolKey ITERATOR = new SymbolKey("Symbol.iterator");
public static final SymbolKey TO_STRING_TAG = new SymbolKey("Symbol.toStringTag");
public static final SymbolKey SPECIES = new SymbolKey("Symbol.species");
public static final SymbolKey ASYNC_ITERATOR = new SymbolKey("Symbol.asyncIterator");
public static final SymbolKey HAS_INSTANCE = new SymbolKey("Symbol.hasInstance");
public static final SymbolKey IS_CONCAT_SPREADABLE = new SymbolKey("Symbol.isConcatSpreadable");
public static final SymbolKey IS_REGEXP = new SymbolKey("Symbol.isRegExp");
public static final SymbolKey TO_PRIMITIVE = new SymbolKey("Symbol.toPrimitive");
public static final SymbolKey ITERATOR = new SymbolKey("Symbol.iterator");
public static final SymbolKey MATCH = new SymbolKey("Symbol.match");
public static final SymbolKey MATCH_ALL = new SymbolKey("Symbol.matchAll");
public static final SymbolKey REPLACE = new SymbolKey("Symbol.replace");
public static final SymbolKey SEARCH = new SymbolKey("Symbol.search");
public static final SymbolKey SPECIES = new SymbolKey("Symbol.species");
public static final SymbolKey SPLIT = new SymbolKey("Symbol.split");
public static final SymbolKey TO_PRIMITIVE = new SymbolKey("Symbol.toPrimitive");
public static final SymbolKey TO_STRING_TAG = new SymbolKey("Symbol.toStringTag");
public static final SymbolKey UNSCOPABLES = new SymbolKey("Symbol.unscopables");

private String name;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,9 @@ msg.bigint.negative.exponent = \
msg.bigint.out.of.range.arithmetic = \
BigInt is too large.

msg.cant.convert.to.primitive = \
Cannot convert object to primitive value.

# ScriptableObject
msg.default.value =\
Cannot find default value for object.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,6 @@ public class Test262SuiteTest {
"regexp-unicode-property-escapes",
"super",
"String.prototype.matchAll",
"Symbol.matchAll",
"tail-call-optimization",
"u180e"));

Expand Down
54 changes: 32 additions & 22 deletions tests/testsrc/test262.properties
Original file line number Diff line number Diff line change
Expand Up @@ -1739,7 +1739,7 @@ built-ins/Promise 429/631 (67.99%)

~built-ins/Reflect

built-ins/RegExp 1169/1853 (63.09%)
built-ins/RegExp 1166/1853 (62.92%)
CharacterClassEscapes 24/24 (100.0%)
dotall 4/4 (100.0%)
escape 20/20 (100.0%)
Expand Down Expand Up @@ -1818,7 +1818,29 @@ built-ins/RegExp 1169/1853 (63.09%)
prototype/sticky/name.js
prototype/sticky/prop-desc.js
prototype/sticky/this-val-regexp-prototype.js
prototype/Symbol.matchAll 26/26 (100.0%)
prototype/Symbol.matchAll/isregexp-called-once.js
prototype/Symbol.matchAll/isregexp-this-throws.js
prototype/Symbol.matchAll/length.js
prototype/Symbol.matchAll/name.js
prototype/Symbol.matchAll/not-a-constructor.js {unsupported: [Reflect.construct]}
prototype/Symbol.matchAll/prop-desc.js
prototype/Symbol.matchAll/regexpcreate-this-throws.js
prototype/Symbol.matchAll/species-constructor.js
prototype/Symbol.matchAll/species-constructor-get-constructor-throws.js
prototype/Symbol.matchAll/species-constructor-get-species-throws.js
prototype/Symbol.matchAll/species-constructor-is-undefined.js
prototype/Symbol.matchAll/species-constructor-species-is-null-or-undefined.js
prototype/Symbol.matchAll/species-constructor-species-throws.js
prototype/Symbol.matchAll/species-regexp-get-global-throws.js
prototype/Symbol.matchAll/species-regexp-get-unicode-throws.js
prototype/Symbol.matchAll/string-tostring.js
prototype/Symbol.matchAll/string-tostring-throws.js
prototype/Symbol.matchAll/this-get-flags.js
prototype/Symbol.matchAll/this-get-flags-throws.js
prototype/Symbol.matchAll/this-lastindex-cached.js
prototype/Symbol.matchAll/this-tolength-lastindex-throws.js
prototype/Symbol.matchAll/this-tostring-flags.js
prototype/Symbol.matchAll/this-tostring-flags-throws.js
prototype/Symbol.match/builtin-infer-unicode.js
prototype/Symbol.match/builtin-success-g-set-lastindex.js
prototype/Symbol.match/builtin-success-g-set-lastindex-err.js
Expand Down Expand Up @@ -2299,8 +2321,8 @@ built-ins/String 140/1182 (11.84%)

built-ins/StringIteratorPrototype 0/7 (0.0%)

built-ins/Symbol 34/92 (36.96%)
asyncIterator/prop-desc.js
built-ins/Symbol 33/92 (35.87%)
asyncIterator/cross-realm.js
for/cross-realm.js
for/description.js
for/not-a-constructor.js {unsupported: [Reflect.construct]}
Expand All @@ -2310,7 +2332,7 @@ built-ins/Symbol 34/92 (36.96%)
keyFor/arg-non-symbol.js
keyFor/cross-realm.js
keyFor/not-a-constructor.js {unsupported: [Reflect.construct]}
matchAll 2/2 (100.0%)
matchAll/cross-realm.js
match/cross-realm.js
prototype/description/description-symboldescriptivestring.js
prototype/description/descriptor.js
Expand Down Expand Up @@ -4637,13 +4659,7 @@ language/expressions/does-not-equals 0/38 (0.0%)

~language/expressions/dynamic-import

language/expressions/equals 6/47 (12.77%)
coerce-symbol-to-prim-err.js
coerce-symbol-to-prim-invocation.js
coerce-symbol-to-prim-return-obj.js
coerce-symbol-to-prim-return-prim.js
get-symbol-to-prim-err.js
to-prim-hint.js
language/expressions/equals 0/47 (0.0%)

language/expressions/exponentiation 3/44 (6.82%)
bigint-toprimitive.js
Expand Down Expand Up @@ -5096,12 +5112,9 @@ language/expressions/generators 232/290 (80.0%)
yield-star-after-newline.js
yield-star-before-newline.js

language/expressions/greater-than 2/49 (4.08%)
bigint-and-incomparable-string.js
bigint-and-string.js
language/expressions/greater-than 0/49 (0.0%)

language/expressions/greater-than-or-equal 1/43 (2.33%)
bigint-and-incomparable-string.js
language/expressions/greater-than-or-equal 0/43 (0.0%)

language/expressions/grouping 0/9 (0.0%)

Expand Down Expand Up @@ -5144,12 +5157,9 @@ language/expressions/left-shift 4/45 (8.89%)
bigint-wrapped-values.js
order-of-evaluation.js

language/expressions/less-than 1/45 (2.22%)
bigint-and-incomparable-string.js
language/expressions/less-than 0/45 (0.0%)

language/expressions/less-than-or-equal 2/47 (4.26%)
bigint-and-incomparable-string.js
bigint-and-string.js
language/expressions/less-than-or-equal 0/47 (0.0%)

language/expressions/logical-and 1/18 (5.56%)
tco-right.js {unsupported: [tail-call-optimization]}
Expand Down