oors 0.9.0

Adding cross-crate inheritance features to Rust structs
Documentation
/*
 * Copyright (c) 2025 eligamii.
 * Licenced under the MIT licence. See the LICENCE file in the project for full licence information
 */

#![deny(missing_docs)]
#![deny(warnings)]
#![doc = include_str!("../README.md")]

#![cfg_attr(feature = "nightly", feature(proc_macro_hygiene, decl_macro))]

pub mod objects;
mod macros;

pub use oors_macros::*;
pub use objects::*;

#[allow(unused_imports)]
pub use macros::*;



#[cfg(test)]
mod tests {
    use oors_macros::object;
    use crate as oors; // For macros to work here
    use crate::*;

    #[allow(unused)]
    mod multi_mod {
        use super::*;

        mod mod1 {
            use super::*;
            #[object]
            pub struct FromMod1;

        }

        mod mod2 {
            #[obj_use]
            use crate::tests::multi_mod::mod1::FromMod1;

            use super::*;
            #[object(parent = FromMod1)]
            pub struct FromMod2;
        }

        mod mod3 {
            #[obj_use]
            use crate::tests::multi_mod::mod2::FromMod2;
            use super::*;

            // Without feature `nightly`, we also have to import FromMod1
            // as the __oors_recursive_impl_*!() macros are not hygienic on stable,
            // and we cannot infer the absolute of a struct during #[object] generation
            #[cfg(not(feature = "nightly"))]
            use crate::tests::multi_mod::mod1::FromMod1;

            #[object(parent = FromMod2)]
            pub struct FromMod3;
        }
    }

    #[object]
    #[derive(Debug, Clone)]
    pub struct ClassA {
        /// This is some documentation that will also appear in
        /// `ObjectBuilder::<IsA<ClassA>>::with_val1()` and `Typed<IsA<ClassA>>::val1_*()`
        /// as is
        pub val1: String,
        pub val2: u64,
    }

    #[object(parent = ClassA)]
    #[derive(Debug, Clone)]
    pub struct ClassB {
        val3: u16,
    }


    #[object(parent = ClassB)]
    #[derive(Debug)]
    pub struct ClassC {
        pub val4: u64,
    }

    #[object(parent = ClassB)]
    pub struct ClassC2 {
        pub val4: String
    }

    #[object(parent = ClassB)]
    pub struct ClassC3; // This a tuple with a unique field of type ClassB

    #[object(parent = ClassC3)]
    pub struct ClassD(u32); // This is a ClassD(ClassC3, u32)


    #[object_impl]
    impl ClassD {
        #[allow(unused)]
        fn new() -> Typed<Self> {
            let typed = Typed::new(
                Self(
                    ClassC3(
                        ClassB::builder()
                            .with_val1("val".into())
                            .with_val2(0)
                            .with_val3(665)
                            .build_as_value()
                    ),
                    5
                )
            );

            assert_eq!(typed.val3(), &665); // We still can access parents fields like on normal fields
            assert_eq!(typed.0.0.val3(), &665); // The only way to parent tuple structs fields (.0.0)

            typed

        }
    }



    // This produce an infinite sized struct
    //#[object(parent = ClassE)]
    //struct ClassE;

    #[object_impl(pub(self))]
    impl ClassC {
        fn new() -> Typed<ClassC> {
            ClassC::builder()
                // The documentation for with_val1() (and val1_*()) should include the documentation from
                // ClassA.val1 (line 20)
                .with_val1("val1".to_string())
                .with_val2(2)
                .with_val3(3)
                .with_val4(4)
                .build()
        }

        fn print_val4(&self) {
            println!("{}", self.val4());
        }
    }


    struct T;
    // recursively impls ClassA, ClassB, ClassC3 and ClassD without getting
    // into orphan rule territory/use T (so even works cross crate)
    __oors_recursive_impl_ClassD!(T);

    const fn impl_t<T0: IsA<T1> + IsA<T2> + IsA<T3> + IsA<T4>, T1, T2, T3, T4>() {}
    const _: fn() = || {
        impl_t::<T, ClassA, ClassB, ClassC3, ClassD>();
    };

    #[test]
    fn test_memery_leaks_and_insert() {
        use std::alloc::System;
        use stats_alloc::{Region, StatsAlloc, INSTRUMENTED_SYSTEM};
        // insert_object uses a custom recursive drop logic to drop the old
        // "partially uninitialized" object before inserting a new one
        // See Object::uninit_selective_drop() and oors-macros::object line 225+

        #[global_allocator]
        static GLOBAL: &StatsAlloc<System> = &INSTRUMENTED_SYSTEM;

        let region = Region::new(&GLOBAL);

        // This tests Object::selective_uninit_drop(...) and the __*Builder::with_*() drop logic
        // See the implementations in oors_macros::object
        for _ in 0..100 {
            let class_c = ClassC::builder()
                .with_val1("ClassA's val1".into())
                .with_val2(2)
                .insert_typed(ClassC::new()) // Should drop val1 and val2
                .insert_typed(ClassC::new()) // Should drop the last ClassC
                .with_val4(7)
                .build();

            assert_eq!(class_c.val1(), "val1"); // |
            assert_eq!(class_c.val2(), &2);     // |- from ClassC::new()
            assert_eq!(class_c.val3(), &3);     // |
            assert_eq!(class_c.val4(), &7);
        }

        let after = region.change();
        println!("{:?}", after);
        assert!(after.allocations <= after.deallocations);
    }

    #[test]
    fn test_cast() {
        let mut class_c = ClassC::new(); // from the #[object_impl] above (line 44)
        let class_b = class_c.try_cast_mut::<ClassB>().unwrap();

        // Casting to parents does not need any runtime check as ClassB IsA<ClassA>
        let class_a = class_b.upcast_mut::<ClassA>();

        class_a.try_cast_ref::<ClassC2>().expect_err("ClassC is not a ClassC2");
        let class_a_bis = class_b.try_cast_ref::<ClassA>().expect("This is a ClassC");

        let _unit = class_a_bis.try_cast_ref::<()>().expect("Everything is an unit"); // Emptiness is in everything


        assert_eq!(class_a_bis.val1(), "val1");
    }

    #[test]
    fn test_builder() {
        let class_c = ClassC::builder()
            .with_val1("String".into())
            .with_val2(2)
            .with_val3(3)
            .with_val4(4)
            .build();

        println!("{:#?}", &class_c);

        assert_eq!(class_c.val1(), "String");
        assert_eq!(*class_c.val2(), 2);
        assert_eq!(*class_c.val3(), 3);
        assert_eq!(*class_c.val4(), 4);

        let _class_c_uninit = ClassB::builder()
            .with_val2(2)
            .with_val3(3)
            .try_build()
            .expect_err("Should not be considered totally initialized");

        let class_c2_builder = ClassC2::builder()
            .insert_typed(
                ClassA::builder()
                    .with_val1("val1".into())
                    .with_val2(2)
                    .build()
            );

        assert!(!class_c2_builder.is_init(), "We did not initialize val3 and val4");

        _ = class_c2_builder
            .with_val3(3)
            .with_val4("v".into())
            .build();
    }
}