Skip to content

Commit

Permalink
improved ScriptRuntime.toPrimitive() (taken from #1611 done by @tonyg…
Browse files Browse the repository at this point in the history
…ermano) (#1661)

* this is an excerpt of from PR #1611 done by @tonygermano (commit 9664e77, e17cc25)

* more standard compliant implementation of ScriptRuntime.toPrimitive();
  * have not used the Optional for more backward compatibility
  * have changed the code a bit to be more in sync with the spec (explicit if stmt's)
* improved support for Symbol/BigInt in equals and compare
* adjusted based on retests with real browsers
  • Loading branch information
rbri authored Oct 2, 2024
1 parent ed9af7e commit 4e0b69b
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 35 deletions.
88 changes: 72 additions & 16 deletions rhino/src/main/java/org/mozilla/javascript/ScriptRuntime.java
Original file line number Diff line number Diff line change
Expand Up @@ -3547,16 +3547,53 @@ 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, null);
}

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, 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;
if (preferredType == null) {
hint = "default";
} else if (StringClass == preferredType) {
hint = "string";
} else {
hint = "number";
}
final Object result = func.call(cx, scope, s, new Object[] {hint});
if (isObject(result)) {
throw typeErrorById("msg.cant.convert.to.primitive");
}
return result;
}
if (!Undefined.isUndefined(exoticToPrim) && exoticToPrim != Scriptable.NOT_FOUND) {
throw notFunctionError(exoticToPrim);
}
Scriptable s = (Scriptable) val;
Object result = s.getDefaultValue(typeHint);
final Class<?> defaultValueHint = preferredType == null ? preferredType : 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 @@ -3599,6 +3636,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 @@ -3612,7 +3651,9 @@ public static boolean eq(Object x, Object y) {
if (y instanceof Delegator && ((Delegator) y).getDelegee() == x) {
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 @@ -3951,18 +3992,33 @@ 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, NumberClass);
val2 = toPrimitive(val2, NumberClass);

if (val1 instanceof CharSequence) {
if (val2 instanceof CharSequence) {
return compareTo(val1.toString(), val2.toString(), op);
}

if (val2 instanceof BigInteger) {
try {
return compareTo(toBigInt(val1.toString()), (BigInteger) val2, op);
} catch (EcmaError e) {
return false;
}
}
}
if (val1 instanceof CharSequence && val2 instanceof CharSequence) {
return compareTo(val1.toString(), val2.toString(), op);
if (val1 instanceof BigInteger && val2 instanceof CharSequence) {
try {
return compareTo((BigInteger) val1, toBigInt(val2.toString()), op);
} catch (EcmaError e) {
return false;
}
}

return compare(toNumeric(val1), toNumeric(val2), op);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -674,6 +674,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
4 changes: 2 additions & 2 deletions tests/testsrc/jstests/harmony/v8-symbols.js
Original file line number Diff line number Diff line change
Expand Up @@ -169,8 +169,8 @@ function TestEquality() {
assertTrue(symbols[i] == symbols[i])
assertFalse(symbols[i] === Object(symbols[i]))
assertFalse(Object(symbols[i]) === symbols[i])
assertFalse(symbols[i] == Object(symbols[i]))
assertFalse(Object(symbols[i]) == symbols[i])
assertTrue(symbols[i] == Object(symbols[i]))
assertTrue(Object(symbols[i]) == symbols[i])
assertTrue(symbols[i] === symbols[i].valueOf())
assertTrue(symbols[i].valueOf() === symbols[i])
assertTrue(symbols[i] == symbols[i].valueOf())
Expand Down
22 changes: 5 additions & 17 deletions tests/testsrc/test262.properties
Original file line number Diff line number Diff line change
Expand Up @@ -4604,13 +4604,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 @@ -4979,12 +4973,9 @@ language/expressions/generators 194/290 (66.9%)
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 @@ -5027,12 +5018,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

0 comments on commit 4e0b69b

Please sign in to comment.