Skip to content

Commit c741c7d

Browse files
committed
Allow #[mlua::userdata_impl] in a different module than the type
`#[derive(UserData)]` used to emit a private `__MluaUserDataRegistration_<Type>` struct next to the type and `#[mlua::userdata_impl]` referred to it by bare identifier, so the impl block had to sit in the same module as the derive. Splitting a type from its Lua bindings failed with: error[E0422]: cannot find struct, variant or union type `__MluaUserDataRegistration_A` in this scope Collect the registrations through the type instead: the derive implements the hidden `UserDataRegistrar` trait, which owns the type's inventory registry, and both macros submit `UserDataRegistration::<Type>` entries. The type is always in scope where the impl block is written, so registrations now resolve from any module, including through `use` aliases and fully qualified paths. Closes #726
1 parent 66b9f08 commit c741c7d

6 files changed

Lines changed: 97 additions & 11 deletions

File tree

docs/UserData.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ Use `#[mlua::userdata_impl]` on an `impl` block to register methods,
5757
metamethods, and constants. All items in the block are registered automatically,
5858
regardless of visibility.
5959

60+
Multiple `impl` blocks are supported and they do not need to live in the same
61+
module as the type definition.
62+
6063
## Method detection
6164

6265
The receiver type determines how a method is registered:

mlua_derive/src/userdata/mod.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -131,31 +131,31 @@ pub fn userdata_type(item: TokenStream) -> TokenStream {
131131
}
132132
}
133133

134-
let registration_type_name = format_ident!("__MluaUserDataRegistration_{type_name}");
135134
let register_fields_fn_name = format_ident!("__mlua_register_{type_name}_fields");
136135

137136
let output = quote! {
138-
#[doc(hidden)]
139-
#[allow(non_camel_case_types)]
140-
struct #registration_type_name {
141-
register: fn(&mut ::mlua::userdata::UserDataRegistry<#type_name>),
137+
// Registrations are collected through the type itself, so that `#[mlua::userdata_impl]`
138+
// can submit them from any module.
139+
impl ::mlua::userdata::UserDataRegistrar for #type_name {
140+
fn inventory_registry() -> &'static ::mlua::__inventory::Registry {
141+
static REGISTRY: ::mlua::__inventory::Registry = ::mlua::__inventory::Registry::new();
142+
&REGISTRY
143+
}
142144
}
143145

144-
::mlua::__inventory::collect!(#registration_type_name);
145-
146146
#[allow(non_snake_case)]
147147
fn #register_fields_fn_name(registry: &mut ::mlua::userdata::UserDataRegistry<#type_name>) {
148148
use ::mlua::userdata::UserDataFields as _;
149149
#(#field_registrations)*
150150
}
151151

152152
::mlua::__inventory::submit! {
153-
#registration_type_name { register: #register_fields_fn_name }
153+
::mlua::userdata::UserDataRegistration::<#type_name> { register: #register_fields_fn_name }
154154
}
155155

156156
impl ::mlua::userdata::UserData for #type_name {
157157
fn register(registry: &mut ::mlua::userdata::UserDataRegistry<Self>) {
158-
for item in ::mlua::__inventory::iter::<#registration_type_name> {
158+
for item in ::mlua::__inventory::iter::<::mlua::userdata::UserDataRegistration<#type_name>> {
159159
(item.register)(registry);
160160
}
161161
}

mlua_derive/src/userdata/userdata_impl.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,6 @@ pub fn userdata_impl(attr: TokenStream, item: TokenStream) -> TokenStream {
323323
static COUNTER: AtomicUsize = AtomicUsize::new(0);
324324
let unique_suffix = COUNTER.fetch_add(1, Ordering::Relaxed);
325325
let register_fn_name = format_ident!("__mlua_register_{type_name}_{unique_suffix}");
326-
let registration_type_name = format_ident!("__MluaUserDataRegistration_{type_name}");
327326

328327
let mut registration_calls = Vec::new();
329328
for item in &input.items {
@@ -522,7 +521,7 @@ pub fn userdata_impl(attr: TokenStream, item: TokenStream) -> TokenStream {
522521
}
523522

524523
::mlua::__inventory::submit! {
525-
#registration_type_name { register: #register_fn_name }
524+
::mlua::userdata::UserDataRegistration::<#type_path> { register: #register_fn_name }
526525
}
527526

528527
#input

src/userdata.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ pub(crate) use cell::UserDataStorage;
3333
pub use r#ref::{UserDataOwned, UserDataRef, UserDataRefMut};
3434
pub use registry::UserDataRegistry;
3535
pub(crate) use registry::{RawUserDataRegistry, UserDataProxy};
36+
#[cfg(feature = "macros")]
37+
#[doc(hidden)]
38+
pub use registry::{UserDataRegistrar, UserDataRegistration};
3639
pub(crate) use util::{
3740
TypeIdHints, borrow_userdata_scoped, borrow_userdata_scoped_mut, collect_userdata,
3841
init_userdata_metatable,

src/userdata/registry.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -677,6 +677,40 @@ lua_userdata_impl!(std::sync::Arc<parking_lot::Mutex<T>>);
677677
#[cfg(feature = "userdata-wrappers")]
678678
lua_userdata_impl!(std::sync::Arc<parking_lot::RwLock<T>>);
679679

680+
/// A single registration function collected by the `mlua` derive macros.
681+
///
682+
/// This is not part of the public API.
683+
#[cfg(feature = "macros")]
684+
#[doc(hidden)]
685+
pub struct UserDataRegistration<T> {
686+
pub register: fn(&mut UserDataRegistry<T>),
687+
}
688+
689+
/// Links a userdata type to the inventory registry holding its registrations.
690+
///
691+
/// Implemented by `#[derive(UserData)]`, which is what allows `#[mlua::userdata_impl]` to submit
692+
/// registrations through the type itself rather than through a name that is only reachable from the
693+
/// module where the type is defined.
694+
///
695+
/// This is not part of the public API.
696+
#[cfg(feature = "macros")]
697+
#[doc(hidden)]
698+
#[diagnostic::on_unimplemented(
699+
message = "`{Self}` is missing `#[derive(UserData)]`",
700+
note = "`#[mlua::userdata_impl]` requires the type to derive `UserData`"
701+
)]
702+
pub trait UserDataRegistrar: Sized + 'static {
703+
fn inventory_registry() -> &'static inventory::Registry;
704+
}
705+
706+
#[cfg(feature = "macros")]
707+
impl<T: UserDataRegistrar> inventory::Collect for UserDataRegistration<T> {
708+
#[inline]
709+
fn registry() -> &'static inventory::Registry {
710+
T::inventory_registry()
711+
}
712+
}
713+
680714
#[cfg(test)]
681715
mod assertions {
682716
#[cfg(feature = "send")]

tests/userdata_macro.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -569,6 +569,53 @@ fn test_wildcard_params() {
569569
.unwrap();
570570
}
571571

572+
// The type definition and its Lua bindings can live in different modules.
573+
mod counter_type {
574+
use mlua::UserData;
575+
576+
#[derive(Clone, Debug, UserData)]
577+
pub struct Counter {
578+
pub count: u32,
579+
}
580+
}
581+
582+
mod counter_bindings {
583+
use mlua::Result;
584+
585+
use super::counter_type::Counter;
586+
587+
#[mlua::userdata_impl]
588+
impl Counter {
589+
#[lua(infallible)]
590+
fn new() -> Counter {
591+
Counter { count: 0 }
592+
}
593+
594+
fn increment(&mut self) -> Result<u32> {
595+
self.count += 1;
596+
Ok(self.count)
597+
}
598+
}
599+
}
600+
601+
#[test]
602+
fn test_impl_in_other_module() {
603+
let lua = Lua::new();
604+
lua.globals()
605+
.set("Counter", lua.create_proxy::<counter_type::Counter>().unwrap())
606+
.unwrap();
607+
lua.load(
608+
r#"
609+
local c = Counter.new()
610+
assert(c:increment() == 1, "increment should return 1")
611+
assert(c:increment() == 2, "increment should return 2")
612+
assert(c.count == 2, "field registered by the derive should be visible too")
613+
"#,
614+
)
615+
.exec()
616+
.unwrap();
617+
}
618+
572619
#[cfg(feature = "async")]
573620
mod async_tests {
574621
use mlua::{Lua, Result, UserData};

0 commit comments

Comments
 (0)