low-luajit 0.4.0

Low level but safe bindings to the LuaJIT C API with no dependencies and no-std support to create Lua modules using Rust.
Documentation
use core::{ffi::CStr, mem::MaybeUninit, ptr};
use crate::{AmountReturned, LuaState, LuaType, ffi, map_ptr};

/// Userdata (full & light) related functions.
impl LuaState {
	/// Returns `true` if the value at the given acceptable `index` is userdata (either full or light), `false` otherwise.
	#[inline]
	#[must_use]
	pub fn is_userdata(&self, index: i32) -> bool {
		// SAFETY: self.0 is always valid.
		unsafe { ffi::lua_isuserdata(self.0, index) != 0 }
	}

	/// Returns `true` if the value at the given acceptable `index` is full userdata, `false` otherwise.
	#[inline]
	#[must_use]
	pub fn is_full_userdata(&self, index: i32) -> bool {
		self.type_of(index) == LuaType::Userdata
	}

	/// Returns `true` if the value at the given acceptable `index` is light userdata, `false` otherwise.
	#[inline]
	#[must_use]
	pub fn is_light_userdata(&self, index: i32) -> bool {
		// SAFETY: self.0 is always valid.
		unsafe { ffi::lua_islightuserdata(self.0, index) != 0 }
	}

	/// Converts the value at the given acceptable `index` to full userdata and returns a mutable reference reference to its block.
	///
	/// # Safety
	///
	/// - The type of the element in the stack must be full userdata.
	/// - `T` must be the same type used when creating this userdata.
	/// - The userdata's value must be valid for `T`.
	/// - There must not be another living reference to this userdata.
	#[inline]
	#[must_use]
	#[allow(clippy::mut_from_ref)]
	pub unsafe fn to_full_userdata<T>(&self, index: i32) -> &mut T {
		// SAFETY: self.0 is always valid and the caller must uphold the safety conditions.
		unsafe { &mut *ffi::lua_touserdata(self.0, index).cast() }
	}

	/// Converts the value at the given acceptable `index` to light userdata and returns it.
	///
	/// Only light and full userdata are accepted, anything else will return a null pointer.
	#[inline]
	#[must_use]
	pub fn to_light_userdata<T>(&self, index: i32) -> *mut T {
		// SAFETY: self.0 is always valid.
		unsafe { ffi::lua_touserdata(self.0, index).cast() }
	}

	/// Allocates a new block the size of `T` containing `value`, pushes onto the stack a new full userdata with the block's address,
	/// and then returns a mutable reference to said block.
	///
	/// Like any other Lua value, this userdata is inevitably collected by the garbage collector to be freed;
	/// but by default this is done by simply deallocating the block instead of properly dropping the value!
	///
	/// *(This won't result in undefined behavior,
	/// but could cause a memory leak for types that allocate dynamically or access some sort of external resource;
	/// more primitive types should not need to be properly dropped.)*
	///
	/// This can be changed by having the `__gc` metamethod (which is triggered before the block is freed) drop the userdata.
	/// For a simple way to do this, see [`LuaState::set_drop_gc_metamethod`].
	#[inline]
	pub fn new_full_userdata<T>(&mut self, value: T) -> &mut T {
		self.new_full_userdata_uninit().write(value)
	}

	/// Behaves exactly as [`LuaState::new_full_userdata`], but uses `T`'s default value as the block's value.
	#[inline]
	pub fn new_full_userdata_default<T: Default>(&mut self) -> &mut T {
		self.new_full_userdata(T::default())
	}

	/// Behaves exactly as [`LuaState::new_full_userdata`], but without actually initializing the block.
	#[inline]
	pub fn new_full_userdata_uninit<T>(&mut self) -> &mut MaybeUninit<T> {
		// SAFETY: self.0 is always valid lua_newuserdata should always return an aligned pointer that isn't accessed anywhere else.
		unsafe { &mut *ffi::lua_newuserdata(self.0, size_of::<T>()).cast() }
	}

	/// Pushes the given `pointer` onto the stack as *light* userdata.
	///
	/// Unlike normal *full* userdata, light userdata is the raw pointer itself.
	#[inline]
	pub fn push_light_userdata<T>(&mut self, pointer: *mut T) {
		// SAFETY: self.0 is always valid and pointer is simply stored, so it doesn't have to be valid.
		unsafe { ffi::lua_pushlightuserdata(self.0, pointer.cast()); }
	}

	/// Checks if there's a *full* userdata of type `userdata_type` at the given index `arg_n` and returns it as full userdata.
	///
	/// Userdata is of type `userdata_type` when it uses the metatable stored in the registry with key `userdata_type`
	/// (see [`LuaState::new_userdata_metatable`]).
	///
	/// If the check fails, [`LuaState::type_error`] is called using `arg_n`.
	///
	/// # Safety
	///
	/// - `T` must be the same type used when creating this userdata.
	/// - The userdata's value must be valid for `T`.
	/// - There must not be another living reference to this userdata.
	///
	/// # Lua Errors
	///
	/// Intentionally errors if the check fails.
	#[inline]
	#[must_use]
	#[allow(clippy::mut_from_ref)]
	pub unsafe fn check_full_userdata<T>(&self, arg_n: i32, userdata_type: &CStr) -> &mut T {
		// SAFETY: self.0 and userdata_type are always valid and the caller must uphold the safety conditions.
		unsafe { &mut *ffi::luaL_checkudata(self.0, arg_n, userdata_type.as_ptr()).cast() }
	}

	/// Checks if there's an userdata (either full or light) of type `userdata_type` at the given index `arg_n` and returns it as light userdata.
	///
	/// Userdata is of type `userdata_type` when it uses the metatable stored in the registry with key `userdata_type`
	/// (see [`LuaState::new_userdata_metatable`]).
	///
	/// If the check fails, [`LuaState::type_error`] is called using `arg_n`.
	///
	/// # Lua Errors
	///
	/// Intentionally errors if the check fails.
	///
	/// Additionally, light userdata that happens to be a null pointer will cause the check to erroneously fail.
	#[inline]
	#[must_use]
	pub fn check_light_userdata<T>(&self, arg_n: i32, userdata_type: &CStr) -> *mut T {
		let ptr = self.test_light_userdata::<T>(arg_n, userdata_type);

		if ptr.is_null() {
			self.type_error(arg_n, userdata_type);
		}

		ptr
	}

	/// Behaves exactly as [`LuaState::check_full_userdata`], but returns `None` instead of erroring if the check fails.
	///
	/// # Safety
	///
	/// - `T` must be the same type used when creating this userdata.
	/// - The userdata's value must be valid for `T`.
	/// - There must not be another living reference to this userdata.
	#[inline]
	#[must_use]
	#[allow(clippy::mut_from_ref)]
	pub unsafe fn test_full_userdata<T>(&self, arg_n: i32, userdata_type: &CStr) -> Option<&mut T> {
		// SAFETY: self.0 and userdata_type are always valid and the caller must uphold the safety conditions.
		let udata = unsafe { ffi::luaL_testudata(self.0, arg_n, userdata_type.as_ptr()).cast::<T>() };
		map_ptr!(udata, unsafe { &mut *udata })
	}

	/// Behaves exactly as [`LuaState::check_full_userdata`], but returns a null pointer instead of erroring if the check fails.
	#[inline]
	#[must_use]
	pub fn test_light_userdata<T>(&self, arg_n: i32, userdata_type: &CStr) -> *mut T {
		// SAFETY: No elements are popped except the two element that are pushed by this function.
		unsafe { self.access_mut(|lua| {
			if self.is_userdata(arg_n) && lua.get_metatable(arg_n) {
				lua.get_userdata_metatable(userdata_type);
				let equals = lua.raw_equal(-1, -2);
				lua.pop(2);

				if equals {
					return lua.to_light_userdata(arg_n)
				}
			}

			ptr::null_mut()
		}) }
	}

	/// Finishes the currently called [`CFunction`], returning the given `pointer` as light userdata to the caller.
	///
	/// The `LuaState` wrapper is consumed to prevent invalidating the `AmountReturned` (the Lua state itself is left intact).
	///
	/// [`CFunction`]: ./type.CFunction.html
	#[inline]
	pub fn return_light_userdata<T>(mut self, pointer: *mut T) -> AmountReturned {
		self.push_light_userdata(pointer);

		// SAFETY: There should always be at least one element in the stack.
		unsafe { AmountReturned::unchecked(1) }
	}

	/// Pushes onto the stack the value in the registry with key `userdata_type`, returning `false`.
	///
	/// If no such value exists yet, a table will be set as the value (before being pushed) and `true` will be returned instead.
	///
	/// This function is used to get the metatable to be set as the metatable of the desired userdata (see [`LuaState::set_metatable`]).
	///
	/// # Examples
	///
	/// ```
	/// use low_luajit::{LuaState, AmountReturned};
	///
	/// extern "C-unwind" fn index_metamethod(lua: LuaState) -> AmountReturned {
	///     // ...
	/// # AmountReturned::NONE
	/// }
	///
	/// extern "C-unwind" fn my_light_userdata_constructor(mut lua: LuaState) -> AmountReturned {
	/// # let ptr = core::ptr::null_mut::<()>();
	///     // ...
	///
	///     // Construct the light userdata.
	///     lua.push_light_userdata(ptr);
	///     if lua.new_userdata_metatable(c"my_light_userdata") {
	///         // Initialize the metatable.
	///         lua.push_cfunction(index_metamethod);
	///         lua.set_field(-2, c"__index");
	///         // ...
	///     }
	///     lua.set_metatable(-2);
	///
	///     lua.return_any(1)
	/// }
	/// ```
	#[inline]
	pub fn new_userdata_metatable(&mut self, userdata_type: &CStr) -> bool {
		// SAFETY: self.0 and userdata_type are always valid.
		unsafe { ffi::luaL_newmetatable(self.0, userdata_type.as_ptr()) != 0 }
	}

	/// Pushes onto the stack the value in the registry with key `userdata_type`, even if `nil`.
	///
	/// This value is usually an userdata's metatable.
	#[inline]
	pub fn get_userdata_metatable(&mut self, userdata_type: &CStr) {
		// SAFETY: self.0 and userdata_type are always valid.
		unsafe { ffi::luaL_getmetatable(self.0, userdata_type.as_ptr()); }
	}

	/* TODO: this would require checking the registry field and then popping it only for the function to push it again. worth it?
	/// Sets the userdata metatable or something as the metatable of the top element
	#[inline]
	pub fn set_userdata_metatable(&mut self, userdata_type: &CStr) {
		//assert!(self.get_top() > 0, "tried to set the metatable for the element at index {index} despite having an empty stack");
		//assert!(self.is_table(-1), "cannot set the metatable for the element at index {index}, as the given metatable is not a table");

		// SAFETY: self.0 and userdata_type are always valid.
		unsafe { ffi::luaL_setmetatable(self.0, userdata_type.as_ptr()); }
	}*/

	/// Sets the `__gc` metamethod of the metatable at the top of the stack to a special destructor metamethod.
	///
	/// This metamethod will drop the full userdata (that is using the metatable) before the garbage collector frees the userdata's block.
	///
	/// This function is usually only necessary when the userdata's type implements `Drop`,
	/// see [`LuaState::new_full_userdata`] for more information.
	///
	/// # Safety
	///
	/// - The type of the value that uses the metatable must be full userdata.
	/// - `T` must be the same type used when creating this userdata.
	/// - The userdata's value must be valid for `T`.
	///
	/// These conditions should always be true when [`LuaState::new_full_userdata`] and [`LuaState::new_userdata_metatable`] are used properly.
	///
	/// # Lua Errors
	///
	/// Indexing the top element may trigger the `__newindex` metamethod which could error.
	///
	/// Additionally, this function will error if for some reason the top element is *not* a table but instead something that can't be indexed.
	///
	/// # Examples
	///
	/// ```
	/// # use low_luajit::{LuaState, AmountReturned};
	/// # extern "C-unwind" fn example(mut lua: LuaState) -> AmountReturned {
	/// lua.new_full_userdata(vec![36.2, 123.456, 0.998]);
	/// if lua.new_userdata_metatable(c"my_full_userdata") {
	///     // SAFETY: The metatable is being used by a valid full userdata of the same type.
	///     unsafe { lua.set_drop_gc_metamethod::<Vec<f64>>() }
	/// }
	/// lua.set_metatable(-2);
	/// # AmountReturned::NONE }
	/// ```
	///
	/// *(For a more complete example on how to implement metatables for userdata, see [`LuaState::new_userdata_metatable`].)*
	#[inline]
	pub unsafe fn set_drop_gc_metamethod<T>(&mut self) {
		extern "C-unwind" fn drop_userdata<T>(lua: LuaState) -> AmountReturned {
			// SAFETY: The caller must uphold the safety conditions.
			unsafe { lua.to_light_userdata::<T>(-1).drop_in_place() };
			AmountReturned::NONE
		}

		self.push_cfunction(drop_userdata::<T>);
		self.set_field(-2, c"__gc");
	}
}