Skip to main content

luaur_rt/
userdata.rs

1//! [`UserData`] / [`UserDataMethods`] / [`UserDataFields`] and the
2//! [`AnyUserData`] handle. Mirrors `mlua::UserData` / `mlua::UserDataMethods` /
3//! `mlua::UserDataFields` / `mlua::AnyUserData`.
4//!
5//! ## Implementation
6//!
7//! A `T: UserData` value is boxed into a Lua userdata as a typed wrapper
8//! [`UserDataCell<T>`] = `{ type_id, RefCell<Option<T>> }` (via
9//! [`lua_newuserdatadtor`], whose destructor drops the cell). The leading
10//! `TypeId` makes Rust-side typed read-back **sound**: every accessor
11//! ([`AnyUserData::borrow`], [`borrow_mut`](AnyUserData::borrow_mut),
12//! [`take`](AnyUserData::take), [`is`](AnyUserData::is)) reads the stored
13//! `TypeId` from the userdata pointer and compares it with `TypeId::of::<T>()`
14//! before downcasting — a mismatch is an [`Error::UserDataTypeMismatch`].
15//! `take` replaces the `Option<T>` with `None`; subsequent access reports
16//! [`Error::UserDataDestructed`].
17//!
18//! Each registered method/field is compiled into a Rust closure wired into a
19//! per-instance metatable:
20//!   - ordinary methods go into a method table,
21//!   - field getters/setters are dispatched by an `__index`/`__newindex`
22//!     function (only when fields are registered),
23//!   - meta-methods (e.g. `__add`) go directly on the metatable.
24
25use std::any::TypeId;
26use std::cell::RefCell;
27use std::marker::PhantomData;
28
29use crate::callback::{create_callback_function, BoxedCallback};
30use crate::error::{Error, Result};
31use crate::state::{Lua, LuaRef};
32use crate::sync::{MaybeSend, MaybeSync, NotSync, XRc, NOT_SYNC};
33use crate::sys::*;
34use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti};
35use crate::value::Value;
36
37/// A Rust type that can be exposed to Lua as userdata.
38///
39/// Mirrors `mlua::UserData`. Implement [`UserData::add_methods`] and/or
40/// [`UserData::add_fields`] to register the surface visible from Lua.
41pub trait UserData: Sized {
42    /// Register fields (getters/setters). Default: none.
43    fn add_fields<F: UserDataFields<Self>>(_fields: &mut F) {}
44
45    /// Register methods and meta-methods. Default: none.
46    fn add_methods<M: UserDataMethods<Self>>(_methods: &mut M) {}
47}
48
49/// Registrar passed to [`UserData::add_methods`].
50///
51/// Mirrors `mlua::UserDataMethods`.
52pub trait UserDataMethods<T> {
53    /// Register a method callable as `obj:name(...)`; receives `&T`.
54    fn add_method<M, A, R>(&mut self, name: impl Into<String>, method: M)
55    where
56        M: Fn(&Lua, &T, A) -> Result<R> + MaybeSend + 'static,
57        A: FromLuaMulti,
58        R: IntoLuaMulti;
59
60    /// Register a method callable as `obj:name(...)`; receives `&mut T`.
61    fn add_method_mut<M, A, R>(&mut self, name: impl Into<String>, method: M)
62    where
63        M: Fn(&Lua, &mut T, A) -> Result<R> + MaybeSend + 'static,
64        A: FromLuaMulti,
65        R: IntoLuaMulti;
66
67    /// Register a plain function in the userdata namespace (no `self`).
68    fn add_function<F, A, R>(&mut self, name: impl Into<String>, function: F)
69    where
70        F: Fn(&Lua, A) -> Result<R> + MaybeSend + 'static,
71        A: FromLuaMulti,
72        R: IntoLuaMulti;
73
74    /// Register a meta-method (e.g. `MetaMethod::Add`, `"__tostring"`);
75    /// receives `&T`.
76    fn add_meta_method<M, A, R>(&mut self, name: impl Into<String>, method: M)
77    where
78        M: Fn(&Lua, &T, A) -> Result<R> + MaybeSend + 'static,
79        A: FromLuaMulti,
80        R: IntoLuaMulti;
81
82    /// Register a meta-method receiving `&mut T`.
83    fn add_meta_method_mut<M, A, R>(&mut self, name: impl Into<String>, method: M)
84    where
85        M: Fn(&Lua, &mut T, A) -> Result<R> + MaybeSend + 'static,
86        A: FromLuaMulti,
87        R: IntoLuaMulti;
88}
89
90/// Registrar passed to [`UserData::add_fields`].
91///
92/// Mirrors `mlua::UserDataFields`. Field getters/setters are dispatched by the
93/// userdata's `__index`/`__newindex`.
94pub trait UserDataFields<T> {
95    /// Register a constant field value (read-only).
96    fn add_field<V>(&mut self, name: impl Into<String>, value: V)
97    where
98        V: IntoLua + Clone + MaybeSend + 'static;
99
100    /// Register a field whose getter receives `&T`.
101    fn add_field_method_get<M, R>(&mut self, name: impl Into<String>, method: M)
102    where
103        M: Fn(&Lua, &T) -> Result<R> + MaybeSend + 'static,
104        R: IntoLua;
105
106    /// Register a field whose setter receives `&mut T` and the assigned value.
107    fn add_field_method_set<M, A>(&mut self, name: impl Into<String>, method: M)
108    where
109        M: Fn(&Lua, &mut T, A) -> Result<()> + MaybeSend + 'static,
110        A: FromLua;
111
112    /// Register a field whose getter receives the [`AnyUserData`] handle.
113    fn add_field_function_get<F, R>(&mut self, name: impl Into<String>, function: F)
114    where
115        F: Fn(&Lua, AnyUserData) -> Result<R> + MaybeSend + 'static,
116        R: IntoLua;
117
118    /// Register a field whose setter receives the [`AnyUserData`] handle and
119    /// the assigned value.
120    fn add_field_function_set<F, A>(&mut self, name: impl Into<String>, function: F)
121    where
122        F: Fn(&Lua, AnyUserData, A) -> Result<()> + MaybeSend + 'static,
123        A: FromLua;
124}
125
126/// A handle to an arbitrary Lua userdata value.
127///
128/// Mirrors `mlua::AnyUserData`. Supports construction, use-from-Lua, and typed
129/// Rust-side borrowing ([`borrow`](AnyUserData::borrow) /
130/// [`borrow_mut`](AnyUserData::borrow_mut) / [`take`](AnyUserData::take) /
131/// [`is`](AnyUserData::is)).
132#[derive(Clone)]
133pub struct AnyUserData {
134    pub(crate) reference: XRc<LuaRef>,
135    pub(crate) _not_sync: NotSync,
136}
137
138impl AnyUserData {
139    pub(crate) fn from_ref(reference: LuaRef) -> AnyUserData {
140        AnyUserData {
141            reference: XRc::new(reference),
142            _not_sync: NOT_SYNC,
143        }
144    }
145
146    pub(crate) unsafe fn push_to_stack(&self) {
147        self.reference.push();
148    }
149
150    /// The owning [`Lua`].
151    pub fn lua(&self) -> Lua {
152        self.reference.lua()
153    }
154
155    /// A raw pointer identifying this userdata. Mirrors
156    /// `mlua::AnyUserData::to_pointer`.
157    pub fn to_pointer(&self) -> *const c_void {
158        let state = self.reference.state();
159        unsafe {
160            self.reference.push();
161            let p = lua_topointer(state, -1);
162            lua_pop(state, 1);
163            p
164        }
165    }
166
167    /// Compare for equality honoring an `__eq` metamethod.
168    /// Mirrors `mlua::AnyUserData::equals`.
169    pub fn equals(&self, other: &AnyUserData) -> Result<bool> {
170        let lua = self.lua();
171        let state = lua.state();
172        unsafe {
173            self.reference.push();
174            other.reference.push();
175            let eq = lua_equal(state, -2, -1);
176            lua_pop(state, 2);
177            Ok(eq != 0)
178        }
179    }
180
181    /// Recover a `&UserDataCell<T>` from the userdata storage, checking the
182    /// embedded `TypeId`. Returns `UserDataTypeMismatch` if the concrete type
183    /// differs.
184    fn cell<T: 'static>(&self) -> Result<&UserDataCell<T>> {
185        let state = self.reference.state();
186        unsafe {
187            self.reference.push();
188            let ptr = lua_touserdata(state, -1);
189            lua_pop(state, 1);
190            if ptr.is_null() {
191                return Err(Error::UserDataTypeMismatch);
192            }
193            // The wrapper stores the TypeId first; check it before downcasting.
194            let header = &*(ptr as *const UserDataHeader);
195            if header.type_id != TypeId::of::<T>() {
196                return Err(Error::UserDataTypeMismatch);
197            }
198            Ok(&*(ptr as *const UserDataCell<T>))
199        }
200    }
201
202    /// Whether the stored value is of concrete type `T`. Mirrors
203    /// `mlua::AnyUserData::is`. Returns `false` after the value has been taken.
204    pub fn is<T: 'static>(&self) -> bool {
205        match self.cell::<T>() {
206            Ok(cell) => cell.cell.borrow().is_some(),
207            Err(_) => false,
208        }
209    }
210
211    /// The [`TypeId`] of the stored value, if it is a luaur-rt userdata.
212    /// Mirrors `mlua::AnyUserData::type_id` (here it returns the concrete
213    /// `TypeId` whenever the userdata carries a luaur-rt wrapper header).
214    pub fn type_id(&self) -> Option<TypeId> {
215        let state = self.reference.state();
216        unsafe {
217            self.reference.push();
218            let ptr = lua_touserdata(state, -1);
219            lua_pop(state, 1);
220            if ptr.is_null() {
221                return None;
222            }
223            // Only luaur-rt userdata carry a header; raw VM userdata do not, but
224            // every userdata this crate creates does.
225            let header = &*(ptr as *const UserDataHeader);
226            Some(header.type_id)
227        }
228    }
229
230    /// Immutably borrow the stored value as `T`. Mirrors
231    /// `mlua::AnyUserData::borrow`. Errors with [`Error::UserDataTypeMismatch`]
232    /// on a type mismatch, [`Error::UserDataDestructed`] if it was taken, or
233    /// [`Error::UserDataBorrowError`] if already mutably borrowed.
234    pub fn borrow<T: 'static>(&self) -> Result<UserDataRef<'_, T>> {
235        let cell = self.cell::<T>()?;
236        let guard = cell
237            .cell
238            .try_borrow()
239            .map_err(|_| Error::UserDataBorrowError)?;
240        if guard.is_none() {
241            return Err(Error::UserDataDestructed);
242        }
243        Ok(UserDataRef {
244            guard,
245            _marker: PhantomData,
246        })
247    }
248
249    /// Mutably borrow the stored value as `T`. Mirrors
250    /// `mlua::AnyUserData::borrow_mut`.
251    pub fn borrow_mut<T: 'static>(&self) -> Result<UserDataRefMut<'_, T>> {
252        let cell = self.cell::<T>()?;
253        let guard = cell
254            .cell
255            .try_borrow_mut()
256            .map_err(|_| Error::UserDataBorrowMutError)?;
257        if guard.is_none() {
258            return Err(Error::UserDataDestructed);
259        }
260        Ok(UserDataRefMut {
261            guard,
262            _marker: PhantomData,
263        })
264    }
265
266    /// Take the stored value out of the userdata, leaving it destructed.
267    /// Mirrors `mlua::AnyUserData::take`. Errors with
268    /// [`Error::UserDataBorrowMutError`] if currently borrowed, or
269    /// [`Error::UserDataDestructed`] if already taken.
270    pub fn take<T: 'static>(&self) -> Result<T> {
271        let cell = self.cell::<T>()?;
272        let mut guard = cell
273            .cell
274            .try_borrow_mut()
275            .map_err(|_| Error::UserDataBorrowMutError)?;
276        guard.take().ok_or(Error::UserDataDestructed)
277    }
278}
279
280/// A RAII guard for an immutable userdata borrow ([`AnyUserData::borrow`]).
281/// Mirrors `mlua::UserDataRef`.
282pub struct UserDataRef<'a, T> {
283    guard: std::cell::Ref<'a, Option<T>>,
284    _marker: PhantomData<T>,
285}
286
287impl<T> std::ops::Deref for UserDataRef<'_, T> {
288    type Target = T;
289    fn deref(&self) -> &T {
290        // Invariant: `borrow` returns only when the option is `Some`.
291        self.guard.as_ref().expect("userdata destructed")
292    }
293}
294
295impl<T: std::fmt::Debug> std::fmt::Debug for UserDataRef<'_, T> {
296    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
297        std::fmt::Debug::fmt(&**self, f)
298    }
299}
300
301impl<T: std::fmt::Display> std::fmt::Display for UserDataRef<'_, T> {
302    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
303        std::fmt::Display::fmt(&**self, f)
304    }
305}
306
307/// A RAII guard for a mutable userdata borrow ([`AnyUserData::borrow_mut`]).
308/// Mirrors `mlua::UserDataRefMut`.
309pub struct UserDataRefMut<'a, T> {
310    guard: std::cell::RefMut<'a, Option<T>>,
311    _marker: PhantomData<T>,
312}
313
314impl<T> std::ops::Deref for UserDataRefMut<'_, T> {
315    type Target = T;
316    fn deref(&self) -> &T {
317        self.guard.as_ref().expect("userdata destructed")
318    }
319}
320
321impl<T> std::ops::DerefMut for UserDataRefMut<'_, T> {
322    fn deref_mut(&mut self) -> &mut T {
323        self.guard.as_mut().expect("userdata destructed")
324    }
325}
326
327impl<T: std::fmt::Debug> std::fmt::Debug for UserDataRefMut<'_, T> {
328    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
329        std::fmt::Debug::fmt(&**self, f)
330    }
331}
332
333impl<T: std::fmt::Display> std::fmt::Display for UserDataRefMut<'_, T> {
334    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
335        std::fmt::Display::fmt(&**self, f)
336    }
337}
338
339impl std::fmt::Debug for AnyUserData {
340    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
341        write!(f, "UserData")
342    }
343}
344
345impl PartialEq for AnyUserData {
346    fn eq(&self, other: &Self) -> bool {
347        // Pointer identity (matches mlua): same underlying userdata object.
348        self.to_pointer() == other.to_pointer()
349    }
350}
351
352// Any `T: UserData` value converts into Lua by wrapping it in a fresh userdata.
353// Mirrors mlua's `impl<T: UserData + MaybeSend + MaybeSync + 'static> IntoLua`.
354// This is what lets `create_registry_value(MyUserdata(..))`,
355// `table.set("k", MyUserdata(..))`, etc. accept a userdata value directly. It
356// coexists with the concrete `IntoLua` impls below because none of those
357// (local) types implement `UserData`.
358impl<T: UserData + crate::sync::MaybeSend + crate::sync::MaybeSync + 'static> IntoLua for T {
359    fn into_lua(self, lua: &Lua) -> Result<Value> {
360        Ok(Value::UserData(lua.create_userdata(self)?))
361    }
362}
363
364impl IntoLua for AnyUserData {
365    fn into_lua(self, _lua: &Lua) -> Result<Value> {
366        Ok(Value::UserData(self))
367    }
368}
369
370impl IntoLua for &AnyUserData {
371    fn into_lua(self, _lua: &Lua) -> Result<Value> {
372        Ok(Value::UserData(self.clone()))
373    }
374}
375
376impl FromLua for AnyUserData {
377    fn from_lua(value: Value, _lua: &Lua) -> Result<Self> {
378        match value {
379            Value::UserData(ud) => Ok(ud),
380            other => Err(Error::FromLuaConversionError {
381                from: other.type_name(),
382                to: "AnyUserData".to_string(),
383                message: None,
384            }),
385        }
386    }
387}
388
389// ---------------------------------------------------------------------------
390// Typed userdata storage
391// ---------------------------------------------------------------------------
392
393/// The fixed leading layout of every luaur-rt userdata wrapper. Reading the
394/// `TypeId` through this header (a prefix of [`UserDataCell<T>`]) lets us check
395/// the concrete type before downcasting. The two share `#[repr(C)]` so the
396/// `type_id` field is at the same offset for any `T`.
397#[repr(C)]
398struct UserDataHeader {
399    type_id: TypeId,
400}
401
402/// The typed userdata storage: a `TypeId` followed by the `RefCell<Option<T>>`.
403#[repr(C)]
404struct UserDataCell<T> {
405    type_id: TypeId,
406    cell: RefCell<Option<T>>,
407}
408
409/// Recover `&UserDataCell<T>` from a `self` userdata [`Value`] (Lua argument 1).
410fn recover_cell<'a, T: 'static>(lua: &Lua, value: &Value) -> Result<&'a UserDataCell<T>> {
411    match value {
412        Value::UserData(ud) => {
413            let state = lua.state();
414            unsafe {
415                ud.reference.push();
416                let ptr = lua_touserdata(state, -1);
417                lua_pop(state, 1);
418                if ptr.is_null() {
419                    return Err(Error::UserDataTypeMismatch);
420                }
421                let header = &*(ptr as *const UserDataHeader);
422                if header.type_id != TypeId::of::<T>() {
423                    return Err(Error::UserDataTypeMismatch);
424                }
425                Ok(&*(ptr as *const UserDataCell<T>))
426            }
427        }
428        _ => Err(Error::UserDataTypeMismatch),
429    }
430}
431
432// ---------------------------------------------------------------------------
433// Method collection
434// ---------------------------------------------------------------------------
435
436/// A registered method or meta-method, paired with its name and whether it is a
437/// meta-method.
438struct Registered {
439    name: String,
440    is_meta: bool,
441    callback: BoxedCallback,
442}
443
444/// A registered field getter or setter.
445struct FieldEntry {
446    name: String,
447    /// `true` if a getter, `false` if a setter.
448    is_get: bool,
449    callback: BoxedCallback,
450}
451
452/// Concrete [`UserDataMethods`] / [`UserDataFields`] implementation that
453/// collects the type-erased callbacks; the metatable is built from these.
454struct Collector<T> {
455    methods: Vec<Registered>,
456    fields: Vec<FieldEntry>,
457    _phantom: PhantomData<T>,
458}
459
460impl<T> Collector<T> {
461    fn new() -> Self {
462        Collector {
463            methods: Vec::new(),
464            fields: Vec::new(),
465            _phantom: PhantomData,
466        }
467    }
468}
469
470impl<T: 'static> UserDataMethods<T> for Collector<T> {
471    fn add_method<M, A, R>(&mut self, name: impl Into<String>, method: M)
472    where
473        M: Fn(&Lua, &T, A) -> Result<R> + MaybeSend + 'static,
474        A: FromLuaMulti,
475        R: IntoLuaMulti,
476    {
477        let callback: BoxedCallback = Box::new(move |lua, mut args| {
478            let this = args.pop_front().unwrap_or(Value::Nil);
479            let cell = recover_cell::<T>(lua, &this)?;
480            let a = A::from_lua_multi(args, lua)?;
481            let borrowed = cell
482                .cell
483                .try_borrow()
484                .map_err(|_| Error::UserDataBorrowError)?;
485            let data = borrowed.as_ref().ok_or(Error::UserDataDestructed)?;
486            let r = method(lua, data, a)?;
487            r.into_lua_multi(lua)
488        });
489        self.methods.push(Registered {
490            name: name.into(),
491            is_meta: false,
492            callback,
493        });
494    }
495
496    fn add_method_mut<M, A, R>(&mut self, name: impl Into<String>, method: M)
497    where
498        M: Fn(&Lua, &mut T, A) -> Result<R> + MaybeSend + 'static,
499        A: FromLuaMulti,
500        R: IntoLuaMulti,
501    {
502        let callback: BoxedCallback = Box::new(move |lua, mut args| {
503            let this = args.pop_front().unwrap_or(Value::Nil);
504            let cell = recover_cell::<T>(lua, &this)?;
505            let a = A::from_lua_multi(args, lua)?;
506            let mut borrowed = cell
507                .cell
508                .try_borrow_mut()
509                .map_err(|_| Error::UserDataBorrowMutError)?;
510            let data = borrowed.as_mut().ok_or(Error::UserDataDestructed)?;
511            let r = method(lua, data, a)?;
512            r.into_lua_multi(lua)
513        });
514        self.methods.push(Registered {
515            name: name.into(),
516            is_meta: false,
517            callback,
518        });
519    }
520
521    fn add_function<F, A, R>(&mut self, name: impl Into<String>, function: F)
522    where
523        F: Fn(&Lua, A) -> Result<R> + MaybeSend + 'static,
524        A: FromLuaMulti,
525        R: IntoLuaMulti,
526    {
527        let callback: BoxedCallback = Box::new(move |lua, args| {
528            let a = A::from_lua_multi(args, lua)?;
529            let r = function(lua, a)?;
530            r.into_lua_multi(lua)
531        });
532        self.methods.push(Registered {
533            name: name.into(),
534            is_meta: false,
535            callback,
536        });
537    }
538
539    fn add_meta_method<M, A, R>(&mut self, name: impl Into<String>, method: M)
540    where
541        M: Fn(&Lua, &T, A) -> Result<R> + MaybeSend + 'static,
542        A: FromLuaMulti,
543        R: IntoLuaMulti,
544    {
545        let callback: BoxedCallback = Box::new(move |lua, mut args| {
546            let this = args.pop_front().unwrap_or(Value::Nil);
547            let cell = recover_cell::<T>(lua, &this)?;
548            let a = A::from_lua_multi(args, lua)?;
549            let borrowed = cell
550                .cell
551                .try_borrow()
552                .map_err(|_| Error::UserDataBorrowError)?;
553            let data = borrowed.as_ref().ok_or(Error::UserDataDestructed)?;
554            let r = method(lua, data, a)?;
555            r.into_lua_multi(lua)
556        });
557        self.methods.push(Registered {
558            name: name.into(),
559            is_meta: true,
560            callback,
561        });
562    }
563
564    fn add_meta_method_mut<M, A, R>(&mut self, name: impl Into<String>, method: M)
565    where
566        M: Fn(&Lua, &mut T, A) -> Result<R> + MaybeSend + 'static,
567        A: FromLuaMulti,
568        R: IntoLuaMulti,
569    {
570        let callback: BoxedCallback = Box::new(move |lua, mut args| {
571            let this = args.pop_front().unwrap_or(Value::Nil);
572            let cell = recover_cell::<T>(lua, &this)?;
573            let a = A::from_lua_multi(args, lua)?;
574            let mut borrowed = cell
575                .cell
576                .try_borrow_mut()
577                .map_err(|_| Error::UserDataBorrowMutError)?;
578            let data = borrowed.as_mut().ok_or(Error::UserDataDestructed)?;
579            let r = method(lua, data, a)?;
580            r.into_lua_multi(lua)
581        });
582        self.methods.push(Registered {
583            name: name.into(),
584            is_meta: true,
585            callback,
586        });
587    }
588}
589
590impl<T: 'static> UserDataFields<T> for Collector<T> {
591    fn add_field<V>(&mut self, name: impl Into<String>, value: V)
592    where
593        V: IntoLua + Clone + MaybeSend + 'static,
594    {
595        let callback: BoxedCallback = Box::new(move |lua, _args| {
596            let v = value.clone().into_lua(lua)?;
597            v.into_lua_multi(lua)
598        });
599        self.fields.push(FieldEntry {
600            name: name.into(),
601            is_get: true,
602            callback,
603        });
604    }
605
606    fn add_field_method_get<M, R>(&mut self, name: impl Into<String>, method: M)
607    where
608        M: Fn(&Lua, &T) -> Result<R> + MaybeSend + 'static,
609        R: IntoLua,
610    {
611        let callback: BoxedCallback = Box::new(move |lua, mut args| {
612            let this = args.pop_front().unwrap_or(Value::Nil);
613            let cell = recover_cell::<T>(lua, &this)?;
614            let borrowed = cell
615                .cell
616                .try_borrow()
617                .map_err(|_| Error::UserDataBorrowError)?;
618            let data = borrowed.as_ref().ok_or(Error::UserDataDestructed)?;
619            let r = method(lua, data)?;
620            r.into_lua_multi(lua)
621        });
622        self.fields.push(FieldEntry {
623            name: name.into(),
624            is_get: true,
625            callback,
626        });
627    }
628
629    fn add_field_method_set<M, A>(&mut self, name: impl Into<String>, method: M)
630    where
631        M: Fn(&Lua, &mut T, A) -> Result<()> + MaybeSend + 'static,
632        A: FromLua,
633    {
634        let callback: BoxedCallback = Box::new(move |lua, mut args| {
635            let this = args.pop_front().unwrap_or(Value::Nil);
636            let cell = recover_cell::<T>(lua, &this)?;
637            let val = A::from_lua(args.pop_front().unwrap_or(Value::Nil), lua)?;
638            let mut borrowed = cell
639                .cell
640                .try_borrow_mut()
641                .map_err(|_| Error::UserDataBorrowMutError)?;
642            let data = borrowed.as_mut().ok_or(Error::UserDataDestructed)?;
643            method(lua, data, val)?;
644            ().into_lua_multi(lua)
645        });
646        self.fields.push(FieldEntry {
647            name: name.into(),
648            is_get: false,
649            callback,
650        });
651    }
652
653    fn add_field_function_get<F, R>(&mut self, name: impl Into<String>, function: F)
654    where
655        F: Fn(&Lua, AnyUserData) -> Result<R> + MaybeSend + 'static,
656        R: IntoLua,
657    {
658        let callback: BoxedCallback = Box::new(move |lua, mut args| {
659            let this = args.pop_front().unwrap_or(Value::Nil);
660            let ud = AnyUserData::from_lua(this, lua)?;
661            let r = function(lua, ud)?;
662            r.into_lua_multi(lua)
663        });
664        self.fields.push(FieldEntry {
665            name: name.into(),
666            is_get: true,
667            callback,
668        });
669    }
670
671    fn add_field_function_set<F, A>(&mut self, name: impl Into<String>, function: F)
672    where
673        F: Fn(&Lua, AnyUserData, A) -> Result<()> + MaybeSend + 'static,
674        A: FromLua,
675    {
676        let callback: BoxedCallback = Box::new(move |lua, mut args| {
677            let this = args.pop_front().unwrap_or(Value::Nil);
678            let ud = AnyUserData::from_lua(this, lua)?;
679            let val = A::from_lua(args.pop_front().unwrap_or(Value::Nil), lua)?;
680            function(lua, ud, val)?;
681            ().into_lua_multi(lua)
682        });
683        self.fields.push(FieldEntry {
684            name: name.into(),
685            is_get: false,
686            callback,
687        });
688    }
689}
690
691/// Destructor for the [`UserDataCell<T>`] stored inside the userdata.
692unsafe extern "C" fn userdata_dtor<T>(ptr: *mut c_void) {
693    if !ptr.is_null() {
694        unsafe { core::ptr::drop_in_place(ptr as *mut UserDataCell<T>) };
695    }
696}
697
698// ---------------------------------------------------------------------------
699// Scoped (non-'static) userdata — used by `Lua::scope`
700// ---------------------------------------------------------------------------
701//
702// Ordinary userdata stores a `TypeId` so values can be soundly read back out by
703// concrete type (`borrow`/`take`/`is`). That requires `T: 'static`.
704//
705// A scope can create userdata wrapping a **non-`'static`** `T` (e.g. one that
706// borrows from the enclosing stack frame). For these there is no `TypeId`, so
707// instead each scoped userdata is tagged with a process-unique `u64` **marker**.
708// Every method/field/meta closure for that userdata captures the *same* marker,
709// so on dispatch it can confirm the `self` it received is exactly the userdata
710// it belongs to (recovering `&ScopedCell<T>` is sound only after the marker
711// matches — markers are never reused, so no other userdata can collide).
712//
713// Soundness over the scope lifetime: while the scope is active the wrapped `T`
714// is `Some` and methods may form a transient `&T`/`&mut T`. On scope exit the
715// scope's destructor `take()`s the value to `None` (dropping the borrowed `T`,
716// ending its borrows) but leaves the cell memory valid; any later dispatch finds
717// `None` and returns `Error::UserDataDestructed`. The cell itself is freed only
718// when the GC collects the userdata, never while a `&ScopedCell<T>` could exist.
719
720use std::sync::atomic::{AtomicU64, Ordering};
721
722/// Source of process-unique scoped-userdata markers.
723static SCOPED_MARKER: AtomicU64 = AtomicU64::new(1);
724
725fn next_scoped_marker() -> u64 {
726    SCOPED_MARKER.fetch_add(1, Ordering::Relaxed)
727}
728
729/// The fixed leading layout of a scoped userdata wrapper: a unique marker used
730/// to recognise the instance (in place of a `TypeId`).
731#[repr(C)]
732struct ScopedHeader {
733    marker: u64,
734}
735
736/// Scoped userdata storage: a unique marker followed by the data cell. Shares
737/// `#[repr(C)]` with [`ScopedHeader`] so the marker is at offset 0 for any `T`.
738#[repr(C)]
739struct ScopedCell<T> {
740    marker: u64,
741    cell: RefCell<Option<T>>,
742}
743
744/// Destructor for the [`ScopedCell<T>`] stored inside a scoped userdata.
745unsafe extern "C" fn scoped_userdata_dtor<T>(ptr: *mut c_void) {
746    if !ptr.is_null() {
747        unsafe { core::ptr::drop_in_place(ptr as *mut ScopedCell<T>) };
748    }
749}
750
751/// Recover `&ScopedCell<T>` from a `self` userdata [`Value`] (Lua argument 1),
752/// verifying the per-instance `marker`. A mismatch is an
753/// [`Error::UserDataTypeMismatch`].
754///
755/// # Safety
756/// The caller guarantees that any userdata carrying `marker` was created as a
757/// `ScopedCell<T>` for this exact `T` (which holds because each marker is handed
758/// out to exactly one `create_scoped_userdata::<T>` call).
759unsafe fn recover_scoped_cell<'a, T>(
760    lua: &Lua,
761    value: &Value,
762    marker: u64,
763) -> Result<&'a ScopedCell<T>> {
764    match value {
765        Value::UserData(ud) => {
766            let state = lua.state();
767            unsafe {
768                ud.reference.push();
769                let ptr = lua_touserdata(state, -1);
770                lua_pop(state, 1);
771                if ptr.is_null() {
772                    return Err(Error::UserDataTypeMismatch);
773                }
774                let header = &*(ptr as *const ScopedHeader);
775                if header.marker != marker {
776                    return Err(Error::UserDataTypeMismatch);
777                }
778                Ok(&*(ptr as *const ScopedCell<T>))
779            }
780        }
781        _ => Err(Error::UserDataTypeMismatch),
782    }
783}
784
785/// A concrete [`UserDataMethods`] / [`UserDataFields`] implementation for a
786/// scoped (non-`'static`) userdata instance: identical surface to [`Collector`],
787/// but it recovers the data cell by the per-instance `marker` rather than a
788/// `TypeId`, so it works for non-`'static` `T`.
789struct ScopedCollector<T> {
790    marker: u64,
791    methods: Vec<Registered>,
792    fields: Vec<FieldEntry>,
793    _phantom: PhantomData<T>,
794}
795
796impl<T> ScopedCollector<T> {
797    fn new(marker: u64) -> Self {
798        ScopedCollector {
799            marker,
800            methods: Vec::new(),
801            fields: Vec::new(),
802            _phantom: PhantomData,
803        }
804    }
805}
806
807impl<T> UserDataMethods<T> for ScopedCollector<T> {
808    fn add_method<M, A, R>(&mut self, name: impl Into<String>, method: M)
809    where
810        M: Fn(&Lua, &T, A) -> Result<R> + MaybeSend + 'static,
811        A: FromLuaMulti,
812        R: IntoLuaMulti,
813    {
814        let marker = self.marker;
815        let callback: BoxedCallback = Box::new(move |lua, mut args| {
816            let this = args.pop_front().unwrap_or(Value::Nil);
817            let cell = unsafe { recover_scoped_cell::<T>(lua, &this, marker)? };
818            let a = A::from_lua_multi(args, lua)?;
819            let borrowed = cell
820                .cell
821                .try_borrow()
822                .map_err(|_| Error::UserDataBorrowError)?;
823            let data = borrowed.as_ref().ok_or(Error::UserDataDestructed)?;
824            let r = method(lua, data, a)?;
825            r.into_lua_multi(lua)
826        });
827        self.methods.push(Registered {
828            name: name.into(),
829            is_meta: false,
830            callback,
831        });
832    }
833
834    fn add_method_mut<M, A, R>(&mut self, name: impl Into<String>, method: M)
835    where
836        M: Fn(&Lua, &mut T, A) -> Result<R> + MaybeSend + 'static,
837        A: FromLuaMulti,
838        R: IntoLuaMulti,
839    {
840        let marker = self.marker;
841        let callback: BoxedCallback = Box::new(move |lua, mut args| {
842            let this = args.pop_front().unwrap_or(Value::Nil);
843            let cell = unsafe { recover_scoped_cell::<T>(lua, &this, marker)? };
844            let a = A::from_lua_multi(args, lua)?;
845            let mut borrowed = cell
846                .cell
847                .try_borrow_mut()
848                .map_err(|_| Error::UserDataBorrowMutError)?;
849            let data = borrowed.as_mut().ok_or(Error::UserDataDestructed)?;
850            let r = method(lua, data, a)?;
851            r.into_lua_multi(lua)
852        });
853        self.methods.push(Registered {
854            name: name.into(),
855            is_meta: false,
856            callback,
857        });
858    }
859
860    fn add_function<F, A, R>(&mut self, name: impl Into<String>, function: F)
861    where
862        F: Fn(&Lua, A) -> Result<R> + MaybeSend + 'static,
863        A: FromLuaMulti,
864        R: IntoLuaMulti,
865    {
866        let callback: BoxedCallback = Box::new(move |lua, args| {
867            let a = A::from_lua_multi(args, lua)?;
868            let r = function(lua, a)?;
869            r.into_lua_multi(lua)
870        });
871        self.methods.push(Registered {
872            name: name.into(),
873            is_meta: false,
874            callback,
875        });
876    }
877
878    fn add_meta_method<M, A, R>(&mut self, name: impl Into<String>, method: M)
879    where
880        M: Fn(&Lua, &T, A) -> Result<R> + MaybeSend + 'static,
881        A: FromLuaMulti,
882        R: IntoLuaMulti,
883    {
884        let marker = self.marker;
885        let callback: BoxedCallback = Box::new(move |lua, mut args| {
886            let this = args.pop_front().unwrap_or(Value::Nil);
887            let cell = unsafe { recover_scoped_cell::<T>(lua, &this, marker)? };
888            let a = A::from_lua_multi(args, lua)?;
889            let borrowed = cell
890                .cell
891                .try_borrow()
892                .map_err(|_| Error::UserDataBorrowError)?;
893            let data = borrowed.as_ref().ok_or(Error::UserDataDestructed)?;
894            let r = method(lua, data, a)?;
895            r.into_lua_multi(lua)
896        });
897        self.methods.push(Registered {
898            name: name.into(),
899            is_meta: true,
900            callback,
901        });
902    }
903
904    fn add_meta_method_mut<M, A, R>(&mut self, name: impl Into<String>, method: M)
905    where
906        M: Fn(&Lua, &mut T, A) -> Result<R> + MaybeSend + 'static,
907        A: FromLuaMulti,
908        R: IntoLuaMulti,
909    {
910        let marker = self.marker;
911        let callback: BoxedCallback = Box::new(move |lua, mut args| {
912            let this = args.pop_front().unwrap_or(Value::Nil);
913            let cell = unsafe { recover_scoped_cell::<T>(lua, &this, marker)? };
914            let a = A::from_lua_multi(args, lua)?;
915            let mut borrowed = cell
916                .cell
917                .try_borrow_mut()
918                .map_err(|_| Error::UserDataBorrowMutError)?;
919            let data = borrowed.as_mut().ok_or(Error::UserDataDestructed)?;
920            let r = method(lua, data, a)?;
921            r.into_lua_multi(lua)
922        });
923        self.methods.push(Registered {
924            name: name.into(),
925            is_meta: true,
926            callback,
927        });
928    }
929}
930
931impl<T> UserDataFields<T> for ScopedCollector<T> {
932    fn add_field<V>(&mut self, name: impl Into<String>, value: V)
933    where
934        V: IntoLua + Clone + MaybeSend + 'static,
935    {
936        let callback: BoxedCallback = Box::new(move |lua, _args| {
937            let v = value.clone().into_lua(lua)?;
938            v.into_lua_multi(lua)
939        });
940        self.fields.push(FieldEntry {
941            name: name.into(),
942            is_get: true,
943            callback,
944        });
945    }
946
947    fn add_field_method_get<M, R>(&mut self, name: impl Into<String>, method: M)
948    where
949        M: Fn(&Lua, &T) -> Result<R> + MaybeSend + 'static,
950        R: IntoLua,
951    {
952        let marker = self.marker;
953        let callback: BoxedCallback = Box::new(move |lua, mut args| {
954            let this = args.pop_front().unwrap_or(Value::Nil);
955            let cell = unsafe { recover_scoped_cell::<T>(lua, &this, marker)? };
956            let borrowed = cell
957                .cell
958                .try_borrow()
959                .map_err(|_| Error::UserDataBorrowError)?;
960            let data = borrowed.as_ref().ok_or(Error::UserDataDestructed)?;
961            let r = method(lua, data)?;
962            r.into_lua_multi(lua)
963        });
964        self.fields.push(FieldEntry {
965            name: name.into(),
966            is_get: true,
967            callback,
968        });
969    }
970
971    fn add_field_method_set<M, A>(&mut self, name: impl Into<String>, method: M)
972    where
973        M: Fn(&Lua, &mut T, A) -> Result<()> + MaybeSend + 'static,
974        A: FromLua,
975    {
976        let marker = self.marker;
977        let callback: BoxedCallback = Box::new(move |lua, mut args| {
978            let this = args.pop_front().unwrap_or(Value::Nil);
979            let cell = unsafe { recover_scoped_cell::<T>(lua, &this, marker)? };
980            let val = A::from_lua(args.pop_front().unwrap_or(Value::Nil), lua)?;
981            let mut borrowed = cell
982                .cell
983                .try_borrow_mut()
984                .map_err(|_| Error::UserDataBorrowMutError)?;
985            let data = borrowed.as_mut().ok_or(Error::UserDataDestructed)?;
986            method(lua, data, val)?;
987            ().into_lua_multi(lua)
988        });
989        self.fields.push(FieldEntry {
990            name: name.into(),
991            is_get: false,
992            callback,
993        });
994    }
995
996    fn add_field_function_get<F, R>(&mut self, name: impl Into<String>, function: F)
997    where
998        F: Fn(&Lua, AnyUserData) -> Result<R> + MaybeSend + 'static,
999        R: IntoLua,
1000    {
1001        let callback: BoxedCallback = Box::new(move |lua, mut args| {
1002            let this = args.pop_front().unwrap_or(Value::Nil);
1003            let ud = AnyUserData::from_lua(this, lua)?;
1004            let r = function(lua, ud)?;
1005            r.into_lua_multi(lua)
1006        });
1007        self.fields.push(FieldEntry {
1008            name: name.into(),
1009            is_get: true,
1010            callback,
1011        });
1012    }
1013
1014    fn add_field_function_set<F, A>(&mut self, name: impl Into<String>, function: F)
1015    where
1016        F: Fn(&Lua, AnyUserData, A) -> Result<()> + MaybeSend + 'static,
1017        A: FromLua,
1018    {
1019        let callback: BoxedCallback = Box::new(move |lua, mut args| {
1020            let this = args.pop_front().unwrap_or(Value::Nil);
1021            let ud = AnyUserData::from_lua(this, lua)?;
1022            let val = A::from_lua(args.pop_front().unwrap_or(Value::Nil), lua)?;
1023            function(lua, ud, val)?;
1024            ().into_lua_multi(lua)
1025        });
1026        self.fields.push(FieldEntry {
1027            name: name.into(),
1028            is_get: false,
1029            callback,
1030        });
1031    }
1032}
1033
1034/// Build a scoped (non-`'static`) userdata wrapping `data`, with a metatable
1035/// assembled from `T::add_fields` + `T::add_methods`. Returns the
1036/// [`AnyUserData`] handle plus a closure that, when called, neutralises the
1037/// userdata (drops `data`, leaving later access to error with
1038/// [`Error::UserDataDestructed`]). The neutraliser is what `Lua::scope`
1039/// registers as a destructor.
1040///
1041/// # Safety
1042/// The returned [`AnyUserData`] must not be used to read `data` back out by
1043/// type (there is no `TypeId`); only metatable-driven method/field/meta dispatch
1044/// is supported. The scope must invoke the returned neutraliser before `data`'s
1045/// borrowed lifetime ends.
1046pub(crate) fn create_scoped_userdata<T: UserData>(
1047    lua: &Lua,
1048    data: T,
1049) -> Result<(AnyUserData, Box<dyn FnOnce()>)> {
1050    let state = lua.state();
1051    let marker = next_scoped_marker();
1052
1053    // 1. Collect fields, methods, and meta-methods (marker-keyed recovery).
1054    let mut collector = ScopedCollector::<T>::new(marker);
1055    T::add_fields(&mut collector);
1056    T::add_methods(&mut collector);
1057
1058    // 2. Build the method table + meta-methods + field getter/setter tables.
1059    let method_table = lua.create_table();
1060    let metatable = lua.create_table();
1061    for item in collector.methods {
1062        let func = create_callback_function(lua, item.callback)?;
1063        if item.is_meta {
1064            metatable.set(item.name, func)?;
1065        } else {
1066            method_table.set(item.name, func)?;
1067        }
1068    }
1069
1070    let has_fields = !collector.fields.is_empty();
1071    let getters = lua.create_table();
1072    let setters = lua.create_table();
1073    for field in collector.fields {
1074        let func = create_callback_function(lua, field.callback)?;
1075        if field.is_get {
1076            getters.set(field.name, func)?;
1077        } else {
1078            setters.set(field.name, func)?;
1079        }
1080    }
1081
1082    if has_fields {
1083        let getters_c = getters.clone();
1084        let methods_c = method_table.clone();
1085        let index_fn = lua.create_function(move |_, (ud, key): (Value, Value)| {
1086            let getter: Value = getters_c.get(key.clone())?;
1087            if let Value::Function(f) = getter {
1088                return f.call::<Value>(ud);
1089            }
1090            let m: Value = methods_c.get(key)?;
1091            Ok(m)
1092        })?;
1093        metatable.set("__index", index_fn)?;
1094
1095        let setters_c = setters.clone();
1096        let newindex_fn =
1097            lua.create_function(move |_, (ud, key, val): (Value, Value, Value)| {
1098                let setter: Value = setters_c.get(key.clone())?;
1099                if let Value::Function(f) = setter {
1100                    f.call::<()>((ud, val))?;
1101                    return Ok(());
1102                }
1103                let name = key.to_string().unwrap_or_default();
1104                Err(Error::RuntimeError(format!(
1105                    "attempt to set unknown field '{name}' on userdata"
1106                )))
1107            })?;
1108        metatable.set("__newindex", newindex_fn)?;
1109    } else {
1110        metatable.set("__index", method_table)?;
1111    }
1112
1113    // 3. Allocate the scoped userdata holding ScopedCell<T> and move `data` in.
1114    let ud = unsafe {
1115        let storage = lua_newuserdatadtor(
1116            state,
1117            core::mem::size_of::<ScopedCell<T>>(),
1118            Some(scoped_userdata_dtor::<T>),
1119        );
1120        if storage.is_null() {
1121            return Err(Error::runtime(
1122                "luaur-rt: failed to allocate scoped userdata",
1123            ));
1124        }
1125        core::ptr::write(
1126            storage as *mut ScopedCell<T>,
1127            ScopedCell {
1128                marker,
1129                cell: RefCell::new(Some(data)),
1130            },
1131        );
1132        metatable.push_to_stack();
1133        lua_setmetatable(state, -2);
1134        AnyUserData::from_ref(lua.pop_ref())
1135    };
1136
1137    // 4. Build the neutraliser: on scope exit, take the data out of the cell,
1138    //    dropping the (possibly borrowing) `T` while the cell memory stays valid.
1139    let ud_for_dtor = ud.clone();
1140    let neutralise: Box<dyn FnOnce()> = Box::new(move || {
1141        let state = ud_for_dtor.reference.state();
1142        unsafe {
1143            ud_for_dtor.reference.push();
1144            let ptr = lua_touserdata(state, -1);
1145            lua_pop(state, 1);
1146            if ptr.is_null() {
1147                return;
1148            }
1149            let cell = &*(ptr as *const ScopedCell<T>);
1150            // Drop the data (ends borrows). If currently borrowed (a method is
1151            // somehow live), `try_borrow_mut` fails and we leave it — but scope
1152            // exit only happens after `f` returns, so no method is in flight.
1153            if let Ok(mut guard) = cell.cell.try_borrow_mut() {
1154                let _ = guard.take();
1155            }
1156        }
1157    });
1158
1159    Ok((ud, neutralise))
1160}
1161
1162/// Build a userdata value wrapping `data`, with a metatable assembled from the
1163/// type's [`UserData::add_fields`] + [`UserData::add_methods`].
1164pub(crate) fn create_userdata<T: UserData + MaybeSend + MaybeSync + 'static>(
1165    lua: &Lua,
1166    data: T,
1167) -> Result<AnyUserData> {
1168    let state = lua.state();
1169
1170    // 1. Collect fields, methods, and meta-methods.
1171    let mut collector = Collector::<T>::new();
1172    T::add_fields(&mut collector);
1173    T::add_methods(&mut collector);
1174
1175    // 2. Build the method table + meta-methods, and the field getter/setter
1176    //    tables (if any fields were registered).
1177    let method_table = lua.create_table();
1178    let metatable = lua.create_table();
1179    for item in collector.methods {
1180        let func = create_callback_function(lua, item.callback)?;
1181        if item.is_meta {
1182            metatable.set(item.name, func)?;
1183        } else {
1184            method_table.set(item.name, func)?;
1185        }
1186    }
1187
1188    let has_fields = !collector.fields.is_empty();
1189    let getters = lua.create_table();
1190    let setters = lua.create_table();
1191    for field in collector.fields {
1192        let func = create_callback_function(lua, field.callback)?;
1193        if field.is_get {
1194            getters.set(field.name, func)?;
1195        } else {
1196            setters.set(field.name, func)?;
1197        }
1198    }
1199
1200    if has_fields {
1201        // __index dispatcher: try a field getter, then the method table.
1202        let getters_c = getters.clone();
1203        let methods_c = method_table.clone();
1204        let index_fn = lua.create_function(move |_, (ud, key): (Value, Value)| {
1205            let getter: Value = getters_c.get(key.clone())?;
1206            if let Value::Function(f) = getter {
1207                return f.call::<Value>(ud);
1208            }
1209            // Fall back to the method table.
1210            let m: Value = methods_c.get(key)?;
1211            Ok(m)
1212        })?;
1213        metatable.set("__index", index_fn)?;
1214
1215        // __newindex dispatcher: try a field setter, else raise.
1216        let setters_c = setters.clone();
1217        let newindex_fn =
1218            lua.create_function(move |_, (ud, key, val): (Value, Value, Value)| {
1219                let setter: Value = setters_c.get(key.clone())?;
1220                if let Value::Function(f) = setter {
1221                    f.call::<()>((ud, val))?;
1222                    return Ok(());
1223                }
1224                let name = key.to_string().unwrap_or_default();
1225                Err(Error::RuntimeError(format!(
1226                    "attempt to set unknown field '{name}' on userdata"
1227                )))
1228            })?;
1229        metatable.set("__newindex", newindex_fn)?;
1230    } else {
1231        // No fields: the metatable's __index is just the method table.
1232        metatable.set("__index", method_table)?;
1233    }
1234
1235    // 3. Allocate the userdata holding UserDataCell<T> and move `data` in.
1236    unsafe {
1237        let storage = lua_newuserdatadtor(
1238            state,
1239            core::mem::size_of::<UserDataCell<T>>(),
1240            Some(userdata_dtor::<T>),
1241        );
1242        if storage.is_null() {
1243            return Err(Error::runtime("luaur-rt: failed to allocate userdata"));
1244        }
1245        core::ptr::write(
1246            storage as *mut UserDataCell<T>,
1247            UserDataCell {
1248                type_id: TypeId::of::<T>(),
1249                cell: RefCell::new(Some(data)),
1250            },
1251        );
1252
1253        // 4. Set the metatable on the userdata (which is on top of stack).
1254        metatable.push_to_stack();
1255        lua_setmetatable(state, -2);
1256
1257        // 5. Take a ref to the userdata and return.
1258        Ok(AnyUserData::from_ref(lua.pop_ref()))
1259    }
1260}