Skip to content

Commit

Permalink
Support Thread::resume_error call (Luau)
Browse files Browse the repository at this point in the history
This method uses Luau-specific C API extension `lua_resumerror`
that allow to throw an error immediately when resuming a thead.
Closes #500 #513
  • Loading branch information
khvzak committed Jan 28, 2025
1 parent 21d39a0 commit cdd6a99
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 0 deletions.
15 changes: 15 additions & 0 deletions mlua-sys/src/luau/compat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ use super::lauxlib::*;
use super::lua::*;
use super::luacode::*;

pub const LUA_RESUMEERROR: c_int = -1;

unsafe fn compat53_reverse(L: *mut lua_State, mut a: c_int, mut b: c_int) {
while a < b {
lua_pushvalue(L, a);
Expand Down Expand Up @@ -284,6 +286,19 @@ pub unsafe fn lua_resume(L: *mut lua_State, from: *mut lua_State, narg: c_int, n
ret
}

#[inline(always)]
pub unsafe fn lua_resumex(L: *mut lua_State, from: *mut lua_State, narg: c_int, nres: *mut c_int) -> c_int {
let ret = if narg == LUA_RESUMEERROR {
lua_resumeerror(L, from)
} else {
lua_resume_(L, from, narg)
};
if (ret == LUA_OK || ret == LUA_YIELD) && !(nres.is_null()) {
*nres = lua_gettop(L);
}
ret
}

//
// lauxlib ported functions
//
Expand Down
36 changes: 36 additions & 0 deletions src/thread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,14 +173,50 @@ impl Thread {
}
}

/// Resumes execution of this thread, immediately raising an error.
///
/// This is a Luau specific extension.
#[cfg(feature = "luau")]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn resume_error<R>(&self, error: impl crate::IntoLua) -> Result<R>
where
R: FromLuaMulti,
{
let lua = self.0.lua.lock();
match self.status_inner(&lua) {
ThreadStatusInner::New(_) | ThreadStatusInner::Yielded(_) => {}
_ => return Err(Error::CoroutineUnresumable),
};

let state = lua.state();
let thread_state = self.state();
unsafe {
let _sg = StackGuard::new(state);
let _thread_sg = StackGuard::with_top(thread_state, 0);

check_stack(state, 1)?;
error.push_into_stack(&lua)?;
ffi::lua_xmove(state, thread_state, 1);

let (_, nresults) = self.resume_inner(&lua, ffi::LUA_RESUMEERROR)?;
check_stack(state, nresults + 1)?;
ffi::lua_xmove(thread_state, state, nresults);

R::from_stack_multi(nresults, &lua)
}
}

/// Resumes execution of this thread.
///
/// It's similar to `resume()` but leaves `nresults` values on the thread stack.
unsafe fn resume_inner(&self, lua: &RawLua, nargs: c_int) -> Result<(ThreadStatusInner, c_int)> {
let state = lua.state();
let thread_state = self.state();
let mut nresults = 0;
#[cfg(not(feature = "luau"))]
let ret = ffi::lua_resume(thread_state, state, nargs, &mut nresults as *mut c_int);
#[cfg(feature = "luau")]
let ret = ffi::lua_resumex(thread_state, state, nargs, &mut nresults as *mut c_int);
match ret {
ffi::LUA_OK => Ok((ThreadStatusInner::Finished, nresults)),
ffi::LUA_YIELD => Ok((ThreadStatusInner::Yielded(0), nresults)),
Expand Down
25 changes: 25 additions & 0 deletions tests/thread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,3 +227,28 @@ fn test_thread_pointer() -> Result<()> {

Ok(())
}

#[test]
#[cfg(feature = "luau")]
fn test_thread_resume_error() -> Result<()> {
let lua = Lua::new();

let thread = lua
.load(
r#"
coroutine.create(function()
local ok, err = pcall(coroutine.yield, 123)
assert(not ok, "yield should fail")
assert(err == "myerror", "unexpected error: " .. tostring(err))
return "success"
end)
"#,
)
.eval::<Thread>()?;

assert_eq!(thread.resume::<i64>(())?, 123);
let status = thread.resume_error::<String>("myerror").unwrap();
assert_eq!(status, "success");

Ok(())
}

0 comments on commit cdd6a99

Please sign in to comment.