Skip to main content

nanvm_lib/vm/any/
mod.rs

1mod add;
2mod bitand;
3mod bitor;
4mod bitxor;
5mod call;
6mod conditional;
7mod div;
8mod dot;
9mod from;
10mod get_iterator;
11#[cfg(test)]
12mod inspection_test;
13mod instanceof_;
14mod mul;
15mod neg;
16mod not;
17mod object_spread;
18mod option_call;
19mod partial_eq;
20mod relational;
21mod rem;
22mod shl;
23mod shr;
24mod sub;
25mod to_json;
26mod typeof_;
27
28pub mod to_any;
29
30pub use instanceof_::Constructor;
31pub use to_json::JsonError;
32
33use crate::vm::{
34    IVm, Number, String, ToAny, Unpacked, bigint_coercion::BigIntCoercion, dispatch::Dispatch,
35    member_access::non_nullish_receiver, nullish::Nullish, number_coercion::NumberCoercion,
36    numeric::Numeric, numeric_coercion, primitive_coercion::ToPrimitivePreferredType,
37    primitive_dispatch::ViaPrimitive, string_coercion::StringCoercion,
38};
39
40/// ```
41/// use nanvm_lib::{
42///     vm::{Any, IVm, ToAny, String, Array, ToArray, ToObject, Object, Nullish, Number, BigInt},
43///     naive::Naive
44/// };
45/// fn any_test<A: IVm>() {
46///     let b: Any<A> = true.to_any();
47///     let n: Any<A> = Nullish::Null.to_any();
48///     let n: Any<A> = Number::from(42.0).to_any();
49///     let c: String<A> = "Hello".into();
50///     let m: Any<A> = c.to_any();
51///     let a: Array<A> = [].to_array();
52///     let o: Any<A> = a.to_any();
53///     let x: Object<A> = [].to_object();
54///     let p: Any<A> = x.to_any();
55///     let u: BigInt<A> = 123u64.into();
56///     let q: Any<A> = u.to_any();
57/// }
58///
59/// any_test::<Naive>();
60/// ```
61#[derive(Clone)]
62pub struct Any<A: IVm>(A);
63
64impl<A: IVm> Any<A> {
65    /// `undefined`.
66    pub fn undefined() -> Self {
67        Nullish::Undefined.to_any()
68    }
69
70    /// Whether `self` is `undefined` or `null`, inspecting its borrowed
71    /// representation. A caller that already holds the `Unpacked` asks it
72    /// instead and keeps its single conversion.
73    pub fn is_nullish(&self) -> bool {
74        self.with_unpacked(Unpacked::is_nullish)
75    }
76
77    /// `None` for `undefined`, `Some(self)` for every other value, `null`
78    /// included: the argument an optional parameter was passed, if it was
79    /// passed one. Borrows to inspect, then returns the owned value.
80    pub fn defined(self) -> Option<Self> {
81        (!self.with_unpacked(Unpacked::is_undefined)).then_some(self)
82    }
83
84    /// Unary plus is nothing but coercion to number.
85    /// We use unary_plus as ECMAScript unary plus operator, and we use coerce_to_number for
86    /// internals in places where ECMAScript's abstract function ToNumber is needed, and also when
87    /// we need Result<Number, Any<A>> result type; here unary_plus returns Result<Any<A>, Any<A>> to
88    /// match public API type of unary plus operator.
89    /// <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Unary_plus>
90    /// <https://tc39.es/ecma262/#sec-unary-plus-operator>
91    pub fn unary_plus(self) -> Result<Any<A>, Any<A>> {
92        self.to_number().map(ToAny::to_any)
93    }
94
95    /// `Number(value)`: `ToNumeric` of the value, a bigint converted to the
96    /// nearest number and anything else as `unary_plus` has it. They differ in
97    /// the bigint alone: `+1n` throws, `Number(1n)` is `1`.
98    /// <https://tc39.es/ecma262/#sec-number-constructor-number-value>
99    pub fn number(self) -> Result<Any<A>, Any<A>> {
100        Ok(match self.to_numeric()? {
101            Numeric::Number(n) => n,
102            Numeric::BigInt(b) => b.to_number(),
103        }
104        .to_any())
105    }
106
107    /// `BigInt(value)`: ordinary primitive conversion with the number hint,
108    /// then an exact integer conversion. This is an operator, not a value.
109    /// <https://tc39.es/ecma262/#sec-bigint-constructor>
110    pub fn bigint(self) -> Result<Any<A>, Any<A>> {
111        self.dispatch(ViaPrimitive(BigIntCoercion))
112            .map(ToAny::to_any)
113    }
114
115    /// `**`. Not a `core::ops` trait — Rust has no operator for
116    /// exponentiation, so this is a plain method, the same as `unary_plus`.
117    pub fn pow(self, rhs: Self) -> Result<Self, Self> {
118        self.numeric_op(rhs, Numeric::pow)
119    }
120
121    /// `~`. Not a `core::ops` trait — `Not` is already claimed by this
122    /// type's own logical `!` (`any/not.rs`) — so this is a plain method,
123    /// the same as `unary_plus`/`pow`.
124    /// <https://tc39.es/ecma262/#sec-bitwise-not-operator>
125    pub fn bitwise_not(self) -> Result<Self, Self> {
126        Ok(Unpacked::from(self.to_numeric()?.bitwise_not()?).into())
127    }
128
129    /// `>>>`. Not a `core::ops` trait — Rust has no unsigned-right-shift
130    /// operator — so this is a plain method, the same as `pow`/`bitwise_not`.
131    /// <https://tc39.es/ecma262/#sec-unsigned-right-shift-operator>
132    pub fn unsigned_right_shift(self, rhs: Self) -> Result<Self, Self> {
133        self.numeric_op(rhs, Numeric::unsigned_right_shift)
134    }
135
136    /// The shape every numeric binary operator shares: `ToNumeric` of the
137    /// left operand, then of the right, then `op` on the two (`+` once it
138    /// has ruled out a string concatenation).
139    /// <https://tc39.es/ecma262/#sec-applystringornumericbinaryoperator>
140    fn numeric_op(
141        self,
142        rhs: Self,
143        op: impl FnOnce(Numeric<A>, Numeric<A>) -> Result<Numeric<A>, Self>,
144    ) -> Result<Self, Self> {
145        Ok(Unpacked::from(op(self.to_numeric()?, rhs.to_numeric()?)?).into())
146    }
147
148    /// The `entry` helper, `(a, b) => { const x =
149    /// Object.getOwnPropertyDescriptor(a, b); return x?.enumerable ? x.value :
150    /// undefined; }`, as a method: the value of the enumerable own property
151    /// the key names — an `Object`'s field (`Object::own_property`), an
152    /// `Array`'s element or a `String`'s code unit by its canonical index
153    /// string (`Array::entry`, `String::entry`) — and `undefined` for none,
154    /// an entry holding `undefined` included, which [`Any::has_entry`] tells
155    /// apart, and for nothing a value owns without enumerating it — never a
156    /// `length`, and nothing of a function. No getter invocation and no
157    /// prototype chain (`nanvm-lib` objects have no `__proto__` to walk in
158    /// the first place). Not a `core::ops` trait — no Rust operator fits a
159    /// keyed property lookup — so this is a plain method, the same as
160    /// `pow`/`bitwise_not`/`unsigned_right_shift`.
161    ///
162    /// The receiver is checked before the key is converted
163    /// (`member_access::non_nullish_receiver`), so a nullish receiver
164    /// throws regardless of what the key is, and a key whose conversion
165    /// throws does so under a receiver that is not. The key converts as
166    /// `ToPropertyKey` converts it, which is `to_string` here, the language
167    /// having no symbols: `0` and `"0"` name one entry, and an object
168    /// converts through its own `toString`. A `Number`, `Boolean` or
169    /// `BigInt` receiver owns no entry and answers `undefined`, as every
170    /// absent key does.
171    pub fn entry(self, key: Self) -> Result<Self, Self> {
172        Ok(self.own_entry(key)?.unwrap_or_else(Any::undefined))
173    }
174
175    /// The `hasEntry` helper: whether the enumerable own property `key`
176    /// names is there, `true` for an entry holding `undefined`, which
177    /// [`Any::entry`] reads as none, the receiver and the key treated as
178    /// `entry` treats them, so a nullish receiver throws and a key converts
179    /// first (`spec/property-access/entries/README.md`).
180    pub fn has_entry(self, key: Self) -> Result<bool, Self> {
181        Ok(self.own_entry(key)?.is_some())
182    }
183
184    /// The enumerable own property `key` names, or `None`: what `entry` and
185    /// `has_entry` share, the receiver checked before the key is converted.
186    fn own_entry(self, key: Self) -> Result<Option<Self>, Self> {
187        let unpacked = non_nullish_receiver(self)?;
188        let key = key.to_string()?;
189        Ok(match unpacked {
190            Unpacked::Object(o) => o.own_property(&key),
191            Unpacked::Array(a) => a.entry(&key),
192            Unpacked::String(s) => s.entry(&key),
193            _ => None,
194        })
195    }
196
197    /// Same as `Number.isNaN` in ECMAScript.
198    pub fn is_nan(&self) -> bool {
199        self.with_unpacked(|v| matches!(v, Unpacked::Number(n) if n.is_nan()))
200    }
201
202    pub fn to_string(self) -> Result<String<A>, Any<A>> {
203        self.dispatch(ViaPrimitive(StringCoercion))
204    }
205
206    /// `ToString` where the algorithm discards the result: its throws still
207    /// happen, and an object with its own `toString` is still refused, but a
208    /// function's text, which cannot throw and is never read here, is not
209    /// needed — so `[].join(f)` answers `""`, as it does in JavaScript
210    /// (`nanvm/lib/todo/to-primitive.md`, Stage 1). An array converts
211    /// through its elements alone, since it owns no `toString`, so a
212    /// function inside one is skipped too: `[].join([f])` is `""`.
213    pub fn to_string_unused(self) -> Result<(), Any<A>> {
214        match self.clone().into() {
215            Unpacked::Function(_) => Ok(()),
216            Unpacked::Array(a) => a.into_iter().try_for_each(Self::to_string_unused),
217            _ => self.to_string().map(|_| ()),
218        }
219    }
220
221    pub fn to_number(self) -> Result<Number, Any<A>> {
222        self.dispatch(ViaPrimitive(NumberCoercion))
223    }
224
225    /// Never fails, unlike `to_number`/`to_string`/`to_numeric` — `ToBoolean`
226    /// inspects the operand's type directly and never calls `ToPrimitive`.
227    pub fn to_boolean(&self) -> bool {
228        self.with_unpacked(Unpacked::to_boolean)
229    }
230
231    pub fn to_numeric(self) -> Result<Numeric<A>, Any<A>> {
232        // https://tc39.es/ecma262/#sec-tonumeric
233        // A function's primitive, its text, is a string and not a bigint, so
234        // its `ToNumber` answers without converting it.
235        if self.with_unpacked(|v| matches!(v, Unpacked::Function(_))) {
236            return Ok(Numeric::Number(self.to_number()?));
237        }
238        numeric_coercion::to_numeric(self.to_primitive(Some(ToPrimitivePreferredType::Number))?)
239    }
240
241    fn dispatch<T: Dispatch<A>>(self, o: T) -> T::Result {
242        self.0.to_unpacked().dispatch(o)
243    }
244
245    pub(crate) fn with_unpacked<R>(&self, f: impl FnOnce(&Unpacked<A>) -> R) -> R {
246        self.0.with_unpacked(f)
247    }
248}
249
250#[cfg(test)]
251mod tests {
252    use crate::{
253        naive::Naive,
254        vm::{Any, Nullish, ToAny, ToArray, ToObject, Unpacked, error},
255    };
256
257    type A = Naive;
258
259    /// The receiver is checked before the key is converted (see `entry`'s
260    /// own doc comment), so a nullish receiver throws the nullish
261    /// `TypeError` whatever the key is — a `throws` expectation alone
262    /// could not tell the two orders apart, so this compares the error.
263    #[test]
264    fn entry_nullish_receiver_outranks_the_key() {
265        let key = (1f64).to_any::<A>();
266        assert_eq!(
267            Nullish::Null.to_any::<A>().entry(key.clone()),
268            Err(error::nullish_to_object())
269        );
270        assert_eq!(Any::undefined().entry(key), Err(error::nullish_to_object()));
271    }
272
273    /// The key converts as `ToPropertyKey` converts it: a number names the
274    /// entry its decimal spelling names, `-0` the entry `"0"` does.
275    #[test]
276    fn entry_converts_the_key() {
277        let o = [("1".into(), 42.0.to_any())].to_object().to_any::<A>();
278        assert_eq!(o.clone().entry("1".into()), Ok(42.0.to_any()));
279        assert_eq!(o.clone().entry(1.0.to_any()), Ok(42.0.to_any()));
280        assert_eq!(o.entry("01".into()), Ok(Any::undefined()));
281        let z = [("0".into(), 7.0.to_any())].to_object().to_any::<A>();
282        assert_eq!(z.entry((-0.0f64).to_any()), Ok(7.0.to_any()));
283    }
284
285    /// `has_entry` is the presence beside the value: `true` for an entry holding
286    /// `undefined`, `false` for none, a `length` or anything of a function,
287    /// with `entry`'s throws for a nullish receiver and a key whose
288    /// conversion throws.
289    #[test]
290    fn has_entry_is_the_presence() {
291        let o = [("u".into(), Any::undefined()), ("1".into(), 42.0.to_any())]
292            .to_object()
293            .to_any::<A>();
294        assert_eq!(o.clone().has_entry("u".into()), Ok(true));
295        assert_eq!(o.clone().has_entry("v".into()), Ok(false));
296        assert_eq!(o.clone().has_entry(1.0.to_any()), Ok(true));
297        assert_eq!(o.has_entry("toString".into()), Ok(false));
298        let a = [7.0.to_any()].to_array().to_any::<A>();
299        assert_eq!(a.clone().has_entry("0".into()), Ok(true));
300        assert_eq!(a.has_entry("length".into()), Ok(false));
301        let s: Any<A> = "ab".into();
302        assert_eq!(s.clone().has_entry(1.0.to_any()), Ok(true));
303        assert_eq!(s.has_entry("length".into()), Ok(false));
304        assert_eq!(5.0.to_any::<A>().has_entry("0".into()), Ok(false));
305        assert_eq!(
306            Nullish::Null.to_any::<A>().has_entry("x".into()),
307            Err(error::nullish_to_object())
308        );
309    }
310
311    /// An entry holding `undefined` reads as none does, `undefined`, and
312    /// `has_entry` is what tells the two apart.
313    #[test]
314    fn entry_holds_undefined() {
315        let o = [("u".into(), Any::undefined())].to_object().to_any::<A>();
316        assert_eq!(o.clone().entry("u".into()), Ok(Any::undefined()));
317        assert_eq!(o.clone().entry("v".into()), Ok(Any::undefined()));
318        assert_eq!(o.clone().has_entry("u".into()), Ok(true));
319        assert_eq!(o.has_entry("v".into()), Ok(false));
320    }
321
322    /// An array's and a string's elements are entries and their `length`
323    /// is not; a function has none, and neither has any other primitive.
324    #[test]
325    fn entry_reads_elements_and_no_length() {
326        let a = [7.0.to_any(), 8.0.to_any()].to_array().to_any::<A>();
327        assert_eq!(a.clone().entry(1.0.to_any()), Ok(8.0.to_any()));
328        assert_eq!(a.clone().entry("1".into()), Ok(8.0.to_any()));
329        assert_eq!(a.clone().entry("2".into()), Ok(Any::undefined()));
330        assert_eq!(a.entry("length".into()), Ok(Any::undefined()));
331        let s: Any<A> = "ab".into();
332        assert_eq!(s.clone().entry(1.0.to_any()), Ok("b".into()));
333        assert_eq!(s.entry("length".into()), Ok(Any::undefined()));
334        assert_eq!(true.to_any::<A>().entry("0".into()), Ok(Any::undefined()));
335        assert_eq!(5.0.to_any::<A>().entry("0".into()), Ok(Any::undefined()));
336    }
337
338    /// `undefined` and `null` are nullish, and nothing else is: not the
339    /// falsy values, which `??` returns unchanged, nor an empty container.
340    #[test]
341    fn is_nullish() {
342        assert_eq!(Any::<A>::undefined(), Nullish::Undefined.to_any());
343        assert!(Any::<A>::undefined().is_nullish());
344        assert!(Nullish::Null.to_any::<A>().is_nullish());
345        for v in [
346            false.to_any::<A>(),
347            0.0.to_any(),
348            f64::NAN.to_any(),
349            "".into(),
350            [].to_array().to_any(),
351            [].to_object().to_any(),
352        ] {
353            assert!(!v.is_nullish());
354        }
355    }
356
357    /// `undefined` alone is absent: `null` and the falsy values are
358    /// arguments that were passed, and come back unchanged.
359    #[test]
360    fn defined() {
361        assert!(Unpacked::from(Any::<A>::undefined()).is_undefined());
362        assert!(!Unpacked::from(Nullish::Null.to_any::<A>()).is_undefined());
363        assert_eq!(Any::<A>::undefined().defined(), None);
364        for v in [
365            Nullish::Null.to_any::<A>(),
366            false.to_any(),
367            0.0.to_any(),
368            "".into(),
369            [].to_array().to_any(),
370            [].to_object().to_any(),
371        ] {
372            assert!(!Unpacked::from(v.clone()).is_undefined());
373            assert_eq!(v.clone().defined(), Some(v));
374        }
375    }
376}