Skip to main content

sim_lib_lang_javascript/objects/
space.rs

1/// Ordinary-object policy composed from `PropertyStore` and `JavascriptHeap`.
2pub struct JavascriptObjects {
3    heap: JavascriptHeap,
4    properties: PropertyStore<ManagedId, JavascriptPropertyKey, JavascriptValue, Accessor>,
5    prototypes: BTreeMap<ManagedId, ManagedHandle>,
6    functions: BTreeMap<ManagedId, JavascriptFunction>,
7    lexical_this: BTreeMap<ManagedId, JavascriptValue>,
8}
9impl JavascriptObjects {
10    /// Create an object graph using the supplied shared heap.
11    pub fn new(heap: JavascriptHeap) -> Self {
12        Self {
13            heap,
14            properties: PropertyStore::new(),
15            prototypes: BTreeMap::new(),
16            functions: BTreeMap::new(),
17            lexical_this: BTreeMap::new(),
18        }
19    }
20    /// Allocate an ordinary object or array. Arrays use the same object and
21    /// descriptor mechanics; only key ordering differs.
22    pub fn ordinary(&mut self) -> Result<ManagedHandle, JavascriptObjectError> {
23        Ok(self
24            .heap
25            .allocate(JavascriptManagedObject::new(JavascriptManagedKind::Object))?)
26    }
27    /// Allocate a closure/function object and connect it to its environment.
28    pub fn function(
29        &mut self,
30        function: JavascriptFunction,
31        lexical_this: Option<JavascriptValue>,
32    ) -> Result<ManagedHandle, JavascriptObjectError> {
33        let h = self.heap.allocate(JavascriptManagedObject::new(
34            JavascriptManagedKind::Function,
35        ))?;
36        for capture in function.captures() {
37            self.heap.connect(h, capture.managed())?;
38        }
39        if let Some(v) = lexical_this {
40            self.lexical_this.insert(h.id(), v);
41        }
42        self.functions.insert(h.id(), function);
43        Ok(h)
44    }
45    /// Select ECMAScript `this` policy without constraining the callable Shape.
46    pub fn call_this(
47        &self,
48        function: ManagedHandle,
49        receiver: JavascriptValue,
50    ) -> Result<JavascriptThis, JavascriptObjectError> {
51        let f = self
52            .functions
53            .get(&function.id())
54            .ok_or(JavascriptObjectError::NotConstructor)?;
55        Ok(if f.policy.kind == JavascriptFunctionKind::Arrow {
56            JavascriptThis::Lexical(
57                self.lexical_this
58                    .get(&function.id())
59                    .cloned()
60                    .unwrap_or(JavascriptValue::Undefined),
61            )
62        } else {
63            JavascriptThis::Dynamic(receiver)
64        })
65    }
66    /// Invoke a codec-lowered body with the captured environment, selected
67    /// receiver, and call arguments. The caller supplies evaluation, keeping
68    /// executable behavior in the direct evaluator rather than this policy.
69    pub fn call<T, E>(
70        &self,
71        function: ManagedHandle,
72        receiver: JavascriptValue,
73        arguments: &[JavascriptValue],
74        body: impl FnOnce(
75            &FunctionPlan,
76            &[CapturedBinding],
77            JavascriptThis,
78            &[JavascriptValue],
79        ) -> Result<T, E>,
80    ) -> Result<T, E>
81    where
82        E: From<JavascriptObjectError>,
83    {
84        let metadata = self
85            .functions
86            .get(&function.id())
87            .ok_or(JavascriptObjectError::NotConstructor)?;
88        let this = self.call_this(function, receiver)?;
89        body(metadata.plan(), metadata.captures(), this, arguments)
90    }
91    /// Allocate the receiver for `new` and link it to the constructor prototype.
92    pub fn construct(
93        &mut self,
94        function: ManagedHandle,
95    ) -> Result<ManagedHandle, JavascriptObjectError> {
96        let f = self
97            .functions
98            .get(&function.id())
99            .ok_or(JavascriptObjectError::NotConstructor)?;
100        if !f.policy.constructable || f.policy.kind == JavascriptFunctionKind::Arrow {
101            return Err(JavascriptObjectError::NotConstructor);
102        }
103        let instance = self.ordinary()?;
104        self.set_prototype(instance, function)?;
105        Ok(instance)
106    }
107    /// Set an ordinary prototype, rejecting cycles.
108    pub fn set_prototype(
109        &mut self,
110        object: ManagedHandle,
111        prototype: ManagedHandle,
112    ) -> Result<(), JavascriptObjectError> {
113        let mut at = Some(prototype);
114        let mut seen = HashSet::new();
115        while let Some(h) = at {
116            if h.id() == object.id() || !seen.insert(h.id()) {
117                return Err(JavascriptObjectError::PrototypeCycle);
118            }
119            at = self.prototypes.get(&h.id()).copied();
120        }
121        self.heap.connect(object, prototype)?;
122        self.prototypes.insert(object.id(), prototype);
123        Ok(())
124    }
125    /// Define an ordinary data property.
126    pub fn define_data(
127        &mut self,
128        object: ManagedHandle,
129        key: JavascriptPropertyKey,
130        value: JavascriptValue,
131        writable: bool,
132        enumerable: bool,
133        configurable: bool,
134    ) -> Result<(), JavascriptObjectError> {
135        self.properties.define(
136            &object.id(),
137            key,
138            Descriptor::Data(DataDescriptor {
139                value,
140                writable,
141                enumerable,
142                configurable,
143            }),
144        )?;
145        Ok(())
146    }
147    /// Define a bounded accessor property.
148    pub fn define_accessor(
149        &mut self,
150        object: ManagedHandle,
151        key: JavascriptPropertyKey,
152        get: Option<JavascriptValue>,
153        set: bool,
154        enumerable: bool,
155        configurable: bool,
156    ) -> Result<(), JavascriptObjectError> {
157        self.properties.define(
158            &object.id(),
159            key,
160            Descriptor::Accessor(AccessorDescriptor {
161                get: Some(Accessor { get, set }),
162                set: Some(Accessor { get: None, set }),
163                enumerable,
164                configurable,
165            }),
166        )?;
167        Ok(())
168    }
169    fn chain(
170        &self,
171        object: ManagedHandle,
172        budget: usize,
173    ) -> Result<Vec<ManagedId>, JavascriptObjectError> {
174        let mut out = Vec::new();
175        let mut at = Some(object);
176        let mut seen = HashSet::new();
177        while let Some(h) = at {
178            if out.len() >= budget {
179                return Err(JavascriptObjectError::Access);
180            }
181            if !seen.insert(h.id()) {
182                break;
183            }
184            out.push(h.id());
185            at = self.prototypes.get(&h.id()).copied();
186        }
187        Ok(out)
188    }
189    /// Read through the prototype chain with the original receiver.
190    pub fn get(
191        &self,
192        object: ManagedHandle,
193        key: &JavascriptPropertyKey,
194        budget: usize,
195    ) -> Result<Option<JavascriptValue>, JavascriptObjectError> {
196        let chain = self.chain(object, budget)?;
197        let mut hooks = Hooks::default();
198        self.properties
199            .get(
200                &chain,
201                &object.id(),
202                key,
203                &mut AccessContext::new(budget),
204                &mut hooks,
205            )
206            .map_err(|_| JavascriptObjectError::Access)
207    }
208    /// Assign through the first descriptor in the prototype chain. Setter
209    /// hooks retain the original receiver and share the traversal budget.
210    pub fn set(
211        &mut self,
212        object: ManagedHandle,
213        key: &JavascriptPropertyKey,
214        value: JavascriptValue,
215        budget: usize,
216    ) -> Result<bool, JavascriptObjectError> {
217        let chain = self.chain(object, budget)?;
218        let mut hooks = Hooks::default();
219        self.properties
220            .set(
221                &chain,
222                &object.id(),
223                key,
224                value,
225                &mut AccessContext::new(budget),
226                &mut hooks,
227            )
228            .map_err(|_| JavascriptObjectError::Access)
229    }
230    /// Delete an own property, respecting configurability.
231    pub fn delete(
232        &mut self,
233        object: ManagedHandle,
234        key: &JavascriptPropertyKey,
235    ) -> Result<bool, JavascriptObjectError> {
236        Ok(self.properties.delete(&object.id(), key)?)
237    }
238    /// ECMAScript ordinary enumeration order: array-index strings ascending,
239    /// then other strings in definition order, then symbols. Private names are hidden.
240    pub fn enumerable_keys(&self, object: ManagedHandle) -> Vec<JavascriptPropertyKey> {
241        let keys = self.properties.own_keys(&object.id(), true);
242        let mut indices = Vec::new();
243        let mut strings = Vec::new();
244        let mut symbols = Vec::new();
245        for key in keys {
246            match &key {
247                JavascriptPropertyKey::String(s) => match s.parse::<u32>() {
248                    Ok(n) if n != u32::MAX && n.to_string() == *s => indices.push((n, key)),
249                    _ => strings.push(key),
250                },
251                JavascriptPropertyKey::Symbol(_) => symbols.push(key),
252                JavascriptPropertyKey::Private { .. } => {}
253            }
254        }
255        indices.sort_by_key(|(n, _)| *n);
256        indices
257            .into_iter()
258            .map(|(_, k)| k)
259            .chain(strings)
260            .chain(symbols)
261            .collect()
262    }
263    /// Validate a declared private name against an instance's constructor brand.
264    pub fn private_key(
265        &self,
266        class: ManagedHandle,
267        name: &str,
268    ) -> Result<JavascriptPropertyKey, JavascriptObjectError> {
269        let f = self
270            .functions
271            .get(&class.id())
272            .ok_or(JavascriptObjectError::PrivateBrand)?;
273        if !f.private_names.iter().any(|n| n == name) {
274            return Err(JavascriptObjectError::PrivateBrand);
275        }
276        Ok(JavascriptPropertyKey::Private {
277            class: class.id(),
278            name: name.into(),
279        })
280    }
281    /// Collect unreachable function/environment/prototype/accessor/array cycles.
282    pub fn collect(
283        &mut self,
284    ) -> Result<Option<sim_lib_gc_tracing::CollectionReceipt>, sim_lib_gc_tracing::CollectionError>
285    {
286        self.heap.collect()
287    }
288    /// Number of live managed identities.
289    pub fn live_len(&self) -> usize {
290        self.heap.live_len()
291    }
292}
293/// Callable browse policy is intentionally neutral: no parameter or return
294/// constraints are synthesized from JavaScript or TypeScript syntax.
295pub const fn javascript_callable_shape_constraints() -> &'static [&'static str] {
296    &[]
297}
298/// Unsupported ordinary-object boundary.
299pub const fn javascript_object_gaps() -> &'static [JavascriptObjectGap] {
300    &[
301        JavascriptObjectGap::ProxyInvariants,
302        JavascriptObjectGap::ExoticObjectInvariants,
303    ]
304}