Skip to main content

conjure_cp_core/ast/
moo.rs

1// NOTE
2//
3// we use a wrapper type over Arc, instead of just using Arc, so that we can implement traits on it
4// (e.g. Uniplate, Serialize).
5//
6// As we are just using Arc for copy on write, not shared ownership, it is safe to break shared
7// ownership in Moo's Uniplate implementation, by calling Arc::make_mut and Arc::new on modified
8// values. In general, this is not safe for all Rc/Arc types, e.g. those that use Cell / RefCell
9// internally.
10//
11// ~niklasdewally 13/08/25
12
13use funcmap::{FuncMap, TryFuncMap};
14use polyquine::Quine;
15use proc_macro2::TokenStream;
16use serde::{Deserialize, Deserializer, Serialize, Serializer};
17use serde_with::de::DeserializeAsWrap;
18use serde_with::ser::SerializeAsWrap;
19use serde_with::{DeserializeAs, SerializeAs};
20use std::hash::{Hash, Hasher};
21use std::ops::DerefMut;
22use std::{collections::VecDeque, fmt::Display, ops::Deref, sync::Arc};
23use uniplate::{
24    Biplate, Tree, Uniplate,
25    impl_helpers::{transmute_if_same_type, try_transmute_if_same_type},
26    spez::try_biplate_to,
27};
28
29/// A clone-on-write, reference counted pointer to an AST type.
30///
31/// Cloning values of this type will not clone the underlying value until it is modified, e.g.,
32/// with [`Moo::make_mut`].
33///
34/// Unlike `Rc` and `Arc`, trait implementations on this type do not need to preserve shared
35/// ownership - that is, two pointers that used to point to the same value may not do so after
36/// calling a trait method on them. In particular, calling Uniplate methods may cause a
37/// clone-on-write to occur.
38///
39/// **Note:** like Box` and `Rc`, methods on `Moo` are all associated functions, which means you
40/// have to call them as, e.g. `Moo::make_mut(&value)` instead of `value.make_mut()`. This is so
41/// that there are no conflicts with the inner type `T`, which this type dereferences to.
42#[derive(PartialEq, Eq, Clone, Debug)]
43pub struct Moo<T> {
44    inner: Arc<T>,
45}
46
47impl<T: Quine> Quine for Moo<T> {
48    fn ctor_tokens(&self) -> TokenStream {
49        let inner = self.inner.as_ref().ctor_tokens();
50        quote::quote! { ::conjure_cp::ast::Moo::new(#inner) }
51    }
52}
53
54impl<T> Moo<T> {
55    /// Constructs a new `Moo<T>`.
56    pub fn new(value: T) -> Moo<T> {
57        Moo {
58            inner: Arc::new(value),
59        }
60    }
61}
62
63impl<T: Clone> Moo<T> {
64    /// Makes a mutable reference into the given `Moo`.
65    ///
66    /// If there are other `Moo` pointers to the same allocation, then `make_mut` will `clone` the
67    /// inner value to a new allocation to ensure unique ownership. This is also referred to as
68    /// clone-on-write.
69    pub fn make_mut(this: &mut Moo<T>) -> &mut T {
70        Arc::make_mut(&mut this.inner)
71    }
72
73    /// If we have the only reference to T then unwrap it. Otherwise, clone T and return the clone.
74    ///
75    /// Assuming moo_t is of type `Moo<T>`, this function is functionally equivalent to
76    /// `(*moo_t).clone()`, but will avoid cloning the inner value where possible.
77    pub fn unwrap_or_clone(this: Moo<T>) -> T {
78        Arc::unwrap_or_clone(this.inner)
79    }
80}
81
82impl<T> AsRef<T> for Moo<T> {
83    fn as_ref(&self) -> &T {
84        self.inner.as_ref()
85    }
86}
87
88impl<T> Deref for Moo<T> {
89    type Target = T;
90
91    fn deref(&self) -> &Self::Target {
92        self.inner.deref()
93    }
94}
95
96impl<T: Clone> DerefMut for Moo<T> {
97    fn deref_mut(&mut self) -> &mut Self::Target {
98        Moo::make_mut(self)
99    }
100}
101
102impl<T> Uniplate for Moo<T>
103where
104    T: Uniplate,
105{
106    fn uniplate(
107        &self,
108    ) -> (
109        uniplate::Tree<Self>,
110        Box<dyn Fn(uniplate::Tree<Self>) -> Self>,
111    ) {
112        let this = Moo::clone(self);
113
114        // do not need to preserve shared ownership, so treat this identically to values of the
115        // inner type.
116        let (tree, ctx) = try_biplate_to!((**self).clone(), Moo<T>);
117        (
118            Tree::Many(VecDeque::from([tree.clone()])),
119            Box::new(move |x| {
120                let Tree::Many(trees) = x else { panic!() };
121                let new_tree = trees.into_iter().next().unwrap();
122                let mut this = Moo::clone(&this);
123
124                // Only update the pointer with the new value if the value has changed. Without
125                // this check, writing to the pointer might trigger a clone on write, even
126                // though the value inside the pointer remained the same.
127                if new_tree != tree {
128                    let this = Moo::make_mut(&mut this);
129                    *this = ctx(new_tree)
130                }
131
132                this
133            }),
134        )
135    }
136}
137
138impl<To, U> Biplate<To> for Moo<U>
139where
140    To: Uniplate,
141    U: Uniplate + Biplate<To>,
142{
143    fn biplate(&self) -> (Tree<To>, Box<dyn Fn(Tree<To>) -> Self>) {
144        if let Some(self_as_to) = transmute_if_same_type::<Self, To>(self) {
145            // To = Self -> return self
146            let tree = Tree::One(self_as_to.clone());
147            let ctx = Box::new(move |x| {
148                let Tree::One(self_as_to) = x else { panic!() };
149
150                let self_as_self = try_transmute_if_same_type::<To, Self>(&self_as_to);
151
152                Moo::clone(self_as_self)
153            });
154
155            (tree, ctx)
156        } else {
157            // To != Self -> return children of type To
158
159            let this = Moo::clone(self);
160
161            // Do not need to preserve shared ownership, so treat this identically to values of the
162            // inner type.
163            let (tree, ctx) = try_biplate_to!((**self).clone(), To);
164            (
165                Tree::Many(VecDeque::from([tree.clone()])),
166                Box::new(move |x| {
167                    let Tree::Many(trees) = x else { panic!() };
168                    let new_tree = trees.into_iter().next().unwrap();
169                    let mut this = Moo::clone(&this);
170
171                    // Only update the pointer with the new value if the value has changed. Without
172                    // this check, writing to the pointer might trigger a clone on write, even
173                    // though the value inside the pointer remained the same.
174                    if new_tree != tree {
175                        let this = Moo::make_mut(&mut this);
176                        *this = ctx(new_tree)
177                    }
178
179                    this
180                }),
181            )
182        }
183    }
184
185    /// Counts children through the pointer, without cloning the pointee.
186    ///
187    /// The default implementation materialises `children_bi`, which goes via [`Biplate::biplate`]
188    /// and therefore clones the whole inner value. Payload syncs call this once per tree level, so
189    /// the default turns a single rewrite under a wide node (e.g. `or` over a large matrix) into
190    /// O(n) work, and a full rewrite pass into O(n^2).
191    fn children_bi_count(&self) -> usize {
192        if std::any::TypeId::of::<Self>() == std::any::TypeId::of::<To>() {
193            // Biplate<T> for T treats the value as its only child.
194            return 1;
195        }
196        <U as Biplate<To>>::children_bi_count(&**self)
197    }
198
199    /// Replaces a child through the pointer, cloning the pointee only when it is shared.
200    ///
201    /// Mirrors [`Biplate::children_bi_count`] above: the default implementation clones the inner
202    /// value twice (once to list children, once to rebuild) and structurally compares the old and
203    /// new trees.
204    fn try_replace_child_at_bi(&mut self, index: usize, child: To) -> bool {
205        if std::any::TypeId::of::<Self>() == std::any::TypeId::of::<To>() {
206            if index != 0 {
207                return false;
208            }
209            // SAFETY: TypeId equality means `Self` and `To` are the same type.
210            unsafe {
211                let child_as_self = std::mem::transmute_copy::<To, Self>(&child);
212                std::mem::forget(child);
213                *self = child_as_self;
214            }
215            return true;
216        }
217        if std::any::TypeId::of::<U>() == std::any::TypeId::of::<To>() {
218            if index != 0 {
219                return false;
220            }
221            // The child is the whole pointee, not a child inside it. Replace the pointer
222            // directly: make_mut would clone the old shared value only to discard it.
223            // SAFETY: TypeId equality means U and To are the same type.
224            let value = unsafe { std::mem::transmute_copy::<To, U>(&child) };
225            std::mem::forget(child);
226            *self = Moo::new(value);
227            return true;
228        }
229        <U as Biplate<To>>::try_replace_child_at_bi(Moo::make_mut(self), index, child)
230    }
231}
232
233impl<'de, T: Deserialize<'de>> Deserialize<'de> for Moo<T> {
234    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
235    where
236        D: serde::Deserializer<'de>,
237    {
238        Ok(Moo::new(T::deserialize(deserializer)?))
239    }
240}
241
242impl<T: Serialize> Serialize for Moo<T> {
243    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
244    where
245        S: serde::Serializer,
246    {
247        T::serialize(&**self, serializer)
248    }
249}
250
251impl<T: Display> Display for Moo<T> {
252    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
253        (**self).fmt(f)
254    }
255}
256
257impl<T: Hash> Hash for Moo<T> {
258    fn hash<H: Hasher>(&self, state: &mut H) {
259        (**self).hash(state);
260    }
261}
262
263impl<T> From<T> for Moo<T> {
264    fn from(value: T) -> Self {
265        Moo::new(value)
266    }
267}
268
269impl<A, B> FuncMap<A, B> for Moo<A>
270where
271    A: Clone,
272{
273    type Output = Moo<B>;
274
275    fn func_map<F>(self, mut f: F) -> Self::Output
276    where
277        F: FnMut(A) -> B,
278    {
279        let inner = Moo::unwrap_or_clone(self);
280        Moo::new(f(inner))
281    }
282}
283
284impl<A, B> TryFuncMap<A, B> for Moo<A>
285where
286    A: Clone,
287{
288    type Output = Moo<B>;
289
290    fn try_func_map<E, F>(self, mut f: F) -> Result<Self::Output, E>
291    where
292        F: FnMut(A) -> Result<B, E>,
293    {
294        let inner = Moo::unwrap_or_clone(self);
295        let res = f(inner)?;
296        Ok(Moo::new(res))
297    }
298}
299
300impl<T, As> SerializeAs<Moo<T>> for Moo<As>
301where
302    As: SerializeAs<T>,
303{
304    fn serialize_as<S>(source: &Moo<T>, serializer: S) -> Result<S::Ok, S::Error>
305    where
306        S: Serializer,
307    {
308        let wrap = SerializeAsWrap::<T, As>::new(&**source);
309        wrap.serialize(serializer)
310    }
311}
312
313impl<'de, T, As> DeserializeAs<'de, Moo<T>> for Moo<As>
314where
315    As: DeserializeAs<'de, T>,
316{
317    fn deserialize_as<D>(deserializer: D) -> Result<Moo<T>, D::Error>
318    where
319        D: Deserializer<'de>,
320    {
321        let wrap = DeserializeAsWrap::<T, As>::deserialize(deserializer)?;
322        Ok(Moo::new(wrap.into_inner()))
323    }
324}
325
326#[cfg(test)]
327mod replacement_tests {
328    use super::*;
329    use std::sync::atomic::{AtomicUsize, Ordering};
330
331    static CLONES: AtomicUsize = AtomicUsize::new(0);
332
333    #[derive(Debug, PartialEq, Eq)]
334    struct CloneProbe(i32);
335
336    impl Clone for CloneProbe {
337        fn clone(&self) -> Self {
338            CLONES.fetch_add(1, Ordering::Relaxed);
339            Self(self.0)
340        }
341    }
342
343    impl Uniplate for CloneProbe {
344        fn uniplate(&self) -> (Tree<Self>, Box<dyn Fn(Tree<Self>) -> Self>) {
345            let value = self.0;
346            (Tree::Zero, Box::new(move |_| Self(value)))
347        }
348    }
349
350    impl Biplate<CloneProbe> for CloneProbe {
351        fn biplate(&self) -> (Tree<Self>, Box<dyn Fn(Tree<Self>) -> Self>) {
352            (
353                Tree::One(self.clone()),
354                Box::new(|tree| {
355                    let Tree::One(value) = tree else {
356                        panic!("expected one child")
357                    };
358                    value
359                }),
360            )
361        }
362    }
363
364    #[test]
365    fn replacing_shared_pointee_does_not_clone_the_discarded_value() {
366        let mut value = Moo::new(CloneProbe(1));
367        let shared = value.clone();
368        CLONES.store(0, Ordering::Relaxed);
369        assert!(!value.try_replace_child_at_bi(1, CloneProbe(2)));
370        assert_eq!(value.0, 1);
371        assert!(value.try_replace_child_at_bi(0, CloneProbe(2)));
372        assert_eq!(value.0, 2);
373        assert_eq!(shared.0, 1);
374        assert_eq!(CLONES.load(Ordering::Relaxed), 0);
375    }
376}