Skip to main content

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>> + 'static,
197        A: FromLuaMulti,
198        R: IntoLuaMulti + 'static,
199        E: crate::error::ExternalError + 'static,
200    {
201        crate::async_support::WrappedAsync::new(move |_lua: Lua, a: A| {
202            let fut = func.call(a);
203            async move { fut.await.map_err(crate::error::ExternalError::into_lua_err) }
204        })
205    }
206
207    /// Like [`Function::wrap_async`] but the closure's output is passed through
208    /// to Lua as-is (a returned `Result` becomes an `(ok, err)`-style multi
209    /// value rather than being raised).
210    ///
211    /// Mirrors `mlua::Function::wrap_raw_async`.
212    #[cfg(feature = "async")]
213    #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
214    pub fn wrap_raw_async<F, A>(func: F) -> impl crate::traits::IntoLua
215    where
216        F: crate::async_support::LuaNativeAsyncFn<A> + 'static,
217        F::Output: IntoLuaMulti + 'static,
218        A: FromLuaMulti,
219    {
220        crate::async_support::WrappedAsync::new(move |_lua: Lua, a: A| {
221            let fut = func.call(a);
222            async move { Ok(fut.await) }
223        })
224    }
225
226    /// Wrap a plain Rust closure as a value convertible into a Lua function.
227    ///
228    /// Mirrors `mlua::Function::wrap`. Unlike
229    /// [`Lua::create_function`](crate::Lua::create_function), the closure does
230    /// **not** receive the [`Lua`] and its arity is free (`||`, `|a|`, `|a, b|`,
231    /// … mapped from the Lua call arguments). The returned value is
232    /// [`IntoLua`](crate::IntoLua) so it can be stored directly (e.g.
233    /// `table.set("f", Function::wrap(|a, b| Ok::<_, Error>(a + b)))`). A
234    /// returned `Err` is raised as a Lua error.
235    pub fn wrap<F, A, R, E>(func: F) -> impl crate::traits::IntoLua
236    where
237        F: LuaNativeFn<A, Output = std::result::Result<R, E>> + MaybeSend + 'static,
238        A: FromLuaMulti,
239        R: IntoLuaMulti,
240        E: crate::error::ExternalError,
241    {
242        WrappedFunction {
243            func,
244            _marker: std::marker::PhantomData,
245        }
246    }
247
248    /// A raw pointer identifying this function (for identity comparison).
249    /// Mirrors `mlua::Function::to_pointer`.
250    pub fn to_pointer(&self) -> *const std::ffi::c_void {
251        let state = self.reference.state();
252        unsafe {
253            self.reference.push();
254            let p = lua_topointer(state, -1);
255            lua_pop(state, 1);
256            p
257        }
258    }
259
260    /// The function's environment table (its globals), or `None` for a Rust
261    /// (C) function. Mirrors `mlua::Function::environment`.
262    pub fn environment(&self) -> Option<crate::table::Table> {
263        let lua = self.lua();
264        let state = lua.state();
265        unsafe {
266            self.reference.push();
267            // `lua_getfenv` only applies to Lua closures; a C function has no
268            // accessible environment.
269            if !self.is_lua_closure() {
270                lua_pop(state, 1);
271                return None;
272            }
273            lua_getfenv(state, -1);
274            // stack: [func, env]
275            if lua_type(state, -1) != ttype::TABLE {
276                lua_pop(state, 2);
277                return None;
278            }
279            let env = crate::table::Table::from_ref(lua.pop_ref());
280            lua_pop(state, 1); // pop func
281            Some(env)
282        }
283    }
284
285    /// Set the function's environment table. Returns `Ok(false)` for a Rust
286    /// (C) function (which has no settable environment) and `Ok(true)` for a
287    /// Lua closure. Mirrors `mlua::Function::set_environment`.
288    pub fn set_environment(&self, env: crate::table::Table) -> Result<bool> {
289        let lua = self.lua();
290        let state = lua.state();
291        unsafe {
292            self.reference.push();
293            if !self.is_lua_closure() {
294                lua_pop(state, 1);
295                return Ok(false);
296            }
297            // stack: [func]; push env, then lua_setfenv(func_index).
298            env.push_to_stack();
299            let ok = lua_setfenv(state, -2);
300            // lua_setfenv pops the env table; pop the function too.
301            lua_pop(state, 1);
302            Ok(ok != 0)
303        }
304    }
305
306    /// Whether the value on top of the stack (this function, just pushed) is a
307    /// Lua closure (vs a C function). Determined via the debug `what` field.
308    unsafe fn is_lua_closure(&self) -> bool {
309        let state = self.reference.state();
310        unsafe {
311            // The function is on top of the stack (index -1). Ask lua_getinfo
312            // about it via the ">" level convention: push the function and use
313            // option ">" so it pops the function and reads its info.
314            lua_pushvalue(state, -1);
315            let mut ar: LuaDebug = core::mem::zeroed();
316            let opt = c">s";
317            let ok = lua_getinfo(state, -1, opt.as_ptr() as *const c_char, &mut ar);
318            if ok == 0 {
319                return false;
320            }
321            if ar.what.is_null() {
322                return false;
323            }
324            let what = std::ffi::CStr::from_ptr(ar.what).to_bytes();
325            // "Lua" and "main" are Lua closures; "C" is a native function.
326            what == b"Lua" || what == b"main"
327        }
328    }
329
330    /// Debug information about this function. Mirrors `mlua::Function::info`.
331    pub fn info(&self) -> FunctionInfo {
332        let lua = self.lua();
333        let state = lua.state();
334        unsafe {
335            self.reference.push();
336            let mut ar: LuaDebug = core::mem::zeroed();
337            // Options: n=name, s=source/what/linedefined, a=params/vararg,
338            // u=upvalues. The ">" prefix pops the function from the stack and
339            // reads info about it.
340            let opt = c">nsau";
341            let ok = lua_getinfo(state, -1, opt.as_ptr() as *const c_char, &mut ar);
342            if ok == 0 {
343                return FunctionInfo::default();
344            }
345            let cstr = |p: *const c_char| -> Option<String> {
346                if p.is_null() {
347                    None
348                } else {
349                    Some(std::ffi::CStr::from_ptr(p).to_string_lossy().into_owned())
350                }
351            };
352            let what = cstr(ar.what).unwrap_or_default();
353            let line_defined = if ar.linedefined > 0 {
354                Some(ar.linedefined as i64)
355            } else {
356                None
357            };
358            // Lua chunks are loaded with a `=<name>` chunkname marker; mlua
359            // reports the bare name in `source`, so strip a single leading
360            // `=`/`@` for Lua/main functions. C functions keep their VM-reported
361            // source verbatim (e.g. `=[C]`), matching mlua.
362            let source = cstr(ar.source).map(|s| {
363                if (what == "Lua" || what == "main") && (s.starts_with('=') || s.starts_with('@')) {
364                    s[1..].to_string()
365                } else {
366                    s
367                }
368            });
369            FunctionInfo {
370                name: cstr(ar.name),
371                source,
372                short_src: cstr(ar.short_src),
373                line_defined,
374                last_line_defined: None, // Luau does not report it.
375                what,
376                num_upvalues: ar.nupvals,
377                num_params: ar.nparams,
378                is_vararg: ar.isvararg != 0,
379            }
380        }
381    }
382}
383
384/// Debug information about a [`Function`]. Mirrors `mlua::debug::FunctionInfo`
385/// (the subset Luau reports).
386#[derive(Clone, Debug, Default)]
387pub struct FunctionInfo {
388    /// The function's name, if known (Luau records the call-site name).
389    pub name: Option<String>,
390    /// The chunk source name (e.g. `"=[C]"` for native functions).
391    pub source: Option<String>,
392    /// A short, human-readable source description.
393    pub short_src: Option<String>,
394    /// The line where the function was defined, if it is a Lua function.
395    pub line_defined: Option<i64>,
396    /// The last line of the function's definition. Always `None` in Luau.
397    pub last_line_defined: Option<i64>,
398    /// `"Lua"`, `"C"`, or `"main"`.
399    pub what: String,
400    /// The number of upvalues.
401    pub num_upvalues: u8,
402    /// The number of fixed parameters.
403    pub num_params: u8,
404    /// Whether the function is variadic.
405    pub is_vararg: bool,
406}
407
408impl std::fmt::Debug for Function {
409    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
410        write!(f, "Function")
411    }
412}
413
414impl PartialEq for Function {
415    fn eq(&self, other: &Self) -> bool {
416        // Pointer identity (matches mlua): same underlying function object.
417        self.to_pointer() == other.to_pointer()
418    }
419}
420
421// ---------------------------------------------------------------------------
422// LuaNativeFn: arity-abstracting sync closure trait (mirrors mlua)
423// ---------------------------------------------------------------------------
424
425/// A function/closure callable with a tuple of `FromLuaMulti` arguments,
426/// abstracting over arity. Mirrors `mlua::LuaNativeFn`. Lets
427/// [`Function::wrap`] accept `||`, `|a|`, `|a, b|`, … closures uniformly (the
428/// closure receives the converted args directly, not the [`Lua`]).
429pub trait LuaNativeFn<A: FromLuaMulti> {
430    /// The closure's return type (typically `Result<R, E>`).
431    type Output;
432
433    /// Invoke the closure with the converted arguments.
434    fn call(&self, args: A) -> Self::Output;
435}
436
437macro_rules! impl_lua_native_fn {
438    ($($A:ident),*) => {
439        impl<FN, $($A,)* R> LuaNativeFn<($($A,)*)> for FN
440        where
441            FN: Fn($($A,)*) -> R,
442            ($($A,)*): FromLuaMulti,
443        {
444            type Output = R;
445
446            #[allow(non_snake_case)]
447            fn call(&self, args: ($($A,)*)) -> R {
448                let ($($A,)*) = args;
449                self($($A,)*)
450            }
451        }
452    };
453}
454
455impl_lua_native_fn!();
456impl_lua_native_fn!(A);
457impl_lua_native_fn!(A, B);
458impl_lua_native_fn!(A, B, C);
459impl_lua_native_fn!(A, B, C, D);
460impl_lua_native_fn!(A, B, C, D, E);
461impl_lua_native_fn!(A, B, C, D, E, F);
462impl_lua_native_fn!(A, B, C, D, E, F, G);
463impl_lua_native_fn!(A, B, C, D, E, F, G, H);
464
465/// A plain closure not yet bound to a [`Lua`]. Becomes a Lua function (via
466/// [`Lua::create_function`]) when converted with [`IntoLua`]. Backs
467/// [`Function::wrap`].
468struct WrappedFunction<F, A, R, E> {
469    func: F,
470    _marker: std::marker::PhantomData<fn(A) -> (R, E)>,
471}
472
473impl<F, A, R, E> crate::traits::IntoLua for WrappedFunction<F, A, R, E>
474where
475    F: LuaNativeFn<A, Output = std::result::Result<R, E>> + MaybeSend + 'static,
476    A: FromLuaMulti,
477    R: IntoLuaMulti,
478    E: crate::error::ExternalError,
479{
480    fn into_lua(self, lua: &Lua) -> Result<crate::value::Value> {
481        let func = self.func;
482        let f = lua.create_function(move |_lua, args: A| {
483            func.call(args)
484                .map_err(crate::error::ExternalError::into_lua_err)
485        })?;
486        Ok(crate::value::Value::Function(f))
487    }
488}