luaur_rt/function.rs
1//! The [`Function`] handle. Mirrors `mlua::Function`.
2
3use crate::error::Result;
4use crate::multi::MultiValue;
5use crate::state::{Lua, LuaRef};
6use crate::sync::{MaybeSend, NotSync, XRc, NOT_SYNC};
7use crate::sys::*;
8use crate::traits::{FromLuaMulti, IntoLuaMulti};
9
10/// A handle to a callable Lua value (a Lua closure or a Rust function).
11///
12/// Mirrors `mlua::Function`. Under the `send` feature it is `Send` but never
13/// `Sync` — see [`crate::sync::NotSync`].
14#[derive(Clone)]
15pub struct Function {
16 pub(crate) reference: XRc<LuaRef>,
17 pub(crate) _not_sync: NotSync,
18}
19
20impl Function {
21 pub(crate) fn from_ref(reference: LuaRef) -> Function {
22 Function {
23 reference: XRc::new(reference),
24 _not_sync: NOT_SYNC,
25 }
26 }
27
28 pub(crate) unsafe fn push_to_stack(&self) {
29 self.reference.push();
30 }
31
32 /// The owning [`Lua`].
33 pub fn lua(&self) -> Lua {
34 self.reference.lua()
35 }
36
37 /// Call the function with `args`, converting the results to `R`.
38 ///
39 /// Mirrors `mlua::Function::call`. Runs under `lua_pcall`, so a Lua runtime
40 /// error (or a Rust callback returning `Err`) becomes `Err(Error)` rather
41 /// than unwinding.
42 pub fn call<R: FromLuaMulti>(&self, args: impl IntoLuaMulti) -> Result<R> {
43 let lua = self.lua();
44 let state = lua.state();
45 let args: MultiValue = args.into_lua_multi(&lua)?;
46
47 unsafe {
48 let base = lua_gettop(state);
49 let nargs = args.len() as c_int;
50 // Guard against pushing more values than the Lua stack can hold:
51 // an unprotected overflow would abort the VM. We need room for the
52 // function + all arguments (+1 slack for the call machinery).
53 if lua_checkstack(state, nargs.saturating_add(2)) == 0 {
54 return Err(crate::error::Error::RuntimeError(
55 "stack overflow: too many arguments to function call".to_string(),
56 ));
57 }
58 // Push the function, then the arguments.
59 self.reference.push();
60 for v in args.iter() {
61 lua.push_value(v)?;
62 }
63 // LUA_MULTRET == -1: keep every result.
64 let status = lua_pcall(state, nargs, -1, 0);
65 if status != 0 {
66 return Err(lua.pop_error(status));
67 }
68 // Collect every value pushed above `base` as the results.
69 let top = lua_gettop(state);
70 let nresults = top - base;
71 let mut results = MultiValue::with_capacity(nresults.max(0) as usize);
72 for i in 0..nresults {
73 let idx = base + 1 + i;
74 results.push_back(lua.value_from_stack(idx)?);
75 }
76 lua_settop(state, base);
77 R::from_lua_multi(results, &lua)
78 }
79 }
80
81 /// Return a new function that, when called, prepends `args` to its own
82 /// arguments and forwards to `self`.
83 ///
84 /// Mirrors `mlua::Function::bind`. Implemented as a Rust closure that
85 /// captures the bound prefix and the target function.
86 #[cfg(not(feature = "async"))]
87 pub fn bind(&self, args: impl IntoLuaMulti) -> Result<Function> {
88 let lua = self.lua();
89 let bound: MultiValue = args.into_lua_multi(&lua)?;
90 let target = self.clone();
91 let bound_vec: Vec<crate::value::Value> = bound.into_vec();
92 lua.create_function(move |_, extra: MultiValue| {
93 let mut all = MultiValue::with_capacity(bound_vec.len() + extra.len());
94 for v in &bound_vec {
95 all.push_back(v.clone());
96 }
97 for v in extra {
98 all.push_back(v);
99 }
100 target.call::<MultiValue>(all)
101 })
102 }
103
104 /// Return a new function that, when called, prepends `args` to its own
105 /// arguments and forwards to `self`.
106 ///
107 /// Mirrors `mlua::Function::bind`. Under the `async` feature this is built as
108 /// a **pure-Lua closure** (`function(...) return func(prepend(...)) end`)
109 /// rather than a Rust trampoline, so the forwarded call is a Lua-level call
110 /// and remains **yield-transparent**: a bound async function can still yield
111 /// while its future is pending. (The `prepend` helper only rearranges
112 /// arguments and returns, so it never yields across a C boundary.) Behavior
113 /// is identical to the non-async implementation for ordinary functions.
114 #[cfg(feature = "async")]
115 pub fn bind(&self, args: impl IntoLuaMulti) -> Result<Function> {
116 let lua = self.lua();
117 let bound: MultiValue = args.into_lua_multi(&lua)?;
118 let bound_vec: Vec<crate::value::Value> = bound.into_vec();
119
120 // `prepend(...)` returns the bound prefix followed by the call args.
121 let prepend = lua.create_function(move |_, extra: MultiValue| {
122 let mut all = MultiValue::with_capacity(bound_vec.len() + extra.len());
123 for v in &bound_vec {
124 all.push_back(v.clone());
125 }
126 for v in extra {
127 all.push_back(v);
128 }
129 Ok(all)
130 })?;
131
132 // Build the wrapper closure in Lua so the inner `func(...)` is a Lua
133 // call (yield-transparent), capturing `func` and `prepend` as upvalues.
134 let builder: Function = lua
135 .load(
136 r#"
137 local func, prepend = ...
138 return function(...)
139 return func(prepend(...))
140 end
141 "#,
142 )
143 .set_name("__luaur_bind")
144 .into_function()?;
145 builder.call::<Function>((self.clone(), prepend))
146 }
147
148 /// Call the function asynchronously: run it on a fresh coroutine and drive
149 /// that coroutine to completion as a Rust [`Future`](std::future::Future).
150 ///
151 /// Mirrors `mlua::Function::call_async`. Works for both async functions
152 /// (created via [`Lua::create_async_function`](crate::Lua::create_async_function))
153 /// — which yield while their inner future is pending — and ordinary
154 /// functions (which simply run to completion). Calling an async function
155 /// with the *synchronous* [`Function::call`] instead raises a runtime error,
156 /// matching mlua.
157 #[cfg(feature = "async")]
158 #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
159 pub fn call_async<R>(
160 &self,
161 args: impl IntoLuaMulti,
162 ) -> impl std::future::Future<Output = Result<R>>
163 where
164 R: FromLuaMulti,
165 {
166 let lua = self.lua();
167 // Build the driver eagerly so argument-conversion / thread-creation
168 // errors surface when awaited (wrapped in a ready future).
169 let setup: Result<crate::async_support::AsyncThread<R>> = (|| {
170 let thread = lua.create_thread(self.clone())?;
171 // The coroutine is *implicit* (created by `call_async`): register it
172 // so `Lua::current_thread` running on it resolves to the owner (the
173 // thread that issued this call). Mirrors mlua's thread-ownership map.
174 crate::async_support::register_implicit_thread(thread.state(), lua.state());
175 let mut th = thread.into_async(args)?;
176 th.set_implicit(true);
177 Ok(th)
178 })();
179 async move { setup?.await }
180 }
181
182 /// Wrap a Rust async function/closure as a value convertible into a Lua
183 /// function.
184 ///
185 /// Mirrors `mlua::Function::wrap_async`. Unlike
186 /// [`Lua::create_async_function`](crate::Lua::create_async_function) the
187 /// closure does not receive the [`Lua`] and its arity is free (0, 1, … args
188 /// mapped from the Lua call arguments). The returned value is
189 /// [`IntoLua`](crate::IntoLua) so it can be stored directly (e.g.
190 /// `globals().set("f", Function::wrap_async(..))`). A returned `Err` is
191 /// raised as a Lua error.
192 #[cfg(feature = "async")]
193 #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
194 pub fn wrap_async<F, A, R, E>(func: F) -> impl crate::traits::IntoLua
195 where
196 F: crate::async_support::LuaNativeAsyncFn<A, Output = std::result::Result<R, E>>
197 + crate::sync::MaybeSend
198 + 'static,
199 A: FromLuaMulti,
200 R: IntoLuaMulti + 'static,
201 E: crate::error::ExternalError + 'static,
202 {
203 crate::async_support::WrappedAsync::new(move |_lua: Lua, a: A| {
204 let fut = func.call(a);
205 async move { fut.await.map_err(crate::error::ExternalError::into_lua_err) }
206 })
207 }
208
209 /// Like [`Function::wrap_async`] but the closure's output is passed through
210 /// to Lua as-is (a returned `Result` becomes an `(ok, err)`-style multi
211 /// value rather than being raised).
212 ///
213 /// Mirrors `mlua::Function::wrap_raw_async`.
214 #[cfg(feature = "async")]
215 #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
216 pub fn wrap_raw_async<F, A>(func: F) -> impl crate::traits::IntoLua
217 where
218 F: crate::async_support::LuaNativeAsyncFn<A> + crate::sync::MaybeSend + 'static,
219 F::Output: IntoLuaMulti + 'static,
220 A: FromLuaMulti,
221 {
222 crate::async_support::WrappedAsync::new(move |_lua: Lua, a: A| {
223 let fut = func.call(a);
224 async move { Ok(fut.await) }
225 })
226 }
227
228 /// Wrap a plain Rust closure as a value convertible into a Lua function.
229 ///
230 /// Mirrors `mlua::Function::wrap`. Unlike
231 /// [`Lua::create_function`](crate::Lua::create_function), the closure does
232 /// **not** receive the [`Lua`] and its arity is free (`||`, `|a|`, `|a, b|`,
233 /// … mapped from the Lua call arguments). The returned value is
234 /// [`IntoLua`](crate::IntoLua) so it can be stored directly (e.g.
235 /// `table.set("f", Function::wrap(|a, b| Ok::<_, Error>(a + b)))`). A
236 /// returned `Err` is raised as a Lua error.
237 pub fn wrap<F, A, R, E>(func: F) -> impl crate::traits::IntoLua
238 where
239 F: LuaNativeFn<A, Output = std::result::Result<R, E>> + MaybeSend + 'static,
240 A: FromLuaMulti,
241 R: IntoLuaMulti,
242 E: crate::error::ExternalError,
243 {
244 WrappedFunction {
245 func,
246 _marker: std::marker::PhantomData,
247 }
248 }
249
250 /// A raw pointer identifying this function (for identity comparison).
251 /// Mirrors `mlua::Function::to_pointer`.
252 pub fn to_pointer(&self) -> *const std::ffi::c_void {
253 let state = self.reference.state();
254 unsafe {
255 self.reference.push();
256 let p = lua_topointer(state, -1);
257 lua_pop(state, 1);
258 p
259 }
260 }
261
262 /// The function's environment table (its globals), or `None` for a Rust
263 /// (C) function. Mirrors `mlua::Function::environment`.
264 pub fn environment(&self) -> Option<crate::table::Table> {
265 let lua = self.lua();
266 let state = lua.state();
267 unsafe {
268 self.reference.push();
269 // `lua_getfenv` only applies to Lua closures; a C function has no
270 // accessible environment.
271 if !self.is_lua_closure() {
272 lua_pop(state, 1);
273 return None;
274 }
275 lua_getfenv(state, -1);
276 // stack: [func, env]
277 if lua_type(state, -1) != ttype::TABLE {
278 lua_pop(state, 2);
279 return None;
280 }
281 let env = crate::table::Table::from_ref(lua.pop_ref());
282 lua_pop(state, 1); // pop func
283 Some(env)
284 }
285 }
286
287 /// Set the function's environment table. Returns `Ok(false)` for a Rust
288 /// (C) function (which has no settable environment) and `Ok(true)` for a
289 /// Lua closure. Mirrors `mlua::Function::set_environment`.
290 pub fn set_environment(&self, env: crate::table::Table) -> Result<bool> {
291 let lua = self.lua();
292 let state = lua.state();
293 unsafe {
294 self.reference.push();
295 if !self.is_lua_closure() {
296 lua_pop(state, 1);
297 return Ok(false);
298 }
299 // stack: [func]; push env, then lua_setfenv(func_index).
300 env.push_to_stack();
301 let ok = lua_setfenv(state, -2);
302 // lua_setfenv pops the env table; pop the function too.
303 lua_pop(state, 1);
304 Ok(ok != 0)
305 }
306 }
307
308 /// Whether the value on top of the stack (this function, just pushed) is a
309 /// Lua closure (vs a C function). Determined via the debug `what` field.
310 unsafe fn is_lua_closure(&self) -> bool {
311 let state = self.reference.state();
312 unsafe {
313 // The function is on top of the stack (index -1). Ask lua_getinfo
314 // about it via the ">" level convention: push the function and use
315 // option ">" so it pops the function and reads its info.
316 lua_pushvalue(state, -1);
317 let mut ar: LuaDebug = core::mem::zeroed();
318 let opt = c">s";
319 let ok = lua_getinfo(state, -1, opt.as_ptr() as *const c_char, &mut ar);
320 if ok == 0 {
321 return false;
322 }
323 if ar.what.is_null() {
324 return false;
325 }
326 let what = std::ffi::CStr::from_ptr(ar.what).to_bytes();
327 // "Lua" and "main" are Lua closures; "C" is a native function.
328 what == b"Lua" || what == b"main"
329 }
330 }
331
332 /// Debug information about this function. Mirrors `mlua::Function::info`.
333 pub fn info(&self) -> FunctionInfo {
334 let lua = self.lua();
335 let state = lua.state();
336 unsafe {
337 self.reference.push();
338 let mut ar: LuaDebug = core::mem::zeroed();
339 // Options: n=name, s=source/what/linedefined, a=params/vararg,
340 // u=upvalues. The ">" prefix pops the function from the stack and
341 // reads info about it.
342 let opt = c">nsau";
343 let ok = lua_getinfo(state, -1, opt.as_ptr() as *const c_char, &mut ar);
344 if ok == 0 {
345 return FunctionInfo::default();
346 }
347 let cstr = |p: *const c_char| -> Option<String> {
348 if p.is_null() {
349 None
350 } else {
351 Some(std::ffi::CStr::from_ptr(p).to_string_lossy().into_owned())
352 }
353 };
354 let what = cstr(ar.what).unwrap_or_default();
355 let line_defined = if ar.linedefined > 0 {
356 Some(ar.linedefined as i64)
357 } else {
358 None
359 };
360 // Lua chunks are loaded with a `=<name>` chunkname marker; mlua
361 // reports the bare name in `source`, so strip a single leading
362 // `=`/`@` for Lua/main functions. C functions keep their VM-reported
363 // source verbatim (e.g. `=[C]`), matching mlua.
364 let source = cstr(ar.source).map(|s| {
365 if (what == "Lua" || what == "main") && (s.starts_with('=') || s.starts_with('@')) {
366 s[1..].to_string()
367 } else {
368 s
369 }
370 });
371 FunctionInfo {
372 name: cstr(ar.name),
373 source,
374 short_src: cstr(ar.short_src),
375 line_defined,
376 last_line_defined: None, // Luau does not report it.
377 what,
378 num_upvalues: ar.nupvals,
379 num_params: ar.nparams,
380 is_vararg: ar.isvararg != 0,
381 }
382 }
383 }
384}
385
386/// Debug information about a [`Function`]. Mirrors `mlua::debug::FunctionInfo`
387/// (the subset Luau reports).
388#[derive(Clone, Debug, Default)]
389pub struct FunctionInfo {
390 /// The function's name, if known (Luau records the call-site name).
391 pub name: Option<String>,
392 /// The chunk source name (e.g. `"=[C]"` for native functions).
393 pub source: Option<String>,
394 /// A short, human-readable source description.
395 pub short_src: Option<String>,
396 /// The line where the function was defined, if it is a Lua function.
397 pub line_defined: Option<i64>,
398 /// The last line of the function's definition. Always `None` in Luau.
399 pub last_line_defined: Option<i64>,
400 /// `"Lua"`, `"C"`, or `"main"`.
401 pub what: String,
402 /// The number of upvalues.
403 pub num_upvalues: u8,
404 /// The number of fixed parameters.
405 pub num_params: u8,
406 /// Whether the function is variadic.
407 pub is_vararg: bool,
408}
409
410impl std::fmt::Debug for Function {
411 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
412 write!(f, "Function")
413 }
414}
415
416impl PartialEq for Function {
417 fn eq(&self, other: &Self) -> bool {
418 // Pointer identity (matches mlua): same underlying function object.
419 self.to_pointer() == other.to_pointer()
420 }
421}
422
423// ---------------------------------------------------------------------------
424// LuaNativeFn: arity-abstracting sync closure trait (mirrors mlua)
425// ---------------------------------------------------------------------------
426
427/// A function/closure callable with a tuple of `FromLuaMulti` arguments,
428/// abstracting over arity. Mirrors `mlua::LuaNativeFn`. Lets
429/// [`Function::wrap`] accept `||`, `|a|`, `|a, b|`, … closures uniformly (the
430/// closure receives the converted args directly, not the [`Lua`]).
431pub trait LuaNativeFn<A: FromLuaMulti> {
432 /// The closure's return type (typically `Result<R, E>`).
433 type Output;
434
435 /// Invoke the closure with the converted arguments.
436 fn call(&self, args: A) -> Self::Output;
437}
438
439macro_rules! impl_lua_native_fn {
440 ($($A:ident),*) => {
441 impl<FN, $($A,)* R> LuaNativeFn<($($A,)*)> for FN
442 where
443 FN: Fn($($A,)*) -> R,
444 ($($A,)*): FromLuaMulti,
445 {
446 type Output = R;
447
448 #[allow(non_snake_case)]
449 fn call(&self, args: ($($A,)*)) -> R {
450 let ($($A,)*) = args;
451 self($($A,)*)
452 }
453 }
454 };
455}
456
457impl_lua_native_fn!();
458impl_lua_native_fn!(A);
459impl_lua_native_fn!(A, B);
460impl_lua_native_fn!(A, B, C);
461impl_lua_native_fn!(A, B, C, D);
462impl_lua_native_fn!(A, B, C, D, E);
463impl_lua_native_fn!(A, B, C, D, E, F);
464impl_lua_native_fn!(A, B, C, D, E, F, G);
465impl_lua_native_fn!(A, B, C, D, E, F, G, H);
466
467/// A plain closure not yet bound to a [`Lua`]. Becomes a Lua function (via
468/// [`Lua::create_function`]) when converted with [`IntoLua`]. Backs
469/// [`Function::wrap`].
470struct WrappedFunction<F, A, R, E> {
471 func: F,
472 _marker: std::marker::PhantomData<fn(A) -> (R, E)>,
473}
474
475impl<F, A, R, E> crate::traits::IntoLua for WrappedFunction<F, A, R, E>
476where
477 F: LuaNativeFn<A, Output = std::result::Result<R, E>> + MaybeSend + 'static,
478 A: FromLuaMulti,
479 R: IntoLuaMulti,
480 E: crate::error::ExternalError,
481{
482 fn into_lua(self, lua: &Lua) -> Result<crate::value::Value> {
483 let func = self.func;
484 let f = lua.create_function(move |_lua, args: A| {
485 func.call(args)
486 .map_err(crate::error::ExternalError::into_lua_err)
487 })?;
488 Ok(crate::value::Value::Function(f))
489 }
490}