luau 0.732.0

Safe lifetime-bound Rust embedding API for the Luau runtime
use core::fmt;

use luau_common::ByteSlice;
use luau_vm::VmErrorResult;
use luau_vm::internal::api::RawStackAccess;
use luau_vm::internal::class::{Class as VmClass, Object as VmObject};
use luau_vm::thread::{LUA_MULTRET, StackGuard, Thread as VmThread};
use luau_vm::types::{LUA_TCLASS, LUA_TOBJECT};

use crate::error::Error;
use crate::function::Function;
use crate::object::{self, ObjectLike};
use crate::thread::Thread;
use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, Value, ValueRef};

/// A rooted Luau class value.
pub struct Class<'lua> {
    reference: ValueRef<'lua>,
    class: VmClass,
}

/// A rooted instance of a Luau class.
pub struct Object<'lua> {
    reference: ValueRef<'lua>,
    object: VmObject,
}

impl<'lua> Class<'lua> {
    pub(crate) unsafe fn from_stack(thread: &Thread<'lua>, index: i32) -> VmErrorResult<Self> {
        let vm_thread = thread.as_vm();
        debug_assert_eq!(unsafe { vm_thread.type_of(index) }, LUA_TCLASS);
        let class = unsafe {
            vm_thread
                .to_object(index)
                .expect("class stack slot should contain a value")
                .class_value()
        };
        Ok(Self {
            reference: ValueRef::from_stack(thread, index)?,
            class,
        })
    }

    /// Creates another root for this class.
    pub fn try_clone(&self) -> Result<Self, Error> {
        Ok(Self {
            reference: self.reference.try_clone()?,
            class: self.class,
        })
    }

    /// Compares two classes using Luau equality semantics.
    pub fn equals(&self, other: &Self) -> Result<bool, Error> {
        equals(&self.reference, &other.reference)
    }

    /// Returns the stable identity pointer for this class while its VM is alive.
    pub fn to_pointer(&self) -> *const () {
        self.reference.pointer()
    }

    pub(crate) fn push_to(&self, target: impl AsRef<VmThread>) -> Result<(), Error> {
        let target = target.as_ref();
        if !unsafe { target.same_vm(self.reference.reference_thread()) } {
            return Err(Error::foreign_lua_handle());
        }
        unsafe {
            target
                .push_class(self.class)
                .map_err(|exit| Error::from_thread_exit(target, exit))
        }
    }

    pub(crate) fn thread(&self) -> Thread<'lua> {
        self.reference.thread()
    }
}

impl<'lua> Object<'lua> {
    pub(crate) unsafe fn from_stack(thread: &Thread<'lua>, index: i32) -> VmErrorResult<Self> {
        let vm_thread = thread.as_vm();
        debug_assert_eq!(unsafe { vm_thread.type_of(index) }, LUA_TOBJECT);
        let object = unsafe {
            vm_thread
                .to_object(index)
                .expect("object stack slot should contain a value")
                .object_value()
        };
        Ok(Self {
            reference: ValueRef::from_stack(thread, index)?,
            object,
        })
    }

    /// Creates another root for this object.
    pub fn try_clone(&self) -> Result<Self, Error> {
        Ok(Self {
            reference: self.reference.try_clone()?,
            object: self.object,
        })
    }

    /// Compares two objects using Luau equality semantics.
    pub fn equals(&self, other: &Self) -> Result<bool, Error> {
        equals(&self.reference, &other.reference)
    }

    /// Returns the class that owns this object's layout and methods.
    pub fn class(&self) -> Result<Class<'lua>, Error> {
        unsafe {
            let thread = self.thread();
            let vm_thread = thread.as_vm();
            let _stack = StackGuard::new(vm_thread);
            vm_thread
                .push_class(self.object.class())
                .map_err(|exit| Error::from_thread_exit(vm_thread, exit))?;
            Class::from_stack(&thread, -1).map_err(|exit| Error::from_thread_exit(vm_thread, exit))
        }
    }

    /// Returns the stable identity pointer for this object while its VM is alive.
    pub fn to_pointer(&self) -> *const () {
        self.reference.pointer()
    }

    pub(crate) fn push_to(&self, target: impl AsRef<VmThread>) -> Result<(), Error> {
        self.reference.push_to(target)
    }

    pub(crate) fn thread(&self) -> Thread<'lua> {
        self.reference.thread()
    }
}

fn equals(left: &ValueRef<'_>, right: &ValueRef<'_>) -> Result<bool, Error> {
    unsafe {
        let thread = left.thread();
        let vm_thread = thread.as_vm();
        let _stack = StackGuard::new(vm_thread);
        left.push_to(vm_thread)?;
        right.push_to(vm_thread)?;
        vm_thread
            .equal(-2, -1)
            .map(|equal| equal != 0)
            .map_err(|exit| Error::from_thread_exit(vm_thread, exit))
    }
}

macro_rules! impl_object {
    ($type:ident, $variant:ident) => {
        impl PartialEq for $type<'_> {
            fn eq(&self, other: &Self) -> bool {
                self.reference == other.reference
            }
        }

        impl Eq for $type<'_> {}

        impl object::private::Sealed for $type<'_> {}

        impl<'lua> ObjectLike<'lua> for $type<'lua> {
            fn get<V>(&self, key: impl IntoLua<'lua>) -> Result<V, Error>
            where
                V: FromLua<'lua>,
            {
                unsafe {
                    let thread = self.thread();
                    let vm_thread = thread.as_vm();
                    let _stack = StackGuard::new(vm_thread);
                    self.push_to(vm_thread)?;
                    let object_index = vm_thread.get_top();
                    key.push_into_stack(&thread)?;
                    vm_thread
                        .get_table(object_index)
                        .map_err(|exit| Error::from_thread_exit(vm_thread, exit))?;
                    V::from_stack(&thread, -1)
                }
            }

            fn set(&self, key: impl IntoLua<'lua>, value: impl IntoLua<'lua>) -> Result<(), Error> {
                unsafe {
                    let thread = self.thread();
                    let vm_thread = thread.as_vm();
                    let _stack = StackGuard::new(vm_thread);
                    self.push_to(vm_thread)?;
                    let object_index = vm_thread.get_top();
                    key.push_into_stack(&thread)?;
                    value.push_into_stack(&thread)?;
                    self.reference.runtime().invalidate_managed_safe_env();
                    vm_thread
                        .set_table(object_index)
                        .map_err(|exit| Error::from_thread_exit(vm_thread, exit))
                }
            }

            fn call<R>(&self, args: impl IntoLuaMulti<'lua>) -> Result<R, Error>
            where
                R: FromLuaMulti<'lua>,
            {
                unsafe {
                    let thread = self.thread();
                    let vm_thread = thread.as_vm();
                    let stack = StackGuard::new(vm_thread);
                    self.push_to(vm_thread)?;
                    let arg_count = i32::try_from(args.push_into_stack_multi(&thread)?)
                        .map_err(|_| Error::StackError)?;
                    vm_thread
                        .protected_call(arg_count, LUA_MULTRET, 0)
                        .map_err(|exit| Error::from_thread_exit(vm_thread, exit))?;
                    R::from_stack_multi(&thread, stack.top(), vm_thread.get_top() - stack.top())
                }
            }

            fn call_method<R>(&self, name: &str, args: impl IntoLuaMulti<'lua>) -> Result<R, Error>
            where
                R: FromLuaMulti<'lua>,
            {
                let function = self.get::<Function<'lua>>(name)?;
                unsafe {
                    let thread = self.thread();
                    let vm_thread = thread.as_vm();
                    let stack = StackGuard::new(vm_thread);
                    function.push_to(&thread)?;
                    self.push_to(vm_thread)?;
                    let arg_count = args
                        .push_into_stack_multi(&thread)?
                        .checked_add(1)
                        .ok_or(Error::StackError)?;
                    let arg_count = i32::try_from(arg_count).map_err(|_| Error::StackError)?;
                    vm_thread
                        .protected_call(arg_count, LUA_MULTRET, 0)
                        .map_err(|exit| Error::from_thread_exit(vm_thread, exit))?;
                    R::from_stack_multi(&thread, stack.top(), vm_thread.get_top() - stack.top())
                }
            }

            fn call_function<R>(
                &self,
                name: &str,
                args: impl IntoLuaMulti<'lua>,
            ) -> Result<R, Error>
            where
                R: FromLuaMulti<'lua>,
            {
                self.get::<Function<'lua>>(name)?.call(args)
            }

            fn to_string(&self) -> Result<String, Error> {
                unsafe {
                    let thread = self.thread();
                    let vm_thread = thread.as_vm();
                    let _stack = StackGuard::new(vm_thread);
                    self.push_to(vm_thread)?;
                    let bytes = vm_thread
                        .lua_to_string(-1)
                        .map_err(|exit| Error::from_thread_exit(vm_thread, exit))?;
                    bytes.to_str().map(str::to_owned).map_err(|error| {
                        let message = error.to_string();
                        Error::from_lua_conversion("string", "String", Some(message.as_str()))
                    })
                }
            }

            fn to_value(&self) -> Result<Value<'lua>, Error> {
                self.try_clone().map(Value::$variant)
            }
        }

        impl<'lua, 'value> IntoLua<'lua> for $type<'value>
        where
            'value: 'lua,
        {
            fn into_lua(self, _: crate::LuaRef<'lua>) -> Result<Value<'lua>, Error> {
                Ok(Value::$variant(self))
            }

            unsafe fn push_into_stack(self, thread: &Thread<'lua>) -> Result<(), Error> {
                self.push_to(thread)
            }
        }

        impl<'lua, 'value> IntoLua<'lua> for &$type<'value>
        where
            'value: 'lua,
        {
            fn into_lua(self, _: crate::LuaRef<'lua>) -> Result<Value<'lua>, Error> {
                self.try_clone().map(Value::$variant)
            }

            unsafe fn push_into_stack(self, thread: &Thread<'lua>) -> Result<(), Error> {
                self.push_to(thread)
            }
        }

        impl<'lua> FromLua<'lua> for $type<'lua> {
            fn from_lua(value: Value<'lua>, _: crate::LuaRef<'lua>) -> Result<Self, Error> {
                match value {
                    Value::$variant(value) => Ok(value),
                    value => Err(Error::from_lua_conversion(
                        value.type_name(),
                        stringify!($type),
                        None,
                    )),
                }
            }
        }

        impl fmt::Debug for $type<'_> {
            fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
                formatter
                    .debug_tuple(stringify!($type))
                    .field(&self.reference)
                    .finish()
            }
        }
    };
}

impl_object!(Class, Class);
impl_object!(Object, Object);