luau 0.732.0

Safe lifetime-bound Rust embedding API for the Luau runtime
use luau_vm::native::{NativeCallContext, NativeCallResult};
use luau_vm::thread::{StackGuard, upvalue_index};

use super::Function;
use crate::error::Error;
use crate::lua::LuaRef;
use crate::value::IntoLuaMulti;

impl<'lua> Function<'lua> {
    /// Returns a function with the given arguments bound to its first parameters.
    pub fn bind(&self, args: impl IntoLuaMulti<'lua>) -> Result<Self, Error> {
        let thread = self.thread();
        let args = args.into_lua_multi(thread.lua_ref())?;
        let arg_count = args.len();

        if arg_count == 0 {
            return self.try_clone();
        }

        if arg_count + 1 > u8::MAX as usize {
            return Err(Error::BindError);
        }

        unsafe {
            let vm_thread = thread.as_vm();
            let _stack = StackGuard::new(vm_thread);

            thread.reserve_stack(arg_count + 1)?;

            vm_thread
                .push_integer(arg_count as i32)
                .map_err(|exit| Error::from_thread_exit(vm_thread, exit))?;
            for arg in &args {
                arg.push_to(&thread)?;
            }
            vm_thread
                .push_native_closure(bind_args_wrapper, Some("__bind_args"), arg_count as i32 + 1)
                .map_err(|exit| Error::from_thread_exit(vm_thread, exit))?;

            let args_wrapper = Function::from_stack(&thread, -1)
                .map_err(|exit| Error::from_thread_exit(vm_thread, exit))?;
            let lua = LuaRef::new(thread.reference_thread(), thread.runtime());

            lua.load(
                r#"
                local func, args_wrapper = ...
                return function(...)
                    return func(args_wrapper(...))
                end
                "#,
            )
            .set_name("=__luau_bind")
            .call((self.try_clone()?, args_wrapper))
        }
    }
}

fn bind_args_wrapper(context: NativeCallContext<'_>) -> NativeCallResult {
    unsafe {
        let thread = context.raw_thread();
        let arg_count = context.arg_count();
        let bound_count = thread.to_integer(upvalue_index(1)).unwrap_or(0);

        thread.lua_check_stack(bound_count, None)?;

        for index in 0..bound_count {
            thread.push_value(upvalue_index(index + 2))?;
        }

        for _ in 0..bound_count {
            thread.insert(1);
        }

        Ok((arg_count + bound_count) as usize)
    }
}