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};
pub struct Class<'lua> {
reference: ValueRef<'lua>,
class: VmClass,
}
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,
})
}
pub fn try_clone(&self) -> Result<Self, Error> {
Ok(Self {
reference: self.reference.try_clone()?,
class: self.class,
})
}
pub fn equals(&self, other: &Self) -> Result<bool, Error> {
equals(&self.reference, &other.reference)
}
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,
})
}
pub fn try_clone(&self) -> Result<Self, Error> {
Ok(Self {
reference: self.reference.try_clone()?,
object: self.object,
})
}
pub fn equals(&self, other: &Self) -> Result<bool, Error> {
equals(&self.reference, &other.reference)
}
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))
}
}
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);