Skip to main content

nanvm_lib/vm/
primitive_coercion.rs

1use crate::vm::{Any, Array, Function, IVm, Object, Unpacked, error, primitive::Primitive};
2
3use std::result::Result;
4
5/// Preferred type for coercion to primitive, as per ECMAScript specification.
6/// <https://tc39.es/ecma262/#sec-toprimitive>
7/// Note that preferredType can be absent - we express that by using None value of
8/// Option<ToPrimitivePreferredType>.
9#[allow(dead_code)]
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum ToPrimitivePreferredType {
12    Number,
13    String,
14}
15
16/// What one method of `OrdinaryToPrimitive` answers: its primitive result,
17/// or `None` to move on to the next method.
18type MethodResult<A> = Result<Option<Primitive<A>>, Any<A>>;
19
20fn value_of<A: IVm>(_: &Array<A>) -> MethodResult<A> {
21    // https://tc39.es/ecma262/#sec-object.prototype.valueof
22    // The stock method answers the array itself, which is not a primitive.
23    Ok(None)
24}
25
26fn arr_to_string<A: IVm>(a: &Array<A>) -> MethodResult<A> {
27    // https://tc39.es/ecma262/#sec-array.prototype.tostring
28    // https://tc39.es/ecma262/#sec-array.prototype.join
29    // The stock method: an array owns only its elements and `length`, never a "toString" or a
30    // "join", so this is the built-in `join` with its default separator.
31    a.join(",".into()).map(|s| Some(Primitive::String(s)))
32}
33
34/// A method `OrdinaryToPrimitive` tries: its name, and what the stock
35/// method answers when the object does not own one — `None` for
36/// <https://tc39.es/ecma262/#sec-object.prototype.valueof>, which answers
37/// the object itself, not a primitive, and `"[object Object]"` for
38/// <https://tc39.es/ecma262/#sec-object.prototype.tostring>.
39type ConversionMethod = (&'static str, Option<&'static str>);
40
41const VALUE_OF: ConversionMethod = ("valueOf", None);
42
43const TO_STRING: ConversionMethod = ("toString", Some("[object Object]"));
44
45/// <https://tc39.es/ecma262/#sec-ordinarytoprimitive>: the hint picks the
46/// order of the two methods, `[value_of, to_string]`, and the first to
47/// answer a primitive is the result. `call` runs one method on the operand,
48/// so an object and an array share the one ordering rule.
49fn ordinary_to_primitive<A: IVm, M>(
50    preferred_type: ToPrimitivePreferredType,
51    [value_of, to_string]: [M; 2],
52    mut call: impl FnMut(M) -> MethodResult<A>,
53) -> Result<Primitive<A>, Any<A>> {
54    let order = match preferred_type {
55        ToPrimitivePreferredType::Number => [value_of, to_string],
56        ToPrimitivePreferredType::String => [to_string, value_of],
57    };
58    for method in order {
59        if let Some(p) = call(method)? {
60            return Ok(p);
61        }
62    }
63    Err(error::cannot_convert_to_primitive())
64}
65
66/// One method of `OrdinaryToPrimitive` on an object.
67///
68/// An own property shadows the stock method, as it does for an explicit
69/// call (`Member`). One that is no function is skipped. A function is
70/// called with no arguments and without a receiver, as `Member`'s call is:
71/// no FunctionalScript function reads `this`. Its throw propagates, and a
72/// result that is not a primitive moves on, unconverted, so the conversion
73/// never recurses through a result.
74fn obj_method<A: IVm>(o: &Object<A>, (name, stock): ConversionMethod) -> MethodResult<A> {
75    match o.own_property(&name.into()) {
76        Some(m) => match Function::try_from(m) {
77            Ok(f) => f.call(Array::default()).map(to_primitive_value),
78            Err(_) => Ok(None),
79        },
80        None => Ok(stock.map(|s| Primitive::String(s.into()))),
81    }
82}
83
84/// The value as a primitive, or `None` for an object, an array or a function.
85fn to_primitive_value<A: IVm>(v: Any<A>) -> Option<Primitive<A>> {
86    Primitive::try_from(Unpacked::from(v)).ok()
87}
88
89impl<A: IVm> Any<A> {
90    /// Coerces the value to a primitive type `Primitive<A>`, possibly producing an error result.
91    /// <https://tc39.es/ecma262/#sec-toprimitive>
92    pub fn to_primitive(
93        self,
94        preferred_type: Option<ToPrimitivePreferredType>,
95    ) -> Result<Primitive<A>, Any<A>> {
96        // https://tc39.es/ecma262/#sec-ordinarytoprimitive - point 2 defaults to number preference
97        let preferred_type = preferred_type.unwrap_or(ToPrimitivePreferredType::Number);
98        match Primitive::try_from(Unpacked::from(self)) {
99            Ok(p) => Ok(p),
100            Err(Unpacked::Object(o)) => {
101                ordinary_to_primitive(preferred_type, [VALUE_OF, TO_STRING], |m| obj_method(&o, m))
102            }
103            Err(Unpacked::Array(a)) => {
104                ordinary_to_primitive(preferred_type, [value_of, arr_to_string], |m| m(&a))
105            }
106            // https://tc39.es/ecma262/#sec-function.prototype.tostring: the
107            // stock `valueOf` answers the function itself, so every hint ends at
108            // the function's text, and one without a text is refused.
109            Err(Unpacked::Function(f)) => f.text().map(Primitive::String),
110            Err(_) => {
111                unreachable!("`Primitive::try_from` refuses only an object, an array or a function")
112            }
113        }
114    }
115}
116
117/// Stage 1 of `nanvm/lib/todo/to-primitive.md`, one test per row: a
118/// function's text is refused where it has none, and what does not depend
119/// on it keeps its value. Stage 2, `OrdinaryToPrimitive` calling an
120/// object's own methods, step by step. Stage 3, a function's text.
121#[cfg(test)]
122mod tests {
123    use super::ToPrimitivePreferredType;
124    use crate::common::sized_index::SizedIndex;
125    use crate::vm::test::function::{returns, throws};
126    use crate::{
127        naive::Naive,
128        vm::{
129            Any, BigInt, IVm, Nullish, Number, ToAny, ToArray, ToObject, error,
130            primitive::Primitive,
131        },
132    };
133
134    type A = Naive;
135
136    fn s(v: &str) -> Any<A> {
137        v.into()
138    }
139
140    fn function() -> Any<A> {
141        returns(1.0.to_any())
142    }
143
144    fn with(props: &[(&str, Any<A>)]) -> Any<A> {
145        props
146            .iter()
147            .map(|(k, v)| ((*k).into(), v.clone()))
148            .collect::<Vec<_>>()
149            .to_object()
150            .to_any()
151    }
152
153    fn refused<T: core::fmt::Debug + PartialEq>(r: Result<T, Any<A>>, thrown: impl Into<Any<A>>) {
154        assert_eq!(r, Err(thrown.into()));
155    }
156
157    fn is_nan(r: Result<Number, Any<A>>) -> bool {
158        r.unwrap().is_nan()
159    }
160
161    /// An own method shadows the stock one, for every caller.
162    #[test]
163    fn own_method_is_called() {
164        let o = || with(&[("toString", returns(s("b")))]);
165        assert_eq!(o().to_string(), Ok("b".into()));
166        assert_eq!(s("a") + o(), Ok(s("ab")));
167        let o = || with(&[("valueOf", returns(1.0.to_any()))]);
168        assert_eq!(o().to_number(), Ok(Number::from(1.0)));
169        assert_eq!(o() + 1.0.to_any(), Ok(2.0.to_any()));
170        assert_eq!(o().lt(2.0.to_any()), Ok(true.to_any()));
171        let big = with(&[("valueOf", returns(BigInt::<A>::from(5i64).to_any()))]);
172        assert_eq!(-big, Ok(BigInt::<A>::from(-5i64).to_any()));
173    }
174
175    /// `valueOf` first for the `number` hint and for none, `toString` first
176    /// for `string`.
177    #[test]
178    fn hint_picks_the_order() {
179        let o = || {
180            with(&[
181                ("valueOf", returns(1.0.to_any())),
182                ("toString", returns(s("t"))),
183            ])
184        };
185        assert_eq!(o().to_string(), Ok("t".into()));
186        assert_eq!(o().to_number(), Ok(Number::from(1.0)));
187        assert_eq!(o() + s("!"), Ok(s("1!")));
188    }
189
190    /// A method that is no function is skipped: the stock `toString`
191    /// answers after a skipped `valueOf`, and the stock `valueOf` answers
192    /// no primitive after a skipped `toString`.
193    #[test]
194    fn non_function_is_skipped() {
195        for value in [s("x"), 1.0.to_any()] {
196            let o = || with(&[("valueOf", value.clone())]);
197            assert_eq!(o().to_string(), Ok("[object Object]".into()));
198            assert!(is_nan(o().to_number()));
199            assert_eq!(o() + s("!"), Ok(s("[object Object]!")));
200            let o = || with(&[("toString", value.clone())]);
201            refused(o().to_string(), error::cannot_convert_to_primitive());
202            refused(o().to_number(), error::cannot_convert_to_primitive());
203        }
204    }
205
206    /// A method's throw is the conversion's, unchanged, and ends it.
207    #[test]
208    fn throw_propagates() {
209        let o = || {
210            with(&[
211                ("valueOf", throws(s("boom"))),
212                ("toString", returns(s("t"))),
213            ])
214        };
215        refused(o().to_number(), "boom");
216        assert_eq!(o().to_string(), Ok("t".into()));
217    }
218
219    /// A binary operator converts its left operand first, so with both
220    /// throwing, the left one's throw is the result. `>` and `<=` ask `<`
221    /// of the swapped operands and still convert the left one first.
222    #[test]
223    fn left_operand_first() {
224        type Op = fn(Any<A>, Any<A>) -> Result<Any<A>, Any<A>>;
225        let ops: [Op; 16] = [
226            |x, y| x + y,
227            |x, y| x - y,
228            |x, y| x * y,
229            |x, y| x / y,
230            |x, y| x % y,
231            |x, y| x.pow(y),
232            |x, y| x & y,
233            |x, y| x | y,
234            |x, y| x ^ y,
235            |x, y| x << y,
236            |x, y| x >> y,
237            |x, y| x.unsigned_right_shift(y),
238            Any::lt,
239            Any::gt,
240            Any::le,
241            Any::ge,
242        ];
243        let o = |v| with(&[("valueOf", throws(s(v)))]);
244        for op in ops {
245            refused(op(o("left"), o("right")), "left");
246        }
247    }
248
249    /// A result that is not a primitive moves on, unconverted, and if
250    /// neither method answers one the conversion throws.
251    #[test]
252    fn non_primitive_result_moves_on() {
253        let inner = || with(&[("toString", returns(s("inner")))]);
254        let o = || with(&[("valueOf", returns(inner()))]);
255        assert!(is_nan(o().to_number()));
256        for result in [inner(), [].to_array().to_any(), function()] {
257            let o = || {
258                with(&[
259                    ("valueOf", returns(result.clone())),
260                    ("toString", returns(s("2"))),
261                ])
262            };
263            assert_eq!(o().to_number(), Ok(Number::from(2.0)));
264            let o = || with(&[("toString", returns(result.clone()))]);
265            refused(o().to_string(), error::cannot_convert_to_primitive());
266        }
267    }
268
269    /// The `string` hint tries `toString` first, and the stock one answers
270    /// before a function `valueOf` is reached: `String({ valueOf: f })` is
271    /// `"[object Object]"`, while `+{ valueOf: f }` calls `f`.
272    #[test]
273    fn string_hint_never_reaches_value_of() {
274        let o = || with(&[("valueOf", function())]);
275        assert_eq!(o().to_string(), Ok("[object Object]".into()));
276        assert_eq!(o().to_number(), Ok(Number::from(1.0)));
277    }
278
279    /// Any other own property keeps the stock conversion.
280    #[test]
281    fn plain_object_is_unchanged() {
282        let o = || with(&[("a", function())]);
283        assert_eq!(o().to_string(), Ok("[object Object]".into()));
284        assert!(is_nan(o().to_number()));
285        assert_eq!((o() + s("!")), Ok(s("[object Object]!")));
286    }
287
288    /// `() => 1` with the text the compiler renders for it.
289    fn texted() -> Any<A> {
290        A::static_function(|_, _| Ok(1.0.to_any()), 0, [].to_array(), Some("()=>1")).to_any()
291    }
292
293    /// `String(f)`, `f + x`, `f < "z"` and an array holding `f` read the
294    /// function's text.
295    #[test]
296    fn function_text() {
297        assert_eq!(texted().to_string(), Ok("()=>1".into()));
298        assert_eq!(texted() + s("!"), Ok(s("()=>1!")));
299        assert_eq!(1.0.to_any() + texted(), Ok(s("1()=>1")));
300        assert_eq!(texted() + texted(), Ok(s("()=>1()=>1")));
301        assert_eq!(texted().lt(s("z")), Ok(true.to_any()));
302        assert_eq!(s("(").lt(texted()), Ok(true.to_any()));
303        assert_eq!(
304            [texted(), 2.0.to_any()]
305                .to_array()
306                .to_any::<A>()
307                .to_string(),
308            Ok("()=>1,2".into())
309        );
310        assert!(is_nan([texted()].to_array().to_any().to_number()));
311        // and `NaN` wherever the number does not depend on it
312        assert!(is_nan(texted().to_number()));
313    }
314
315    /// `String(f)` and `f + x` observe the text, which a function without
316    /// one does not have.
317    #[test]
318    fn function_without_text_is_refused() {
319        refused(function().to_string(), error::function_text());
320        refused(function() + s("!"), error::function_text());
321        refused(function() + 1.0.to_any(), error::function_text());
322        refused(1.0.to_any() + function(), error::function_text());
323        refused(function() + function(), error::function_text());
324    }
325
326    /// `+f`, `-f`, `f - 1` and `f ^ 6`: `NaN` for every text, so
327    /// unchanged.
328    #[test]
329    fn function_number_is_nan() {
330        assert!(is_nan(function().to_number()));
331        assert_eq!((-function()).as_ref().map(Any::is_nan), Ok(true));
332        assert_eq!(
333            (function() - 1.0.to_any()).as_ref().map(Any::is_nan),
334            Ok(true)
335        );
336        assert_eq!(function() ^ 6.0.to_any(), Ok(6.0.to_any()));
337    }
338
339    /// `f < "z"` compares the text; `f < 5` and `f < 5n` are `false` for
340    /// every text.
341    #[test]
342    fn function_comparison() {
343        refused(function().lt(s("z")), error::function_text());
344        refused(s("z").lt(function()), error::function_text());
345        refused(function().lt(function()), error::function_text());
346        refused(function().ge(s("z")), error::function_text());
347        let five = || 5.0.to_any();
348        let big = || BigInt::<A>::from(5i64).to_any();
349        // An array's primitive is a string too.
350        refused(
351            function().lt([].to_array().to_any()),
352            error::function_text(),
353        );
354        for other in [five(), big(), true.to_any()] {
355            assert_eq!(function().lt(other.clone()), Ok(false.to_any()));
356            assert_eq!(other.clone().lt(function()), Ok(false.to_any()));
357            assert_eq!(function().le(other.clone()), Ok(false.to_any()));
358            assert_eq!(function().ge(other), Ok(false.to_any()));
359        }
360    }
361
362    /// `x`, an object whose every conversion method throws `"boom"`.
363    fn boom() -> Any<A> {
364        with(&[
365            ("valueOf", throws(s("boom"))),
366            ("toString", throws(s("boom"))),
367        ])
368    }
369
370    /// Each member function that converts an argument reaches the
371    /// conversion: with `x` throwing, the call throws `x`'s throw.
372    #[test]
373    fn member_arguments_are_converted() {
374        let arr = || {
375            [1.0.to_any(), 2.0.to_any(), 3.0.to_any()]
376                .to_array()
377                .to_any()
378        };
379        let str = || s("abc");
380        let (zero, one) = (|| 0.0.to_any(), || 1.0.to_any());
381        let x = boom;
382        type Case = (&'static str, Any<A>, &'static str, Vec<Any<A>>);
383        let cases: Vec<Case> = vec![
384            ("array.at", arr(), "at", vec![x()]),
385            ("array.slice start", arr(), "slice", vec![x()]),
386            ("array.slice end", arr(), "slice", vec![zero(), x()]),
387            ("array.indexOf from", arr(), "indexOf", vec![one(), x()]),
388            (
389                "array.lastIndexOf from",
390                arr(),
391                "lastIndexOf",
392                vec![one(), x()],
393            ),
394            ("array.includes from", arr(), "includes", vec![one(), x()]),
395            ("array.join", arr(), "join", vec![x()]),
396            ("array.toSpliced start", arr(), "toSpliced", vec![x()]),
397            (
398                "array.toSpliced skip",
399                arr(),
400                "toSpliced",
401                vec![zero(), x()],
402            ),
403            ("array.with index", arr(), "with", vec![x(), one()]),
404            ("array.flat depth", arr(), "flat", vec![x()]),
405            ("string.at", str(), "at", vec![x()]),
406            ("string.charAt", str(), "charAt", vec![x()]),
407            ("string.charCodeAt", str(), "charCodeAt", vec![x()]),
408            ("string.codePointAt", str(), "codePointAt", vec![x()]),
409            ("string.concat", str(), "concat", vec![x()]),
410            ("string.endsWith needle", str(), "endsWith", vec![x()]),
411            ("string.endsWith end", str(), "endsWith", vec![s("c"), x()]),
412            ("string.startsWith needle", str(), "startsWith", vec![x()]),
413            (
414                "string.startsWith start",
415                str(),
416                "startsWith",
417                vec![s("a"), x()],
418            ),
419            ("string.includes needle", str(), "includes", vec![x()]),
420            ("string.includes pos", str(), "includes", vec![s("a"), x()]),
421            ("string.indexOf needle", str(), "indexOf", vec![x()]),
422            ("string.indexOf pos", str(), "indexOf", vec![s("a"), x()]),
423            ("string.lastIndexOf needle", str(), "lastIndexOf", vec![x()]),
424            (
425                "string.lastIndexOf pos",
426                str(),
427                "lastIndexOf",
428                vec![s("a"), x()],
429            ),
430            ("string.padEnd length", str(), "padEnd", vec![x()]),
431            (
432                "string.padEnd fill",
433                str(),
434                "padEnd",
435                vec![5.0.to_any(), x()],
436            ),
437            ("string.padStart length", str(), "padStart", vec![x()]),
438            (
439                "string.padStart fill",
440                str(),
441                "padStart",
442                vec![5.0.to_any(), x()],
443            ),
444            ("string.repeat", str(), "repeat", vec![x()]),
445            (
446                "string.replace pattern",
447                str(),
448                "replace",
449                vec![x(), s("z")],
450            ),
451            (
452                "string.replace replacement",
453                str(),
454                "replace",
455                vec![s("a"), x()],
456            ),
457            (
458                "string.replaceAll pattern",
459                str(),
460                "replaceAll",
461                vec![x(), s("z")],
462            ),
463            (
464                "string.replaceAll replacement",
465                str(),
466                "replaceAll",
467                vec![s("a"), x()],
468            ),
469            ("string.slice start", str(), "slice", vec![x()]),
470            ("string.slice end", str(), "slice", vec![zero(), x()]),
471            ("string.substring start", str(), "substring", vec![x()]),
472            (
473                "string.substring end",
474                str(),
475                "substring",
476                vec![zero(), x()],
477            ),
478            ("string.split separator", str(), "split", vec![x()]),
479            ("string.split limit", str(), "split", vec![s("b"), x()]),
480            ("number.toFixed", 1.5.to_any(), "toFixed", vec![x()]),
481            (
482                "number.toExponential",
483                1.5.to_any(),
484                "toExponential",
485                vec![x()],
486            ),
487            ("number.toPrecision", 1.5.to_any(), "toPrecision", vec![x()]),
488            ("number.toString radix", 1.5.to_any(), "toString", vec![x()]),
489            (
490                "bigint.toString radix",
491                BigInt::<A>::from(5i64).to_any(),
492                "toString",
493                vec![x()],
494            ),
495        ];
496        for (name, recv, method, args) in cases {
497            assert_eq!(call(recv, method, args), Err("boom".into()), "{name}");
498        }
499    }
500
501    fn big(n: i64) -> Any<A> {
502        BigInt::<A>::from(n).to_any()
503    }
504
505    fn value_of(v: Any<A>) -> Any<A> {
506        with(&[("valueOf", returns(v))])
507    }
508
509    fn to_string_of(v: Any<A>) -> Any<A> {
510        with(&[("toString", returns(v))])
511    }
512
513    /// The members the call reads: `("slice", [1])` is `a.slice(1)`.
514    fn call(recv: Any<A>, name: &str, args: Vec<Any<A>>) -> Result<Any<A>, Any<A>> {
515        recv.dot(name.into())
516            .end_call(&crate::vm::runtime::<A>().unwrap(), || {
517                Ok(args.to_array().to_any())
518            })
519    }
520
521    /// `ToNumeric` keeps a `BigInt` answer of `valueOf`, so the operators
522    /// answer a `BigInt` with a `BigInt` and refuse to mix it with a number.
523    #[test]
524    fn big_int_value_of_is_numeric() {
525        type Op = fn(Any<A>, Any<A>) -> Result<Any<A>, Any<A>>;
526        let ops: [(&str, Op, i64); 11] = [
527            ("+", |x, y| x + y, 8),
528            ("-", |x, y| x - y, 4),
529            ("*", |x, y| x * y, 12),
530            ("/", |x, y| x / y, 3),
531            ("%", |x, y| x % y, 0),
532            ("**", |x, y| x.pow(y), 36),
533            ("&", |x, y| x & y, 2),
534            ("|", |x, y| x | y, 6),
535            ("^", |x, y| x ^ y, 4),
536            ("<<", |x, y| x << y, 24),
537            (">>", |x, y| x >> y, 1),
538        ];
539        for (name, op, expected) in ops {
540            assert_eq!(op(value_of(big(6)), big(2)), Ok(big(expected)), "{name}");
541            assert_eq!(op(big(6), value_of(big(2))), Ok(big(expected)), "{name}");
542            assert!(op(value_of(big(6)), 2.0.to_any()).is_err(), "{name} mixes");
543            assert!(op(2.0.to_any(), value_of(big(6))).is_err(), "{name} mixes");
544        }
545        assert!(value_of(big(6)).unsigned_right_shift(big(1)).is_err());
546        assert_eq!(value_of(big(1)).bitwise_not(), Ok(big(-2)));
547        // `~(2^1048576 - 1)` is `-2^1048576`: one word past the size limit.
548        let half = (big(1) << big(1_048_575)).unwrap();
549        let ones = (half.clone() + (half - big(1)).unwrap()).unwrap();
550        assert_eq!(ones.bitwise_not(), Err(error::bigint_too_large()));
551        assert_eq!(-value_of(big(5)), Ok(big(-5)));
552        // `ToNumber` has no `BigInt`: unary `+` refuses it, `ToNumeric` keeps it.
553        refused(value_of(big(1)).to_number(), error::bigint_to_number());
554        assert!(value_of(big(1)).unary_plus().is_err());
555        // A `toString` answering a number string is a number, never a `BigInt`.
556        assert!((big(1) * to_string_of(s("2"))).is_err());
557        assert_eq!(to_string_of(s("2")) * 3.0.to_any(), Ok(6.0.to_any()));
558        // `+` with a string concatenates the `BigInt`'s digits.
559        assert_eq!(value_of(big(1)) + s("a"), Ok(s("1a")));
560        assert_eq!(s("a") + value_of(big(-1)), Ok(s("a-1")));
561    }
562
563    /// `+` has no hint: an object with only a `toString` falls through the
564    /// stock `valueOf`, and a primitive string on either side wins.
565    #[test]
566    fn plus_without_a_hint() {
567        assert_eq!(to_string_of(s("x")) + 1.0.to_any(), Ok(s("x1")));
568        assert_eq!(1.0.to_any() + to_string_of(s("x")), Ok(s("1x")));
569        assert_eq!(to_string_of(s("2")) + 1.0.to_any(), Ok(s("21")));
570        assert_eq!(value_of(s("a")) + 1.0.to_any(), Ok(s("a1")));
571        assert_eq!(value_of(1.0.to_any()) + to_string_of(s("t")), Ok(s("1t")));
572        assert_eq!(
573            value_of(1.0.to_any()) + value_of(2.0.to_any()),
574            Ok(3.0.to_any())
575        );
576        assert_eq!(value_of(true.to_any()) + 1.0.to_any(), Ok(2.0.to_any()));
577        assert_eq!(
578            value_of(Nullish::Null.to_any()) + 1.0.to_any(),
579            Ok(1.0.to_any())
580        );
581        assert!(is_nan(
582            (value_of(Any::undefined()) + 1.0.to_any()).and_then(Any::to_number)
583        ));
584        // Both operands convert before either answer is used.
585        let t = |v| with(&[("toString", throws(s(v)))]);
586        refused(t("a") + t("b"), "a");
587        refused(1.0.to_any() + t("b"), "b");
588    }
589
590    /// Every primitive an own method may answer, through `ToString` and
591    /// `ToNumber`.
592    #[test]
593    fn every_primitive_result() {
594        let cases: [(Any<A>, &str, Option<f64>); 7] = [
595            (Nullish::Null.to_any(), "null", Some(0.0)),
596            (Any::undefined(), "undefined", None),
597            (true.to_any(), "true", Some(1.0)),
598            (s("7"), "7", Some(7.0)),
599            (s(""), "", Some(0.0)),
600            (2.5.to_any(), "2.5", Some(2.5)),
601            ((-0.0).to_any(), "0", Some(-0.0)),
602        ];
603        for (v, text, number) in cases {
604            assert_eq!(to_string_of(v.clone()).to_string(), Ok(text.into()));
605            assert_eq!(
606                value_of(v.clone()).to_string(),
607                Ok("[object Object]".into())
608            );
609            let n = value_of(v.clone()).to_number().unwrap();
610            match number {
611                Some(x) => assert_eq!(f64::from(n).to_bits(), x.to_bits(), "{text}"),
612                None => assert!(n.is_nan()),
613            }
614            // The result is the primitive itself, whichever method answers it.
615            let n = f64::from(to_string_of(v).to_number().unwrap());
616            assert_eq!(n.to_bits(), number.unwrap_or(f64::NAN).to_bits(), "{text}");
617        }
618        assert_eq!(to_string_of(big(5)).to_string(), Ok("5".into()));
619        assert_eq!(to_string_of(s("-3")).to_number(), Ok(Number::from(-3.0)));
620        assert_eq!(-value_of(s("3")), Ok((-3.0).to_any()));
621        assert!(is_nan(value_of(s("5n")).to_number()));
622    }
623
624    /// The caller picks the hint. With `valueOf` answering `5` and `toString`
625    /// answering `"1"`, a `number` hint reads `5` and a `string` hint `"1"`.
626    #[test]
627    fn hint_per_caller() {
628        let o = || {
629            with(&[
630                ("valueOf", returns(5.0.to_any())),
631                ("toString", returns(s("1"))),
632            ])
633        };
634        // `number`: `ToNumber`, `ToNumeric` and the relational operators.
635        assert_eq!(o().to_number(), Ok(Number::from(5.0)));
636        assert_eq!(o() - 0.0.to_any(), Ok(5.0.to_any()));
637        assert_eq!(o().lt(2.0.to_any()), Ok(false.to_any()));
638        assert_eq!(o().ge(2.0.to_any()), Ok(true.to_any()));
639        assert_eq!(2.0.to_any().gt(o()), Ok(false.to_any()));
640        assert_eq!(2.0.to_any().le(o()), Ok(true.to_any()));
641        // none, which is `number` as well.
642        assert_eq!(o() + 0.0.to_any(), Ok(5.0.to_any()));
643        // `string`: `ToString`, so a string method's argument and a joined element.
644        assert_eq!(o().to_string(), Ok("1".into()));
645        assert_eq!(call(s("a1b"), "indexOf", vec![o()]), Ok(1.0.to_any()));
646        assert_eq!(
647            call([o(), o()].to_array().to_any(), "join", vec![]),
648            Ok(s("1,1"))
649        );
650        assert_eq!(
651            call([1.0.to_any()].to_array().to_any(), "join", vec![o()]),
652            Ok(s("1"))
653        );
654        // The public entry point: no hint is `number`.
655        let hint = |h| match o().to_primitive(h).unwrap() {
656            Primitive::Number(n) => format!("n{}", f64::from(n)),
657            Primitive::String(t) => format!("s{}", std::string::String::from(t)),
658            _ => unreachable!(),
659        };
660        assert_eq!(hint(None), "n5");
661        assert_eq!(hint(Some(ToPrimitivePreferredType::Number)), "n5");
662        assert_eq!(hint(Some(ToPrimitivePreferredType::String)), "s1");
663    }
664
665    /// The relational operators with objects: both operands convert with the
666    /// `number` hint, then strings compare as strings, `BigInt` against a
667    /// number exactly, and `NaN` makes all four `false`.
668    #[test]
669    fn relational_with_objects() {
670        let two = || value_of(2.0.to_any());
671        assert_eq!(value_of(1.0.to_any()).lt(two()), Ok(true.to_any()));
672        assert_eq!(two().gt(value_of(1.0.to_any())), Ok(true.to_any()));
673        assert_eq!(two().le(two()), Ok(true.to_any()));
674        assert_eq!(two().ge(two()), Ok(true.to_any()));
675        assert_eq!(two().lt(two()), Ok(false.to_any()));
676        // `toString` answers a string, so `"b" < "c"` is a string comparison:
677        // `"10" < "9"`, though `10 > 9`.
678        assert_eq!(to_string_of(s("b")).lt(s("c")), Ok(true.to_any()));
679        assert_eq!(
680            to_string_of(s("10")).lt(to_string_of(s("9"))),
681            Ok(true.to_any())
682        );
683        assert_eq!(value_of(s("10")).lt(9.0.to_any()), Ok(false.to_any()));
684        assert_eq!(big(1).lt(two()), Ok(true.to_any()));
685        assert_eq!(value_of(big(3)).gt(2.0.to_any()), Ok(true.to_any()));
686        assert_eq!(value_of(big(3)).ge(value_of(big(3))), Ok(true.to_any()));
687        // `NaN`, and a string no `BigInt` reads, answer `false` for all four.
688        for nan in [value_of(Number::NAN.to_any()), value_of(s("x"))] {
689            for r in [
690                nan.clone().lt(1.0.to_any()),
691                nan.clone().gt(1.0.to_any()),
692                nan.clone().le(1.0.to_any()),
693                nan.clone().ge(1.0.to_any()),
694                1.0.to_any().lt(nan.clone()),
695                1.0.to_any().ge(nan.clone()),
696            ] {
697                assert_eq!(r, Ok(false.to_any()));
698            }
699        }
700        assert_eq!(value_of(s("x")).lt(big(1)), Ok(false.to_any()));
701        assert_eq!(value_of(s("x")).ge(big(1)), Ok(false.to_any()));
702        // A throw is the operator's, from either side.
703        let t = |v| with(&[("valueOf", throws(s(v)))]);
704        refused(t("l").lt(1.0.to_any()), "l");
705        refused(1.0.to_any().lt(t("r")), "r");
706        refused(1.0.to_any().ge(t("r")), "r");
707    }
708
709    /// An array converts through its elements with the `string` hint, so an
710    /// element's `toString` counts, its `valueOf` does not, and a throw
711    /// reaches every operator.
712    #[test]
713    fn array_elements_are_converted() {
714        let arr = |items: Vec<Any<A>>| items.to_array().to_any::<A>();
715        assert_eq!(
716            arr(vec![to_string_of(s("x")), 1.0.to_any()]).to_string(),
717            Ok("x,1".into())
718        );
719        assert_eq!(
720            arr(vec![value_of(1.0.to_any())]).to_string(),
721            Ok("[object Object]".into())
722        );
723        assert_eq!(arr(vec![to_string_of(s("x"))]) + 1.0.to_any(), Ok(s("x1")));
724        assert_eq!(-arr(vec![to_string_of(s("5"))]), Ok((-5.0).to_any()));
725        assert_eq!(
726            arr(vec![to_string_of(s("5"))]).to_number(),
727            Ok(Number::from(5.0))
728        );
729        assert!(is_nan(arr(vec![value_of(5.0.to_any())]).to_number()));
730        assert_eq!(
731            arr(vec![to_string_of(s("1"))]).lt(arr(vec![to_string_of(s("2"))])),
732            Ok(true.to_any())
733        );
734        let nested = arr(vec![arr(vec![
735            to_string_of(s("n")),
736            Nullish::Null.to_any(),
737        ])]);
738        assert_eq!(nested.to_string(), Ok("n,".into()));
739        for r in [
740            arr(vec![1.0.to_any(), boom()]).to_string().map(|_| ()),
741            arr(vec![arr(vec![boom()])]).to_number().map(|_| ()),
742            (arr(vec![boom()]) + 1.0.to_any()).map(|_| ()),
743            (1.0.to_any() - arr(vec![boom()])).map(|_| ()),
744            (-arr(vec![boom()])).map(|_| ()),
745            arr(vec![boom()]).lt(1.0.to_any()).map(|_| ()),
746        ] {
747            assert_eq!(r, Err("boom".into()));
748        }
749        // An element's result that is no primitive is refused.
750        refused(
751            arr(vec![to_string_of(arr(vec![]))]).to_string(),
752            error::cannot_convert_to_primitive(),
753        );
754    }
755
756    /// What needs no primitive never converts: a throwing object is only
757    /// refused where `ToPrimitive` is asked.
758    #[test]
759    fn no_conversion_where_none_is_asked() {
760        let o = boom;
761        assert!(o().to_boolean());
762        assert_eq!(!o(), Ok(false.to_any()));
763        assert_eq!(o().typeof_(), Ok(s("object")));
764        let and_condition = o();
765        assert_eq!(
766            Any::conditional(
767                and_condition.clone(),
768                || Ok(1.0.to_any()),
769                || Ok(and_condition)
770            ),
771            Ok(1.0.to_any())
772        );
773        let or_condition = o();
774        assert!(
775            Any::conditional(
776                or_condition.clone(),
777                || Ok(or_condition),
778                || Ok(1.0.to_any())
779            )
780            .is_ok()
781        );
782        // an absent entry reads as `undefined`
783        assert_eq!(o().entry(s("a")), Ok(Any::undefined()));
784        // `===` compares by identity.
785        let x = o();
786        assert!(x == x.clone());
787        assert!(o() != o());
788        // An own method is read, never called, by a member access.
789        let m = with(&[("toString", throws(s("boom")))]);
790        assert!(m.dot("toString".into()).end().is_ok());
791    }
792
793    /// An own method gets no arguments, and the conversion calls it once per
794    /// method it reaches.
795    #[test]
796    fn method_gets_no_arguments() {
797        let arity = A::static_function(
798            |_, args| Ok(Number::from(args.length() as f64).to_any()),
799            0,
800            [].to_array(),
801            None,
802        )
803        .to_any();
804        assert_eq!(
805            with(&[("valueOf", arity.clone())]).to_number(),
806            Ok(Number::from(0.0))
807        );
808        assert_eq!(with(&[("toString", arity)]).to_string(), Ok("0".into()));
809    }
810
811    /// Whatever else an own `toString` or `valueOf` holds is skipped, an
812    /// object, an array or a nullish value included.
813    #[test]
814    fn every_non_function_is_skipped() {
815        let others = [
816            Nullish::Null.to_any(),
817            Any::undefined(),
818            true.to_any(),
819            big(1),
820            [].to_array().to_any(),
821            with(&[]),
822        ];
823        for v in others {
824            let o = || with(&[("valueOf", v.clone())]);
825            assert_eq!(o().to_string(), Ok("[object Object]".into()));
826            assert!(is_nan(o().to_number()));
827            let o = || with(&[("toString", v.clone())]);
828            refused(o().to_string(), error::cannot_convert_to_primitive());
829            refused(o() + 1.0.to_any(), error::cannot_convert_to_primitive());
830            // `valueOf` is still reached first, and answers.
831            let o = || with(&[("toString", v.clone()), ("valueOf", returns(4.0.to_any()))]);
832            assert_eq!(o().to_number(), Ok(Number::from(4.0)));
833            // The `string` hint skips it too, and `valueOf` answers.
834            assert_eq!(o().to_string(), Ok("4".into()));
835        }
836    }
837
838    /// A method's result is never converted, even a function that has a
839    /// text, and the next method's throw is the conversion's.
840    #[test]
841    fn result_is_not_converted() {
842        for result in [texted(), with(&[("toString", returns(s("a")))])] {
843            let o = || with(&[("toString", returns(result.clone()))]);
844            refused(o().to_string(), error::cannot_convert_to_primitive());
845            refused(o().to_number(), error::cannot_convert_to_primitive());
846            let o = || {
847                with(&[
848                    ("valueOf", returns(result.clone())),
849                    ("toString", throws(s("t"))),
850                ])
851            };
852            refused(o().to_number(), "t");
853            refused(o().to_string(), "t");
854        }
855        let o = with(&[
856            ("valueOf", returns(with(&[]))),
857            ("toString", returns(with(&[]))),
858        ]);
859        refused(o.clone().to_number(), error::cannot_convert_to_primitive());
860        refused(o + 1.0.to_any(), error::cannot_convert_to_primitive());
861    }
862
863    /// A conversion whose result is discarded still runs the methods, so
864    /// their throws, and their non-primitive results, are the call's.
865    #[test]
866    fn discarded_conversion_still_throws() {
867        let empty = || [].to_array().to_any::<A>();
868        refused(call(empty(), "join", vec![boom()]), "boom");
869        refused(
870            call(empty(), "join", vec![to_string_of(with(&[]))]),
871            error::cannot_convert_to_primitive(),
872        );
873        refused(
874            call(empty(), "join", vec![[boom()].to_array().to_any()]),
875            "boom",
876        );
877        assert_eq!(call(empty(), "join", vec![function()]), Ok(s("")));
878        assert_eq!(
879            call(s("a"), "split", vec![to_string_of(s("a")), 0.0.to_any()]).map(|a| a.to_string()),
880            Ok(Ok("".into()))
881        );
882        refused(call(s("a"), "split", vec![boom(), 0.0.to_any()]), "boom");
883        // The limit converts before the separator, as `split` does.
884        let t = |v| with(&[("valueOf", throws(s(v)))]);
885        refused(call(s("a"), "split", vec![t("sep"), t("limit")]), "limit");
886    }
887
888    /// The converted value, not only the conversion, reaches the member.
889    #[test]
890    fn member_arguments_use_the_value() {
891        let abc = || s("abc");
892        let one = || value_of(1.0.to_any());
893        let text = |r: Result<Any<A>, Any<A>>| r.and_then(Any::to_string);
894        let arr = || {
895            [1.0.to_any(), 2.0.to_any(), 3.0.to_any()]
896                .to_array()
897                .to_any::<A>()
898        };
899        assert_eq!(text(call(abc(), "charAt", vec![one()])), Ok("b".into()));
900        assert_eq!(text(call(abc(), "slice", vec![one()])), Ok("bc".into()));
901        assert_eq!(
902            text(call(abc(), "repeat", vec![value_of(2.0.to_any())])),
903            Ok("abcabc".into())
904        );
905        assert_eq!(
906            text(call(
907                abc(),
908                "padStart",
909                vec![value_of(5.0.to_any()), to_string_of(s("-"))]
910            )),
911            Ok("--abc".into())
912        );
913        assert_eq!(
914            text(call(abc(), "concat", vec![to_string_of(s("d"))])),
915            Ok("abcd".into())
916        );
917        assert_eq!(
918            call(abc(), "indexOf", vec![to_string_of(s("c"))]),
919            Ok(2.0.to_any())
920        );
921        assert_eq!(
922            call(abc(), "startsWith", vec![to_string_of(s("b")), one()]),
923            Ok(true.to_any())
924        );
925        assert_eq!(
926            call(arr(), "at", vec![value_of((-1.0).to_any())]),
927            Ok(3.0.to_any())
928        );
929        assert_eq!(
930            call(arr(), "indexOf", vec![2.0.to_any(), one()]),
931            Ok(1.0.to_any())
932        );
933        assert_eq!(
934            text(call(arr(), "join", vec![to_string_of(s("+"))])),
935            Ok("1+2+3".into())
936        );
937        assert_eq!(
938            text(call(1.5.to_any(), "toFixed", vec![one()])),
939            Ok("1.5".into())
940        );
941        assert_eq!(
942            text(call(
943                255.0.to_any(),
944                "toString",
945                vec![value_of(16.0.to_any())]
946            )),
947            Ok("ff".into())
948        );
949    }
950}