1use std::{collections::HashSet, hash::Hash};
4
5#[derive(Clone, Debug, Eq, PartialEq)]
7pub struct DataDescriptor<V> {
8 pub value: V,
10 pub writable: bool,
12 pub enumerable: bool,
14 pub configurable: bool,
16}
17
18#[derive(Clone, Debug, Eq, PartialEq)]
20pub struct AccessorDescriptor<H> {
21 pub get: Option<H>,
23 pub set: Option<H>,
25 pub enumerable: bool,
27 pub configurable: bool,
29}
30
31#[derive(Clone, Debug, Eq, PartialEq)]
33pub enum Descriptor<V, H> {
34 Data(DataDescriptor<V>),
36 Accessor(AccessorDescriptor<H>),
38}
39
40impl<V, H> Descriptor<V, H> {
41 fn configurable(&self) -> bool {
42 match self {
43 Self::Data(value) => value.configurable,
44 Self::Accessor(value) => value.configurable,
45 }
46 }
47
48 fn enumerable(&self) -> bool {
49 match self {
50 Self::Data(value) => value.enumerable,
51 Self::Accessor(value) => value.enumerable,
52 }
53 }
54}
55
56#[derive(Clone, Copy, Debug, Eq, PartialEq)]
58pub enum DefineError {
59 InvariantViolation,
61}
62
63#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
65pub enum AccessKind {
66 Get,
68 Set,
70}
71
72#[derive(Clone, Copy, Debug, Eq, PartialEq)]
74pub enum AccessError<E> {
75 BudgetExhausted,
77 RecursiveReentry,
79 Hook(E),
81}
82
83pub struct AccessContext<O, K> {
85 remaining: usize,
86 active: HashSet<(AccessKind, O, K)>,
87}
88
89impl<O, K> AccessContext<O, K>
90where
91 O: Clone + Eq + Hash,
92 K: Clone + Eq + Hash,
93{
94 pub fn new(work_budget: usize) -> Self {
96 Self {
97 remaining: work_budget,
98 active: HashSet::new(),
99 }
100 }
101
102 pub fn remaining(&self) -> usize {
104 self.remaining
105 }
106
107 fn charge<E>(&mut self) -> Result<(), AccessError<E>> {
108 self.remaining = self
109 .remaining
110 .checked_sub(1)
111 .ok_or(AccessError::BudgetExhausted)?;
112 Ok(())
113 }
114
115 pub fn intercept<T, E>(
118 &mut self,
119 kind: AccessKind,
120 receiver: &O,
121 key: &K,
122 call: impl FnOnce(&mut Self) -> Result<T, AccessError<E>>,
123 ) -> Result<T, AccessError<E>> {
124 self.charge()?;
125 let signature = (kind, receiver.clone(), key.clone());
126 if !self.active.insert(signature.clone()) {
127 return Err(AccessError::RecursiveReentry);
128 }
129 let result = call(self);
130 self.active.remove(&signature);
131 result
132 }
133}
134
135pub trait PropertyHook<O, K, V, H> {
137 type Error;
139
140 fn get(
143 &mut self,
144 context: &mut AccessContext<O, K>,
145 hook: &H,
146 receiver: &O,
147 key: &K,
148 ) -> Result<V, AccessError<Self::Error>>;
149
150 fn set(
152 &mut self,
153 context: &mut AccessContext<O, K>,
154 hook: &H,
155 receiver: &O,
156 key: &K,
157 value: V,
158 ) -> Result<(), AccessError<Self::Error>>;
159}
160
161#[derive(Clone, Debug)]
162struct OwnProperty<K, V, H> {
163 key: K,
164 descriptor: Descriptor<V, H>,
165}
166
167type PropertyObject<O, K, V, H> = (O, Vec<OwnProperty<K, V, H>>);
168
169#[derive(Clone, Debug, Default)]
175pub struct PropertyStore<O, K, V, H> {
176 objects: Vec<PropertyObject<O, K, V, H>>,
177}
178
179impl<O, K, V, H> PropertyStore<O, K, V, H> {
180 pub const fn new() -> Self {
183 Self {
184 objects: Vec::new(),
185 }
186 }
187}
188
189impl<O, K, V, H> PropertyStore<O, K, V, H>
190where
191 O: Clone + Eq + Hash,
192 K: Clone + Eq + Hash,
193 V: Clone + PartialEq,
194 H: Clone + PartialEq,
195{
196 fn properties(&self, owner: &O) -> Option<&[OwnProperty<K, V, H>]> {
197 self.objects
198 .iter()
199 .find(|(candidate, _)| candidate == owner)
200 .map(|(_, properties)| properties.as_slice())
201 }
202
203 fn properties_mut(&mut self, owner: &O) -> &mut Vec<OwnProperty<K, V, H>> {
204 if let Some(index) = self
205 .objects
206 .iter()
207 .position(|(candidate, _)| candidate == owner)
208 {
209 return &mut self.objects[index].1;
210 }
211 self.objects.push((owner.clone(), Vec::new()));
212 &mut self.objects.last_mut().expect("object was inserted").1
213 }
214
215 pub fn own(&self, owner: &O, key: &K) -> Option<&Descriptor<V, H>> {
217 self.properties(owner)?
218 .iter()
219 .find(|property| &property.key == key)
220 .map(|property| &property.descriptor)
221 }
222
223 pub fn define(
225 &mut self,
226 owner: &O,
227 key: K,
228 descriptor: Descriptor<V, H>,
229 ) -> Result<(), DefineError> {
230 let properties = self.properties_mut(owner);
231 if let Some(property) = properties.iter_mut().find(|property| property.key == key) {
232 if !compatible_redefinition(&property.descriptor, &descriptor) {
233 return Err(DefineError::InvariantViolation);
234 }
235 property.descriptor = descriptor;
236 } else {
237 properties.push(OwnProperty { key, descriptor });
238 }
239 Ok(())
240 }
241
242 pub fn delete(&mut self, owner: &O, key: &K) -> Result<bool, DefineError> {
244 let Some((_, properties)) = self
245 .objects
246 .iter_mut()
247 .find(|(candidate, _)| candidate == owner)
248 else {
249 return Ok(false);
250 };
251 let Some(index) = properties.iter().position(|property| &property.key == key) else {
252 return Ok(false);
253 };
254 if !properties[index].descriptor.configurable() {
255 return Err(DefineError::InvariantViolation);
256 }
257 properties.remove(index);
258 Ok(true)
259 }
260
261 pub fn own_keys(&self, owner: &O, enumerable_only: bool) -> Vec<K> {
264 self.properties(owner)
265 .unwrap_or_default()
266 .iter()
267 .filter(|property| !enumerable_only || property.descriptor.enumerable())
268 .map(|property| property.key.clone())
269 .collect()
270 }
271
272 pub fn get<E>(
275 &self,
276 owners: &[O],
277 receiver: &O,
278 key: &K,
279 context: &mut AccessContext<O, K>,
280 hooks: &mut impl PropertyHook<O, K, V, H, Error = E>,
281 ) -> Result<Option<V>, AccessError<E>> {
282 let mut visited = HashSet::new();
283 for owner in owners {
284 context.charge()?;
285 if !visited.insert(owner.clone()) {
286 continue;
287 }
288 let Some(descriptor) = self.own(owner, key) else {
289 continue;
290 };
291 return match descriptor {
292 Descriptor::Data(data) => Ok(Some(data.value.clone())),
293 Descriptor::Accessor(accessor) => match &accessor.get {
294 Some(hook) => context
295 .intercept(AccessKind::Get, receiver, key, |context| {
296 hooks.get(context, hook, receiver, key)
297 })
298 .map(Some),
299 None => Ok(None),
300 },
301 };
302 }
303 Ok(None)
304 }
305
306 pub fn set<E>(
310 &mut self,
311 owners: &[O],
312 receiver: &O,
313 key: &K,
314 value: V,
315 context: &mut AccessContext<O, K>,
316 hooks: &mut impl PropertyHook<O, K, V, H, Error = E>,
317 ) -> Result<bool, AccessError<E>> {
318 let mut visited = HashSet::new();
319 for owner in owners {
320 context.charge()?;
321 if !visited.insert(owner.clone()) {
322 continue;
323 }
324 let Some(descriptor) = self.own(owner, key).cloned() else {
325 continue;
326 };
327 return match descriptor {
328 Descriptor::Data(data) if data.writable => {
329 let property = self
330 .properties_mut(owner)
331 .iter_mut()
332 .find(|property| &property.key == key)
333 .expect("descriptor was found");
334 let Descriptor::Data(data) = &mut property.descriptor else {
335 unreachable!("cloned descriptor kind remains stable")
336 };
337 data.value = value;
338 Ok(true)
339 }
340 Descriptor::Data(_) => Ok(false),
341 Descriptor::Accessor(accessor) => match accessor.set {
342 Some(hook) => context
343 .intercept(AccessKind::Set, receiver, key, |context| {
344 hooks.set(context, &hook, receiver, key, value)
345 })
346 .map(|()| true),
347 None => Ok(false),
348 },
349 };
350 }
351 Ok(false)
352 }
353}
354
355fn compatible_redefinition<V: PartialEq, H: PartialEq>(
356 current: &Descriptor<V, H>,
357 replacement: &Descriptor<V, H>,
358) -> bool {
359 if current.configurable() {
360 return true;
361 }
362 match (current, replacement) {
363 (Descriptor::Data(old), Descriptor::Data(new)) => {
364 !new.configurable
365 && old.enumerable == new.enumerable
366 && (old.writable || !new.writable)
367 && (old.writable || old.value == new.value)
368 }
369 (Descriptor::Accessor(old), Descriptor::Accessor(new)) => {
370 !new.configurable
371 && old.enumerable == new.enumerable
372 && old.get == new.get
373 && old.set == new.set
374 }
375 _ => false,
376 }
377}