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