irys 0.3.0

Compile-time trait reflection for Rust
Documentation
use irys::*;
use std::any::Any;
use std::fmt;

trait Resettable: Send + Sync {
    fn reset(&mut self);
    fn value(&self) -> String;
}

struct DisplayCap;
impl Capability for DisplayCap { type Handle = dyn fmt::Display; }

struct DebugCap;
impl Capability for DebugCap { type Handle = dyn fmt::Debug; }

struct ResettableCap;
impl Capability for ResettableCap { type Handle = dyn Resettable; }

register_capability! { slot: 0, cap: DisplayCap, trait_bound: fmt::Display }
register_capability! { slot: 1, cap: DebugCap, trait_bound: fmt::Debug }
register_capability! { slot: 2, cap: ResettableCap, trait_bound: Resettable }

trait DynCloneAny: Send + Sync {
    fn clone_boxed(&self) -> Box<dyn Any + Send + Sync>;
}
impl<T: Clone + Send + Sync + 'static> DynCloneAny for T {
    fn clone_boxed(&self) -> Box<dyn Any + Send + Sync> { Box::new(self.clone()) }
}

struct CloneCap;
impl Capability for CloneCap { type Handle = dyn DynCloneAny; }

register_capability! { slot: 3, cap: CloneCap, trait_bound: DynCloneAny }

#[derive(Debug, Clone)]
struct FullType { name: String, value: i32 }

impl fmt::Display for FullType {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}={}", self.name, self.value)
    }
}

impl Resettable for FullType {
    fn reset(&mut self) { self.value = 0; }
    fn value(&self) -> String { format!("value={}", self.value) }
}

struct OpaqueType;

// === Basic detection ===

#[test]
fn test_has_and_capability_count() {
    let envelope = reflect!(FullType { name: "x".into(), value: 1 });
    assert!(envelope.has::<DisplayCap>());
    assert!(envelope.has::<DebugCap>());
    assert!(envelope.has::<ResettableCap>());
    assert!(envelope.has::<CloneCap>());
    assert_eq!(envelope.capability_count(), 4);

    let opaque = reflect!(OpaqueType);
    assert!(!opaque.has::<DisplayCap>());
    assert_eq!(opaque.capability_count(), 0);
}

#[test]
fn test_get_trait_objects() {
    let envelope = reflect!(FullType { name: "hello".into(), value: 7 });

    let display = envelope.get::<DisplayCap>().unwrap();
    assert_eq!(format!("{}", display), "hello=7");

    let debug = envelope.get::<DebugCap>().unwrap();
    assert!(format!("{:?}", debug).contains("hello"));

    assert!(envelope.get::<ResettableCap>().unwrap().value().contains("7"));
    assert!(reflect!(OpaqueType).get::<DisplayCap>().is_none());
}

#[test]
fn test_get_mut() {
    let mut envelope = reflect!(FullType { name: "x".into(), value: 42 });
    envelope.get_mut::<ResettableCap>().unwrap().reset();
    assert_eq!(envelope.get::<ResettableCap>().unwrap().value(), "value=0");
}

#[test]
fn test_data_and_into_data() {
    let envelope = reflect!(FullType { name: "owned".into(), value: 55 });
    assert_eq!(envelope.data::<FullType>().unwrap().value, 55);
    assert!(envelope.data::<OpaqueType>().is_none());

    let data = envelope.into_data::<FullType>().unwrap();
    assert_eq!(data.name, "owned");
}

// === Ownership model ===

#[test]
fn test_reflect_ref() {
    let value = FullType { name: "alive".into(), value: 10 };
    let envelope_ref = reflect_ref!(&value);

    assert!(envelope_ref.has::<DisplayCap>());
    let display = envelope_ref.get::<DisplayCap>().unwrap();
    assert_eq!(format!("{}", display), "alive=10");

    assert_eq!(value.value, 10); // still available
}

#[test]
fn test_reflect_mut() {
    let mut value = FullType { name: "mut".into(), value: 99 };
    {
        let mut envelope_mut = reflect_mut!(&mut value);
        assert!(envelope_mut.has::<ResettableCap>());
        envelope_mut.get_mut::<ResettableCap>().unwrap().reset();
    }
    assert_eq!(value.value, 0); // mutated in place
}

#[test]
fn test_as_ref_as_mut_conversions() {
    let mut envelope = reflect!(FullType { name: "conv".into(), value: 33 });

    let r = envelope.as_ref();
    assert_eq!(format!("{}", r.get::<DisplayCap>().unwrap()), "conv=33");

    {
        let mut m = envelope.as_mut();
        m.get_mut::<ResettableCap>().unwrap().reset();
    }
    assert_eq!(envelope.data::<FullType>().unwrap().value, 0);
}

// === Registries ===

#[test]
fn test_custom_registry_and_isolation() {
    struct RegA;
    struct RegB;

    struct CapA;
    impl Capability for CapA { type Handle = dyn fmt::Display; }

    struct CapB;
    impl Capability for CapB { type Handle = dyn fmt::Debug; }

    register_capability! { registry: RegA, slot: 0, cap: CapA, trait_bound: fmt::Display }
    register_capability! { registry: RegB, slot: 0, cap: CapB, trait_bound: fmt::Debug }

    // Only probe RegA
    let env = reflect!(FullType { name: "x".into(), value: 1 }, [{ registry: RegA, slots: 0..1 }]);
    assert!(env.has::<CapA>());
    assert!(!env.has::<CapB>());

    // Probe both
    let env = reflect!(FullType { name: "x".into(), value: 1 }, [
        { registry: RegA, slots: 0..1 },
        { registry: RegB, slots: 0..1 },
    ]);
    assert!(env.has::<CapA>());
    assert!(env.has::<CapB>());
}

#[test]
fn test_slot_range_controls_detection() {
    let env = reflect!(FullType { name: "x".into(), value: 0 }, [
        { registry: DefaultRegistry, slots: 0..2 },
    ]);
    assert!(env.has::<DisplayCap>());  // slot 0
    assert!(env.has::<DebugCap>());    // slot 1
    assert!(!env.has::<ResettableCap>()); // slot 2, not probed
}

// === caps() + from_raw() ===

#[test]
fn test_clone_envelope_via_caps_from_raw() {
    let envelope = reflect!(FullType { name: "clone".into(), value: 77 });

    let cloned = envelope.get::<CloneCap>().unwrap().clone_boxed();
    let cloned_env = Envelope::from_raw(cloned, envelope.caps().clone());

    assert_eq!(format!("{}", cloned_env.get::<DisplayCap>().unwrap()), "clone=77");
    assert_eq!(cloned_env.capability_count(), envelope.capability_count());
}

#[test]
fn test_mismatched_map_returns_none() {
    let envelope = reflect!(FullType { name: "a".into(), value: 1 });
    let wrong_data: Box<dyn Any + Send + Sync> = Box::new(42u64);
    let mismatched = Envelope::from_raw(wrong_data, envelope.caps().clone());

    assert!(mismatched.has::<DisplayCap>()); // map says yes
    assert!(mismatched.get::<DisplayCap>().is_none()); // but cast fails gracefully
}