Skip to main content

nanvm_lib/vm/any/
object_spread.rs

1use crate::vm::{
2    Any, IVm, ObjectSpread, ToAny, ToArray, Unpacked, member_access::non_nullish_receiver,
3};
4
5impl<A: IVm> Any<A> {
6    /// What `{...self}` copies: the source side of ECMAScript's
7    /// `CopyDataProperties`
8    /// (<https://tc39.es/ecma262/#sec-copydataproperties>), the own
9    /// enumerable string-keyed properties of `self` in `[[OwnPropertyKeys]]`
10    /// order. An object gives its entries, each key once with its last
11    /// value; an array its elements keyed `"0"`, `"1"`, โ€ฆ; a string one
12    /// entry per UTF-16 code unit; and a number, a `bigint`, a boolean, a
13    /// function, `null` and `undefined` none.
14    ///
15    /// Never throws, unlike [`Any::get_iterator`]: `{...null}` is `{}`, where
16    /// `Object.entries(null)` and object rest destructuring both throw. So
17    /// an executor may evaluate an object spread whose result goes unused
18    /// and lose nothing.
19    pub fn object_spread(self) -> ObjectSpread<A> {
20        match self.into() {
21            Unpacked::Object(object) => ObjectSpread::Object(object.own_entries().into_iter()),
22            Unpacked::Array(array) => ObjectSpread::Array(array.into_iter().enumerate()),
23            Unpacked::String(string) => ObjectSpread::String(string.into_iter().enumerate()),
24            _ => ObjectSpread::Empty,
25        }
26    }
27
28    /// `Object.entries(self)`: what [`Any::object_spread`] copies, each
29    /// entry a fresh `[key, value]` array, in a fresh array โ€” an object's
30    /// entries, an array's elements and a string's code units under their
31    /// index strings, nothing for a number, a `bigint`, a boolean or a
32    /// function. Unlike the spread it throws on `null` and `undefined`,
33    /// as `ToObject` does (`member_access::non_nullish_receiver`).
34    ///
35    /// The code of the static function no FunctionalScript body defines,
36    /// enumerating keys being what the language has no other way to do:
37    /// `Object.entries` in the runtime value is a closure over it
38    /// (`fjs/js/runtime/README.md`, "How NaNVM finds it").
39    pub fn entries(self) -> Result<Self, Self> {
40        non_nullish_receiver(self.clone())?;
41        Ok(self
42            .object_spread()
43            .map(|(key, value)| [key.to_any(), value].to_array().to_any())
44            .to_array()
45            .to_any())
46    }
47}
48
49#[cfg(test)]
50mod tests {
51    use crate::{
52        naive::Naive,
53        vm::{
54            Any, IVm, Nullish, String, ToAny, ToArray, ToObject, ToString,
55            unstable::{bigint_any, f64_any},
56        },
57    };
58
59    type A = Naive;
60
61    fn s(x: &str) -> Any<A> {
62        x.into()
63    }
64
65    fn key(x: &str) -> String<A> {
66        x.into()
67    }
68
69    fn units(u: &[u16]) -> Any<A> {
70        let s: String<A> = u.iter().copied().to_string();
71        s.to_any()
72    }
73
74    fn spread(v: Any<A>) -> Vec<(String<A>, Any<A>)> {
75        v.object_spread().collect()
76    }
77
78    /// Each key once, its last value at its first position, array-index keys
79    /// first by value: `{b: 1, 10: 2, 2: 3, b: 4}` spreads as
80    /// `{2: 3, 10: 2, b: 4}`.
81    #[test]
82    fn object_entries_in_own_property_order() {
83        let o: Any<A> = [
84            (key("b"), s("1")),
85            (key("10"), s("2")),
86            (key("2"), s("3")),
87            (key("b"), s("4")),
88        ]
89        .to_object()
90        .to_any();
91        assert_eq!(
92            spread(o),
93            [(key("2"), s("3")), (key("10"), s("2")), (key("b"), s("4"))]
94        );
95    }
96
97    #[test]
98    fn array_elements_by_index() {
99        let a: Any<A> = [s("x"), s("y")].to_array().to_any();
100        assert_eq!(spread(a), [(key("0"), s("x")), (key("1"), s("y"))]);
101    }
102
103    /// `{...'๐Ÿ˜€'}` is `{0: '\ud83d', 1: '\ude00'}`: code units, not code
104    /// points, unlike `[...'๐Ÿ˜€']`.
105    #[test]
106    fn string_code_units() {
107        assert_eq!(
108            spread(s("๐Ÿ˜€")),
109            [(key("0"), units(&[0xD83D])), (key("1"), units(&[0xDE00]))]
110        );
111        assert!(spread(s("")).is_empty());
112    }
113
114    /// `{...1}`, `{...null}` and the rest are `{}`, and nothing throws.
115    #[test]
116    fn no_own_properties() {
117        let values: [Any<A>; 7] = [
118            Nullish::Null.to_any(),
119            Any::undefined(),
120            true.to_any(),
121            f64_any(0x3ff0_0000_0000_0000),
122            bigint_any(1),
123            [].to_object().to_any(),
124            A::static_function(|_, _| Ok(Any::undefined()), 0, [].to_array(), None).to_any(),
125        ];
126        for v in values {
127            assert!(spread(v).is_empty());
128        }
129    }
130
131    fn entries(v: Any<A>) -> std::string::String {
132        v.entries().unwrap().to_json().unwrap()
133    }
134
135    /// `Object.entries` lists what the spread copies, in its order, each
136    /// entry a `[key, value]` pair; a value with no own enumerable
137    /// property lists none.
138    #[test]
139    fn entries_pairs() {
140        let o: Any<A> = [(key("b"), s("1")), (key("1"), s("2")), (key("b"), s("3"))]
141            .to_object()
142            .to_any();
143        assert_eq!(entries(o), r#"[["1","2"],["b","3"]]"#);
144        assert_eq!(entries([s("x")].to_array().to_any()), r#"[["0","x"]]"#);
145        assert_eq!(entries(s("ab")), r#"[["0","a"],["1","b"]]"#);
146        let f: Any<A> =
147            A::static_function(|_, _| Ok(Any::undefined()), 0, [].to_array(), None).to_any();
148        for v in [true.to_any(), bigint_any(1), [].to_object().to_any(), f] {
149            assert_eq!(entries(v), "[]");
150        }
151    }
152
153    /// Every call answers fresh arrays, and a nullish receiver throws, as
154    /// `ToObject` does, where `{...null}` is `{}`.
155    #[test]
156    fn entries_fresh_and_nullish() {
157        let a: Any<A> = [s("x")].to_array().to_any();
158        assert_ne!(a.clone().entries(), a.entries());
159        assert!(Nullish::Null.to_any::<A>().entries().is_err());
160        assert!(Any::<A>::undefined().entries().is_err());
161    }
162
163    #[test]
164    fn exact_size_hints() {
165        let mut a = [s("x"), s("y")].to_array().to_any::<A>().object_spread();
166        assert_eq!(a.size_hint(), (2, Some(2)));
167        a.next();
168        assert_eq!(a.len(), 1);
169        assert_eq!(s("๐Ÿ˜€").object_spread().len(), 2);
170        let o: Any<A> = [(key("a"), s("1")), (key("a"), s("2"))]
171            .to_object()
172            .to_any();
173        assert_eq!(o.object_spread().len(), 1);
174        let mut e = Nullish::Null.to_any::<A>().object_spread();
175        assert_eq!(e.size_hint(), (0, Some(0)));
176        assert!(e.next().is_none());
177    }
178}