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()).map(Any::is_nan), Ok(true));
332        assert_eq!((function() - 1.0.to_any()).map(Any::is_nan), Ok(true));
333        assert_eq!(function() ^ 6.0.to_any(), Ok(6.0.to_any()));
334    }
335
336    /// `f < "z"` compares the text; `f < 5` and `f < 5n` are `false` for
337    /// every text.
338    #[test]
339    fn function_comparison() {
340        refused(function().lt(s("z")), error::function_text());
341        refused(s("z").lt(function()), error::function_text());
342        refused(function().lt(function()), error::function_text());
343        refused(function().ge(s("z")), error::function_text());
344        let five = || 5.0.to_any();
345        let big = || BigInt::<A>::from(5i64).to_any();
346        // An array's primitive is a string too.
347        refused(
348            function().lt([].to_array().to_any()),
349            error::function_text(),
350        );
351        for other in [five(), big(), true.to_any()] {
352            assert_eq!(function().lt(other.clone()), Ok(false.to_any()));
353            assert_eq!(other.clone().lt(function()), Ok(false.to_any()));
354            assert_eq!(function().le(other.clone()), Ok(false.to_any()));
355            assert_eq!(function().ge(other), Ok(false.to_any()));
356        }
357    }
358
359    /// `x`, an object whose every conversion method throws `"boom"`.
360    fn boom() -> Any<A> {
361        with(&[
362            ("valueOf", throws(s("boom"))),
363            ("toString", throws(s("boom"))),
364        ])
365    }
366
367    /// Each member function that converts an argument reaches the
368    /// conversion: with `x` throwing, the call throws `x`'s throw.
369    #[test]
370    fn member_arguments_are_converted() {
371        let arr = || {
372            [1.0.to_any(), 2.0.to_any(), 3.0.to_any()]
373                .to_array()
374                .to_any()
375        };
376        let str = || s("abc");
377        let (zero, one) = (|| 0.0.to_any(), || 1.0.to_any());
378        let x = boom;
379        type Case = (&'static str, Any<A>, &'static str, Vec<Any<A>>);
380        let cases: Vec<Case> = vec![
381            ("array.at", arr(), "at", vec![x()]),
382            ("array.slice start", arr(), "slice", vec![x()]),
383            ("array.slice end", arr(), "slice", vec![zero(), x()]),
384            ("array.indexOf from", arr(), "indexOf", vec![one(), x()]),
385            (
386                "array.lastIndexOf from",
387                arr(),
388                "lastIndexOf",
389                vec![one(), x()],
390            ),
391            ("array.includes from", arr(), "includes", vec![one(), x()]),
392            ("array.join", arr(), "join", vec![x()]),
393            ("array.toSpliced start", arr(), "toSpliced", vec![x()]),
394            (
395                "array.toSpliced skip",
396                arr(),
397                "toSpliced",
398                vec![zero(), x()],
399            ),
400            ("array.with index", arr(), "with", vec![x(), one()]),
401            ("array.flat depth", arr(), "flat", vec![x()]),
402            ("string.at", str(), "at", vec![x()]),
403            ("string.charAt", str(), "charAt", vec![x()]),
404            ("string.charCodeAt", str(), "charCodeAt", vec![x()]),
405            ("string.codePointAt", str(), "codePointAt", vec![x()]),
406            ("string.concat", str(), "concat", vec![x()]),
407            ("string.endsWith needle", str(), "endsWith", vec![x()]),
408            ("string.endsWith end", str(), "endsWith", vec![s("c"), x()]),
409            ("string.startsWith needle", str(), "startsWith", vec![x()]),
410            (
411                "string.startsWith start",
412                str(),
413                "startsWith",
414                vec![s("a"), x()],
415            ),
416            ("string.includes needle", str(), "includes", vec![x()]),
417            ("string.includes pos", str(), "includes", vec![s("a"), x()]),
418            ("string.indexOf needle", str(), "indexOf", vec![x()]),
419            ("string.indexOf pos", str(), "indexOf", vec![s("a"), x()]),
420            ("string.lastIndexOf needle", str(), "lastIndexOf", vec![x()]),
421            (
422                "string.lastIndexOf pos",
423                str(),
424                "lastIndexOf",
425                vec![s("a"), x()],
426            ),
427            ("string.padEnd length", str(), "padEnd", vec![x()]),
428            (
429                "string.padEnd fill",
430                str(),
431                "padEnd",
432                vec![5.0.to_any(), x()],
433            ),
434            ("string.padStart length", str(), "padStart", vec![x()]),
435            (
436                "string.padStart fill",
437                str(),
438                "padStart",
439                vec![5.0.to_any(), x()],
440            ),
441            ("string.repeat", str(), "repeat", vec![x()]),
442            (
443                "string.replace pattern",
444                str(),
445                "replace",
446                vec![x(), s("z")],
447            ),
448            (
449                "string.replace replacement",
450                str(),
451                "replace",
452                vec![s("a"), x()],
453            ),
454            (
455                "string.replaceAll pattern",
456                str(),
457                "replaceAll",
458                vec![x(), s("z")],
459            ),
460            (
461                "string.replaceAll replacement",
462                str(),
463                "replaceAll",
464                vec![s("a"), x()],
465            ),
466            ("string.slice start", str(), "slice", vec![x()]),
467            ("string.slice end", str(), "slice", vec![zero(), x()]),
468            ("string.substring start", str(), "substring", vec![x()]),
469            (
470                "string.substring end",
471                str(),
472                "substring",
473                vec![zero(), x()],
474            ),
475            ("string.split separator", str(), "split", vec![x()]),
476            ("string.split limit", str(), "split", vec![s("b"), x()]),
477            ("number.toFixed", 1.5.to_any(), "toFixed", vec![x()]),
478            (
479                "number.toExponential",
480                1.5.to_any(),
481                "toExponential",
482                vec![x()],
483            ),
484            ("number.toPrecision", 1.5.to_any(), "toPrecision", vec![x()]),
485            ("number.toString radix", 1.5.to_any(), "toString", vec![x()]),
486            (
487                "bigint.toString radix",
488                BigInt::<A>::from(5i64).to_any(),
489                "toString",
490                vec![x()],
491            ),
492        ];
493        for (name, recv, method, args) in cases {
494            assert_eq!(call(recv, method, args), Err("boom".into()), "{name}");
495        }
496    }
497
498    fn big(n: i64) -> Any<A> {
499        BigInt::<A>::from(n).to_any()
500    }
501
502    fn value_of(v: Any<A>) -> Any<A> {
503        with(&[("valueOf", returns(v))])
504    }
505
506    fn to_string_of(v: Any<A>) -> Any<A> {
507        with(&[("toString", returns(v))])
508    }
509
510    /// The members the call reads: `("slice", [1])` is `a.slice(1)`.
511    fn call(recv: Any<A>, name: &str, args: Vec<Any<A>>) -> Result<Any<A>, Any<A>> {
512        recv.dot(name.into())
513            .end_call(&crate::vm::runtime::<A>().unwrap(), || {
514                Ok(args.to_array().to_any())
515            })
516    }
517
518    /// `ToNumeric` keeps a `BigInt` answer of `valueOf`, so the operators
519    /// answer a `BigInt` with a `BigInt` and refuse to mix it with a number.
520    #[test]
521    fn big_int_value_of_is_numeric() {
522        type Op = fn(Any<A>, Any<A>) -> Result<Any<A>, Any<A>>;
523        let ops: [(&str, Op, i64); 11] = [
524            ("+", |x, y| x + y, 8),
525            ("-", |x, y| x - y, 4),
526            ("*", |x, y| x * y, 12),
527            ("/", |x, y| x / y, 3),
528            ("%", |x, y| x % y, 0),
529            ("**", |x, y| x.pow(y), 36),
530            ("&", |x, y| x & y, 2),
531            ("|", |x, y| x | y, 6),
532            ("^", |x, y| x ^ y, 4),
533            ("<<", |x, y| x << y, 24),
534            (">>", |x, y| x >> y, 1),
535        ];
536        for (name, op, expected) in ops {
537            assert_eq!(op(value_of(big(6)), big(2)), Ok(big(expected)), "{name}");
538            assert_eq!(op(big(6), value_of(big(2))), Ok(big(expected)), "{name}");
539            assert!(op(value_of(big(6)), 2.0.to_any()).is_err(), "{name} mixes");
540            assert!(op(2.0.to_any(), value_of(big(6))).is_err(), "{name} mixes");
541        }
542        assert!(value_of(big(6)).unsigned_right_shift(big(1)).is_err());
543        assert_eq!(value_of(big(1)).bitwise_not(), Ok(big(-2)));
544        // `~(2^1048576 - 1)` is `-2^1048576`: one word past the size limit.
545        let half = (big(1) << big(1_048_575)).unwrap();
546        let ones = (half.clone() + (half - big(1)).unwrap()).unwrap();
547        assert_eq!(ones.bitwise_not(), Err(error::bigint_too_large()));
548        assert_eq!(-value_of(big(5)), Ok(big(-5)));
549        // `ToNumber` has no `BigInt`: unary `+` refuses it, `ToNumeric` keeps it.
550        refused(value_of(big(1)).to_number(), error::bigint_to_number());
551        assert!(value_of(big(1)).unary_plus().is_err());
552        // A `toString` answering a number string is a number, never a `BigInt`.
553        assert!((big(1) * to_string_of(s("2"))).is_err());
554        assert_eq!(to_string_of(s("2")) * 3.0.to_any(), Ok(6.0.to_any()));
555        // `+` with a string concatenates the `BigInt`'s digits.
556        assert_eq!(value_of(big(1)) + s("a"), Ok(s("1a")));
557        assert_eq!(s("a") + value_of(big(-1)), Ok(s("a-1")));
558    }
559
560    /// `+` has no hint: an object with only a `toString` falls through the
561    /// stock `valueOf`, and a primitive string on either side wins.
562    #[test]
563    fn plus_without_a_hint() {
564        assert_eq!(to_string_of(s("x")) + 1.0.to_any(), Ok(s("x1")));
565        assert_eq!(1.0.to_any() + to_string_of(s("x")), Ok(s("1x")));
566        assert_eq!(to_string_of(s("2")) + 1.0.to_any(), Ok(s("21")));
567        assert_eq!(value_of(s("a")) + 1.0.to_any(), Ok(s("a1")));
568        assert_eq!(value_of(1.0.to_any()) + to_string_of(s("t")), Ok(s("1t")));
569        assert_eq!(
570            value_of(1.0.to_any()) + value_of(2.0.to_any()),
571            Ok(3.0.to_any())
572        );
573        assert_eq!(value_of(true.to_any()) + 1.0.to_any(), Ok(2.0.to_any()));
574        assert_eq!(
575            value_of(Nullish::Null.to_any()) + 1.0.to_any(),
576            Ok(1.0.to_any())
577        );
578        assert!(is_nan(
579            (value_of(Any::undefined()) + 1.0.to_any()).and_then(Any::to_number)
580        ));
581        // Both operands convert before either answer is used.
582        let t = |v| with(&[("toString", throws(s(v)))]);
583        refused(t("a") + t("b"), "a");
584        refused(1.0.to_any() + t("b"), "b");
585    }
586
587    /// Every primitive an own method may answer, through `ToString` and
588    /// `ToNumber`.
589    #[test]
590    fn every_primitive_result() {
591        let cases: [(Any<A>, &str, Option<f64>); 7] = [
592            (Nullish::Null.to_any(), "null", Some(0.0)),
593            (Any::undefined(), "undefined", None),
594            (true.to_any(), "true", Some(1.0)),
595            (s("7"), "7", Some(7.0)),
596            (s(""), "", Some(0.0)),
597            (2.5.to_any(), "2.5", Some(2.5)),
598            ((-0.0).to_any(), "0", Some(-0.0)),
599        ];
600        for (v, text, number) in cases {
601            assert_eq!(to_string_of(v.clone()).to_string(), Ok(text.into()));
602            assert_eq!(
603                value_of(v.clone()).to_string(),
604                Ok("[object Object]".into())
605            );
606            let n = value_of(v.clone()).to_number().unwrap();
607            match number {
608                Some(x) => assert_eq!(f64::from(n).to_bits(), x.to_bits(), "{text}"),
609                None => assert!(n.is_nan()),
610            }
611            // The result is the primitive itself, whichever method answers it.
612            let n = f64::from(to_string_of(v).to_number().unwrap());
613            assert_eq!(n.to_bits(), number.unwrap_or(f64::NAN).to_bits(), "{text}");
614        }
615        assert_eq!(to_string_of(big(5)).to_string(), Ok("5".into()));
616        assert_eq!(to_string_of(s("-3")).to_number(), Ok(Number::from(-3.0)));
617        assert_eq!(-value_of(s("3")), Ok((-3.0).to_any()));
618        assert!(is_nan(value_of(s("5n")).to_number()));
619    }
620
621    /// The caller picks the hint. With `valueOf` answering `5` and `toString`
622    /// answering `"1"`, a `number` hint reads `5` and a `string` hint `"1"`.
623    #[test]
624    fn hint_per_caller() {
625        let o = || {
626            with(&[
627                ("valueOf", returns(5.0.to_any())),
628                ("toString", returns(s("1"))),
629            ])
630        };
631        // `number`: `ToNumber`, `ToNumeric` and the relational operators.
632        assert_eq!(o().to_number(), Ok(Number::from(5.0)));
633        assert_eq!(o() - 0.0.to_any(), Ok(5.0.to_any()));
634        assert_eq!(o().lt(2.0.to_any()), Ok(false.to_any()));
635        assert_eq!(o().ge(2.0.to_any()), Ok(true.to_any()));
636        assert_eq!(2.0.to_any().gt(o()), Ok(false.to_any()));
637        assert_eq!(2.0.to_any().le(o()), Ok(true.to_any()));
638        // none, which is `number` as well.
639        assert_eq!(o() + 0.0.to_any(), Ok(5.0.to_any()));
640        // `string`: `ToString`, so a string method's argument and a joined element.
641        assert_eq!(o().to_string(), Ok("1".into()));
642        assert_eq!(call(s("a1b"), "indexOf", vec![o()]), Ok(1.0.to_any()));
643        assert_eq!(
644            call([o(), o()].to_array().to_any(), "join", vec![]),
645            Ok(s("1,1"))
646        );
647        assert_eq!(
648            call([1.0.to_any()].to_array().to_any(), "join", vec![o()]),
649            Ok(s("1"))
650        );
651        // The public entry point: no hint is `number`.
652        let hint = |h| match o().to_primitive(h).unwrap() {
653            Primitive::Number(n) => format!("n{}", f64::from(n)),
654            Primitive::String(t) => format!("s{}", std::string::String::from(t)),
655            _ => unreachable!(),
656        };
657        assert_eq!(hint(None), "n5");
658        assert_eq!(hint(Some(ToPrimitivePreferredType::Number)), "n5");
659        assert_eq!(hint(Some(ToPrimitivePreferredType::String)), "s1");
660    }
661
662    /// The relational operators with objects: both operands convert with the
663    /// `number` hint, then strings compare as strings, `BigInt` against a
664    /// number exactly, and `NaN` makes all four `false`.
665    #[test]
666    fn relational_with_objects() {
667        let two = || value_of(2.0.to_any());
668        assert_eq!(value_of(1.0.to_any()).lt(two()), Ok(true.to_any()));
669        assert_eq!(two().gt(value_of(1.0.to_any())), Ok(true.to_any()));
670        assert_eq!(two().le(two()), Ok(true.to_any()));
671        assert_eq!(two().ge(two()), Ok(true.to_any()));
672        assert_eq!(two().lt(two()), Ok(false.to_any()));
673        // `toString` answers a string, so `"b" < "c"` is a string comparison:
674        // `"10" < "9"`, though `10 > 9`.
675        assert_eq!(to_string_of(s("b")).lt(s("c")), Ok(true.to_any()));
676        assert_eq!(
677            to_string_of(s("10")).lt(to_string_of(s("9"))),
678            Ok(true.to_any())
679        );
680        assert_eq!(value_of(s("10")).lt(9.0.to_any()), Ok(false.to_any()));
681        assert_eq!(big(1).lt(two()), Ok(true.to_any()));
682        assert_eq!(value_of(big(3)).gt(2.0.to_any()), Ok(true.to_any()));
683        assert_eq!(value_of(big(3)).ge(value_of(big(3))), Ok(true.to_any()));
684        // `NaN`, and a string no `BigInt` reads, answer `false` for all four.
685        for nan in [value_of(Number::NAN.to_any()), value_of(s("x"))] {
686            for r in [
687                nan.clone().lt(1.0.to_any()),
688                nan.clone().gt(1.0.to_any()),
689                nan.clone().le(1.0.to_any()),
690                nan.clone().ge(1.0.to_any()),
691                1.0.to_any().lt(nan.clone()),
692                1.0.to_any().ge(nan.clone()),
693            ] {
694                assert_eq!(r, Ok(false.to_any()));
695            }
696        }
697        assert_eq!(value_of(s("x")).lt(big(1)), Ok(false.to_any()));
698        assert_eq!(value_of(s("x")).ge(big(1)), Ok(false.to_any()));
699        // A throw is the operator's, from either side.
700        let t = |v| with(&[("valueOf", throws(s(v)))]);
701        refused(t("l").lt(1.0.to_any()), "l");
702        refused(1.0.to_any().lt(t("r")), "r");
703        refused(1.0.to_any().ge(t("r")), "r");
704    }
705
706    /// An array converts through its elements with the `string` hint, so an
707    /// element's `toString` counts, its `valueOf` does not, and a throw
708    /// reaches every operator.
709    #[test]
710    fn array_elements_are_converted() {
711        let arr = |items: Vec<Any<A>>| items.to_array().to_any::<A>();
712        assert_eq!(
713            arr(vec![to_string_of(s("x")), 1.0.to_any()]).to_string(),
714            Ok("x,1".into())
715        );
716        assert_eq!(
717            arr(vec![value_of(1.0.to_any())]).to_string(),
718            Ok("[object Object]".into())
719        );
720        assert_eq!(arr(vec![to_string_of(s("x"))]) + 1.0.to_any(), Ok(s("x1")));
721        assert_eq!(-arr(vec![to_string_of(s("5"))]), Ok((-5.0).to_any()));
722        assert_eq!(
723            arr(vec![to_string_of(s("5"))]).to_number(),
724            Ok(Number::from(5.0))
725        );
726        assert!(is_nan(arr(vec![value_of(5.0.to_any())]).to_number()));
727        assert_eq!(
728            arr(vec![to_string_of(s("1"))]).lt(arr(vec![to_string_of(s("2"))])),
729            Ok(true.to_any())
730        );
731        let nested = arr(vec![arr(vec![
732            to_string_of(s("n")),
733            Nullish::Null.to_any(),
734        ])]);
735        assert_eq!(nested.to_string(), Ok("n,".into()));
736        for r in [
737            arr(vec![1.0.to_any(), boom()]).to_string().map(|_| ()),
738            arr(vec![arr(vec![boom()])]).to_number().map(|_| ()),
739            (arr(vec![boom()]) + 1.0.to_any()).map(|_| ()),
740            (1.0.to_any() - arr(vec![boom()])).map(|_| ()),
741            (-arr(vec![boom()])).map(|_| ()),
742            arr(vec![boom()]).lt(1.0.to_any()).map(|_| ()),
743        ] {
744            assert_eq!(r, Err("boom".into()));
745        }
746        // An element's result that is no primitive is refused.
747        refused(
748            arr(vec![to_string_of(arr(vec![]))]).to_string(),
749            error::cannot_convert_to_primitive(),
750        );
751    }
752
753    /// What needs no primitive never converts: a throwing object is only
754    /// refused where `ToPrimitive` is asked.
755    #[test]
756    fn no_conversion_where_none_is_asked() {
757        let o = boom;
758        assert!(o().to_boolean());
759        assert_eq!(!o(), Ok(false.to_any()));
760        assert_eq!(o().typeof_(), Ok(s("object")));
761        let and_condition = o();
762        assert_eq!(
763            Any::conditional(
764                and_condition.clone(),
765                || Ok(1.0.to_any()),
766                || Ok(and_condition)
767            ),
768            Ok(1.0.to_any())
769        );
770        let or_condition = o();
771        assert!(
772            Any::conditional(
773                or_condition.clone(),
774                || Ok(or_condition),
775                || Ok(1.0.to_any())
776            )
777            .is_ok()
778        );
779        // an absent entry reads as `undefined`
780        assert_eq!(o().entry(s("a")), Ok(Any::undefined()));
781        // `===` compares by identity.
782        let x = o();
783        assert!(x == x.clone());
784        assert!(o() != o());
785        // An own method is read, never called, by a member access.
786        let m = with(&[("toString", throws(s("boom")))]);
787        assert!(m.dot("toString".into()).end().is_ok());
788    }
789
790    /// An own method gets no arguments, and the conversion calls it once per
791    /// method it reaches.
792    #[test]
793    fn method_gets_no_arguments() {
794        let arity = A::static_function(
795            |_, args| Ok(Number::from(args.length() as f64).to_any()),
796            0,
797            [].to_array(),
798            None,
799        )
800        .to_any();
801        assert_eq!(
802            with(&[("valueOf", arity.clone())]).to_number(),
803            Ok(Number::from(0.0))
804        );
805        assert_eq!(with(&[("toString", arity)]).to_string(), Ok("0".into()));
806    }
807
808    /// Whatever else an own `toString` or `valueOf` holds is skipped, an
809    /// object, an array or a nullish value included.
810    #[test]
811    fn every_non_function_is_skipped() {
812        let others = [
813            Nullish::Null.to_any(),
814            Any::undefined(),
815            true.to_any(),
816            big(1),
817            [].to_array().to_any(),
818            with(&[]),
819        ];
820        for v in others {
821            let o = || with(&[("valueOf", v.clone())]);
822            assert_eq!(o().to_string(), Ok("[object Object]".into()));
823            assert!(is_nan(o().to_number()));
824            let o = || with(&[("toString", v.clone())]);
825            refused(o().to_string(), error::cannot_convert_to_primitive());
826            refused(o() + 1.0.to_any(), error::cannot_convert_to_primitive());
827            // `valueOf` is still reached first, and answers.
828            let o = || with(&[("toString", v.clone()), ("valueOf", returns(4.0.to_any()))]);
829            assert_eq!(o().to_number(), Ok(Number::from(4.0)));
830            // The `string` hint skips it too, and `valueOf` answers.
831            assert_eq!(o().to_string(), Ok("4".into()));
832        }
833    }
834
835    /// A method's result is never converted, even a function that has a
836    /// text, and the next method's throw is the conversion's.
837    #[test]
838    fn result_is_not_converted() {
839        for result in [texted(), with(&[("toString", returns(s("a")))])] {
840            let o = || with(&[("toString", returns(result.clone()))]);
841            refused(o().to_string(), error::cannot_convert_to_primitive());
842            refused(o().to_number(), error::cannot_convert_to_primitive());
843            let o = || {
844                with(&[
845                    ("valueOf", returns(result.clone())),
846                    ("toString", throws(s("t"))),
847                ])
848            };
849            refused(o().to_number(), "t");
850            refused(o().to_string(), "t");
851        }
852        let o = with(&[
853            ("valueOf", returns(with(&[]))),
854            ("toString", returns(with(&[]))),
855        ]);
856        refused(o.clone().to_number(), error::cannot_convert_to_primitive());
857        refused(o + 1.0.to_any(), error::cannot_convert_to_primitive());
858    }
859
860    /// A conversion whose result is discarded still runs the methods, so
861    /// their throws, and their non-primitive results, are the call's.
862    #[test]
863    fn discarded_conversion_still_throws() {
864        let empty = || [].to_array().to_any::<A>();
865        refused(call(empty(), "join", vec![boom()]), "boom");
866        refused(
867            call(empty(), "join", vec![to_string_of(with(&[]))]),
868            error::cannot_convert_to_primitive(),
869        );
870        refused(
871            call(empty(), "join", vec![[boom()].to_array().to_any()]),
872            "boom",
873        );
874        assert_eq!(call(empty(), "join", vec![function()]), Ok(s("")));
875        assert_eq!(
876            call(s("a"), "split", vec![to_string_of(s("a")), 0.0.to_any()]).map(|a| a.to_string()),
877            Ok(Ok("".into()))
878        );
879        refused(call(s("a"), "split", vec![boom(), 0.0.to_any()]), "boom");
880        // The limit converts before the separator, as `split` does.
881        let t = |v| with(&[("valueOf", throws(s(v)))]);
882        refused(call(s("a"), "split", vec![t("sep"), t("limit")]), "limit");
883    }
884
885    /// The converted value, not only the conversion, reaches the member.
886    #[test]
887    fn member_arguments_use_the_value() {
888        let abc = || s("abc");
889        let one = || value_of(1.0.to_any());
890        let text = |r: Result<Any<A>, Any<A>>| r.and_then(Any::to_string);
891        let arr = || {
892            [1.0.to_any(), 2.0.to_any(), 3.0.to_any()]
893                .to_array()
894                .to_any::<A>()
895        };
896        assert_eq!(text(call(abc(), "charAt", vec![one()])), Ok("b".into()));
897        assert_eq!(text(call(abc(), "slice", vec![one()])), Ok("bc".into()));
898        assert_eq!(
899            text(call(abc(), "repeat", vec![value_of(2.0.to_any())])),
900            Ok("abcabc".into())
901        );
902        assert_eq!(
903            text(call(
904                abc(),
905                "padStart",
906                vec![value_of(5.0.to_any()), to_string_of(s("-"))]
907            )),
908            Ok("--abc".into())
909        );
910        assert_eq!(
911            text(call(abc(), "concat", vec![to_string_of(s("d"))])),
912            Ok("abcd".into())
913        );
914        assert_eq!(
915            call(abc(), "indexOf", vec![to_string_of(s("c"))]),
916            Ok(2.0.to_any())
917        );
918        assert_eq!(
919            call(abc(), "startsWith", vec![to_string_of(s("b")), one()]),
920            Ok(true.to_any())
921        );
922        assert_eq!(
923            call(arr(), "at", vec![value_of((-1.0).to_any())]),
924            Ok(3.0.to_any())
925        );
926        assert_eq!(
927            call(arr(), "indexOf", vec![2.0.to_any(), one()]),
928            Ok(1.0.to_any())
929        );
930        assert_eq!(
931            text(call(arr(), "join", vec![to_string_of(s("+"))])),
932            Ok("1+2+3".into())
933        );
934        assert_eq!(
935            text(call(1.5.to_any(), "toFixed", vec![one()])),
936            Ok("1.5".into())
937        );
938        assert_eq!(
939            text(call(
940                255.0.to_any(),
941                "toString",
942                vec![value_of(16.0.to_any())]
943            )),
944            Ok("ff".into())
945        );
946    }
947}