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>>
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}