luau 0.732.0

Safe lifetime-bound Rust embedding API for the Luau runtime
use core::any::TypeId;
use core::marker::PhantomData;

use super::UserdataRegistry;
use super::registry::UserdataRegistryState;

/// One inherent-impl registration emitted by `#[luau::userdata_impl]`.
#[doc(hidden)]
pub struct UserdataRegistration {
    type_id: fn() -> TypeId,
    file: &'static str,
    line: u32,
    column: u32,
    register: for<'lua> fn(&mut UserdataMacroRegistry<'lua>),
}

inventory::collect!(UserdataRegistration);

impl UserdataRegistration {
    /// Creates a static userdata registration descriptor.
    #[doc(hidden)]
    pub const fn new<T: 'static>(
        file: &'static str,
        line: u32,
        column: u32,
        register: for<'lua> fn(&mut UserdataMacroRegistry<'lua>),
    ) -> Self {
        Self {
            type_id: userdata_type_id::<T>,
            file,
            line,
            column,
            register,
        }
    }
}

/// Type-erased access used while dispatching generated userdata registrations.
#[doc(hidden)]
pub struct UserdataMacroRegistry<'lua> {
    type_id: TypeId,
    state: Option<UserdataRegistryState<'lua>>,
}

impl<'lua> UserdataMacroRegistry<'lua> {
    /// Temporarily views this registry as the concrete userdata type selected
    /// by its registration descriptor.
    #[doc(hidden)]
    pub fn with_type<T: 'static>(&mut self, register: impl FnOnce(&mut UserdataRegistry<'lua, T>)) {
        assert_eq!(
            self.type_id,
            TypeId::of::<T>(),
            "userdata registration descriptor used with the wrong type"
        );

        let mut registry = UserdataRegistry {
            state: self.state.take(),
            _marker: PhantomData,
        };
        register(&mut registry);
        self.state = registry.state.take();
    }
}

fn userdata_type_id<T: 'static>() -> TypeId {
    TypeId::of::<T>()
}

/// Applies every generated inherent impl for `T` in deterministic source
/// order.
#[doc(hidden)]
pub fn register_userdata_impls<T: 'static>(registry: &mut UserdataRegistry<'_, T>) {
    let type_id = TypeId::of::<T>();
    let mut registrations: Vec<_> = inventory::iter::<UserdataRegistration>
        .into_iter()
        .filter(|registration| (registration.type_id)() == type_id)
        .collect();
    registrations.sort_unstable_by(|left, right| {
        (left.file, left.line, left.column).cmp(&(right.file, right.line, right.column))
    });

    if registrations.is_empty() {
        return;
    }

    let mut erased = UserdataMacroRegistry {
        type_id,
        state: registry.state.take(),
    };
    for registration in registrations {
        (registration.register)(&mut erased);
    }
    registry.state = erased.state.take();
}