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}