use core::any::TypeId;
#[expect(unused_imports)]
use crate::accessor;
#[derive(Debug)]
pub struct Accessor<S, T> {
ref_fn: fn(&S) -> &T,
mut_fn: fn(&mut S) -> &mut 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 }
}
#[inline]
pub fn get_ref<'a>(&self, source: &'a S) -> &'a T {
(self.ref_fn)(source)
}
#[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,
{
#[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> {}
#[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)+
)
};
}
#[derive(Debug, Clone, Copy)]
pub struct UntypedAccessor {
ref_fn: *const (),
mut_fn: *const (),
source_id: TypeId,
target_id: TypeId,
}
impl UntypedAccessor {
#[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: ref_fn as *const (),
mut_fn: mut_fn as *const (),
source_id: TypeId::of::<S>(),
target_id: TypeId::of::<T>(),
}
}
pub const unsafe fn typed_unchecked<S, T>(
self,
) -> Accessor<S, T> {
unsafe {
Accessor {
ref_fn: core::mem::transmute::<*const (), fn(&S) -> &T>(
self.ref_fn,
),
mut_fn: core::mem::transmute::<
*const (),
fn(&mut S) -> &mut T,
>(self.mut_fn),
}
}
}
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>()
{
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();
let wrong: Option<Accessor<Foo, f32>> = untyped.typed();
assert!(wrong.is_none());
}
}