field_path 0.4.1

Type-safe, no-std field access and reflection utilities.
Documentation
//! Accessors for mapping source structures to target fields.
//!
//! This module defines the core [`Accessor`] and [`UntypedAccessor`]
//! types, providing a way to abstract over field access within a
//! source structure.
//!
//! Use the [`accessor!`] macro to safely generate accessors. It
//! ensures that the immutable and mutable paths to a field are
//! identical, preventing logical errors.

use core::any::TypeId;

use func_pointers::{MutFn, MutFnPtr, RefFn, RefFnPtr};

pub mod func_pointers;

// For docs.
#[expect(unused_imports)]
use crate::accessor;

/// A typed accessor to a field of type `T` within a source type `S`.
///
/// This holds both immutable and mutable function pointers, which
/// allows retrieving references to the target field inside a source.
///
/// ## Validation
///
/// The [`accessor!`] macro ensures that both immutable and mutable
/// references are pointing towards the same field. Constructing
/// `Accessor` manually may result in mismatches.
///
/// ## Example
///
/// ```
/// use field_path::accessor::Accessor;
///
/// struct Foo { value: i32 }
///
/// fn ref_fn(s: &Foo) -> &i32 { &s.value }
/// fn mut_fn(s: &mut Foo) -> &mut i32 { &mut s.value }
///
/// const FOO_ACC: Accessor<Foo, i32> = Accessor::new(ref_fn, mut_fn);
/// let mut foo = Foo { value: 42 };
///
/// assert_eq!(FOO_ACC.get_ref(&foo), &42);
/// *FOO_ACC.get_mut(&mut foo) = 999;
/// assert_eq!(foo.value, 999);
/// ```
#[derive(Debug)]
pub struct Accessor<S, T> {
    ref_fn: RefFn<S, T>,
    mut_fn: MutFn<S, T>,
}

impl<S, T> Accessor<S, T> {
    #[inline]
    pub const fn new(
        ref_fn: fn(&S) -> &T,
        mut_fn: fn(&mut S) -> &mut T,
    ) -> Self {
        Self { ref_fn, mut_fn }
    }

    /// Get an immutable reference to the target type.
    #[inline]
    pub fn get_ref<'a>(&self, source: &'a S) -> &'a T {
        (self.ref_fn)(source)
    }

    /// Get a mutable reference to the target type.
    #[inline]
    pub fn get_mut<'a>(&self, source: &'a mut S) -> &'a mut T {
        (self.mut_fn)(source)
    }
}

impl<S, T> Accessor<S, T>
where
    S: 'static,
    T: 'static,
{
    /// Erases the type by converting it into an [`UntypedAccessor`].
    #[inline]
    pub const fn untyped(&self) -> UntypedAccessor {
        UntypedAccessor::new(self.ref_fn, self.mut_fn)
    }
}

impl<S, T> Clone for Accessor<S, T> {
    fn clone(&self) -> Self {
        *self
    }
}

impl<S, T> Copy for Accessor<S, T> {}

/// Creates an [`Accessor`] that ensures the fields being accessed are
/// correct for both immutable and mutable reference.
///
/// ## Example
///
/// ```
/// use field_path::accessor;
/// use field_path::accessor::Accessor;
///
/// struct Foo { value: i32 }
///
/// const FOO_ACC: Accessor<Foo, i32> = accessor!(<Foo>::value);
/// let mut foo = Foo { value: 42 };
///
/// assert_eq!(FOO_ACC.get_ref(&foo), &42);
/// *FOO_ACC.get_mut(&mut foo) = 999;
/// assert_eq!(foo.value, 999);
/// ```
#[macro_export]
macro_rules! accessor {
    (<$source:ty>) => {
        $crate::accessor::Accessor::new(
            #[inline(always)]
            |s: &$source| s,
            #[inline(always)]
            |s: &mut $source| s,
        )
    };
    (<$source:ty>$(::$field:tt)+) => {
        $crate::accessor::Accessor::new(
            #[inline(always)]
            |s: &$source| &s$(.$field)+,
            #[inline(always)]
            |s: &mut $source| &mut s$(.$field)+
        )
    };
}

/// A type-erased version of [`Accessor`].
///
/// Stores the raw function pointers along with [`TypeId`]s of both
/// source and target. This allows dynamically checking and restoring
/// the original [`Accessor`].
#[derive(Debug, Clone, Copy)]
pub struct UntypedAccessor {
    ref_fn: RefFnPtr,
    mut_fn: MutFnPtr,
    source_id: TypeId,
    target_id: TypeId,
}

impl UntypedAccessor {
    /// Create a new type-erased accessor from a typed accessor pair.
    #[inline]
    pub const fn new<S, T>(
        ref_fn: fn(&S) -> &T,
        mut_fn: fn(&mut S) -> &mut T,
    ) -> Self
    where
        S: 'static,
        T: 'static,
    {
        Self {
            ref_fn: RefFnPtr::new(ref_fn),
            mut_fn: MutFnPtr::new(mut_fn),
            source_id: TypeId::of::<S>(),
            target_id: TypeId::of::<T>(),
        }
    }

    /// Re-interpret this accessor as a typed [`Accessor`] without
    /// checking [`TypeId`]s. Caller must guarantee type correctness.
    ///
    /// # Safety
    ///
    /// Undefined behavior if `S` and `T` do not match the types used
    /// when constructing this accessor.
    pub const unsafe fn typed_unchecked<S, T>(
        self,
    ) -> Accessor<S, T> {
        unsafe {
            Accessor {
                ref_fn: self.ref_fn.typed_unchecked::<S, T>(),
                mut_fn: self.mut_fn.typed_unchecked::<S, T>(),
            }
        }
    }

    /// Attempt to re-interpret this accessor as a typed [`Accessor`],
    /// returning `None` if the [`TypeId`]s do not match.
    pub fn typed<S, T>(self) -> Option<Accessor<S, T>>
    where
        S: 'static,
        T: 'static,
    {
        if self.source_id == TypeId::of::<S>()
            && self.target_id == TypeId::of::<T>()
        {
            // SAFETY: The types are checked beforehand.
            unsafe {
                return Some(self.typed_unchecked());
            }
        }

        None
    }
}

impl<S, T> From<Accessor<S, T>> for UntypedAccessor
where
    S: 'static,
    T: 'static,
{
    #[inline]
    fn from(accessor: Accessor<S, T>) -> Self {
        accessor.untyped()
    }
}

impl<S, T> From<&Accessor<S, T>> for UntypedAccessor
where
    S: 'static,
    T: 'static,
{
    #[inline]
    fn from(accessor: &Accessor<S, T>) -> Self {
        accessor.untyped()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[derive(Debug, PartialEq)]
    struct Foo {
        x: i32,
        y: f32,
    }

    #[test]
    fn accessor_roundtrip_typed_untyped() {
        let acc: Accessor<Foo, i32> = accessor!(<Foo>::x);

        let untyped = acc.untyped();
        let typed_back: Accessor<Foo, i32> = untyped.typed().unwrap();

        let mut foo = Foo { x: 42, y: 1.5 };

        assert_eq!((typed_back.ref_fn)(&foo), &42);

        let x_mut = (typed_back.mut_fn)(&mut foo);
        *x_mut = 99;

        assert_eq!(foo.x, 99);
    }

    #[test]
    fn untyped_typed_mismatch_fails() {
        let acc: Accessor<Foo, i32> = accessor!(<Foo>::x);

        let untyped = acc.untyped();

        // Mismatched type parameters should return None
        let wrong: Option<Accessor<Foo, f32>> = untyped.typed();
        assert!(wrong.is_none());
    }
}