low-luajit 0.3.1

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;
use crate::{AmountReturned, LuaState, ffi};

/// 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.is_userdata(index) && !self.is_light_userdata(index)
	}

	/// 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 {
		// SAFETY: self.0 is always valid and lua_newuserdata should always return a pointer that is valid, aligned and not accessed anywhere else.
		unsafe {
			let ptr = ffi::lua_newuserdata(self.0, size_of::<T>()).cast::<T>();
			ptr.write(value);
			&mut *ptr
		}
	}

	/// 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())
	}

	/// 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 an userdata (either full or light) 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, n_arg: i32, userdata_type: &CStr) -> &mut T {
		unsafe { &mut *ffi::luaL_checkudata(self.0, n_arg, 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.
	#[inline]
	#[must_use]
	pub fn check_light_userdata<T>(&self, n_arg: i32, userdata_type: &CStr) -> *mut T {
		unsafe { ffi::luaL_checkudata(self.0, n_arg, userdata_type.as_ptr()).cast() }
	}

	/// 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 {
		unsafe { ffi::luaL_newmetatable(self.0, userdata_type.as_ptr()) != 0 }
	}

	/// 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
	///
	/// Not every top element can be indexed: for example, if `t` is `nil` this function will error.
	///
	/// Indexing the top element may also trigger the `__newindex` metamethod which could error.
	///
	/// # 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");
	}
}