Skip to content

Commit

Permalink
Use to primitive for plus operation and date (taken from #1611 done by
Browse files Browse the repository at this point in the history
…@tonygermano) (#1685)

* rewritten plus operator to use toPrimitive
* NativeDate#jsConstructor uses toPrimitive
* Date.prototype[Symbol.toPrimitive] implemented
* fix e4x handling
* fix other backward compatibility issues
  • Loading branch information
rbri authored Oct 11, 2024
1 parent 53e49ac commit 66622e5
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 80 deletions.
51 changes: 40 additions & 11 deletions rhino/src/main/java/org/mozilla/javascript/NativeDate.java
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,10 @@ protected void initPrototypeId(int id) {
arity = 1;
s = "toJSON";
break;
case SymbolId_toPrimitive:
initPrototypeMethod(
DATE_TAG, id, SymbolKey.TO_PRIMITIVE, "[Symbol.toPrimitive]", 1);
return;
default:
throw new IllegalArgumentException(String.valueOf(id));
}
Expand Down Expand Up @@ -323,6 +327,24 @@ public Object execIdCall(
}
return result;
}
case SymbolId_toPrimitive:
{
Scriptable o = ScriptRuntime.toObject(cx, scope, thisObj);
final Object arg0 = args.length > 0 ? args[0] : Undefined.instance;
final String hint = (arg0 instanceof CharSequence) ? arg0.toString() : null;
Class<?> typeHint = null;
if ("string".equals(hint) || "default".equals(hint)) {
typeHint = ScriptRuntime.StringClass;
} else if ("number".equals(hint)) {
typeHint = ScriptRuntime.NumberClass;
}
if (typeHint == null) {
throw ScriptRuntime.typeErrorById(
"msg.invalid.toprimitive.hint", ScriptRuntime.toString(arg0));
}

return ScriptableObject.getDefaultValue(o, typeHint);
}
}

// The rest of Date.prototype methods require thisObj to be Date
Expand Down Expand Up @@ -1374,21 +1396,19 @@ private static Object jsConstructor(Context cx, Object[] args) {

// if called with just one arg -
if (args.length == 1) {
Object arg0 = args[0];
if (arg0 instanceof NativeDate) {
obj.date = ((NativeDate) arg0).date;
final Object value = args[0];
if (value instanceof NativeDate) {
obj.date = ((NativeDate) value).date;
return obj;
}
if (arg0 instanceof Scriptable) {
arg0 = ((Scriptable) arg0).getDefaultValue(null);
}
double date;
if (arg0 instanceof CharSequence) {
final Object v = ScriptRuntime.toPrimitive(value);
final double date;
if (v instanceof CharSequence) {
// it's a string; parse it.
date = date_parseString(cx, arg0.toString());
date = date_parseString(cx, v.toString());
} else {
// if it's not a string, use it as a millisecond date
date = ScriptRuntime.toNumber(arg0);
date = ScriptRuntime.toNumber(v);
}
obj.date = TimeClip(date);
return obj;
Expand Down Expand Up @@ -1900,6 +1920,14 @@ protected int findPrototypeId(String s) {
return id;
}

@Override
protected int findPrototypeId(Symbol key) {
if (SymbolKey.TO_PRIMITIVE.equals(key)) {
return SymbolId_toPrimitive;
}
return 0;
}

private static final int ConstructorId_now = -3,
ConstructorId_parse = -2,
ConstructorId_UTC = -1,
Expand Down Expand Up @@ -1950,7 +1978,8 @@ protected int findPrototypeId(String s) {
Id_setYear = 45,
Id_toISOString = 46,
Id_toJSON = 47,
MAX_PROTOTYPE_ID = Id_toJSON;
SymbolId_toPrimitive = 48,
MAX_PROTOTYPE_ID = SymbolId_toPrimitive;

private static final int Id_toGMTString = Id_toUTCString; // Alias, see Ecma B.2.6

Expand Down
76 changes: 39 additions & 37 deletions rhino/src/main/java/org/mozilla/javascript/ScriptRuntime.java
Original file line number Diff line number Diff line change
Expand Up @@ -3030,55 +3030,58 @@ public static boolean isObject(Object value) {
// implement the '~' operator inline in the caller
// as "~toInt32(val)"

public static Object add(Object val1, Object val2, Context cx) {
if (val1 instanceof BigInteger && val2 instanceof BigInteger) {
return ((BigInteger) val1).add((BigInteger) val2);
}
if ((val1 instanceof Number && val2 instanceof BigInteger)
|| (val1 instanceof BigInteger && val2 instanceof Number)) {
throw ScriptRuntime.typeErrorById("msg.cant.convert.to.number", "BigInt");
public static Object add(Object lval, Object rval, Context cx) {
// if lval and rval are primitive numerics of the same type, give them priority
if (lval instanceof Integer && rval instanceof Integer) {
return add((Integer) lval, (Integer) rval);
}
if (val1 instanceof Integer && val2 instanceof Integer) {
return add((Integer) val1, (Integer) val2);
if (lval instanceof BigInteger && rval instanceof BigInteger) {
return ((BigInteger) lval).add((BigInteger) rval);
}
if (val1 instanceof Number && val2 instanceof Number) {
return wrapNumber(((Number) val1).doubleValue() + ((Number) val2).doubleValue());
}
if (val1 instanceof CharSequence && val2 instanceof CharSequence) {
// If we let this happen later, then the "getDefaultValue" logic
// undoes many optimizations
return new ConsString((CharSequence) val1, (CharSequence) val2);
if (lval instanceof Number
&& !(lval instanceof BigInteger)
&& rval instanceof Number
&& !(rval instanceof BigInteger)) {
return wrapNumber(((Number) lval).doubleValue() + ((Number) rval).doubleValue());
}
if (val1 instanceof XMLObject) {
Object test = ((XMLObject) val1).addValues(cx, true, val2);

// e4x extension start
if (lval instanceof XMLObject) {
Object test = ((XMLObject) lval).addValues(cx, true, rval);
if (test != Scriptable.NOT_FOUND) {
return test;
}
}
if (val2 instanceof XMLObject) {
Object test = ((XMLObject) val2).addValues(cx, false, val1);
if (rval instanceof XMLObject) {
Object test = ((XMLObject) rval).addValues(cx, false, lval);
if (test != Scriptable.NOT_FOUND) {
return test;
}
}
if ((val1 instanceof Symbol) || (val2 instanceof Symbol)) {
throw typeErrorById("msg.not.a.number");
// e4x extension end

// spec starts here for abstract operation ApplyStringOrNumericBinaryOperator
// where opText is "+".
final Object lprim = toPrimitive(lval);
final Object rprim = toPrimitive(rval);
if (lprim instanceof CharSequence || rprim instanceof CharSequence) {
final CharSequence lstr =
(lprim instanceof CharSequence) ? (CharSequence) lprim : toString(lprim);
final CharSequence rstr =
(rprim instanceof CharSequence) ? (CharSequence) rprim : toString(rprim);
return new ConsString(lstr, rstr);
}
if (val1 instanceof Scriptable) val1 = ((Scriptable) val1).getDefaultValue(null);
if (val2 instanceof Scriptable) val2 = ((Scriptable) val2).getDefaultValue(null);
if (!(val1 instanceof CharSequence) && !(val2 instanceof CharSequence)) {
Number num1 = val1 instanceof Number ? (Number) val1 : toNumeric(val1);
Number num2 = val2 instanceof Number ? (Number) val2 : toNumeric(val2);

if (num1 instanceof BigInteger && num2 instanceof BigInteger) {
return ((BigInteger) num1).add((BigInteger) num2);
}
if (num1 instanceof BigInteger || num2 instanceof BigInteger) {
throw ScriptRuntime.typeErrorById("msg.cant.convert.to.number", "BigInt");
}
return num1.doubleValue() + num2.doubleValue();
// Skipping (lval = lprim, rval = rprim) and using xprim values directly.
final Number lnum = toNumeric(lprim);
final Number rnum = toNumeric(rprim);
if (lnum instanceof BigInteger && rnum instanceof BigInteger) {
return ((BigInteger) lnum).add((BigInteger) rnum);
}
if (lnum instanceof BigInteger || rnum instanceof BigInteger) {
throw ScriptRuntime.typeErrorById("msg.cant.convert.to.number", "BigInt");
}
return new ConsString(toCharSequence(val1), toCharSequence(val2));
return lnum.doubleValue() + rnum.doubleValue();
}

/**
Expand Down Expand Up @@ -3600,8 +3603,7 @@ public static Object toPrimitive(Object input, Class<?> preferredType) {
&& !Undefined.isUndefined(exoticToPrim)) {
throw notFunctionError(exoticToPrim);
}
final Class<?> defaultValueHint = preferredType == null ? NumberClass : preferredType;
final Object result = s.getDefaultValue(defaultValueHint);
final Object result = s.getDefaultValue(preferredType);
if ((result instanceof Scriptable) && !isSymbol(result))
throw typeErrorById("msg.bad.default.value");
return result;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,9 @@ msg.invalid.date =\
msg.toisostring.must.return.primitive =\
toISOString must return a primitive value, but instead returned "{0}"

msg.invalid.toprimitive.hint =\
[Symbol.toPrimitive]: expected "string", "number", or "default", but got "{0}"

# NativeJSON
msg.json.cant.serialize =\
Do not know how to serialize a {0}
Expand Down
35 changes: 3 additions & 32 deletions tests/testsrc/test262.properties
Original file line number Diff line number Diff line change
Expand Up @@ -659,7 +659,7 @@ built-ins/DataView 254/550 (46.18%)
toindex-bytelength-sab.js {unsupported: [SharedArrayBuffer]}
toindex-byteoffset-sab.js {unsupported: [SharedArrayBuffer]}

built-ins/Date 90/770 (11.69%)
built-ins/Date 68/770 (8.83%)
now/not-a-constructor.js {unsupported: [Reflect.construct]}
parse/not-a-constructor.js {unsupported: [Reflect.construct]}
parse/year-zero.js
Expand Down Expand Up @@ -697,18 +697,7 @@ built-ins/Date 90/770 (11.69%)
prototype/setUTCMinutes/not-a-constructor.js {unsupported: [Reflect.construct]}
prototype/setUTCMonth/not-a-constructor.js {unsupported: [Reflect.construct]}
prototype/setUTCSeconds/not-a-constructor.js {unsupported: [Reflect.construct]}
prototype/Symbol.toPrimitive/hint-default-first-invalid.js
prototype/Symbol.toPrimitive/hint-default-first-non-callable.js
prototype/Symbol.toPrimitive/hint-default-first-valid.js
prototype/Symbol.toPrimitive/hint-invalid.js
prototype/Symbol.toPrimitive/hint-number-first-invalid.js
prototype/Symbol.toPrimitive/hint-number-first-non-callable.js
prototype/Symbol.toPrimitive/hint-number-first-valid.js
prototype/Symbol.toPrimitive/hint-string-first-invalid.js
prototype/Symbol.toPrimitive/hint-string-first-non-callable.js
prototype/Symbol.toPrimitive/hint-string-first-valid.js
prototype/Symbol.toPrimitive/length.js
prototype/Symbol.toPrimitive/name.js
prototype/Symbol.toPrimitive/called-as-function.js
prototype/Symbol.toPrimitive/prop-desc.js
prototype/Symbol.toPrimitive/this-val-non-obj.js
prototype/toDateString/not-a-constructor.js {unsupported: [Reflect.construct]}
Expand Down Expand Up @@ -738,17 +727,6 @@ built-ins/Date 90/770 (11.69%)
proto-from-ctor-realm-two.js {unsupported: [Reflect]}
proto-from-ctor-realm-zero.js {unsupported: [Reflect]}
subclassing.js {unsupported: [Reflect]}
value-get-symbol-to-prim-err.js
value-symbol-to-prim-err.js
value-symbol-to-prim-invocation.js
value-symbol-to-prim-return-obj.js
value-symbol-to-prim-return-prim.js
value-to-primitive-call.js
value-to-primitive-call-err.js
value-to-primitive-get-meth-err.js
value-to-primitive-result-faulty.js
value-to-primitive-result-non-string-prim.js
value-to-primitive-result-string.js
year-zero.js

built-ins/Error 6/41 (14.63%)
Expand Down Expand Up @@ -3862,15 +3840,8 @@ language/eval-code 253/347 (72.91%)

~language/export

language/expressions/addition 9/48 (18.75%)
language/expressions/addition 2/48 (4.17%)
bigint-errors.js
bigint-toprimitive.js
bigint-wrapped-values.js
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
order-of-evaluation.js

language/expressions/array 41/52 (78.85%)
Expand Down

0 comments on commit 66622e5

Please sign in to comment.