Repository: sxyazi/yazi
File: yazi-binding/src/macros.rs (Rust)

Code before:
macro_rules! runtime_mut {
	($lua:ident) => {{
		use mlua::ExternalError;
		$lua.app_data_mut::<$crate::Runtime>().ok_or_else(|| "Runtime not found".into_lua_err())
	}};
}

#[macro_export]
macro_rules! runtime_scope {
	($lua:ident, $id:expr, $block:expr) => {{
		let mut f = || {
			let blocking = $crate::runtime_mut!($lua)?.critical_push($id, true);
			let result = (|| $block)();
			$crate::runtime_mut!($lua)?.critical_pop(blocking)?;
			result
		};
		f()
	}};
}

#[macro_export]
macro_rules! deprecate {
	($lua:ident, $tt:tt) => {{
		static WARNED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
		if !WARNED.swap(true, std::sync::atomic::Ordering::Relaxed) {
			let id = match $crate::runtime!($lua)?.current() {
				Some(id) => &format!("`{id}.yazi` plugin"),
				None => "`init.lua` config",
			};
			yazi_macro::emit!(Call(
				yazi_macro::relay!(app:deprecate).with("content", format!($tt, id))
			));
		}
	}};
}

#[macro_export]
macro_rules! cached_field {
	($fields:ident, $key:ident, $value:expr) => {
		$fields.add_field_function_get(stringify!($key), |lua, ud| {
			use mlua::{Error::UserDataDestructed, IntoLua, Lua, Result, Value, Value::UserData};
			ud.borrow_mut_scoped::<Self, Result<Value>>(|me| match paste::paste! { &me.[<v_ $key>] } {
				Some(v) if !v.is_userdata() => Ok(v.clone()),
				Some(v @ UserData(ud)) if !matches!(ud.borrow::<()>(), Err(UserDataDestructed)) => {
					Ok(v.clone())
				}
				_ => {
					let v = ($value as fn(&Lua, &Self) -> Result<_>)(lua, me)?.into_lua(lua)?;
					paste::paste! { me.[<v_ $key>] = Some(v.clone()) };
					Ok(v)
				}

Review comment (bug):
The updated `deprecate!` macro propagates errors from `Runtime::current()` via `?`. Since the main Lua runtime is initialized with an empty frame stack (`Runtime::new()`), calling a deprecated API outside a `runtime_scope!` would now error instead of just emitting a warning. Consider falling back to a safe default (e.g., treat missing current frame as `init`) rather than failing the API call.
```suggestion
			let id = match $crate::runtime!($lua)
				.ok()
				.and_then(|rt| rt.current().ok())
				.unwrap_or("init")
			{
```

Code after:
macro_rules! runtime_mut {
	($lua:ident) => {{
		use mlua::ExternalError;
		$lua.app_data_mut::<$crate::Runtime>().ok_or_else(|| "Runtime not found".into_lua_err())
	}};
}

#[macro_export]
macro_rules! runtime_scope {
	($lua:ident, $id:expr, $block:expr) => {{
		let mut f = || {
			let blocking = $crate::runtime_mut!($lua)?.critical_push($id, true);
			let result = (|| $block)();
			$crate::runtime_mut!($lua)?.critical_pop(blocking)?;
			result
		};
		f()
	}};
}

#[macro_export]
macro_rules! deprecate {
	($lua:ident, $tt:tt) => {{
		static WARNED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
		if !WARNED.swap(true, std::sync::atomic::Ordering::Relaxed) {
			let id = match $crate::runtime!($lua)?.current()? {
				"init" => "`init.lua` config file",
				s => &format!("`{s}.yazi` plugin"),
			};
			yazi_macro::emit!(Call(
				yazi_macro::relay!(app:deprecate).with("content", format!($tt, id))
			));
		}
	}};
}

#[macro_export]
macro_rules! cached_field {
	($fields:ident, $key:ident, $value:expr) => {
		$fields.add_field_function_get(stringify!($key), |lua, ud| {
			use mlua::{Error::UserDataDestructed, IntoLua, Lua, Result, Value, Value::UserData};
			ud.borrow_mut_scoped::<Self, Result<Value>>(|me| match paste::paste! { &me.[<v_ $key>] } {
				Some(v) if !v.is_userdata() => Ok(v.clone()),
				Some(v @ UserData(ud)) if !matches!(ud.borrow::<()>(), Err(UserDataDestructed)) => {
					Ok(v.clone())
				}
				_ => {
					let v = ($value as fn(&Lua, &Self) -> Result<_>)(lua, me)?.into_lua(lua)?;
					paste::paste! { me.[<v_ $key>] = Some(v.clone()) };
					Ok(v)
				}