use core::fmt;
use luau_vm::VmErrorResult;
use luau_vm::native::{NativeCallContext, NativeCallResult};
use luau_vm::thread::{StackGuard, Thread as VmThread};
use crate::error::Error;
use crate::lua::runtime::RuntimeData;
use crate::table::Table;
use crate::thread::Thread;
use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, LuaType, Value, ValueRef};
mod bind;
pub(crate) mod callback;
mod debug;
pub use debug::{CoverageInfo, FunctionInfo};
pub struct Function<'lua> {
handle: FunctionHandle<'lua>,
}
enum FunctionHandle<'lua> {
Rooted(ValueRef<'lua>),
BorrowedStack {
source_thread: &'lua VmThread,
reference_thread: &'lua VmThread,
runtime: &'lua RuntimeData,
index: i32,
pointer: *const (),
},
}
impl<'lua> Function<'lua> {
pub(crate) fn from_ref(
thread: &'lua VmThread,
runtime: &'lua RuntimeData,
reference: i32,
pointer: *const (),
) -> Self {
Self {
handle: FunctionHandle::Rooted(ValueRef::new(thread, runtime, reference, pointer)),
}
}
pub(crate) unsafe fn from_stack(thread: &Thread<'lua>, index: i32) -> VmErrorResult<Self> {
debug_assert_ne!(unsafe { thread.as_vm().is_function(index) }, 0);
Ok(Self {
handle: FunctionHandle::Rooted(ValueRef::from_stack(thread, index)?),
})
}
pub(crate) unsafe fn from_borrowed_stack(thread: &'lua Thread<'_>, index: i32) -> Self {
let source_thread = thread.as_vm();
debug_assert_ne!(unsafe { source_thread.is_function(index) }, 0);
Self {
handle: FunctionHandle::BorrowedStack {
source_thread,
reference_thread: thread.reference_thread(),
runtime: thread.runtime(),
index,
pointer: unsafe { source_thread.to_pointer(index) },
},
}
}
pub fn try_clone(&self) -> Result<Self, Error> {
match &self.handle {
FunctionHandle::Rooted(reference) => Ok(Self {
handle: FunctionHandle::Rooted(reference.try_clone()?),
}),
FunctionHandle::BorrowedStack { .. } => unsafe {
let thread = self.thread();
let vm_thread = thread.as_vm();
let _stack = StackGuard::new(vm_thread);
self.push_to(vm_thread)?;
Self::from_stack(&thread, -1)
.map_err(|exit| Error::from_thread_exit(vm_thread, exit))
},
}
}
pub(crate) fn push_to(&self, target: impl AsRef<VmThread>) -> Result<(), Error> {
match &self.handle {
FunctionHandle::Rooted(reference) => reference.push_to(target),
FunctionHandle::BorrowedStack {
source_thread,
index,
..
} => unsafe {
let source_thread = *source_thread;
let target = target.as_ref();
if !source_thread.same_vm(target) {
return Err(Error::foreign_lua_handle());
}
if *source_thread == *target {
source_thread
.push_value(*index)
.map_err(|exit| Error::from_thread_exit(source_thread, exit))?;
} else {
let _stack = StackGuard::new(source_thread);
source_thread
.push_value(*index)
.map_err(|exit| Error::from_thread_exit(source_thread, exit))?;
source_thread
.x_move(target, 1)
.map_err(|exit| Error::from_thread_exit(source_thread, exit))?;
}
Ok(())
},
}
}
pub(crate) fn thread(&self) -> Thread<'lua> {
Thread::new(self.reference_thread(), self.runtime())
}
fn reference_thread(&self) -> &'lua VmThread {
match &self.handle {
FunctionHandle::Rooted(reference) => reference.reference_thread(),
FunctionHandle::BorrowedStack {
reference_thread, ..
} => reference_thread,
}
}
fn runtime(&self) -> &'lua RuntimeData {
match &self.handle {
FunctionHandle::Rooted(reference) => reference.runtime(),
FunctionHandle::BorrowedStack { runtime, .. } => runtime,
}
}
pub(crate) fn pointer(&self) -> *const () {
match &self.handle {
FunctionHandle::Rooted(reference) => reference.pointer(),
FunctionHandle::BorrowedStack { pointer, .. } => *pointer,
}
}
pub fn to_pointer(&self) -> *const () {
self.pointer()
}
pub fn environment(&self) -> Result<Option<Table<'lua>>, Error> {
unsafe {
let thread = self.thread();
let vm_thread = thread.as_vm();
let _stack = StackGuard::new(vm_thread);
self.push_to(vm_thread)?;
if vm_thread.is_native_function(-1) != 0 {
return Ok(None);
}
vm_thread
.get_fenv(-1)
.map_err(|exit| Error::from_thread_exit(vm_thread, exit))?;
if vm_thread.is_table(-1) == 0 {
return Ok(None);
}
Table::from_stack(&thread, -1)
.map(Some)
.map_err(|exit| Error::from_thread_exit(vm_thread, exit))
}
}
pub fn set_environment(&self, environment: &Table<'_>) -> Result<bool, Error> {
unsafe {
let thread = self.thread();
let vm_thread = thread.as_vm();
let _stack = StackGuard::new(vm_thread);
self.push_to(vm_thread)?;
if vm_thread.is_native_function(-1) != 0 {
return Ok(false);
}
let function_index = vm_thread.get_top();
environment.push_to(&thread)?;
vm_thread.set_safe_env(-1, 0);
let changed = vm_thread.set_fenv(function_index) != 0;
if changed {
self.runtime().invalidate_managed_safe_env();
}
Ok(changed)
}
}
pub fn deep_clone(&self) -> Result<Self, Error> {
unsafe {
let thread = self.thread();
let vm_thread = thread.as_vm();
let _stack = StackGuard::new(vm_thread);
self.push_to(vm_thread)?;
if vm_thread.is_native_function(-1) != 0 {
return self.try_clone();
}
vm_thread
.clone_function(-1)
.map_err(|exit| Error::from_thread_exit(vm_thread, exit))?;
Function::from_stack(&thread, -1)
.map_err(|exit| Error::from_thread_exit(vm_thread, exit))
}
}
pub fn into_thread(self) -> Result<Thread<'lua>, Error> {
unsafe {
let reference_thread = self.thread();
let vm_thread = reference_thread.as_vm();
let _stack = StackGuard::new(vm_thread);
let thread = vm_thread
.new_thread()
.map_err(|exit| Error::from_thread_exit(vm_thread, exit))?;
let thread_pointer = vm_thread.to_pointer(-1);
let thread_reference = vm_thread
.ref_value(-1)
.map_err(|exit| Error::from_thread_exit(vm_thread, exit))?;
let Err(error) = ({
let target = Thread::new(&thread, reference_thread.runtime());
self.push_to(&target)
}) else {
return Ok(Thread::from_ref(
self.reference_thread(),
self.runtime(),
thread,
thread_reference,
thread_pointer,
));
};
vm_thread.unref_value(thread_reference);
Err(error)
}
}
pub 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, luau_vm::thread::LUA_MULTRET, 0)
.map_err(|exit| Error::from_thread_exit(vm_thread, exit))?;
let result_count = vm_thread.get_top() - stack.top();
R::from_stack_multi(&thread, stack.top(), result_count)
}
}
}
impl LuaType for Function<'_> {
fn push_type_key(thread: impl AsRef<VmThread>) -> Result<(), Error> {
let thread = thread.as_ref();
unsafe {
thread
.push_native_function(type_key_nop, None)
.map_err(|exit| Error::from_thread_exit(thread, exit))
}
}
}
fn type_key_nop(_: NativeCallContext<'_>) -> NativeCallResult {
Ok(0)
}
impl<'lua, 'function> IntoLua<'lua> for Function<'function>
where
'function: 'lua,
{
fn into_lua(self, _: crate::LuaRef<'lua>) -> Result<Value<'lua>, Error> {
Ok(Value::Function(self))
}
unsafe fn push_into_stack(self, thread: &Thread<'lua>) -> Result<(), Error> {
self.push_to(thread)
}
}
impl<'lua, 'function> IntoLua<'lua> for &Function<'function>
where
'function: 'lua,
{
fn into_lua(self, _: crate::LuaRef<'lua>) -> Result<Value<'lua>, Error> {
self.try_clone().map(Value::Function)
}
unsafe fn push_into_stack(self, thread: &Thread<'lua>) -> Result<(), Error> {
self.push_to(thread)
}
}
impl<'lua> FromLua<'lua> for Function<'lua> {
fn from_lua(value: Value<'lua>, _: crate::LuaRef<'lua>) -> Result<Self, Error> {
match value {
Value::Function(function) => Ok(function),
value => Err(Error::from_lua_conversion(
value.type_name(),
"function",
None,
)),
}
}
}
impl PartialEq for Function<'_> {
fn eq(&self, other: &Self) -> bool {
(unsafe { self.reference_thread().same_vm(other.reference_thread()) })
&& self.pointer() == other.pointer()
}
}
impl Eq for Function<'_> {}
impl fmt::Debug for Function<'_> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_tuple("Function")
.field(&self.pointer())
.finish()
}
}