Skip to content

Commit 2b2df70

Browse files
committed
Add UserDataFields API.
Provide safe access to UserData metatable and allow to define custom metamethods..
1 parent cb1ac28 commit 2b2df70

File tree

8 files changed

+990
-149
lines changed

8 files changed

+990
-149
lines changed

src/error.rs

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,18 @@ pub enum Error {
131131
/// [`AnyUserData`]: struct.AnyUserData.html
132132
/// [`UserData`]: trait.UserData.html
133133
UserDataBorrowMutError,
134+
/// A [`MetaMethod`] operation is restricted (typically for `__gc` or `__metatable`).
135+
///
136+
/// [`MetaMethod`]: enum.MetaMethod.html
137+
MetaMethodRestricted(StdString),
138+
/// A [`MetaMethod`] (eg. `__index` or `__newindex`) has invalid type.
139+
///
140+
/// [`MetaMethod`]: enum.MetaMethod.html
141+
MetaMethodTypeError {
142+
method: StdString,
143+
type_name: &'static str,
144+
message: Option<StdString>,
145+
},
134146
/// A `RegistryKey` produced from a different Lua state was used.
135147
MismatchedRegistryKey,
136148
/// A Rust callback returned `Err`, raising the contained `Error` as a Lua error.
@@ -203,22 +215,14 @@ impl fmt::Display for Error {
203215
fmt,
204216
"too many arguments to Function::bind"
205217
),
206-
Error::ToLuaConversionError {
207-
from,
208-
to,
209-
ref message,
210-
} => {
218+
Error::ToLuaConversionError { from, to, ref message } => {
211219
write!(fmt, "error converting {} to Lua {}", from, to)?;
212220
match *message {
213221
None => Ok(()),
214222
Some(ref message) => write!(fmt, " ({})", message),
215223
}
216224
}
217-
Error::FromLuaConversionError {
218-
from,
219-
to,
220-
ref message,
221-
} => {
225+
Error::FromLuaConversionError { from, to, ref message } => {
222226
write!(fmt, "error converting Lua {} to {}", from, to)?;
223227
match *message {
224228
None => Ok(()),
@@ -230,6 +234,14 @@ impl fmt::Display for Error {
230234
Error::UserDataDestructed => write!(fmt, "userdata has been destructed"),
231235
Error::UserDataBorrowError => write!(fmt, "userdata already mutably borrowed"),
232236
Error::UserDataBorrowMutError => write!(fmt, "userdata already borrowed"),
237+
Error::MetaMethodRestricted(ref method) => write!(fmt, "metamethod {} is restricted", method),
238+
Error::MetaMethodTypeError { ref method, type_name, ref message } => {
239+
write!(fmt, "metamethod {} has unsupported type {}", method, type_name)?;
240+
match *message {
241+
None => Ok(()),
242+
Some(ref message) => write!(fmt, " ({})", message),
243+
}
244+
}
233245
Error::MismatchedRegistryKey => {
234246
write!(fmt, "RegistryKey used from different Lua state")
235247
}

src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
//!
2424
//! The [`UserData`] trait can be implemented by user-defined types to make them available to Lua.
2525
//! Methods and operators to be used from Lua can be added using the [`UserDataMethods`] API.
26+
//! Fields are supported using the [`UserDataFields`] API.
2627
//!
2728
//! # Serde support
2829
//!
@@ -59,6 +60,7 @@
5960
//! [`FromLuaMulti`]: trait.FromLuaMulti.html
6061
//! [`Function`]: struct.Function.html
6162
//! [`UserData`]: trait.UserData.html
63+
//! [`UserDataFields`]: trait.UserDataFields.html
6264
//! [`UserDataMethods`]: trait.UserDataMethods.html
6365
//! [`LuaSerdeExt`]: serde/trait.LuaSerdeExt.html
6466
//! [`Value`]: enum.Value.html
@@ -109,7 +111,7 @@ pub use crate::string::String;
109111
pub use crate::table::{Table, TableExt, TablePairs, TableSequence};
110112
pub use crate::thread::{Thread, ThreadStatus};
111113
pub use crate::types::{Integer, LightUserData, Number, RegistryKey};
112-
pub use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataMethods};
114+
pub use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataFields, UserDataMethods};
113115
pub use crate::value::{FromLua, FromLuaMulti, MultiValue, Nil, ToLua, ToLuaMulti, Value};
114116

115117
#[cfg(feature = "async")]

src/lua.rs

Lines changed: 190 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ use crate::types::{
2121
Callback, HookCallback, Integer, LightUserData, LuaRef, MaybeSend, Number, RegistryKey,
2222
UserDataCell,
2323
};
24-
use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataMethods, UserDataWrapped};
24+
use crate::userdata::{
25+
AnyUserData, MetaMethod, UserData, UserDataFields, UserDataMethods, UserDataWrapped,
26+
};
2527
use crate::util::{
2628
assert_stack, callback_error, check_stack, get_gc_userdata, get_main_state, get_userdata,
2729
get_wrapped_error, init_error_registry, init_gc_metatable_for, init_userdata_metatable,
@@ -1523,37 +1525,86 @@ impl Lua {
15231525
}
15241526

15251527
let _sg = StackGuard::new(self.state);
1526-
assert_stack(self.state, 8);
1528+
assert_stack(self.state, 10);
15271529

1530+
let mut fields = StaticUserDataFields::default();
15281531
let mut methods = StaticUserDataMethods::default();
1532+
T::add_fields(&mut fields);
15291533
T::add_methods(&mut methods);
15301534

1535+
// Prepare metatable, add meta methods first and then meta fields
15311536
protect_lua_closure(self.state, 0, 1, |state| {
15321537
ffi::lua_newtable(state);
15331538
})?;
15341539
for (k, m) in methods.meta_methods {
1535-
push_string(self.state, k.name())?;
1540+
push_string(self.state, k.validate()?.name())?;
15361541
self.push_value(Value::Function(self.create_callback(m)?))?;
15371542

15381543
protect_lua_closure(self.state, 3, 1, |state| {
15391544
ffi::lua_rawset(state, -3);
15401545
})?;
15411546
}
1547+
for (k, f) in fields.meta_fields {
1548+
push_string(self.state, k.validate()?.name())?;
1549+
self.push_value(f(self)?)?;
1550+
1551+
protect_lua_closure(self.state, 3, 1, |state| {
1552+
ffi::lua_rawset(state, -3);
1553+
})?;
1554+
}
1555+
let metatable_index = ffi::lua_absindex(self.state, -1);
15421556

1557+
let mut extra_tables_count = 0;
1558+
1559+
let mut field_getters_index = None;
1560+
let has_field_getters = fields.field_getters.len() > 0;
1561+
if has_field_getters {
1562+
protect_lua_closure(self.state, 0, 1, |state| {
1563+
ffi::lua_newtable(state);
1564+
})?;
1565+
for (k, m) in fields.field_getters {
1566+
push_string(self.state, &k)?;
1567+
self.push_value(Value::Function(self.create_callback(m)?))?;
1568+
1569+
protect_lua_closure(self.state, 3, 1, |state| {
1570+
ffi::lua_rawset(state, -3);
1571+
})?;
1572+
}
1573+
field_getters_index = Some(ffi::lua_absindex(self.state, -1));
1574+
extra_tables_count += 1;
1575+
}
1576+
1577+
let mut field_setters_index = None;
1578+
let has_field_setters = fields.field_setters.len() > 0;
1579+
if has_field_setters {
1580+
protect_lua_closure(self.state, 0, 1, |state| {
1581+
ffi::lua_newtable(state);
1582+
})?;
1583+
for (k, m) in fields.field_setters {
1584+
push_string(self.state, &k)?;
1585+
self.push_value(Value::Function(self.create_callback(m)?))?;
1586+
1587+
protect_lua_closure(self.state, 3, 1, |state| {
1588+
ffi::lua_rawset(state, -3);
1589+
})?;
1590+
}
1591+
field_setters_index = Some(ffi::lua_absindex(self.state, -1));
1592+
extra_tables_count += 1;
1593+
}
1594+
1595+
let mut methods_index = None;
15431596
#[cfg(feature = "async")]
1544-
let no_methods = methods.methods.is_empty() && methods.async_methods.is_empty();
1597+
let has_methods = methods.methods.len() > 0 || methods.async_methods.len() > 0;
15451598
#[cfg(not(feature = "async"))]
1546-
let no_methods = methods.methods.is_empty();
1547-
1548-
if no_methods {
1549-
init_userdata_metatable::<UserDataCell<T>>(self.state, -1, None)?;
1550-
} else {
1599+
let has_methods = methods.methods.len() > 0;
1600+
if has_methods {
15511601
protect_lua_closure(self.state, 0, 1, |state| {
15521602
ffi::lua_newtable(state);
15531603
})?;
15541604
for (k, m) in methods.methods {
15551605
push_string(self.state, &k)?;
15561606
self.push_value(Value::Function(self.create_callback(m)?))?;
1607+
15571608
protect_lua_closure(self.state, 3, 1, |state| {
15581609
ffi::lua_rawset(state, -3);
15591610
})?;
@@ -1562,15 +1613,26 @@ impl Lua {
15621613
for (k, m) in methods.async_methods {
15631614
push_string(self.state, &k)?;
15641615
self.push_value(Value::Function(self.create_async_callback(m)?))?;
1616+
15651617
protect_lua_closure(self.state, 3, 1, |state| {
15661618
ffi::lua_rawset(state, -3);
15671619
})?;
15681620
}
1569-
1570-
init_userdata_metatable::<UserDataCell<T>>(self.state, -2, Some(-1))?;
1571-
ffi::lua_pop(self.state, 1);
1621+
methods_index = Some(ffi::lua_absindex(self.state, -1));
1622+
extra_tables_count += 1;
15721623
}
15731624

1625+
init_userdata_metatable::<UserDataCell<T>>(
1626+
self.state,
1627+
metatable_index,
1628+
field_getters_index,
1629+
field_setters_index,
1630+
methods_index,
1631+
)?;
1632+
1633+
// Pop extra tables to get metatable on top of the stack
1634+
ffi::lua_pop(self.state, extra_tables_count);
1635+
15741636
let ptr = ffi::lua_topointer(self.state, -1);
15751637
let id = protect_lua_closure(self.state, 1, 0, |state| {
15761638
ffi::luaL_ref(state, ffi::LUA_REGISTRYINDEX)
@@ -2317,41 +2379,48 @@ impl<'lua, T: 'static + UserData> UserDataMethods<'lua, T> for StaticUserDataMet
23172379
.push((name.as_ref().to_vec(), Self::box_async_function(function)));
23182380
}
23192381

2320-
fn add_meta_method<A, R, M>(&mut self, meta: MetaMethod, method: M)
2382+
fn add_meta_method<S, A, R, M>(&mut self, meta: S, method: M)
23212383
where
2384+
S: Into<MetaMethod>,
23222385
A: FromLuaMulti<'lua>,
23232386
R: ToLuaMulti<'lua>,
23242387
M: 'static + MaybeSend + Fn(&'lua Lua, &T, A) -> Result<R>,
23252388
{
2326-
self.meta_methods.push((meta, Self::box_method(method)));
2389+
self.meta_methods
2390+
.push((meta.into(), Self::box_method(method)));
23272391
}
23282392

2329-
fn add_meta_method_mut<A, R, M>(&mut self, meta: MetaMethod, method: M)
2393+
fn add_meta_method_mut<S, A, R, M>(&mut self, meta: S, method: M)
23302394
where
2395+
S: Into<MetaMethod>,
23312396
A: FromLuaMulti<'lua>,
23322397
R: ToLuaMulti<'lua>,
23332398
M: 'static + MaybeSend + FnMut(&'lua Lua, &mut T, A) -> Result<R>,
23342399
{
2335-
self.meta_methods.push((meta, Self::box_method_mut(method)));
2400+
self.meta_methods
2401+
.push((meta.into(), Self::box_method_mut(method)));
23362402
}
23372403

2338-
fn add_meta_function<A, R, F>(&mut self, meta: MetaMethod, function: F)
2404+
fn add_meta_function<S, A, R, F>(&mut self, meta: S, function: F)
23392405
where
2406+
S: Into<MetaMethod>,
23402407
A: FromLuaMulti<'lua>,
23412408
R: ToLuaMulti<'lua>,
23422409
F: 'static + MaybeSend + Fn(&'lua Lua, A) -> Result<R>,
23432410
{
2344-
self.meta_methods.push((meta, Self::box_function(function)));
2411+
self.meta_methods
2412+
.push((meta.into(), Self::box_function(function)));
23452413
}
23462414

2347-
fn add_meta_function_mut<A, R, F>(&mut self, meta: MetaMethod, function: F)
2415+
fn add_meta_function_mut<S, A, R, F>(&mut self, meta: S, function: F)
23482416
where
2417+
S: Into<MetaMethod>,
23492418
A: FromLuaMulti<'lua>,
23502419
R: ToLuaMulti<'lua>,
23512420
F: 'static + MaybeSend + FnMut(&'lua Lua, A) -> Result<R>,
23522421
{
23532422
self.meta_methods
2354-
.push((meta, Self::box_function_mut(function)));
2423+
.push((meta.into(), Self::box_function_mut(function)));
23552424
}
23562425
}
23572426

@@ -2473,3 +2542,104 @@ impl<'lua, T: 'static + UserData> StaticUserDataMethods<'lua, T> {
24732542
})
24742543
}
24752544
}
2545+
2546+
struct StaticUserDataFields<'lua, T: 'static + UserData> {
2547+
field_getters: Vec<(Vec<u8>, Callback<'lua, 'static>)>,
2548+
field_setters: Vec<(Vec<u8>, Callback<'lua, 'static>)>,
2549+
meta_fields: Vec<(
2550+
MetaMethod,
2551+
Box<dyn Fn(&'lua Lua) -> Result<Value<'lua>> + 'static>,
2552+
)>,
2553+
_type: PhantomData<T>,
2554+
}
2555+
2556+
impl<'lua, T: 'static + UserData> Default for StaticUserDataFields<'lua, T> {
2557+
fn default() -> StaticUserDataFields<'lua, T> {
2558+
StaticUserDataFields {
2559+
field_getters: Vec::new(),
2560+
field_setters: Vec::new(),
2561+
meta_fields: Vec::new(),
2562+
_type: PhantomData,
2563+
}
2564+
}
2565+
}
2566+
2567+
impl<'lua, T: 'static + UserData> UserDataFields<'lua, T> for StaticUserDataFields<'lua, T> {
2568+
fn add_field_method_get<S, R, M>(&mut self, name: &S, method: M)
2569+
where
2570+
S: AsRef<[u8]> + ?Sized,
2571+
R: ToLua<'lua>,
2572+
M: 'static + MaybeSend + Fn(&'lua Lua, &T) -> Result<R>,
2573+
{
2574+
self.field_getters.push((
2575+
name.as_ref().to_vec(),
2576+
StaticUserDataMethods::box_method(move |lua, data, ()| method(lua, data)),
2577+
));
2578+
}
2579+
2580+
fn add_field_method_set<S, A, M>(&mut self, name: &S, method: M)
2581+
where
2582+
S: AsRef<[u8]> + ?Sized,
2583+
A: FromLua<'lua>,
2584+
M: 'static + MaybeSend + FnMut(&'lua Lua, &mut T, A) -> Result<()>,
2585+
{
2586+
self.field_setters.push((
2587+
name.as_ref().to_vec(),
2588+
StaticUserDataMethods::box_method_mut(method),
2589+
));
2590+
}
2591+
2592+
fn add_field_function_get<S, R, F>(&mut self, name: &S, function: F)
2593+
where
2594+
S: AsRef<[u8]> + ?Sized,
2595+
R: ToLua<'lua>,
2596+
F: 'static + MaybeSend + Fn(&'lua Lua, AnyUserData<'lua>) -> Result<R>,
2597+
{
2598+
self.field_getters.push((
2599+
name.as_ref().to_vec(),
2600+
StaticUserDataMethods::<T>::box_function(move |lua, data| function(lua, data)),
2601+
));
2602+
}
2603+
2604+
fn add_field_function_set<S, A, F>(&mut self, name: &S, mut function: F)
2605+
where
2606+
S: AsRef<[u8]> + ?Sized,
2607+
A: FromLua<'lua>,
2608+
F: 'static + MaybeSend + FnMut(&'lua Lua, AnyUserData<'lua>, A) -> Result<()>,
2609+
{
2610+
self.field_setters.push((
2611+
name.as_ref().to_vec(),
2612+
StaticUserDataMethods::<T>::box_function_mut(move |lua, (data, val)| {
2613+
function(lua, data, val)
2614+
}),
2615+
));
2616+
}
2617+
2618+
fn add_meta_field_with<S, R, F>(&mut self, meta: S, f: F)
2619+
where
2620+
S: Into<MetaMethod>,
2621+
R: ToLua<'lua>,
2622+
F: 'static + MaybeSend + Fn(&'lua Lua) -> Result<R>,
2623+
{
2624+
let meta = meta.into();
2625+
self.meta_fields.push((
2626+
meta.clone(),
2627+
Box::new(move |lua| {
2628+
let value = f(lua)?.to_lua(lua)?;
2629+
if meta == MetaMethod::Index || meta == MetaMethod::NewIndex {
2630+
match value {
2631+
Value::Nil | Value::Table(_) | Value::Function(_) => {}
2632+
_ => {
2633+
return Err(Error::MetaMethodTypeError {
2634+
method: meta.to_string(),
2635+
type_name: value.type_name(),
2636+
message: Some("expected nil, table or function".to_string()),
2637+
})
2638+
}
2639+
}
2640+
}
2641+
Ok(value)
2642+
}),
2643+
));
2644+
}
2645+
}

0 commit comments

Comments
 (0)