Skip to content

Commit 00b80a4

Browse files
committed
Add UserDataFields API.
Provide safe access to UserData metatable and allow to define custom metamethods..
1 parent c363fb9 commit 00b80a4

File tree

8 files changed

+989
-149
lines changed

8 files changed

+989
-149
lines changed

src/error.rs

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,18 @@ pub enum Error {
130130
/// [`AnyUserData`]: struct.AnyUserData.html
131131
/// [`UserData`]: trait.UserData.html
132132
UserDataBorrowMutError,
133+
/// A [`MetaMethod`] operation is restricted (typically for `__gc` or `__metatable`).
134+
///
135+
/// [`MetaMethod`]: enum.MetaMethod.html
136+
MetaMethodRestricted(StdString),
137+
/// A [`MetaMethod`] (eg. `__index` or `__newindex`) has invalid type.
138+
///
139+
/// [`MetaMethod`]: enum.MetaMethod.html
140+
MetaMethodTypeError {
141+
method: StdString,
142+
type_name: &'static str,
143+
message: Option<StdString>,
144+
},
133145
/// A `RegistryKey` produced from a different Lua state was used.
134146
MismatchedRegistryKey,
135147
/// A Rust callback returned `Err`, raising the contained `Error` as a Lua error.
@@ -197,22 +209,14 @@ impl fmt::Display for Error {
197209
fmt,
198210
"too many arguments to Function::bind"
199211
),
200-
Error::ToLuaConversionError {
201-
from,
202-
to,
203-
ref message,
204-
} => {
212+
Error::ToLuaConversionError { from, to, ref message } => {
205213
write!(fmt, "error converting {} to Lua {}", from, to)?;
206214
match *message {
207215
None => Ok(()),
208216
Some(ref message) => write!(fmt, " ({})", message),
209217
}
210218
}
211-
Error::FromLuaConversionError {
212-
from,
213-
to,
214-
ref message,
215-
} => {
219+
Error::FromLuaConversionError { from, to, ref message } => {
216220
write!(fmt, "error converting Lua {} to {}", from, to)?;
217221
match *message {
218222
None => Ok(()),
@@ -224,6 +228,14 @@ impl fmt::Display for Error {
224228
Error::UserDataDestructed => write!(fmt, "userdata has been destructed"),
225229
Error::UserDataBorrowError => write!(fmt, "userdata already mutably borrowed"),
226230
Error::UserDataBorrowMutError => write!(fmt, "userdata already borrowed"),
231+
Error::MetaMethodRestricted(ref method) => write!(fmt, "metamethod {} is restricted", method),
232+
Error::MetaMethodTypeError { ref method, type_name, ref message } => {
233+
write!(fmt, "metamethod {} has unsupported type {}", method, type_name)?;
234+
match *message {
235+
None => Ok(()),
236+
Some(ref message) => write!(fmt, " ({})", message),
237+
}
238+
}
227239
Error::MismatchedRegistryKey => {
228240
write!(fmt, "RegistryKey used from different Lua state")
229241
}

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
@@ -20,7 +20,9 @@ use crate::types::{
2020
Callback, HookCallback, Integer, LightUserData, LuaRef, MaybeSend, Number, RegistryKey,
2121
UserDataCell,
2222
};
23-
use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataMethods, UserDataWrapped};
23+
use crate::userdata::{
24+
AnyUserData, MetaMethod, UserData, UserDataFields, UserDataMethods, UserDataWrapped,
25+
};
2426
use crate::util::{
2527
assert_stack, callback_error, check_stack, get_gc_userdata, get_main_state, get_userdata,
2628
get_wrapped_error, init_error_registry, init_gc_metatable_for, init_userdata_metatable,
@@ -1511,37 +1513,86 @@ impl Lua {
15111513
}
15121514

15131515
let _sg = StackGuard::new(self.state);
1514-
assert_stack(self.state, 8);
1516+
assert_stack(self.state, 10);
15151517

1518+
let mut fields = StaticUserDataFields::default();
15161519
let mut methods = StaticUserDataMethods::default();
1520+
T::add_fields(&mut fields);
15171521
T::add_methods(&mut methods);
15181522

1523+
// Prepare metatable, add meta methods first and then meta fields
15191524
protect_lua_closure(self.state, 0, 1, |state| {
15201525
ffi::lua_newtable(state);
15211526
})?;
15221527
for (k, m) in methods.meta_methods {
1523-
push_string(self.state, k.name())?;
1528+
push_string(self.state, k.validate()?.name())?;
15241529
self.push_value(Value::Function(self.create_callback(m)?))?;
15251530

15261531
protect_lua_closure(self.state, 3, 1, |state| {
15271532
ffi::lua_rawset(state, -3);
15281533
})?;
15291534
}
1535+
for (k, f) in fields.meta_fields {
1536+
push_string(self.state, k.validate()?.name())?;
1537+
self.push_value(f(self)?)?;
1538+
1539+
protect_lua_closure(self.state, 3, 1, |state| {
1540+
ffi::lua_rawset(state, -3);
1541+
})?;
1542+
}
1543+
let metatable_index = ffi::lua_absindex(self.state, -1);
1544+
1545+
let mut extra_tables_count = 0;
1546+
1547+
let mut field_getters_index = None;
1548+
let has_field_getters = fields.field_getters.len() > 0;
1549+
if has_field_getters {
1550+
protect_lua_closure(self.state, 0, 1, |state| {
1551+
ffi::lua_newtable(state);
1552+
})?;
1553+
for (k, m) in fields.field_getters {
1554+
push_string(self.state, &k)?;
1555+
self.push_value(Value::Function(self.create_callback(m)?))?;
1556+
1557+
protect_lua_closure(self.state, 3, 1, |state| {
1558+
ffi::lua_rawset(state, -3);
1559+
})?;
1560+
}
1561+
field_getters_index = Some(ffi::lua_absindex(self.state, -1));
1562+
extra_tables_count += 1;
1563+
}
1564+
1565+
let mut field_setters_index = None;
1566+
let has_field_setters = fields.field_setters.len() > 0;
1567+
if has_field_setters {
1568+
protect_lua_closure(self.state, 0, 1, |state| {
1569+
ffi::lua_newtable(state);
1570+
})?;
1571+
for (k, m) in fields.field_setters {
1572+
push_string(self.state, &k)?;
1573+
self.push_value(Value::Function(self.create_callback(m)?))?;
1574+
1575+
protect_lua_closure(self.state, 3, 1, |state| {
1576+
ffi::lua_rawset(state, -3);
1577+
})?;
1578+
}
1579+
field_setters_index = Some(ffi::lua_absindex(self.state, -1));
1580+
extra_tables_count += 1;
1581+
}
15301582

1583+
let mut methods_index = None;
15311584
#[cfg(feature = "async")]
1532-
let no_methods = methods.methods.is_empty() && methods.async_methods.is_empty();
1585+
let has_methods = methods.methods.len() > 0 || methods.async_methods.len() > 0;
15331586
#[cfg(not(feature = "async"))]
1534-
let no_methods = methods.methods.is_empty();
1535-
1536-
if no_methods {
1537-
init_userdata_metatable::<UserDataCell<T>>(self.state, -1, None)?;
1538-
} else {
1587+
let has_methods = methods.methods.len() > 0;
1588+
if has_methods {
15391589
protect_lua_closure(self.state, 0, 1, |state| {
15401590
ffi::lua_newtable(state);
15411591
})?;
15421592
for (k, m) in methods.methods {
15431593
push_string(self.state, &k)?;
15441594
self.push_value(Value::Function(self.create_callback(m)?))?;
1595+
15451596
protect_lua_closure(self.state, 3, 1, |state| {
15461597
ffi::lua_rawset(state, -3);
15471598
})?;
@@ -1550,15 +1601,26 @@ impl Lua {
15501601
for (k, m) in methods.async_methods {
15511602
push_string(self.state, &k)?;
15521603
self.push_value(Value::Function(self.create_async_callback(m)?))?;
1604+
15531605
protect_lua_closure(self.state, 3, 1, |state| {
15541606
ffi::lua_rawset(state, -3);
15551607
})?;
15561608
}
1557-
1558-
init_userdata_metatable::<UserDataCell<T>>(self.state, -2, Some(-1))?;
1559-
ffi::lua_pop(self.state, 1);
1609+
methods_index = Some(ffi::lua_absindex(self.state, -1));
1610+
extra_tables_count += 1;
15601611
}
15611612

1613+
init_userdata_metatable::<UserDataCell<T>>(
1614+
self.state,
1615+
metatable_index,
1616+
field_getters_index,
1617+
field_setters_index,
1618+
methods_index,
1619+
)?;
1620+
1621+
// Pop extra tables to get metatable on top of the stack
1622+
ffi::lua_pop(self.state, extra_tables_count);
1623+
15621624
let id = protect_lua_closure(self.state, 1, 0, |state| {
15631625
ffi::luaL_ref(state, ffi::LUA_REGISTRYINDEX)
15641626
})?;
@@ -2287,41 +2349,48 @@ impl<'lua, T: 'static + UserData> UserDataMethods<'lua, T> for StaticUserDataMet
22872349
.push((name.as_ref().to_vec(), Self::box_async_function(function)));
22882350
}
22892351

2290-
fn add_meta_method<A, R, M>(&mut self, meta: MetaMethod, method: M)
2352+
fn add_meta_method<S, A, R, M>(&mut self, meta: S, method: M)
22912353
where
2354+
S: Into<MetaMethod>,
22922355
A: FromLuaMulti<'lua>,
22932356
R: ToLuaMulti<'lua>,
22942357
M: 'static + MaybeSend + Fn(&'lua Lua, &T, A) -> Result<R>,
22952358
{
2296-
self.meta_methods.push((meta, Self::box_method(method)));
2359+
self.meta_methods
2360+
.push((meta.into(), Self::box_method(method)));
22972361
}
22982362

2299-
fn add_meta_method_mut<A, R, M>(&mut self, meta: MetaMethod, method: M)
2363+
fn add_meta_method_mut<S, A, R, M>(&mut self, meta: S, method: M)
23002364
where
2365+
S: Into<MetaMethod>,
23012366
A: FromLuaMulti<'lua>,
23022367
R: ToLuaMulti<'lua>,
23032368
M: 'static + MaybeSend + FnMut(&'lua Lua, &mut T, A) -> Result<R>,
23042369
{
2305-
self.meta_methods.push((meta, Self::box_method_mut(method)));
2370+
self.meta_methods
2371+
.push((meta.into(), Self::box_method_mut(method)));
23062372
}
23072373

2308-
fn add_meta_function<A, R, F>(&mut self, meta: MetaMethod, function: F)
2374+
fn add_meta_function<S, A, R, F>(&mut self, meta: S, function: F)
23092375
where
2376+
S: Into<MetaMethod>,
23102377
A: FromLuaMulti<'lua>,
23112378
R: ToLuaMulti<'lua>,
23122379
F: 'static + MaybeSend + Fn(&'lua Lua, A) -> Result<R>,
23132380
{
2314-
self.meta_methods.push((meta, Self::box_function(function)));
2381+
self.meta_methods
2382+
.push((meta.into(), Self::box_function(function)));
23152383
}
23162384

2317-
fn add_meta_function_mut<A, R, F>(&mut self, meta: MetaMethod, function: F)
2385+
fn add_meta_function_mut<S, A, R, F>(&mut self, meta: S, function: F)
23182386
where
2387+
S: Into<MetaMethod>,
23192388
A: FromLuaMulti<'lua>,
23202389
R: ToLuaMulti<'lua>,
23212390
F: 'static + MaybeSend + FnMut(&'lua Lua, A) -> Result<R>,
23222391
{
23232392
self.meta_methods
2324-
.push((meta, Self::box_function_mut(function)));
2393+
.push((meta.into(), Self::box_function_mut(function)));
23252394
}
23262395
}
23272396

@@ -2443,3 +2512,104 @@ impl<'lua, T: 'static + UserData> StaticUserDataMethods<'lua, T> {
24432512
})
24442513
}
24452514
}
2515+
2516+
struct StaticUserDataFields<'lua, T: 'static + UserData> {
2517+
field_getters: Vec<(Vec<u8>, Callback<'lua, 'static>)>,
2518+
field_setters: Vec<(Vec<u8>, Callback<'lua, 'static>)>,
2519+
meta_fields: Vec<(
2520+
MetaMethod,
2521+
Box<dyn Fn(&'lua Lua) -> Result<Value<'lua>> + 'static>,
2522+
)>,
2523+
_type: PhantomData<T>,
2524+
}
2525+
2526+
impl<'lua, T: 'static + UserData> Default for StaticUserDataFields<'lua, T> {
2527+
fn default() -> StaticUserDataFields<'lua, T> {
2528+
StaticUserDataFields {
2529+
field_getters: Vec::new(),
2530+
field_setters: Vec::new(),
2531+
meta_fields: Vec::new(),
2532+
_type: PhantomData,
2533+
}
2534+
}
2535+
}
2536+
2537+
impl<'lua, T: 'static + UserData> UserDataFields<'lua, T> for StaticUserDataFields<'lua, T> {
2538+
fn add_field_method_get<S, R, M>(&mut self, name: &S, method: M)
2539+
where
2540+
S: AsRef<[u8]> + ?Sized,
2541+
R: ToLua<'lua>,
2542+
M: 'static + MaybeSend + Fn(&'lua Lua, &T) -> Result<R>,
2543+
{
2544+
self.field_getters.push((
2545+
name.as_ref().to_vec(),
2546+
StaticUserDataMethods::box_method(move |lua, data, ()| method(lua, data)),
2547+
));
2548+
}
2549+
2550+
fn add_field_method_set<S, A, M>(&mut self, name: &S, method: M)
2551+
where
2552+
S: AsRef<[u8]> + ?Sized,
2553+
A: FromLua<'lua>,
2554+
M: 'static + MaybeSend + FnMut(&'lua Lua, &mut T, A) -> Result<()>,
2555+
{
2556+
self.field_setters.push((
2557+
name.as_ref().to_vec(),
2558+
StaticUserDataMethods::box_method_mut(method),
2559+
));
2560+
}
2561+
2562+
fn add_field_function_get<S, R, F>(&mut self, name: &S, function: F)
2563+
where
2564+
S: AsRef<[u8]> + ?Sized,
2565+
R: ToLua<'lua>,
2566+
F: 'static + MaybeSend + Fn(&'lua Lua, AnyUserData<'lua>) -> Result<R>,
2567+
{
2568+
self.field_getters.push((
2569+
name.as_ref().to_vec(),
2570+
StaticUserDataMethods::<T>::box_function(move |lua, data| function(lua, data)),
2571+
));
2572+
}
2573+
2574+
fn add_field_function_set<S, A, F>(&mut self, name: &S, mut function: F)
2575+
where
2576+
S: AsRef<[u8]> + ?Sized,
2577+
A: FromLua<'lua>,
2578+
F: 'static + MaybeSend + FnMut(&'lua Lua, AnyUserData<'lua>, A) -> Result<()>,
2579+
{
2580+
self.field_setters.push((
2581+
name.as_ref().to_vec(),
2582+
StaticUserDataMethods::<T>::box_function_mut(move |lua, (data, val)| {
2583+
function(lua, data, val)
2584+
}),
2585+
));
2586+
}
2587+
2588+
fn add_meta_field_with<S, R, F>(&mut self, meta: S, f: F)
2589+
where
2590+
S: Into<MetaMethod>,
2591+
R: ToLua<'lua>,
2592+
F: 'static + MaybeSend + Fn(&'lua Lua) -> Result<R>,
2593+
{
2594+
let meta = meta.into();
2595+
self.meta_fields.push((
2596+
meta.clone(),
2597+
Box::new(move |lua| {
2598+
let value = f(lua)?.to_lua(lua)?;
2599+
if meta == MetaMethod::Index || meta == MetaMethod::NewIndex {
2600+
match value {
2601+
Value::Nil | Value::Table(_) | Value::Function(_) => {}
2602+
_ => {
2603+
return Err(Error::MetaMethodTypeError {
2604+
method: meta.to_string(),
2605+
type_name: value.type_name(),
2606+
message: Some("expected nil, table or function".to_string()),
2607+
})
2608+
}
2609+
}
2610+
}
2611+
Ok(value)
2612+
}),
2613+
));
2614+
}
2615+
}

0 commit comments

Comments
 (0)