Compare commits

..

27 Commits

Author SHA1 Message Date
Alex Orlenko 618874ef3c v0.5.0 2020-12-31 13:39:42 +00:00
Alex Orlenko 9f82cbe0c5 Update documentation 2020-12-31 13:39:38 +00:00
Alex Orlenko 1a81f8d447 Update compile tests 2020-12-29 23:20:06 +00:00
Alex Orlenko bedd430eb5 Re-export mlua_derive 2020-12-29 22:26:15 +00:00
Alex Orlenko afc41ab23c Add serialization example & update others 2020-12-29 21:39:34 +00:00
Alex Orlenko c5d0ccc433 Add reqwest http client example to fetch json 2020-12-28 15:21:45 +00:00
Alex Orlenko ce8955f5b9 Don't try to disable c modules without package loaded. Fix #24 2020-12-22 23:05:08 +00:00
Alex Orlenko 368c4428eb Wider lua-src and luajit-src versions 2020-12-14 00:51:35 +00:00
Alex Orlenko 6e2b687cb7 Serde support (serialize feature flag) 2020-12-14 00:51:26 +00:00
Alex Orlenko 661f8e592a Update num-traits dependency
Fix f32-to-f64 conversion tests
2020-11-04 21:22:24 +00:00
Alex Orlenko c8a7aa947c Update compile tests (temporary set TRYBUILD=overwrite) 2020-10-27 23:39:52 +00:00
Alex Orlenko a52135b62d Update compile tests error messages 2020-09-28 23:24:53 +01:00
Alex Orlenko 68378a3957 Merge pull request #15 from kaikalii/master
impl `ToLua` and `FromLua` for `HashSet` and `BTreeSet`
2020-09-28 23:14:17 +01:00
Kai Schmidt c65058a40a impl ToLua and FromLua for HashSet and BTreeSet 2020-09-27 17:54:47 -07:00
Alex Orlenko 93d63cef35 v0.4.2 2020-08-17 12:17:08 +01:00
Alex Orlenko b743245aba Update CHANGELOG 2020-08-17 11:14:40 +01:00
Alex Orlenko a172c021c9 Update set_memory_limit doc 2020-07-30 12:16:25 +01:00
Alex Orlenko de2c5cd9a9 Fix compilation warnings on nightly rust 2020-07-28 21:10:55 +01:00
Alex Orlenko d201beadc9 Add ChunkMode enum to mark chunks as text or binary 2020-07-28 21:04:21 +01:00
Alex Orlenko dd58cdad52 Add Function::dump() to dump lua function to a binary chunk 2020-07-27 23:26:33 +01:00
Alex Orlenko 5c8a5e0a5a Merge pull request #9 from HybridEidolon/bytecode-chunks
Make Lua::load load binary chunks when unsafe
2020-07-27 14:19:33 +01:00
Alex Orlenko e07c53eafe Update compile tests (2) 2020-07-27 13:51:21 +01:00
Alex Orlenko ad619390e1 Run compile tests on macos (was ubuntu-18.04) 2020-07-27 11:34:48 +01:00
Alex Orlenko 350602ab6e Update lua-src dependency to 5.4.0 2020-07-27 10:52:28 +01:00
Alex Orlenko 4b1bc88273 Update compile tests 2020-07-27 10:49:01 +01:00
Eidolon 883bf082b9 Make Lua::load load binary chunks when unsafe 2020-07-27 03:37:38 -05:00
Alex Orlenko 4265250cfd 0.4.1 release
Fix docs.rs build features
Update Cargo.toml description
2020-06-08 14:08:07 +01:00
45 changed files with 2314 additions and 254 deletions
+14 -19
View File
@@ -27,7 +27,7 @@ jobs:
- name: Build ${{ matrix.lua }} vendored
run: |
cargo build --release --features "${{ matrix.lua }} vendored"
cargo build --release --features "${{ matrix.lua }} vendored async send"
cargo build --release --features "${{ matrix.lua }} vendored async send serialize"
shell: bash
- name: Build ${{ matrix.lua }} pkg-config
if: ${{ matrix.os == 'ubuntu-18.04' && matrix.lua != 'lua54' }}
@@ -63,13 +63,13 @@ jobs:
if: ${{ matrix.os != 'macos-latest' || matrix.lua != 'luajit' }}
run: |
cargo test --release --features "${{ matrix.lua }} vendored"
cargo test --release --features "${{ matrix.lua }} vendored async send"
cargo test --release --features "${{ matrix.lua }} vendored async send serialize"
shell: bash
- name: Run compile tests
if: ${{ matrix.os == 'ubuntu-18.04' && matrix.lua == 'lua53' }}
- name: Run compile tests (macos lua53)
if: ${{ matrix.os == 'macos-latest' && matrix.lua == 'lua53' }}
run: |
cargo test --release --features "${{ matrix.lua }} vendored" -- --ignored
cargo test --release --features "${{ matrix.lua }} vendored async send" -- --ignored
TRYBUILD=overwrite cargo test --release --features "${{ matrix.lua }} vendored" -- --ignored
TRYBUILD=overwrite cargo test --release --features "${{ matrix.lua }} vendored async send serialize" -- --ignored
shell: bash
test_luajit_macos:
@@ -85,12 +85,12 @@ jobs:
override: true
- name: Run LuaJIT 2.0.5 tests
run: |
brew install luajit pkg-config
cargo test --tests --release --features "luajit async send" -- --test-threads=1
brew install luajit
cargo test --tests --release --features "luajit async send serialize" -- --test-threads=1
shell: bash
- name: Run LuaJIT vendored tests
run: |
cargo test --release --features "luajit vendored async send"
cargo test --release --features "luajit vendored async send serialize"
shell: bash
test_modules:
@@ -126,21 +126,16 @@ jobs:
needs: build
strategy:
matrix:
lua: [lua53, lua51, luajit]
lua: [lua53, luajit]
defaults:
run:
shell: bash.exe --login -eo pipefail "{0}"
env:
MSYSTEM: MINGW64
CHERE_INVOKING: 1
shell: msys2 {0}
steps:
- uses: msys2/setup-msys2@v2
- uses: actions/checkout@v2
- name: Set up shell
run: echo ::add-path::C:\msys64\usr\bin\
shell: pwsh
- name: Install Rust & Lua
run: |
pacman -S --noconfirm mingw-w64-x86_64-rust mingw-w64-x86_64-lua mingw-w64-x86_64-lua51 mingw-w64-x86_64-luajit
pacman -S --noconfirm mingw-w64-x86_64-rust mingw-w64-x86_64-lua mingw-w64-x86_64-luajit mingw-w64-x86_64-pkg-config
- name: Run ${{ matrix.lua }} module tests
run: |
(cd examples/module && cargo build --release --features "${{ matrix.lua }}")
@@ -174,4 +169,4 @@ jobs:
- uses: actions-rs/clippy-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --features "${{ matrix.lua }},async,send,vendored
args: --features "${{ matrix.lua }},vendored,async,send,serialize
+11
View File
@@ -1,3 +1,14 @@
## v0.5.0
- Serde support under `serialize` feature flag.
- Re-export `mlua_derive`.
## v0.4.2
- Added `Function::dump()` to dump lua function to a binary chunk
- Added `ChunkMode` enum to mark chunks as text or binary
- Updated `set_memory_limit` doc
## v0.4.0
- Lua 5.4 support with `MetaMethod::Close`.
+23 -12
View File
@@ -1,6 +1,6 @@
[package]
name = "mlua"
version = "0.4.0"
version = "0.5.0" # remember to update html_root_url and mlua_derive
authors = ["Aleksandr Orlenko <zxteam@pm.me>", "kyren <catherine@chucklefish.org>"]
edition = "2018"
repository = "https://github.com/khvzak/mlua"
@@ -12,16 +12,13 @@ license = "MIT"
links = "lua"
build = "build/main.rs"
description = """
High level bindings to Lua 5.1/5.2/5.3/5.4 (including LuaJIT)
High level bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT)
with async/await features and support of writing native lua modules in Rust.
"""
[badges]
# github-actions = { repository = "khvzak/mlua", workflow = "CI" }
maintenance = { status = "actively-developed" }
[package.metadata.docs.rs]
features = ["async"]
features = ["lua53", "async", "send", "serialize"]
rustdoc-args = ["--cfg", "docsrs"]
[workspace]
members = [
@@ -37,32 +34,38 @@ lua52 = []
lua51 = []
luajit = []
vendored = ["lua-src", "luajit-src"]
module = []
module = ["mlua_derive"]
async = ["futures-core", "futures-task", "futures-util"]
send = []
serialize = ["serde", "erased-serde"]
[dependencies]
mlua_derive = { version = "0.5", optional = true, path = "mlua_derive" }
bstr = { version = "0.2", features = ["std"], default_features = false }
lazy_static = { version = "1.4" }
num-traits = { version = "0.2.11" }
num-traits = { version = "0.2.14" }
futures-core = { version = "0.3.5", optional = true }
futures-task = { version = "0.3.5", optional = true }
futures-util = { version = "0.3.5", optional = true }
serde = { version = "1.0", optional = true }
erased-serde = { version = "0.3", optional = true }
[build-dependencies]
cc = { version = "1.0" }
pkg-config = { version = "0.3.17" }
lua-src = { version = "535.0.4", optional = true }
luajit-src = { version = "210.1.0", optional = true }
lua-src = { version = ">= 540.0.0, < 550.0.0", optional = true }
luajit-src = { version = ">= 210.1.0, < 220.0.0", optional = true }
[dev-dependencies]
rustyline = "6.0"
rustyline = "7.0"
criterion = "0.3"
trybuild = "1.0"
futures = "0.3.5"
hyper = "0.13"
reqwest = { version = "0.10", features = ["json"] }
tokio = { version = "0.2", features = ["full"] }
futures-timer = "3.0"
serde_json = "1.0"
[[bench]]
name = "benchmark"
@@ -72,6 +75,10 @@ harness = false
name = "async_http_client"
required-features = ["async"]
[[example]]
name = "async_http_reqwest"
required-features = ["async", "serialize"]
[[example]]
name = "async_http_server"
required-features = ["async", "send"]
@@ -79,3 +86,7 @@ required-features = ["async", "send"]
[[example]]
name = "async_tcp_server"
required-features = ["async"]
[[example]]
name = "serialize"
required-features = ["serialize"]
+14 -6
View File
@@ -33,9 +33,20 @@ Starting from v0.3, `mlua` supports async/await for all Lua versions. This works
**Examples**:
- [HTTP Client](examples/async_http_client.rs)
- [HTTP Client (json)](examples/async_http_reqwest.rs)
- [HTTP Server](examples/async_http_server.rs)
- [TCP Server](examples/async_tcp_server.rs)
### Serialization (serde) support
With `serialize` feature flag enabled, `mlua` allows you to serialize/deserialize any type that implements [`serde::Serialize`] and [`serde::Deserialize`] into/from [`mlua::Value`]. In addition `mlua` provides [`serde::Serialize`] trait implementation for it (including user data support).
[Example](examples/serialize.rs)
[`serde::Serialize`]: https://docs.serde.rs/serde/ser/trait.Serialize.html
[`serde::Deserialize`]: https://docs.serde.rs/serde/de/trait.Deserialize.html
[`mlua::Value`]: https://docs.rs/mlua/latest/mlua/enum.Value.html
### Compiling
You have to enable one of the features `lua54`, `lua53`, `lua52`, `lua51` or `luajit`, according to the choosen Lua version.
@@ -59,7 +70,7 @@ Add to `Cargo.toml` :
``` toml
[dependencies]
mlua = { version = "0.4", features = ["lua53"] }
mlua = { version = "0.5", features = ["lua53"] }
```
`main.rs`
@@ -93,15 +104,12 @@ Add to `Cargo.toml` :
crate-type = ["cdylib"]
[dependencies]
mlua = { version = "0.4", features = ["lua53", "module"] }
mlua_derive = "0.4"
mlua = { version = "0.5", features = ["lua53", "module"] }
```
`lib.rs` :
``` rust
#[macro_use]
extern crate mlua_derive;
use mlua::prelude::*;
fn hello(_: &Lua, name: String) -> LuaResult<()> {
@@ -109,7 +117,7 @@ fn hello(_: &Lua, name: String) -> LuaResult<()> {
Ok(())
}
#[lua_module]
#[mlua::lua_module]
fn my_module(lua: &Lua) -> LuaResult<LuaTable> {
let exports = lua.create_table()?;
exports.set("hello", lua.create_function(hello)?)?;
+2 -1
View File
@@ -5,7 +5,8 @@
#[cfg_attr(
all(feature = "luajit", target_os = "macos", target_arch = "x86_64"),
link_args = "-pagezero_size 10000 -image_base 100000000"
link_args = "-pagezero_size 10000 -image_base 100000000",
allow(unused_attributes)
)]
extern "system" {}
+1 -1
View File
@@ -61,7 +61,7 @@ async fn main() -> Result<()> {
let f = lua
.load(
r#"
local res = fetch_url(...);
local res = fetch_url(...)
print(res.status)
for key, vals in pairs(res.headers) do
for _, val in ipairs(vals) do
+41
View File
@@ -0,0 +1,41 @@
use mlua::{Error, Lua, LuaSerdeExt, Result};
#[tokio::main]
async fn main() -> Result<()> {
let lua = Lua::new();
let globals = lua.globals();
globals.set("null", lua.null()?)?;
let fetch_json = lua.create_async_function(|lua, uri: String| async move {
let resp = reqwest::get(&uri)
.await
.and_then(|resp| resp.error_for_status())
.map_err(Error::external)?;
let json = resp
.json::<serde_json::Value>()
.await
.map_err(Error::external)?;
lua.to_value(&json)
})?;
globals.set("fetch_json", fetch_json)?;
let f = lua
.load(
r#"
function print_r(t, indent)
local indent = indent or ''
for k, v in pairs(t) do
io.write(indent, tostring(k))
if type(v) == "table" then io.write(':\n') print_r(v, indent..' ')
else io.write(': ', v == null and "null" or tostring(v), '\n') end
end
end
local res = fetch_json(...)
print_r(res)
"#,
)
.into_function()?;
f.call_async("https://httpbin.org/anything?arg0=val0").await
}
+2 -2
View File
@@ -2,8 +2,8 @@ use std::net::Shutdown;
use std::sync::Arc;
use bstr::BString;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::{TcpListener, TcpStream};
use tokio::prelude::*;
use tokio::sync::Mutex;
use tokio::task;
@@ -63,7 +63,7 @@ impl UserData for LuaTcpStream {
async fn run_server(lua: &'static Lua) -> Result<()> {
let spawn = lua.create_function(move |_, func: Function| {
task::spawn_local(async move { func.call_async::<_, ()>(()).await.unwrap() });
task::spawn_local(async move { func.call_async::<_, ()>(()).await });
Ok(())
})?;
+27
View File
@@ -164,6 +164,33 @@ fn main() -> Result<()> {
< f32::EPSILON
);
// Normally, Rust types passed to `Lua` must be `'static`, because there is no way to be
// sure of their lifetime inside the Lua state. There is, however, a limited way to lift this
// requirement. You can call `Lua::scope` to create userdata and callbacks types that only live
// for as long as the call to scope, but do not have to be `'static` (and `Send`).
{
let mut rust_val = 0;
lua.scope(|scope| {
// We create a 'sketchy' Lua callback that holds a mutable reference to the variable
// `rust_val`. Outside of a `Lua::scope` call, this would not be allowed
// because it could be unsafe.
lua.globals().set(
"sketchy",
scope.create_function_mut(|_, ()| {
rust_val = 42;
Ok(())
})?,
)?;
lua.load("sketchy()").exec()
})?;
assert_eq!(rust_val, 42);
}
// We were able to run our 'sketchy' function inside the scope just fine. However, if we
// try to run our 'sketchy' function outside of the scope, the function we created will have
// been invalidated and we will generate an error. If our function wasn't invalidated, we
-1
View File
@@ -17,4 +17,3 @@ vendored = ["mlua/vendored"]
[dependencies]
mlua = { path = "../..", features = ["module"] }
mlua_derive = { path = "../../mlua_derive" }
+1 -1
View File
@@ -8,7 +8,7 @@ fn used_memory(lua: &Lua, _: ()) -> LuaResult<usize> {
Ok(lua.used_memory())
}
#[mlua_derive::lua_module]
#[mlua::lua_module]
fn rust_module(lua: &Lua) -> LuaResult<LuaTable> {
let exports = lua.create_table()?;
exports.set("sum", lua.create_function(sum)?)?;
+66
View File
@@ -0,0 +1,66 @@
use mlua::{Error, Lua, LuaSerdeExt, Result, UserData, Value};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
enum Transmission {
Manual,
Automatic,
}
#[derive(Serialize, Deserialize)]
struct Engine {
v: u32,
kw: u32,
}
#[derive(Serialize, Deserialize)]
struct Car {
active: bool,
model: String,
transmission: Transmission,
engine: Engine,
}
impl UserData for Car {}
fn main() -> Result<()> {
let lua = Lua::new();
let globals = lua.globals();
// Create Car struct from a Lua table
let car: Car = lua.from_value(lua.load(r#"
{active = true, model = "Volkswagen Golf", transmission = "Automatic", engine = {v = 1499, kw = 90}}
"#).eval()?)?;
// Set it as (serializable) userdata
globals.set("null", lua.null()?)?;
globals.set("array_mt", lua.array_metatable()?)?;
globals.set("car", lua.create_ser_userdata(car)?)?;
// Create a Lua table with multiple data types
let val: Value = lua
.load(r#"{driver = "Boris", car = car, price = null, points = setmetatable({}, array_mt)}"#)
.eval()?;
// Serialize the table above to JSON
let json_str = serde_json::to_string(&val).map_err(Error::external)?;
println!("{}", json_str);
// Create Lua Value from JSON (or any serializable type)
let json = serde_json::json!({
"key": "value",
"null": null,
"array": [],
});
globals.set("json_value", lua.to_value(&json)?)?;
lua.load(
r#"
assert(json_value["key"] == "value")
assert(json_value["null"] == null)
assert(#(json_value["array"]) == 0)
"#,
)
.exec()?;
Ok(())
}
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "mlua_derive"
version = "0.4.0"
version = "0.5.0"
authors = ["Aleksandr Orlenko <zxteam@pm.me>"]
edition = "2018"
description = "Procedural macros for the mlua crate."
+51 -1
View File
@@ -1,4 +1,4 @@
use std::collections::{BTreeMap, HashMap};
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
use std::ffi::{CStr, CString};
use std::hash::{BuildHasher, Hash};
use std::string::String as StdString;
@@ -481,6 +481,56 @@ impl<'lua, K: Ord + FromLua<'lua>, V: FromLua<'lua>> FromLua<'lua> for BTreeMap<
}
}
impl<'lua, T: Eq + Hash + ToLua<'lua>, S: BuildHasher> ToLua<'lua> for HashSet<T, S> {
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::Table(lua.create_table_from(
self.into_iter().map(|val| (val, true)),
)?))
}
}
impl<'lua, T: Eq + Hash + FromLua<'lua>, S: BuildHasher + Default> FromLua<'lua> for HashSet<T, S> {
fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result<Self> {
if let Value::Table(table) = value {
table
.pairs::<T, Value<'lua>>()
.map(|res| res.map(|(k, _)| k))
.collect()
} else {
Err(Error::FromLuaConversionError {
from: value.type_name(),
to: "HashSet",
message: Some("expected table".to_string()),
})
}
}
}
impl<'lua, T: Ord + ToLua<'lua>> ToLua<'lua> for BTreeSet<T> {
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::Table(lua.create_table_from(
self.into_iter().map(|val| (val, true)),
)?))
}
}
impl<'lua, T: Ord + FromLua<'lua>> FromLua<'lua> for BTreeSet<T> {
fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result<Self> {
if let Value::Table(table) = value {
table
.pairs::<T, Value<'lua>>()
.map(|res| res.map(|(k, _)| k))
.collect()
} else {
Err(Error::FromLuaConversionError {
from: value.type_name(),
to: "BTreeSet",
message: Some("expected table".to_string()),
})
}
}
}
impl<'lua, T: ToLua<'lua>> ToLua<'lua> for Option<T> {
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
match self {
+38
View File
@@ -105,6 +105,13 @@ pub enum Error {
/// [`AnyUserData`]: struct.AnyUserData.html
/// [`UserDataMethods`]: trait.UserDataMethods.html
UserDataTypeMismatch,
/// An [`AnyUserData`] borrow failed because it has been destructed.
///
/// This error can happen either due to to being destructed in a previous __gc, or due to being
/// destructed from exiting a `Lua::scope` call.
///
/// [`AnyUserData`]: struct.AnyUserData.html
UserDataDestructed,
/// An [`AnyUserData`] immutable borrow failed because it is already borrowed mutably.
///
/// This error can occur when a method on a [`UserData`] type calls back into Lua, which then
@@ -132,6 +139,14 @@ pub enum Error {
/// Original error returned by the Rust code.
cause: Arc<Error>,
},
/// Serialization error.
#[cfg(feature = "serialize")]
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
SerializeError(StdString),
/// Deserialization error.
#[cfg(feature = "serialize")]
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
DeserializeError(StdString),
/// A custom error.
///
/// This can be used for returning user-defined errors from callbacks.
@@ -206,6 +221,7 @@ impl fmt::Display for Error {
}
Error::CoroutineInactive => write!(fmt, "cannot resume inactive coroutine"),
Error::UserDataTypeMismatch => write!(fmt, "userdata is not expected type"),
Error::UserDataDestructed => write!(fmt, "userdata has been destructed"),
Error::UserDataBorrowError => write!(fmt, "userdata already mutably borrowed"),
Error::UserDataBorrowMutError => write!(fmt, "userdata already borrowed"),
Error::MismatchedRegistryKey => {
@@ -214,6 +230,14 @@ impl fmt::Display for Error {
Error::CallbackError { ref traceback, .. } => {
write!(fmt, "callback error: {}", traceback)
}
#[cfg(feature = "serialize")]
Error::SerializeError(ref err) => {
write!(fmt, "serialize error: {}", err)
},
#[cfg(feature = "serialize")]
Error::DeserializeError(ref err) => {
write!(fmt, "deserialize error: {}", err)
},
Error::ExternalError(ref err) => write!(fmt, "{}", err),
}
}
@@ -289,3 +313,17 @@ impl std::convert::From<Utf8Error> for Error {
Error::external(err)
}
}
#[cfg(feature = "serialize")]
impl serde::ser::Error for Error {
fn custom<T: fmt::Display>(msg: T) -> Self {
Self::SerializeError(msg.to_string())
}
}
#[cfg(feature = "serialize")]
impl serde::de::Error for Error {
fn custom<T: fmt::Display>(msg: T) -> Self {
Self::DeserializeError(msg.to_string())
}
}
+3
View File
@@ -38,7 +38,10 @@ pub use super::glue::LUA_REGISTRYINDEX;
#[cfg(any(feature = "lua51", feature = "luajit"))]
pub use super::glue::{LUA_ENVIRONINDEX, LUA_GLOBALSINDEX};
#[cfg(not(feature = "luajit"))]
pub const LUA_SIGNATURE: &[u8] = b"\x1bLua";
#[cfg(feature = "luajit")]
pub const LUA_SIGNATURE: &[u8] = b"\x1bLJ";
// option for multiple returns in 'lua_pcall' and 'lua_call'
pub const LUA_MULTRET: c_int = -1;
+2 -2
View File
@@ -211,8 +211,8 @@ pub use self::lua::{
LUA_HOOKCOUNT, LUA_HOOKLINE, LUA_HOOKRET, LUA_HOOKTAILCALL, LUA_MASKCALL, LUA_MASKCOUNT,
LUA_MASKLINE, LUA_MASKRET, LUA_MINSTACK, LUA_MULTRET, LUA_OK, LUA_OPADD, LUA_OPDIV, LUA_OPEQ,
LUA_OPLE, LUA_OPLT, LUA_OPMOD, LUA_OPMUL, LUA_OPPOW, LUA_OPSUB, LUA_OPUNM, LUA_REGISTRYINDEX,
LUA_TBOOLEAN, LUA_TFUNCTION, LUA_TLIGHTUSERDATA, LUA_TNIL, LUA_TNONE, LUA_TNUMBER, LUA_TSTRING,
LUA_TTABLE, LUA_TTHREAD, LUA_TUSERDATA, LUA_YIELD,
LUA_SIGNATURE, LUA_TBOOLEAN, LUA_TFUNCTION, LUA_TLIGHTUSERDATA, LUA_TNIL, LUA_TNONE,
LUA_TNUMBER, LUA_TSTRING, LUA_TTABLE, LUA_TTHREAD, LUA_TUSERDATA, LUA_YIELD,
};
#[cfg(any(feature = "lua54", feature = "lua53"))]
+39 -2
View File
@@ -1,5 +1,5 @@
use std::os::raw::c_int;
use std::ptr;
use std::os::raw::{c_int, c_void};
use std::{ptr, slice};
use crate::error::{Error, Result};
use crate::ffi;
@@ -119,6 +119,7 @@ impl<'lua> Function<'lua> {
///
/// [`AsyncThread`]: struct.AsyncThread.html
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub fn call_async<'fut, A, R>(&self, args: A) -> LocalBoxFuture<'fut, Result<R>>
where
'lua: 'fut,
@@ -205,6 +206,42 @@ impl<'lua> Function<'lua> {
Ok(Function(lua.pop_ref()))
}
}
/// Dumps the function as a binary chunk.
///
/// If `strip` is true, the binary representation may not include all debug information
/// about the function, to save space.
pub fn dump(&self, strip: bool) -> Result<Vec<u8>> {
unsafe extern "C" fn writer(
_state: *mut ffi::lua_State,
buf: *const c_void,
buf_len: usize,
data: *mut c_void,
) -> c_int {
let data = &mut *(data as *mut Vec<u8>);
let buf = slice::from_raw_parts(buf as *const u8, buf_len);
data.extend_from_slice(buf);
0
}
let lua = self.0.lua;
let mut data: Vec<u8> = Vec::new();
unsafe {
let _sg = StackGuard::new(lua.state);
assert_stack(lua.state, 1);
lua.push_ref(&self.0);
let strip = if strip { 1 } else { 0 };
ffi::lua_dump(
lua.state,
writer,
&mut data as *mut Vec<u8> as *mut c_void,
strip,
);
ffi::lua_pop(lua.state, 1);
}
Ok(data)
}
}
impl<'lua> PartialEq for Function<'lua> {
+1 -1
View File
@@ -37,7 +37,7 @@ impl<'a> Debug<'a> {
}
}
/// Corresponds to the `n` what mask.
/// Corresponds to the `S` what mask.
pub fn source(&self) -> DebugSource<'a> {
unsafe {
mlua_assert!(
+37 -2
View File
@@ -24,6 +24,18 @@
//! The [`UserData`] trait can be implemented by user-defined types to make them available to Lua.
//! Methods and operators to be used from Lua can be added using the [`UserDataMethods`] API.
//!
//! # Serde support
//!
//! The [`LuaSerdeExt`] trait implemented for [`Lua`] allows conversion from Rust types to Lua values
//! and vice versa using serde. Any user defined data type that implements [`serde::Serialize`] or
//! [`serde::Deserialize`] can be converted.
//! For convenience, additional functionality to handle `NULL` values and arrays is provided.
//!
//! The [`Value`] enum implements [`serde::Serialize`] trait to support serializing Lua values
//! (including [`UserData`]) into Rust values.
//!
//! Requires `feature = "serialize"`.
//!
//! # Async/await support
//!
//! The [`create_async_function`] allows creating non-blocking functions that returns [`Future`].
@@ -34,7 +46,7 @@
//!
//! # `Send` requirement
//! By default `mlua` is `!Send`. This can be changed by enabling `feature = "send"` that adds `Send` requirement
//! to `Function`s and [`UserData`].
//! to [`Function`]s and [`UserData`].
//!
//! [Lua programming language]: https://www.lua.org/
//! [`Lua`]: struct.Lua.html
@@ -45,16 +57,24 @@
//! [`FromLua`]: trait.FromLua.html
//! [`ToLuaMulti`]: trait.ToLuaMulti.html
//! [`FromLuaMulti`]: trait.FromLuaMulti.html
//! [`Function`]: struct.Function.html
//! [`UserData`]: trait.UserData.html
//! [`UserDataMethods`]: trait.UserDataMethods.html
//! [`LuaSerdeExt`]: serde/trait.LuaSerdeExt.html
//! [`Value`]: enum.Value.html
//! [`create_async_function`]: struct.Lua.html#method.create_async_function
//! [`call_async`]: struct.Function.html#method.call_async
//! [`AsyncThread`]: struct.AsyncThread.html
//! [`Future`]: ../futures_core/future/trait.Future.html
//! [`serde::Serialize`]: https://docs.serde.rs/serde/ser/trait.Serialize.html
//! [`serde::Deserialize`]: https://docs.serde.rs/serde/de/trait.Deserialize.html
// mlua types in rustdoc of other crates get linked to here.
#![doc(html_root_url = "https://docs.rs/mlua/0.5.0")]
// Deny warnings inside doc tests / examples. When this isn't present, rustdoc doesn't show *any*
// warnings at all.
#![doc(test(attr(deny(warnings))))]
#![cfg_attr(docsrs, feature(doc_cfg))]
#[macro_use]
mod macros;
@@ -81,7 +101,7 @@ pub use crate::ffi::lua_State;
pub use crate::error::{Error, ExternalError, ExternalResult, Result};
pub use crate::function::Function;
pub use crate::hook::{Debug, DebugNames, DebugSource, DebugStack, HookTriggers};
pub use crate::lua::{Chunk, GCMode, Lua};
pub use crate::lua::{Chunk, ChunkMode, GCMode, Lua};
pub use crate::multi::Variadic;
pub use crate::scope::Scope;
pub use crate::stdlib::StdLib;
@@ -95,4 +115,19 @@ pub use crate::value::{FromLua, FromLuaMulti, MultiValue, Nil, ToLua, ToLuaMulti
#[cfg(feature = "async")]
pub use crate::thread::AsyncThread;
#[cfg(feature = "serialize")]
pub use crate::serde::LuaSerdeExt;
pub mod prelude;
#[cfg(feature = "serialize")]
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
pub mod serde;
// Re-export #[mlua_derive::lua_module].
#[cfg(feature = "mlua_derive")]
#[allow(unused_imports)]
#[macro_use]
extern crate mlua_derive;
#[cfg(feature = "mlua_derive")]
#[doc(hidden)]
pub use mlua_derive::*;
+175 -47
View File
@@ -1,6 +1,6 @@
use std::any::TypeId;
use std::cell::{RefCell, UnsafeCell};
use std::collections::HashMap;
use std::collections::{HashMap, HashSet};
use std::ffi::CString;
use std::marker::PhantomData;
use std::os::raw::{c_char, c_int, c_void};
@@ -18,8 +18,9 @@ use crate::table::Table;
use crate::thread::Thread;
use crate::types::{
Callback, HookCallback, Integer, LightUserData, LuaRef, MaybeSend, Number, RegistryKey,
UserDataCell,
};
use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataMethods};
use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataMethods, UserDataWrapped};
use crate::util::{
assert_stack, callback_error, check_stack, get_gc_userdata, get_main_state,
get_meta_gc_userdata, get_wrapped_error, init_error_registry, init_gc_metatable_for,
@@ -39,6 +40,9 @@ use {
futures_util::future::{self, TryFutureExt},
};
#[cfg(feature = "serialize")]
use {crate::util::get_destructed_userdata_metatable, serde::Serialize};
/// Top level Lua struct which holds the Lua state itself.
pub struct Lua {
pub(crate) state: *mut ffi::lua_State,
@@ -53,8 +57,10 @@ pub struct Lua {
// Data associated with the lua_State.
struct ExtraData {
registered_userdata: HashMap<TypeId, c_int>,
registered_userdata_mt: HashSet<isize>,
registry_unref_list: Arc<Mutex<Option<Vec<c_int>>>>,
libs: StdLib,
mem_info: *mut MemoryInfo,
ref_thread: *mut ffi::lua_State,
@@ -94,19 +100,22 @@ pub(crate) static EXTRA_REGISTRY_KEY: u8 = 0;
/// Requires `feature = "send"`
#[cfg(feature = "send")]
#[cfg_attr(docsrs, doc(cfg(feature = "send")))]
unsafe impl Send for Lua {}
impl Drop for Lua {
fn drop(&mut self) {
unsafe {
if !self.ephemeral {
let mut extra = mlua_expect!(self.extra.lock(), "extra is poisoned");
let extra = mlua_expect!(self.extra.lock(), "extra is poisoned");
mlua_debug_assert!(
ffi::lua_gettop(extra.ref_thread) == extra.ref_stack_max
&& extra.ref_stack_max as usize == extra.ref_free.len(),
"reference leak detected"
);
*mlua_expect!(extra.registry_unref_list.lock(), "unref list poisoned") = None;
let mut unref_list =
mlua_expect!(extra.registry_unref_list.lock(), "unref list poisoned");
*unref_list = None;
ffi::lua_close(self.main_state.expect("main_state is null"));
if !extra.mem_info.is_null() {
Box::from_raw(extra.mem_info);
@@ -164,7 +173,9 @@ impl Lua {
let mut lua = unsafe { Self::unsafe_new_with(libs) };
mlua_expect!(lua.disable_c_modules(), "Error during disabling C modules");
if libs.contains(StdLib::PACKAGE) {
mlua_expect!(lua.disable_c_modules(), "Error during disabling C modules");
}
lua.safe = true;
Ok(lua)
@@ -254,7 +265,7 @@ impl Lua {
lua.ephemeral = false;
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
{
lua.extra.lock().unwrap().mem_info = mem_info;
mlua_expect!(lua.extra.lock(), "extra is poisoned").mem_info = mem_info;
}
mlua_expect!(
@@ -263,6 +274,7 @@ impl Lua {
}),
"Error during loading standard libraries"
);
mlua_expect!(lua.extra.lock(), "extra is poisoned").libs |= libs;
lua
}
@@ -292,6 +304,10 @@ impl Lua {
init_gc_metatable_for::<Waker>(state, None);
}
// Init serde metatables
#[cfg(feature = "serialize")]
crate::serde::init_metatables(state);
// Create ref stack thread and place it in the registry to prevent it from being garbage
// collected.
@@ -306,8 +322,10 @@ impl Lua {
let extra = Arc::new(Mutex::new(ExtraData {
registered_userdata: HashMap::new(),
registered_userdata_mt: HashSet::new(),
registry_unref_list: Arc::new(Mutex::new(Some(Vec::new()))),
ref_thread,
libs: StdLib::NONE,
mem_info: ptr::null_mut(),
// We need 1 extra stack space to move values in and out of the ref stack.
ref_stack_size: ffi::LUA_MINSTACK - 1,
@@ -368,11 +386,20 @@ impl Lua {
}
let state = self.main_state.unwrap_or(self.state);
unsafe {
let res = unsafe {
protect_lua_closure(state, 0, 0, |state| {
load_from_std_lib(state, libs);
})
};
// If `package` library loaded into a safe lua state then disable C modules
let curr_libs = mlua_expect!(self.extra.lock(), "extra is poisoned").libs;
if self.safe && (curr_libs ^ (curr_libs | libs)).contains(StdLib::PACKAGE) {
mlua_expect!(self.disable_c_modules(), "Error during disabling C modules");
}
mlua_expect!(self.extra.lock(), "extra is poisoned").libs |= libs;
res
}
/// Consumes and leaks `Lua` object, returning a static reference `&'static Lua`.
@@ -490,7 +517,7 @@ impl Lua {
unsafe { (*extra.mem_info).used_memory as usize }
}
/// Sets a memory limit on this Lua state.
/// Sets a memory limit (in bytes) on this Lua state.
///
/// Once an allocation occurs that would pass this memory limit,
/// a `Error::MemoryError` is generated instead.
@@ -658,6 +685,9 @@ impl Lua {
/// similar on the returned builder. Code is not even parsed until one of these methods is
/// called.
///
/// If this `Lua` was created with `unsafe_new`, `load` will automatically detect and load
/// chunks of either text or binary type, as if passing `bt` mode to `luaL_loadbufferx`.
///
/// [`Chunk::exec`]: struct.Chunk.html#method.exec
pub fn load<'lua, 'a, S>(&'lua self, source: &'a S) -> Chunk<'lua, 'a>
where
@@ -668,6 +698,7 @@ impl Lua {
source: source.as_ref(),
name: None,
env: None,
mode: None,
}
}
@@ -676,28 +707,35 @@ impl Lua {
source: &[u8],
name: Option<&CString>,
env: Option<Value<'lua>>,
mode: Option<ChunkMode>,
) -> Result<Function<'lua>> {
unsafe {
let _sg = StackGuard::new(self.state);
assert_stack(self.state, 1);
match if let Some(name) = name {
ffi::luaL_loadbufferx(
self.state,
source.as_ptr() as *const c_char,
source.len(),
name.as_ptr() as *const c_char,
cstr!("t"),
)
} else {
ffi::luaL_loadbufferx(
self.state,
source.as_ptr() as *const c_char,
source.len(),
ptr::null(),
cstr!("t"),
)
} {
let mode_str = match mode {
Some(ChunkMode::Binary) if self.safe => {
return Err(Error::SafetyError(
"binary chunks are disabled in safe mode".to_string(),
))
}
Some(ChunkMode::Binary) => cstr!("b"),
Some(ChunkMode::Text) => cstr!("t"),
None if source.starts_with(ffi::LUA_SIGNATURE) && self.safe => {
return Err(Error::SafetyError(
"binary chunks are disabled in safe mode".to_string(),
))
}
None => cstr!("bt"),
};
match ffi::luaL_loadbufferx(
self.state,
source.as_ptr() as *const c_char,
source.len(),
name.map(|n| n.as_ptr()).unwrap_or_else(ptr::null),
mode_str,
) {
ffi::LUA_OK => {
if let Some(env) = env {
self.push_value(env)?;
@@ -728,7 +766,7 @@ impl Lua {
}
}
/// Creates and returns a new table.
/// Creates and returns a new empty table.
pub fn create_table(&self) -> Result<Table> {
unsafe {
let _sg = StackGuard::new(self.state);
@@ -742,6 +780,21 @@ impl Lua {
}
}
/// Creates and returns a new empty table, with the specified capacity.
/// `narr` is a hint for how many elements the table will have as a sequence;
/// `nrec` is a hint for how many other elements the table will have.
/// Lua may use these hints to preallocate memory for the new table.
pub fn create_table_with_capacity(&self, narr: c_int, nrec: c_int) -> Result<Table> {
unsafe {
let _sg = StackGuard::new(self.state);
assert_stack(self.state, 4);
protect_lua_closure(self.state, 0, 1, |state| {
ffi::lua_createtable(state, narr, nrec)
})?;
Ok(Table(self.pop_ref()))
}
}
/// Creates a table and fills it with values from an iterator.
pub fn create_table_from<'lua, K, V, I>(&'lua self, cont: I) -> Result<Table<'lua>>
where
@@ -903,8 +956,9 @@ impl Lua {
/// ```
///
/// [`Thread`]: struct.Thread.html
/// [`ThreadStream`]: struct.ThreadStream.html
/// [`AsyncThread`]: struct.AsyncThread.html
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub fn create_async_function<'lua, 'callback, A, R, F, FR>(
&'lua self,
func: F,
@@ -947,7 +1001,19 @@ impl Lua {
where
T: 'static + MaybeSend + UserData,
{
unsafe { self.make_userdata(data) }
unsafe { self.make_userdata(UserDataWrapped::new(data)) }
}
/// Create a Lua userdata object from a custom serializable userdata type.
///
/// Requires `feature = "serialize"`
#[cfg(feature = "serialize")]
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
pub fn create_ser_userdata<T>(&self, data: T) -> Result<AnyUserData>
where
T: 'static + MaybeSend + UserData + Serialize,
{
unsafe { self.make_userdata(UserDataWrapped::new_ser(data)) }
}
/// Returns a handle to the global environment.
@@ -1008,6 +1074,7 @@ impl Lua {
///
/// [`scope`]: #method.scope
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub fn async_scope<'lua, 'scope, R, F, FR>(
&'lua self,
f: F,
@@ -1274,11 +1341,10 @@ impl Lua {
/// by `Lua::remove_registry_value`.
pub fn expire_registry_values(&self) {
unsafe {
let mut extra = mlua_expect!(self.extra.lock(), "extra is poisoned");
let unref_list = mem::replace(
&mut *mlua_expect!(extra.registry_unref_list.lock(), "unref list poisoned"),
Some(Vec::new()),
);
let extra = mlua_expect!(self.extra.lock(), "extra is poisoned");
let mut unref_list =
mlua_expect!(extra.registry_unref_list.lock(), "unref list poisoned");
let unref_list = mem::replace(&mut *unref_list, Some(Vec::new()));
for id in mlua_expect!(unref_list, "unref list not set") {
ffi::luaL_unref(self.state, ffi::LUA_REGISTRYINDEX, id);
}
@@ -1469,7 +1535,7 @@ impl Lua {
let no_methods = methods.methods.is_empty();
if no_methods {
init_userdata_metatable::<RefCell<T>>(self.state, -1, None)?;
init_userdata_metatable::<UserDataCell<T>>(self.state, -1, None)?;
} else {
protect_lua_closure(self.state, 0, 1, |state| {
ffi::lua_newtable(state);
@@ -1490,21 +1556,49 @@ impl Lua {
})?;
}
init_userdata_metatable::<RefCell<T>>(self.state, -2, Some(-1))?;
init_userdata_metatable::<UserDataCell<T>>(self.state, -2, Some(-1))?;
ffi::lua_pop(self.state, 1);
}
let id = protect_lua_closure(self.state, 1, 0, |state| {
ffi::luaL_ref(state, ffi::LUA_REGISTRYINDEX)
let (ptr, id) = protect_lua_closure(self.state, 1, 0, |state| {
let ptr = ffi::lua_topointer(state, -1) as isize;
let id = ffi::luaL_ref(state, ffi::LUA_REGISTRYINDEX);
(ptr, id)
})?;
mlua_expect!(self.extra.lock(), "extra is poisoned")
.registered_userdata
.insert(TypeId::of::<T>(), id);
let mut extra = mlua_expect!(self.extra.lock(), "extra is poisoned");
extra.registered_userdata.insert(TypeId::of::<T>(), id);
extra.registered_userdata_mt.insert(ptr);
Ok(id)
}
// Pushes a LuaRef value onto the stack, checking that it's any registered userdata
// Uses 2 stack spaces, does not call checkstack
#[cfg(feature = "serialize")]
pub(crate) unsafe fn push_userdata_ref(&self, lref: &LuaRef) -> Result<()> {
self.push_ref(lref);
if ffi::lua_getmetatable(self.state, -1) == 0 {
Err(Error::UserDataTypeMismatch)
} else {
// Check that this is our metatable
let ptr = ffi::lua_topointer(self.state, -1) as isize;
let extra = mlua_expect!(self.extra.lock(), "extra is poisoned");
if !extra.registered_userdata_mt.contains(&ptr) {
// Maybe UserData destructed?
get_destructed_userdata_metatable(self.state);
if ffi::lua_rawequal(self.state, -1, -2) == 1 {
Err(Error::UserDataDestructed)
} else {
Err(Error::UserDataTypeMismatch)
}
} else {
ffi::lua_pop(self.state, 1);
Ok(())
}
}
}
// Creates a Function out of a Callback containing a 'static Fn. This is safe ONLY because the
// Fn is 'static, otherwise it could capture 'callback arguments improperly. Without ATCs, we
// cannot easily deal with the "correct" callback type of:
@@ -1578,7 +1672,12 @@ impl Lua {
'lua: 'callback,
{
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
self.load_from_std_lib(StdLib::COROUTINE)?;
{
let libs = mlua_expect!(self.extra.lock(), "extra is poisoned").libs;
if !libs.contains(StdLib::COROUTINE) {
self.load_from_std_lib(StdLib::COROUTINE)?;
}
}
unsafe extern "C" fn call_callback(state: *mut ffi::lua_State) -> c_int {
callback_error(state, |nargs| {
@@ -1705,7 +1804,7 @@ impl Lua {
.into_function()
}
pub(crate) unsafe fn make_userdata<T>(&self, data: T) -> Result<AnyUserData>
pub(crate) unsafe fn make_userdata<T>(&self, data: UserDataWrapped<T>) -> Result<AnyUserData>
where
T: 'static + UserData,
{
@@ -1713,7 +1812,7 @@ impl Lua {
assert_stack(self.state, 4);
let ud_index = self.userdata_metatable::<T>()?;
push_userdata::<RefCell<T>>(self.state, RefCell::new(data))?;
push_userdata::<UserDataCell<T>>(self.state, RefCell::new(data))?;
ffi::lua_rawgeti(
self.state,
@@ -1802,6 +1901,14 @@ pub struct Chunk<'lua, 'a> {
source: &'a [u8],
name: Option<CString>,
env: Option<Value<'lua>>,
mode: Option<ChunkMode>,
}
/// Represents chunk mode (text or binary).
#[derive(Clone, Copy)]
pub enum ChunkMode {
Text,
Binary,
}
impl<'lua, 'a> Chunk<'lua, 'a> {
@@ -1833,6 +1940,17 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
Ok(self)
}
/// Sets whether the chunk is text or binary (autodetected by default).
///
/// Lua does not check the consistency of binary chunks, therefore this mode is allowed only
/// for instances created with [`Lua::unsafe_new`].
///
/// [`Lua::unsafe_new`]: struct.Lua.html#method.unsafe_new
pub fn set_mode(mut self, mode: ChunkMode) -> Chunk<'lua, 'a> {
self.mode = Some(mode);
self
}
/// Execute this chunk of code.
///
/// This is equivalent to calling the chunk function with no arguments and no return values.
@@ -1849,6 +1967,7 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
///
/// [`Chunk::exec`]: struct.Chunk.html#method.exec
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub fn exec_async<'fut>(self) -> LocalBoxFuture<'fut, Result<()>>
where
'lua: 'fut,
@@ -1862,13 +1981,17 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
/// the value that it evaluates to. Otherwise, the chunk is interpreted as a block as normal,
/// and this is equivalent to calling `exec`.
pub fn eval<R: FromLuaMulti<'lua>>(self) -> Result<R> {
// First, try interpreting the lua as an expression by adding
// Bytecode is always interpreted as a statement.
// For source code, first try interpreting the lua as an expression by adding
// "return", then as a statement. This is the same thing the
// actual lua repl does.
if let Ok(function) = self.lua.load_chunk(
if self.source.starts_with(ffi::LUA_SIGNATURE) {
self.call(())
} else if let Ok(function) = self.lua.load_chunk(
&self.expression_source(),
self.name.as_ref(),
self.env.clone(),
self.mode,
) {
function.call(())
} else {
@@ -1884,15 +2007,19 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
///
/// [`Chunk::eval`]: struct.Chunk.html#method.eval
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub fn eval_async<'fut, R>(self) -> LocalBoxFuture<'fut, Result<R>>
where
'lua: 'fut,
R: FromLuaMulti<'lua> + 'fut,
{
if let Ok(function) = self.lua.load_chunk(
if self.source.starts_with(ffi::LUA_SIGNATURE) {
self.call_async(())
} else if let Ok(function) = self.lua.load_chunk(
&self.expression_source(),
self.name.as_ref(),
self.env.clone(),
self.mode,
) {
function.call_async(())
} else {
@@ -1915,6 +2042,7 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
///
/// [`Chunk::call`]: struct.Chunk.html#method.call
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub fn call_async<'fut, A, R>(self, args: A) -> LocalBoxFuture<'fut, Result<R>>
where
'lua: 'fut,
@@ -1932,7 +2060,7 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
/// This simply compiles the chunk without actually executing it.
pub fn into_function(self) -> Result<Function<'lua>> {
self.lua
.load_chunk(self.source, self.name.as_ref(), self.env)
.load_chunk(self.source, self.name.as_ref(), self.env, self.mode)
}
fn expression_source(&self) -> Vec<u8> {
+53 -20
View File
@@ -1,16 +1,19 @@
use std::any::Any;
use std::cell::{Cell, RefCell};
use std::cell::{Cell, Ref, RefCell, RefMut};
use std::marker::PhantomData;
use std::mem;
use std::os::raw::c_void;
use std::rc::Rc;
#[cfg(feature = "serialize")]
use serde::Serialize;
use crate::error::{Error, Result};
use crate::ffi;
use crate::function::Function;
use crate::lua::Lua;
use crate::types::{Callback, LuaRef, MaybeSend};
use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataMethods};
use crate::types::{Callback, LuaRef, MaybeSend, UserDataCell};
use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataMethods, UserDataWrapped};
use crate::util::{
assert_stack, init_userdata_metatable, protect_lua_closure, push_string, push_userdata,
take_userdata, StackGuard,
@@ -112,6 +115,7 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
/// [`Lua::scope`]: struct.Lua.html#method.scope
/// [`Lua::async_scope`]: struct.Lua.html#method.async_scope
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub fn create_async_function<'callback, A, R, F, FR>(
&'callback self,
func: F,
@@ -143,6 +147,33 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
/// [`Lua::create_userdata`]: struct.Lua.html#method.create_userdata
/// [`Lua::scope`]: struct.Lua.html#method.scope
pub fn create_userdata<T>(&self, data: T) -> Result<AnyUserData<'lua>>
where
T: 'static + UserData,
{
self.create_userdata_inner(UserDataWrapped::new(data))
}
/// Create a Lua userdata object from a custom serializable userdata type.
///
/// This is a version of [`Lua::create_ser_userdata`] that creates a userdata which expires on
/// scope drop, and does not require that the userdata type be Send (but still requires that the
/// UserData be 'static).
/// See [`Lua::scope`] for more details.
///
/// Requires `feature = "serialize"`
///
/// [`Lua::create_ser_userdata`]: struct.Lua.html#method.create_ser_userdata
/// [`Lua::scope`]: struct.Lua.html#method.scope
#[cfg(feature = "serialize")]
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
pub fn create_ser_userdata<T>(&self, data: T) -> Result<AnyUserData<'lua>>
where
T: 'static + UserData + Serialize,
{
self.create_userdata_inner(UserDataWrapped::new_ser(data))
}
fn create_userdata_inner<T>(&self, data: UserDataWrapped<T>) -> Result<AnyUserData<'lua>>
where
T: 'static + UserData,
{
@@ -156,7 +187,7 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
u.lua.push_ref(&u);
// We know the destructor has not run yet because we hold a reference to the
// userdata.
vec![Box::new(take_userdata::<RefCell<T>>(state))]
vec![Box::new(take_userdata::<UserDataCell<T>>(state))]
}));
Ok(u)
}
@@ -189,7 +220,7 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
where
T: 'scope + UserData,
{
let data = Rc::new(RefCell::new(data));
let data = Rc::new(RefCell::new(UserDataWrapped::new(data)));
// 'callback outliving 'scope is a lie to make the types work out, required due to the
// inability to work with the more correct callback type that is universally quantified over
@@ -198,7 +229,7 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
// parameters.
fn wrap_method<'scope, 'lua, 'callback: 'scope, T: 'scope>(
scope: &Scope<'lua, 'scope>,
data: Rc<RefCell<T>>,
data: Rc<UserDataCell<T>>,
method: NonStaticMethod<'callback, T>,
) -> Result<Function<'lua>> {
// On methods that actually receive the userdata, we fake a type check on the passed in
@@ -210,20 +241,18 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
// first argument).
let check_data = data.clone();
let check_ud_type = move |lua: &'callback Lua, value| {
if let Some(value) = value {
if let Value::UserData(u) = value {
unsafe {
assert_stack(lua.state, 1);
lua.push_ref(&u.0);
ffi::lua_getuservalue(lua.state, -1);
#[cfg(any(feature = "lua52", feature = "lua51", feature = "luajit"))]
{
ffi::lua_rawgeti(lua.state, -1, 1);
ffi::lua_remove(lua.state, -2);
}
return ffi::lua_touserdata(lua.state, -1)
== check_data.as_ptr() as *mut c_void;
if let Some(Value::UserData(ud)) = value {
unsafe {
assert_stack(lua.state, 1);
lua.push_ref(&ud.0);
ffi::lua_getuservalue(lua.state, -1);
#[cfg(any(feature = "lua52", feature = "lua51", feature = "luajit"))]
{
ffi::lua_rawgeti(lua.state, -1, 1);
ffi::lua_remove(lua.state, -2);
}
return ffi::lua_touserdata(lua.state, -1)
== check_data.as_ptr() as *mut c_void;
}
}
@@ -236,7 +265,10 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
if !check_ud_type(lua, args.pop_front()) {
return Err(Error::UserDataTypeMismatch);
}
let data = data.try_borrow().map_err(|_| Error::UserDataBorrowError)?;
let data = data
.try_borrow()
.map(|cell| Ref::map(cell, AsRef::as_ref))
.map_err(|_| Error::UserDataBorrowError)?;
method(lua, &*data, args)
});
unsafe { scope.create_callback(f) }
@@ -252,6 +284,7 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
.map_err(|_| Error::RecursiveMutCallback)?;
let mut data = data
.try_borrow_mut()
.map(|cell| RefMut::map(cell, AsMut::as_mut))
.map_err(|_| Error::UserDataBorrowMutError)?;
(&mut *method)(lua, &mut *data, args)
});
+327
View File
@@ -0,0 +1,327 @@
use std::string::String as StdString;
use serde::de::{self, IntoDeserializer};
use crate::error::{Error, Result};
use crate::table::{TablePairs, TableSequence};
use crate::value::Value;
/// A struct for deserializing Lua values into Rust values.
pub struct Deserializer<'lua>(pub Value<'lua>);
impl<'lua, 'de> serde::Deserializer<'de> for Deserializer<'lua> {
type Error = Error;
#[inline]
fn deserialize_any<V>(self, visitor: V) -> Result<V::Value>
where
V: de::Visitor<'de>,
{
match self.0 {
Value::Nil => visitor.visit_unit(),
Value::Boolean(b) => visitor.visit_bool(b),
Value::Integer(i) => visitor.visit_i64(i),
Value::Number(n) => visitor.visit_f64(n),
Value::String(s) => match s.to_str() {
Ok(s) => visitor.visit_str(s),
Err(_) => visitor.visit_bytes(s.as_bytes()),
},
Value::Table(ref t) if t.raw_len() > 0 || t.is_array() => self.deserialize_seq(visitor),
Value::Table(_) => self.deserialize_map(visitor),
Value::LightUserData(ud) if ud.0.is_null() => visitor.visit_none(),
Value::Function(_)
| Value::Thread(_)
| Value::UserData(_)
| Value::LightUserData(_)
| Value::Error(_) => Err(de::Error::custom("invalid value type")),
}
}
#[inline]
fn deserialize_option<V>(self, visitor: V) -> Result<V::Value>
where
V: de::Visitor<'de>,
{
match self.0 {
Value::Nil => visitor.visit_none(),
Value::LightUserData(ud) if ud.0.is_null() => visitor.visit_none(),
_ => visitor.visit_some(self),
}
}
#[inline]
fn deserialize_enum<V>(
self,
_name: &str,
_variants: &'static [&'static str],
visitor: V,
) -> Result<V::Value>
where
V: de::Visitor<'de>,
{
let (variant, value) = match self.0 {
Value::Table(value) => {
let mut iter = value.pairs::<StdString, Value>();
let (variant, value) = match iter.next() {
Some(v) => v?,
None => {
return Err(de::Error::invalid_value(
de::Unexpected::Map,
&"map with a single key",
))
}
};
if iter.next().is_some() {
return Err(de::Error::invalid_value(
de::Unexpected::Map,
&"map with a single key",
));
}
(variant, Some(value))
}
Value::String(variant) => (variant.to_str()?.to_owned(), None),
_ => return Err(de::Error::custom("bad enum value")),
};
visitor.visit_enum(EnumDeserializer { variant, value })
}
#[inline]
fn deserialize_seq<V>(self, visitor: V) -> Result<V::Value>
where
V: de::Visitor<'de>,
{
match self.0 {
Value::Table(t) => {
let len = t.raw_len() as usize;
let mut deserializer = SeqDeserializer(t.raw_sequence_values());
let seq = visitor.visit_seq(&mut deserializer)?;
if deserializer.0.count() == 0 {
Ok(seq)
} else {
Err(de::Error::invalid_length(
len,
&"fewer elements in the table",
))
}
}
_ => Err(de::Error::custom("invalid value type")),
}
}
#[inline]
fn deserialize_tuple<V>(self, _len: usize, visitor: V) -> Result<V::Value>
where
V: de::Visitor<'de>,
{
self.deserialize_seq(visitor)
}
#[inline]
fn deserialize_tuple_struct<V>(
self,
_name: &'static str,
_len: usize,
visitor: V,
) -> Result<V::Value>
where
V: de::Visitor<'de>,
{
self.deserialize_seq(visitor)
}
#[inline]
fn deserialize_map<V>(self, visitor: V) -> Result<V::Value>
where
V: de::Visitor<'de>,
{
match self.0 {
Value::Table(t) => {
let mut deserializer = MapDeserializer::new(t.pairs());
let map = visitor.visit_map(&mut deserializer)?;
let count = deserializer.pairs.count();
if count == 0 {
Ok(map)
} else {
Err(de::Error::invalid_length(
deserializer.processed + count,
&"fewer elements in the table",
))
}
}
_ => Err(de::Error::custom("invalid value type")),
}
}
#[inline]
fn deserialize_struct<V>(
self,
_name: &'static str,
_fields: &'static [&'static str],
visitor: V,
) -> Result<V::Value>
where
V: de::Visitor<'de>,
{
self.deserialize_map(visitor)
}
serde::forward_to_deserialize_any! {
bool i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 char str string bytes
byte_buf unit unit_struct newtype_struct
identifier ignored_any
}
}
struct SeqDeserializer<'lua>(TableSequence<'lua, Value<'lua>>);
impl<'lua, 'de> de::SeqAccess<'de> for SeqDeserializer<'lua> {
type Error = Error;
fn next_element_seed<T>(&mut self, seed: T) -> Result<Option<T::Value>>
where
T: de::DeserializeSeed<'de>,
{
match self.0.next() {
Some(value) => seed.deserialize(Deserializer(value?)).map(Some),
None => Ok(None),
}
}
fn size_hint(&self) -> Option<usize> {
match self.0.size_hint() {
(lower, Some(upper)) if lower == upper => Some(upper),
_ => None,
}
}
}
struct MapDeserializer<'lua> {
pairs: TablePairs<'lua, Value<'lua>, Value<'lua>>,
value: Option<Value<'lua>>,
processed: usize,
}
impl<'lua> MapDeserializer<'lua> {
fn new(pairs: TablePairs<'lua, Value<'lua>, Value<'lua>>) -> Self {
MapDeserializer {
pairs,
value: None,
processed: 0,
}
}
}
impl<'lua, 'de> de::MapAccess<'de> for MapDeserializer<'lua> {
type Error = Error;
fn next_key_seed<T>(&mut self, seed: T) -> Result<Option<T::Value>>
where
T: de::DeserializeSeed<'de>,
{
match self.pairs.next() {
Some(item) => {
let (key, value) = item?;
self.processed += 1;
self.value = Some(value);
let key_de = Deserializer(key);
seed.deserialize(key_de).map(Some)
}
None => Ok(None),
}
}
fn next_value_seed<T>(&mut self, seed: T) -> Result<T::Value>
where
T: de::DeserializeSeed<'de>,
{
match self.value.take() {
Some(value) => seed.deserialize(Deserializer(value)),
None => Err(de::Error::custom("value is missing")),
}
}
fn size_hint(&self) -> Option<usize> {
match self.pairs.size_hint() {
(lower, Some(upper)) if lower == upper => Some(upper),
_ => None,
}
}
}
struct EnumDeserializer<'lua> {
variant: StdString,
value: Option<Value<'lua>>,
}
impl<'lua, 'de> de::EnumAccess<'de> for EnumDeserializer<'lua> {
type Error = Error;
type Variant = VariantDeserializer<'lua>;
fn variant_seed<T>(self, seed: T) -> Result<(T::Value, Self::Variant)>
where
T: de::DeserializeSeed<'de>,
{
let variant = self.variant.into_deserializer();
let variant_access = VariantDeserializer { value: self.value };
seed.deserialize(variant).map(|v| (v, variant_access))
}
}
struct VariantDeserializer<'lua> {
value: Option<Value<'lua>>,
}
impl<'lua, 'de> de::VariantAccess<'de> for VariantDeserializer<'lua> {
type Error = Error;
fn unit_variant(self) -> Result<()> {
match self.value {
Some(_) => Err(de::Error::invalid_type(
de::Unexpected::NewtypeVariant,
&"unit variant",
)),
None => Ok(()),
}
}
fn newtype_variant_seed<T>(self, seed: T) -> Result<T::Value>
where
T: de::DeserializeSeed<'de>,
{
match self.value {
Some(value) => seed.deserialize(Deserializer(value)),
None => Err(de::Error::invalid_type(
de::Unexpected::UnitVariant,
&"newtype variant",
)),
}
}
fn tuple_variant<V>(self, _len: usize, visitor: V) -> Result<V::Value>
where
V: de::Visitor<'de>,
{
match self.value {
Some(value) => serde::Deserializer::deserialize_seq(Deserializer(value), visitor),
None => Err(de::Error::invalid_type(
de::Unexpected::UnitVariant,
&"tuple variant",
)),
}
}
fn struct_variant<V>(self, _fields: &'static [&'static str], visitor: V) -> Result<V::Value>
where
V: de::Visitor<'de>,
{
match self.value {
Some(value) => serde::Deserializer::deserialize_map(Deserializer(value), visitor),
None => Err(de::Error::invalid_type(
de::Unexpected::UnitVariant,
&"struct variant",
)),
}
}
}
+200
View File
@@ -0,0 +1,200 @@
//! (De)Serialization support using serde.
use std::os::raw::{c_int, c_void};
use std::ptr;
use serde::{Deserialize, Serialize};
use crate::error::Result;
use crate::ffi;
use crate::lua::Lua;
use crate::table::Table;
use crate::util::{assert_stack, protect_lua, StackGuard};
use crate::value::Value;
pub trait LuaSerdeExt<'lua> {
/// A special value (lightuserdata) to encode/decode optional (none) values.
///
/// Requires `feature = "serialize"`
///
/// # Example
///
/// ```
/// use std::collections::HashMap;
/// use mlua::{Lua, Result, LuaSerdeExt};
///
/// fn main() -> Result<()> {
/// let lua = Lua::new();
/// lua.globals().set("null", lua.null()?)?;
///
/// let val = lua.load(r#"{a = null}"#).eval()?;
/// let map: HashMap<String, Option<String>> = lua.from_value(val)?;
/// assert_eq!(map["a"], None);
///
/// Ok(())
/// }
/// ```
fn null(&'lua self) -> Result<Value<'lua>>;
/// A metatable attachable to a Lua table to systematically encode it as Array (instead of Map).
/// As result, encoded Array will contain only sequence part of the table, with the same length
/// as the `#` operator on that table.
///
/// Requires `feature = "serialize"`
///
/// # Example
///
/// ```
/// use mlua::{Lua, Result, LuaSerdeExt};
/// use serde_json::Value as JsonValue;
///
/// fn main() -> Result<()> {
/// let lua = Lua::new();
/// lua.globals().set("array_mt", lua.array_metatable()?)?;
///
/// // Encode as an empty array (no sequence part in the lua table)
/// let val = lua.load("setmetatable({a = 5}, array_mt)").eval()?;
/// let j: JsonValue = lua.from_value(val)?;
/// assert_eq!(j.to_string(), "[]");
///
/// // Encode as object
/// let val = lua.load("{a = 5}").eval()?;
/// let j: JsonValue = lua.from_value(val)?;
/// assert_eq!(j.to_string(), r#"{"a":5}"#);
///
/// Ok(())
/// }
/// ```
fn array_metatable(&'lua self) -> Result<Table<'lua>>;
/// Converts `T` into a `Value` instance.
///
/// Requires `feature = "serialize"`
///
/// [`Value`]: enum.Value.html
///
/// # Example
///
/// ```
/// use mlua::{Lua, Result, LuaSerdeExt};
/// use serde::Serialize;
///
/// #[derive(Serialize)]
/// struct User {
/// name: String,
/// age: u8,
/// }
///
/// fn main() -> Result<()> {
/// let lua = Lua::new();
/// let u = User {
/// name: "John Smith".into(),
/// age: 20,
/// };
/// lua.globals().set("user", lua.to_value(&u)?)?;
/// lua.load(r#"
/// assert(user["name"] == "John Smith")
/// assert(user["age"] == 20)
/// "#).exec()
/// }
/// ```
fn to_value<T: Serialize + ?Sized>(&'lua self, t: &T) -> Result<Value<'lua>>;
/// Deserializes a `Value` into any serde deserializable object.
///
/// Requires `feature = "serialize"`
///
/// [`Value`]: enum.Value.html
///
/// # Example
///
/// ```
/// use mlua::{Lua, Result, LuaSerdeExt};
/// use serde::Deserialize;
///
/// #[derive(Deserialize, Debug, PartialEq)]
/// struct User {
/// name: String,
/// age: u8,
/// }
///
/// fn main() -> Result<()> {
/// let lua = Lua::new();
/// let val = lua.load(r#"{name = "John Smith", age = 20}"#).eval()?;
/// let u: User = lua.from_value(val)?;
///
/// assert_eq!(u, User { name: "John Smith".into(), age: 20 });
///
/// Ok(())
/// }
/// ```
fn from_value<T: Deserialize<'lua>>(&'lua self, value: Value<'lua>) -> Result<T>;
}
impl<'lua> LuaSerdeExt<'lua> for Lua {
fn null(&'lua self) -> Result<Value<'lua>> {
unsafe {
let _sg = StackGuard::new(self.state);
assert_stack(self.state, 3);
unsafe extern "C" fn push_null(state: *mut ffi::lua_State) -> c_int {
ffi::lua_pushlightuserdata(state, ptr::null_mut());
1
}
protect_lua(self.state, 0, push_null)?;
Ok(self.pop_value())
}
}
fn array_metatable(&'lua self) -> Result<Table<'lua>> {
unsafe {
let _sg = StackGuard::new(self.state);
assert_stack(self.state, 3);
unsafe extern "C" fn get_array_mt(state: *mut ffi::lua_State) -> c_int {
push_array_metatable(state);
1
}
protect_lua(self.state, 0, get_array_mt)?;
Ok(Table(self.pop_ref()))
}
}
fn to_value<T>(&'lua self, t: &T) -> Result<Value<'lua>>
where
T: Serialize + ?Sized,
{
t.serialize(ser::Serializer(self))
}
fn from_value<T>(&'lua self, value: Value<'lua>) -> Result<T>
where
T: Deserialize<'lua>,
{
T::deserialize(de::Deserializer(value))
}
}
pub(crate) unsafe fn init_metatables(state: *mut ffi::lua_State) {
ffi::lua_pushlightuserdata(
state,
&ARRAY_METATABLE_REGISTRY_KEY as *const u8 as *mut c_void,
);
ffi::lua_newtable(state);
ffi::lua_pushstring(state, cstr!("__metatable"));
ffi::lua_pushboolean(state, 0);
ffi::lua_rawset(state, -3);
ffi::lua_rawset(state, ffi::LUA_REGISTRYINDEX);
}
pub(crate) unsafe fn push_array_metatable(state: *mut ffi::lua_State) {
let key = &ARRAY_METATABLE_REGISTRY_KEY as *const u8 as *mut c_void;
ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, key);
}
static ARRAY_METATABLE_REGISTRY_KEY: u8 = 0;
pub mod de;
pub mod ser;
+377
View File
@@ -0,0 +1,377 @@
use std::os::raw::c_int;
use serde::{ser, Serialize};
use super::LuaSerdeExt;
use crate::error::{Error, Result};
use crate::ffi;
use crate::lua::Lua;
use crate::string::String;
use crate::table::Table;
use crate::types::{Integer, Number};
use crate::util::{assert_stack, protect_lua, StackGuard};
use crate::value::Value;
/// A struct for serializing Rust values into Lua values.
pub struct Serializer<'lua>(pub &'lua Lua);
macro_rules! lua_serialize_integer {
($name:ident, $t:ty) => {
#[inline]
fn $name(self, value: $t) -> Result<Value<'lua>> {
Ok(Value::Integer(value as Integer))
}
};
}
impl<'lua> ser::Serializer for Serializer<'lua> {
type Ok = Value<'lua>;
type Error = Error;
// Associated types for keeping track of additional state while serializing
// compound data structures like sequences and maps.
type SerializeSeq = SerializeVec<'lua>;
type SerializeTuple = SerializeVec<'lua>;
type SerializeTupleStruct = SerializeVec<'lua>;
type SerializeTupleVariant = SerializeTupleVariant<'lua>;
type SerializeMap = SerializeMap<'lua>;
type SerializeStruct = SerializeMap<'lua>;
type SerializeStructVariant = SerializeStructVariant<'lua>;
#[inline]
fn serialize_bool(self, value: bool) -> Result<Value<'lua>> {
Ok(Value::Boolean(value))
}
lua_serialize_integer!(serialize_i8, i8);
lua_serialize_integer!(serialize_u8, u8);
lua_serialize_integer!(serialize_i16, i16);
lua_serialize_integer!(serialize_u16, u16);
lua_serialize_integer!(serialize_i32, i32);
lua_serialize_integer!(serialize_u32, u32);
lua_serialize_integer!(serialize_u64, u64);
#[inline]
fn serialize_i64(self, value: i64) -> Result<Value<'lua>> {
Ok(Value::Integer(value))
}
#[inline]
fn serialize_f32(self, value: f32) -> Result<Value<'lua>> {
Ok(Value::Number(value as Number))
}
#[inline]
fn serialize_f64(self, value: f64) -> Result<Value<'lua>> {
Ok(Value::Number(value))
}
#[inline]
fn serialize_char(self, value: char) -> Result<Value<'lua>> {
self.serialize_str(&value.to_string())
}
#[inline]
fn serialize_str(self, value: &str) -> Result<Value<'lua>> {
self.0.create_string(value).map(Value::String)
}
#[inline]
fn serialize_bytes(self, value: &[u8]) -> Result<Value<'lua>> {
self.0.create_string(value).map(Value::String)
}
#[inline]
fn serialize_none(self) -> Result<Value<'lua>> {
self.0.null()
}
#[inline]
fn serialize_some<T>(self, value: &T) -> Result<Value<'lua>>
where
T: ?Sized + Serialize,
{
value.serialize(self)
}
#[inline]
fn serialize_unit(self) -> Result<Value<'lua>> {
self.0.null()
}
#[inline]
fn serialize_unit_struct(self, _name: &'static str) -> Result<Value<'lua>> {
self.0.null()
}
#[inline]
fn serialize_unit_variant(
self,
_name: &'static str,
_variant_index: u32,
variant: &'static str,
) -> Result<Value<'lua>> {
self.serialize_str(variant)
}
#[inline]
fn serialize_newtype_struct<T>(self, _name: &'static str, value: &T) -> Result<Value<'lua>>
where
T: ?Sized + Serialize,
{
value.serialize(self)
}
#[inline]
fn serialize_newtype_variant<T>(
self,
_name: &'static str,
_variant_index: u32,
variant: &'static str,
value: &T,
) -> Result<Value<'lua>>
where
T: ?Sized + Serialize,
{
let table = self.0.create_table()?;
let variant = self.0.create_string(variant)?;
let value = self.0.to_value(value)?;
table.raw_set(variant, value)?;
Ok(Value::Table(table))
}
#[inline]
fn serialize_seq(self, len: Option<usize>) -> Result<Self::SerializeSeq> {
let len = len.unwrap_or(0) as c_int;
let table = self.0.create_table_with_capacity(len, 0)?;
table.set_metatable(Some(self.0.array_metatable()?));
Ok(SerializeVec { table })
}
#[inline]
fn serialize_tuple(self, len: usize) -> Result<Self::SerializeTuple> {
self.serialize_seq(Some(len))
}
#[inline]
fn serialize_tuple_struct(
self,
_name: &'static str,
len: usize,
) -> Result<Self::SerializeTupleStruct> {
self.serialize_seq(Some(len))
}
#[inline]
fn serialize_tuple_variant(
self,
_name: &'static str,
_variant_index: u32,
variant: &'static str,
_len: usize,
) -> Result<Self::SerializeTupleVariant> {
let name = self.0.create_string(variant)?;
let table = self.0.create_table()?;
Ok(SerializeTupleVariant { name, table })
}
#[inline]
fn serialize_map(self, len: Option<usize>) -> Result<Self::SerializeMap> {
let len = len.unwrap_or(0) as c_int;
Ok(SerializeMap {
key: None,
table: self.0.create_table_with_capacity(0, len)?,
})
}
#[inline]
fn serialize_struct(self, _name: &'static str, len: usize) -> Result<Self::SerializeStruct> {
self.serialize_map(Some(len))
}
#[inline]
fn serialize_struct_variant(
self,
_name: &'static str,
_variant_index: u32,
variant: &'static str,
len: usize,
) -> Result<Self::SerializeStructVariant> {
let name = self.0.create_string(variant)?;
let table = self.0.create_table_with_capacity(0, len as c_int)?;
Ok(SerializeStructVariant { name, table })
}
}
pub struct SerializeVec<'lua> {
table: Table<'lua>,
}
impl<'lua> ser::SerializeSeq for SerializeVec<'lua> {
type Ok = Value<'lua>;
type Error = Error;
fn serialize_element<T>(&mut self, value: &T) -> Result<()>
where
T: ?Sized + Serialize,
{
let lua = self.table.0.lua;
let value = lua.to_value(value)?;
unsafe {
let _sg = StackGuard::new(lua.state);
assert_stack(lua.state, 4);
lua.push_ref(&self.table.0);
lua.push_value(value)?;
unsafe extern "C" fn push_to_table(state: *mut ffi::lua_State) -> c_int {
let len = ffi::lua_rawlen(state, -2) as Integer;
ffi::lua_rawseti(state, -2, len + 1);
1
}
protect_lua(lua.state, 2, push_to_table)
}
}
fn end(self) -> Result<Value<'lua>> {
Ok(Value::Table(self.table))
}
}
impl<'lua> ser::SerializeTuple for SerializeVec<'lua> {
type Ok = Value<'lua>;
type Error = Error;
fn serialize_element<T>(&mut self, value: &T) -> Result<()>
where
T: ?Sized + Serialize,
{
ser::SerializeSeq::serialize_element(self, value)
}
fn end(self) -> Result<Value<'lua>> {
ser::SerializeSeq::end(self)
}
}
impl<'lua> ser::SerializeTupleStruct for SerializeVec<'lua> {
type Ok = Value<'lua>;
type Error = Error;
fn serialize_field<T>(&mut self, value: &T) -> Result<()>
where
T: ?Sized + Serialize,
{
ser::SerializeSeq::serialize_element(self, value)
}
fn end(self) -> Result<Value<'lua>> {
ser::SerializeSeq::end(self)
}
}
pub struct SerializeTupleVariant<'lua> {
name: String<'lua>,
table: Table<'lua>,
}
impl<'lua> ser::SerializeTupleVariant for SerializeTupleVariant<'lua> {
type Ok = Value<'lua>;
type Error = Error;
fn serialize_field<T>(&mut self, value: &T) -> Result<()>
where
T: ?Sized + Serialize,
{
let lua = self.table.0.lua;
let idx = self.table.raw_len() + 1;
self.table.raw_insert(idx, lua.to_value(value)?)
}
fn end(self) -> Result<Value<'lua>> {
let lua = self.table.0.lua;
let table = lua.create_table()?;
table.raw_set(self.name, self.table)?;
Ok(Value::Table(table))
}
}
pub struct SerializeMap<'lua> {
table: Table<'lua>,
key: Option<Value<'lua>>,
}
impl<'lua> ser::SerializeMap for SerializeMap<'lua> {
type Ok = Value<'lua>;
type Error = Error;
fn serialize_key<T>(&mut self, key: &T) -> Result<()>
where
T: ?Sized + Serialize,
{
let lua = self.table.0.lua;
self.key = Some(lua.to_value(key)?);
Ok(())
}
fn serialize_value<T>(&mut self, value: &T) -> Result<()>
where
T: ?Sized + Serialize,
{
let lua = self.table.0.lua;
let key = mlua_expect!(
self.key.take(),
"serialize_value called before serialize_key"
);
let value = lua.to_value(value)?;
self.table.raw_set(key, value)
}
fn end(self) -> Result<Value<'lua>> {
Ok(Value::Table(self.table))
}
}
impl<'lua> ser::SerializeStruct for SerializeMap<'lua> {
type Ok = Value<'lua>;
type Error = Error;
fn serialize_field<T>(&mut self, key: &'static str, value: &T) -> Result<()>
where
T: ?Sized + Serialize,
{
ser::SerializeMap::serialize_key(self, key)?;
ser::SerializeMap::serialize_value(self, value)
}
fn end(self) -> Result<Value<'lua>> {
ser::SerializeMap::end(self)
}
}
pub struct SerializeStructVariant<'lua> {
name: String<'lua>,
table: Table<'lua>,
}
impl<'lua> ser::SerializeStructVariant for SerializeStructVariant<'lua> {
type Ok = Value<'lua>;
type Error = Error;
fn serialize_field<T>(&mut self, key: &'static str, value: &T) -> Result<()>
where
T: ?Sized + Serialize,
{
let lua = self.table.0.lua;
self.table.raw_set(key, lua.to_value(value)?)?;
Ok(())
}
fn end(self) -> Result<Value<'lua>> {
let lua = self.table.0.lua;
let table = lua.create_table()?;
table.raw_set(self.name, self.table)?;
Ok(Value::Table(table))
}
}
+2
View File
@@ -47,6 +47,8 @@ impl StdLib {
/// (unsafe) [`debug`](https://www.lua.org/manual/5.3/manual.html#6.10) library
pub const DEBUG: StdLib = StdLib(1 << 31);
/// No libraries
pub const NONE: StdLib = StdLib(0);
/// (unsafe) All standard libraries
pub const ALL: StdLib = StdLib(u32::MAX);
/// The safe subset of the standard libraries
+19
View File
@@ -1,5 +1,11 @@
use std::{slice, str};
#[cfg(feature = "serialize")]
use {
serde::ser::{Serialize, Serializer},
std::result::Result as StdResult,
};
use crate::error::{Error, Result};
use crate::ffi;
use crate::types::LuaRef;
@@ -105,3 +111,16 @@ where
self.as_bytes() == other.as_ref()
}
}
#[cfg(feature = "serialize")]
impl<'lua> Serialize for String<'lua> {
fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
where
S: Serializer,
{
match self.to_str() {
Ok(s) => serializer.serialize_str(s),
Err(_) => serializer.serialize_bytes(self.as_bytes()),
}
}
}
+86 -3
View File
@@ -1,6 +1,12 @@
use std::marker::PhantomData;
use std::os::raw::c_int;
#[cfg(feature = "serialize")]
use {
serde::ser::{Serialize, SerializeMap, SerializeSeq, Serializer},
std::result::Result as StdResult,
};
use crate::error::{Error, Result};
use crate::ffi;
use crate::function::Function;
@@ -447,9 +453,53 @@ impl<'lua> Table<'lua> {
TableSequence {
table: self.0,
index: Some(1),
len: None,
raw: false,
_phantom: PhantomData,
}
}
/// Consume this table and return an iterator over all values in the sequence part of the table.
///
/// Unlike the `sequence_values`, does not invoke `__index` metamethod when iterating.
///
/// [`sequence_values`]: #method.sequence_values
pub fn raw_sequence_values<V: FromLua<'lua>>(self) -> TableSequence<'lua, V> {
TableSequence {
table: self.0,
index: Some(1),
len: None,
raw: true,
_phantom: PhantomData,
}
}
#[cfg(feature = "serialize")]
pub(crate) fn raw_sequence_values_by_len<V: FromLua<'lua>>(self) -> TableSequence<'lua, V> {
let len = self.raw_len();
TableSequence {
table: self.0,
index: Some(1),
len: Some(len),
raw: true,
_phantom: PhantomData,
}
}
#[cfg(feature = "serialize")]
pub(crate) fn is_array(&self) -> bool {
let lua = self.0.lua;
unsafe {
let _sg = StackGuard::new(lua.state);
assert_stack(lua.state, 3);
lua.push_ref(&self.0);
if ffi::lua_getmetatable(lua.state, -1) == 0 {
return false;
}
crate::serde::push_array_metatable(lua.state);
ffi::lua_rawequal(lua.state, -1, -2) != 0
}
}
}
impl<'lua> PartialEq for Table<'lua> {
@@ -500,6 +550,7 @@ pub trait TableExt<'lua> {
///
/// This might invoke the `__index` metamethod.
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn call_async_method<'fut, K, A, R>(&self, key: K, args: A) -> LocalBoxFuture<'fut, Result<R>>
where
'lua: 'fut,
@@ -514,6 +565,7 @@ pub trait TableExt<'lua> {
///
/// This might invoke the `__index` metamethod.
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn call_async_function<'fut, K, A, R>(
&self,
key: K,
@@ -580,6 +632,31 @@ impl<'lua> TableExt<'lua> for Table<'lua> {
}
}
#[cfg(feature = "serialize")]
impl<'lua> Serialize for Table<'lua> {
fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
where
S: Serializer,
{
let len = self.raw_len() as usize;
if len > 0 || self.is_array() {
let mut seq = serializer.serialize_seq(Some(len))?;
for v in self.clone().raw_sequence_values_by_len::<Value>() {
let v = v.map_err(serde::ser::Error::custom)?;
seq.serialize_element(&v)?;
}
return seq.end();
}
let mut map = serializer.serialize_map(None)?;
for kv in self.clone().pairs::<Value, Value>() {
let (k, v) = kv.map_err(serde::ser::Error::custom)?;
map.serialize_entry(&k, &v)?;
}
map.end()
}
}
/// An iterator over the pairs of a Lua table.
///
/// This struct is created by the [`Table::pairs`] method.
@@ -651,6 +728,8 @@ where
pub struct TableSequence<'lua, V> {
table: LuaRef<'lua>,
index: Option<Integer>,
len: Option<Integer>,
raw: bool,
_phantom: PhantomData<V>,
}
@@ -669,9 +748,13 @@ where
assert_stack(lua.state, 5);
lua.push_ref(&self.table);
match protect_lua_closure(lua.state, 1, 1, |state| ffi::lua_geti(state, -1, index))
{
Ok(ffi::LUA_TNIL) => None,
let lua_geti = if self.raw {
ffi::lua_rawgeti
} else {
ffi::lua_geti
};
match protect_lua_closure(lua.state, 1, 1, |state| lua_geti(state, -1, index)) {
Ok(ffi::LUA_TNIL) if index > self.len.unwrap_or(0) => None,
Ok(_) => {
let value = lua.pop_value();
self.index = Some(index + 1);
+2
View File
@@ -52,6 +52,7 @@ pub struct Thread<'lua>(pub(crate) LuaRef<'lua>);
/// [`Future`]: ../futures_core/future/trait.Future.html
/// [`Stream`]: ../futures_core/stream/trait.Stream.html
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
#[derive(Debug)]
pub struct AsyncThread<'lua, R> {
thread: Thread<'lua>,
@@ -218,6 +219,7 @@ impl<'lua> Thread<'lua> {
/// # }
/// ```
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub fn into_async<A, R>(self, args: A) -> AsyncThread<'lua, R>
where
A: ToLuaMulti<'lua>,
+3
View File
@@ -10,6 +10,7 @@ use crate::error::Result;
use crate::ffi;
use crate::hook::Debug;
use crate::lua::Lua;
use crate::userdata::UserDataWrapped;
use crate::util::{assert_stack, StackGuard};
use crate::value::MultiValue;
@@ -31,6 +32,8 @@ pub(crate) type AsyncCallback<'lua, 'a> =
pub(crate) type HookCallback = Arc<RefCell<dyn FnMut(&Lua, Debug) -> Result<()>>>;
pub(crate) type UserDataCell<T> = RefCell<UserDataWrapped<T>>;
#[cfg(feature = "send")]
pub trait MaybeSend: Send {}
#[cfg(feature = "send")]
+115 -10
View File
@@ -1,15 +1,21 @@
use std::cell::{Ref, RefCell, RefMut};
use std::cell::{Ref, RefMut};
#[cfg(feature = "async")]
use std::future::Future;
#[cfg(feature = "serialize")]
use {
serde::ser::{self, Serialize, Serializer},
std::result::Result as StdResult,
};
use crate::error::{Error, Result};
use crate::ffi;
use crate::function::Function;
use crate::lua::Lua;
use crate::table::Table;
use crate::types::{LuaRef, MaybeSend};
use crate::util::{assert_stack, get_userdata, StackGuard};
use crate::types::{LuaRef, MaybeSend, UserDataCell};
use crate::util::{assert_stack, get_destructed_userdata_metatable, get_userdata, StackGuard};
use crate::value::{FromLua, FromLuaMulti, ToLua, ToLuaMulti, Value};
/// Kinds of metamethods that can be overridden.
@@ -185,6 +191,7 @@ pub trait UserDataMethods<'lua, T: UserData> {
///
/// [`add_method`]: #method.add_method
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn add_async_method<S, A, R, M, MR>(&mut self, name: &S, method: M)
where
T: Clone,
@@ -231,6 +238,7 @@ pub trait UserDataMethods<'lua, T: UserData> {
///
/// [`add_function`]: #method.add_function
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn add_async_function<S, A, R, F, FR>(&mut self, name: &S, function: F)
where
T: Clone,
@@ -360,6 +368,71 @@ pub trait UserData: Sized {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(_methods: &mut M) {}
}
pub(crate) struct UserDataWrapped<T> {
pub(crate) data: *mut T,
#[cfg(feature = "serialize")]
ser: *mut dyn erased_serde::Serialize,
}
impl<T> Drop for UserDataWrapped<T> {
fn drop(&mut self) {
unsafe {
drop(Box::from_raw(self.data));
#[cfg(feature = "serialize")]
if self.data as *mut () != self.ser as *mut () {
drop(Box::from_raw(self.ser));
}
}
}
}
impl<T> UserDataWrapped<T> {
pub(crate) fn new(data: T) -> Self {
UserDataWrapped {
data: Box::into_raw(Box::new(data)),
#[cfg(feature = "serialize")]
ser: Box::into_raw(Box::new(UserDataSerializeError)),
}
}
#[cfg(feature = "serialize")]
pub(crate) fn new_ser(data: T) -> Self
where
T: 'static + Serialize,
{
let data_raw = Box::into_raw(Box::new(data));
UserDataWrapped {
data: data_raw,
ser: data_raw,
}
}
}
impl<T> AsRef<T> for UserDataWrapped<T> {
fn as_ref(&self) -> &T {
unsafe { &*self.data }
}
}
impl<T> AsMut<T> for UserDataWrapped<T> {
fn as_mut(&mut self) -> &mut T {
unsafe { &mut *self.data }
}
}
#[cfg(feature = "serialize")]
pub(crate) struct UserDataSerializeError;
#[cfg(feature = "serialize")]
impl Serialize for UserDataSerializeError {
fn serialize<S>(&self, _serializer: S) -> StdResult<S::Ok, S::Error>
where
S: Serializer,
{
Err(ser::Error::custom("cannot serialize <userdata>"))
}
}
/// Handle to an internal Lua userdata for any type that implements [`UserData`].
///
/// Similar to `std::any::Any`, this provides an interface for dynamic type checking via the [`is`]
@@ -382,7 +455,7 @@ pub struct AnyUserData<'lua>(pub(crate) LuaRef<'lua>);
impl<'lua> AnyUserData<'lua> {
/// Checks whether the type of this userdata is `T`.
pub fn is<T: 'static + UserData>(&self) -> bool {
match self.inspect(|_: &RefCell<T>| Ok(())) {
match self.inspect(|_: &UserDataCell<T>| Ok(())) {
Ok(()) => true,
Err(Error::UserDataTypeMismatch) => false,
Err(_) => unreachable!(),
@@ -396,7 +469,10 @@ impl<'lua> AnyUserData<'lua> {
/// Returns a `UserDataBorrowError` if the userdata is already mutably borrowed. Returns a
/// `UserDataTypeMismatch` if the userdata is not of type `T`.
pub fn borrow<T: 'static + UserData>(&self) -> Result<Ref<T>> {
self.inspect(|cell| Ok(cell.try_borrow().map_err(|_| Error::UserDataBorrowError)?))
self.inspect(|cell| {
let cell_ref = cell.try_borrow().map_err(|_| Error::UserDataBorrowError)?;
Ok(Ref::map(cell_ref, |x| unsafe { &*x.data }))
})
}
/// Borrow this userdata mutably if it is of type `T`.
@@ -407,9 +483,10 @@ impl<'lua> AnyUserData<'lua> {
/// `UserDataTypeMismatch` if the userdata is not of type `T`.
pub fn borrow_mut<T: 'static + UserData>(&self) -> Result<RefMut<T>> {
self.inspect(|cell| {
Ok(cell
let cell_ref = cell
.try_borrow_mut()
.map_err(|_| Error::UserDataBorrowMutError)?)
.map_err(|_| Error::UserDataBorrowMutError)?;
Ok(RefMut::map(cell_ref, |x| unsafe { &mut *x.data }))
})
}
@@ -515,7 +592,7 @@ impl<'lua> AnyUserData<'lua> {
fn inspect<'a, T, R, F>(&'a self, func: F) -> Result<R>
where
T: 'static + UserData,
F: FnOnce(&'a RefCell<T>) -> Result<R>,
F: FnOnce(&'a UserDataCell<T>) -> Result<R>,
{
unsafe {
let lua = self.0.lua;
@@ -534,9 +611,16 @@ impl<'lua> AnyUserData<'lua> {
);
if ffi::lua_rawequal(lua.state, -1, -2) == 0 {
Err(Error::UserDataTypeMismatch)
// Maybe UserData destructed?
ffi::lua_pop(lua.state, 1);
get_destructed_userdata_metatable(lua.state);
if ffi::lua_rawequal(lua.state, -1, -2) == 1 {
Err(Error::UserDataDestructed)
} else {
Err(Error::UserDataTypeMismatch)
}
} else {
func(&*get_userdata::<RefCell<T>>(lua.state, -3))
func(&*get_userdata::<UserDataCell<T>>(lua.state, -3))
}
}
}
@@ -555,3 +639,24 @@ impl<'lua> AsRef<AnyUserData<'lua>> for AnyUserData<'lua> {
self
}
}
#[cfg(feature = "serialize")]
impl<'lua> Serialize for AnyUserData<'lua> {
fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
where
S: Serializer,
{
let f = || unsafe {
let lua = self.0.lua;
let _sg = StackGuard::new(lua.state);
assert_stack(lua.state, 2);
lua.push_userdata_ref(&self.0)?;
let ud = &*get_userdata::<UserDataCell<()>>(lua.state, -1);
(*ud.try_borrow().map_err(|_| Error::UserDataBorrowError)?.ser)
.serialize(serializer)
.map_err(|err| Error::SerializeError(err.to_string()))
};
f().map_err(ser::Error::custom)
}
}
+1 -1
View File
@@ -759,7 +759,7 @@ unsafe fn to_string<'a>(state: *mut ffi::lua_State, index: c_int) -> Cow<'a, str
}
}
unsafe fn get_destructed_userdata_metatable(state: *mut ffi::lua_State) {
pub(crate) unsafe fn get_destructed_userdata_metatable(state: *mut ffi::lua_State) {
ffi::lua_pushlightuserdata(
state,
&DESTRUCTED_USERDATA_METATABLE as *const u8 as *mut c_void,
+29
View File
@@ -1,6 +1,12 @@
use std::iter::{self, FromIterator};
use std::{slice, str, vec};
#[cfg(feature = "serialize")]
use {
serde::ser::{self, Serialize, Serializer},
std::result::Result as StdResult,
};
use crate::error::{Error, Result};
use crate::ffi;
use crate::function::Function;
@@ -110,6 +116,29 @@ impl<'lua> AsRef<Value<'lua>> for Value<'lua> {
}
}
#[cfg(feature = "serialize")]
impl<'lua> Serialize for Value<'lua> {
fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
where
S: Serializer,
{
match self {
Value::Nil => serializer.serialize_unit(),
Value::Boolean(b) => serializer.serialize_bool(*b),
Value::Integer(i) => serializer.serialize_i64(*i),
Value::Number(n) => serializer.serialize_f64(*n),
Value::String(s) => s.serialize(serializer),
Value::Table(t) => t.serialize(serializer),
Value::UserData(ud) => ud.serialize(serializer),
Value::LightUserData(ud) if ud.0.is_null() => serializer.serialize_none(),
Value::Error(_) | Value::LightUserData(_) | Value::Function(_) | Value::Thread(_) => {
let msg = format!("cannot serialize <{}>", self.type_name());
Err(ser::Error::custom(msg))
}
}
}
}
/// Trait for types convertible to `Value`.
pub trait ToLua<'lua> {
/// Performs the conversion.
@@ -22,14 +22,14 @@ note: ...so that the types are compatible
13 | | Ok(())
14 | | });
| |_____________^
= note: expected `main::MyUserData<'_>`
found `main::MyUserData<'a>`
= note: expected `(MyUserData<'_>,)`
found `(MyUserData<'a>,)`
note: but, the lifetime must be valid for the lifetime `'lua` as defined on the method body at 10:24...
--> $DIR/async_nonstatic_userdata.rs:10:24
|
10 | fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
| ^^^^
note: ...so that the type `impl std::future::Future` will meet its required lifetime bounds
note: ...so that the type `impl Future` will meet its required lifetime bounds
--> $DIR/async_nonstatic_userdata.rs:11:21
|
11 | methods.add_async_method("print", |_, data, ()| async move {
+12 -7
View File
@@ -1,11 +1,16 @@
error[E0277]: the type `std::cell::UnsafeCell<()>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
error[E0277]: the type `UnsafeCell<()>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
--> $DIR/lua_norefunwindsafe.rs:7:5
|
7 | catch_unwind(|| lua.create_table().unwrap());
| ^^^^^^^^^^^^ `std::cell::UnsafeCell<()>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
| ^^^^^^^^^^^^ `UnsafeCell<()>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
|
= help: within `mlua::lua::Lua`, the trait `std::panic::RefUnwindSafe` is not implemented for `std::cell::UnsafeCell<()>`
= note: required because it appears within the type `std::marker::PhantomData<std::cell::UnsafeCell<()>>`
= note: required because it appears within the type `mlua::lua::Lua`
= note: required because of the requirements on the impl of `std::panic::UnwindSafe` for `&mlua::lua::Lua`
= note: required because it appears within the type `[closure@$DIR/tests/compile/lua_norefunwindsafe.rs:7:18: 7:48 lua:&mlua::lua::Lua]`
::: $RUST/std/src/panic.rs
|
| pub fn catch_unwind<F: FnOnce() -> R + UnwindSafe, R>(f: F) -> Result<R> {
| ---------- required by this bound in `catch_unwind`
|
= help: within `Lua`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<()>`
= note: required because it appears within the type `PhantomData<UnsafeCell<()>>`
= note: required because it appears within the type `Lua`
= note: required because of the requirements on the impl of `UnwindSafe` for `&Lua`
= note: required because it appears within the type `[closure@$DIR/tests/compile/lua_norefunwindsafe.rs:7:18: 7:48]`
+6 -6
View File
@@ -1,14 +1,14 @@
error[E0277]: `std::rc::Rc<std::cell::Cell<i32>>` cannot be sent between threads safely
error[E0277]: `Rc<Cell<i32>>` cannot be sent between threads safely
--> $DIR/non_send.rs:11:9
|
11 | lua.create_function(move |_, ()| {
| _________^^^^^^^^^^^^^^^_-
| | |
| | `std::rc::Rc<std::cell::Cell<i32>>` cannot be sent between threads safely
| | `Rc<Cell<i32>>` cannot be sent between threads safely
12 | | Ok(data.get())
13 | | })?
| |_____- within this `[closure@$DIR/tests/compile/non_send.rs:11:25: 13:6 data:std::rc::Rc<std::cell::Cell<i32>>]`
| |_____- within this `[closure@$DIR/tests/compile/non_send.rs:11:25: 13:6]`
|
= help: within `[closure@$DIR/tests/compile/non_send.rs:11:25: 13:6 data:std::rc::Rc<std::cell::Cell<i32>>]`, the trait `std::marker::Send` is not implemented for `std::rc::Rc<std::cell::Cell<i32>>`
= note: required because it appears within the type `[closure@$DIR/tests/compile/non_send.rs:11:25: 13:6 data:std::rc::Rc<std::cell::Cell<i32>>]`
= note: required because of the requirements on the impl of `mlua::types::MaybeSend` for `[closure@$DIR/tests/compile/non_send.rs:11:25: 13:6 data:std::rc::Rc<std::cell::Cell<i32>>]`
= help: within `[closure@$DIR/tests/compile/non_send.rs:11:25: 13:6]`, the trait `Send` is not implemented for `Rc<Cell<i32>>`
= note: required because it appears within the type `[closure@$DIR/tests/compile/non_send.rs:11:25: 13:6]`
= note: required because of the requirements on the impl of `mlua::types::MaybeSend` for `[closure@$DIR/tests/compile/non_send.rs:11:25: 13:6]`
+13 -8
View File
@@ -1,13 +1,18 @@
error[E0277]: the type `std::cell::UnsafeCell<()>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
error[E0277]: the type `UnsafeCell<()>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
--> $DIR/ref_nounwindsafe.rs:8:5
|
8 | catch_unwind(move || table.set("a", "b").unwrap());
| ^^^^^^^^^^^^ `std::cell::UnsafeCell<()>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
| ^^^^^^^^^^^^ `UnsafeCell<()>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
|
= help: within `mlua::lua::Lua`, the trait `std::panic::RefUnwindSafe` is not implemented for `std::cell::UnsafeCell<()>`
= note: required because it appears within the type `std::marker::PhantomData<std::cell::UnsafeCell<()>>`
= note: required because it appears within the type `mlua::lua::Lua`
= note: required because of the requirements on the impl of `std::panic::UnwindSafe` for `&mlua::lua::Lua`
::: $RUST/std/src/panic.rs
|
| pub fn catch_unwind<F: FnOnce() -> R + UnwindSafe, R>(f: F) -> Result<R> {
| ---------- required by this bound in `catch_unwind`
|
= help: within `Lua`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<()>`
= note: required because it appears within the type `PhantomData<UnsafeCell<()>>`
= note: required because it appears within the type `Lua`
= note: required because of the requirements on the impl of `UnwindSafe` for `&Lua`
= note: required because it appears within the type `mlua::types::LuaRef<'_>`
= note: required because it appears within the type `mlua::table::Table<'_>`
= note: required because it appears within the type `[closure@$DIR/tests/compile/ref_nounwindsafe.rs:8:18: 8:54 table:mlua::table::Table<'_>]`
= note: required because it appears within the type `LuaTable<'_>`
= note: required because it appears within the type `[closure@$DIR/tests/compile/ref_nounwindsafe.rs:8:18: 8:54]`
+22 -41
View File
@@ -1,45 +1,26 @@
error[E0495]: cannot infer an appropriate lifetime for autoref due to conflicting requirements
--> $DIR/scope_callback_capture.rs:8:14
|
8 | .create_function_mut(move |_, t: Table| {
| ^^^^^^^^^^^^^^^^^^^
|
note: first, the lifetime cannot outlive the anonymous lifetime #2 defined on the body at 5:15...
--> $DIR/scope_callback_capture.rs:5:15
|
5 | lua.scope(|scope| {
| _______________^
6 | | let mut inner: Option<Table> = None;
7 | | let f = scope
8 | | .create_function_mut(move |_, t: Table| {
... |
16 | | Ok(())
17 | | });
| |_____^
note: ...so that reference does not outlive borrowed content
warning: unused variable: `old`
--> $DIR/scope_callback_capture.rs:9:29
|
9 | if let Some(old) = inner.take() {
| ^^^ help: if this is intentional, prefix it with an underscore: `_old`
|
= note: `#[warn(unused_variables)]` on by default
error[E0521]: borrowed data escapes outside of closure
--> $DIR/scope_callback_capture.rs:7:17
|
7 | let f = scope
| ^^^^^
note: but, the lifetime must be valid for the method call at 5:5...
--> $DIR/scope_callback_capture.rs:5:5
|
5 | / lua.scope(|scope| {
6 | | let mut inner: Option<Table> = None;
7 | | let f = scope
5 | lua.scope(|scope| {
| -----
| |
| `scope` declared here, outside of the closure body
| `scope` is a reference that is only valid in the closure body
6 | let mut inner: Option<Table> = None;
7 | let f = scope
| _________________^
8 | | .create_function_mut(move |_, t: Table| {
9 | | if let Some(old) = inner.take() {
10 | | // Access old callback `Lua`.
... |
16 | | Ok(())
17 | | });
| |______^
note: ...so that a type/lifetime parameter is in scope here
--> $DIR/scope_callback_capture.rs:5:5
|
5 | / lua.scope(|scope| {
6 | | let mut inner: Option<Table> = None;
7 | | let f = scope
8 | | .create_function_mut(move |_, t: Table| {
... |
16 | | Ok(())
17 | | });
| |______^
13 | | Ok(())
14 | | })?;
| |______________^ `scope` escapes the closure body here
+37 -40
View File
@@ -1,45 +1,42 @@
error[E0495]: cannot infer an appropriate lifetime for autoref due to conflicting requirements
--> $DIR/scope_callback_inner.rs:8:14
|
8 | .create_function_mut(|_, t: Table| {
| ^^^^^^^^^^^^^^^^^^^
|
note: first, the lifetime cannot outlive the anonymous lifetime #2 defined on the body at 5:15...
--> $DIR/scope_callback_inner.rs:5:15
|
5 | lua.scope(|scope| {
| _______________^
6 | | let mut inner: Option<Table> = None;
7 | | let f = scope
8 | | .create_function_mut(|_, t: Table| {
... |
13 | | Ok(())
14 | | });
| |_____^
note: ...so that reference does not outlive borrowed content
error[E0521]: borrowed data escapes outside of closure
--> $DIR/scope_callback_inner.rs:7:17
|
7 | let f = scope
| ^^^^^
note: but, the lifetime must be valid for the method call at 5:5...
--> $DIR/scope_callback_inner.rs:5:5
|
5 | / lua.scope(|scope| {
6 | | let mut inner: Option<Table> = None;
7 | | let f = scope
5 | lua.scope(|scope| {
| -----
| |
| `scope` declared here, outside of the closure body
| `scope` is a reference that is only valid in the closure body
6 | let mut inner: Option<Table> = None;
7 | let f = scope
| _________________^
8 | | .create_function_mut(|_, t: Table| {
... |
13 | | Ok(())
14 | | });
| |______^
note: ...so that a type/lifetime parameter is in scope here
--> $DIR/scope_callback_inner.rs:5:5
9 | | inner = Some(t);
10 | | Ok(())
11 | | })?;
| |______________^ `scope` escapes the closure body here
error[E0373]: closure may outlive the current function, but it borrows `inner`, which is owned by the current function
--> $DIR/scope_callback_inner.rs:8:34
|
5 | / lua.scope(|scope| {
6 | | let mut inner: Option<Table> = None;
7 | | let f = scope
5 | lua.scope(|scope| {
| ----- has type `&Scope<'_, '2>`
...
8 | .create_function_mut(|_, t: Table| {
| ^^^^^^^^^^^^^ may outlive borrowed value `inner`
9 | inner = Some(t);
| ----- `inner` is borrowed here
|
note: function requires argument type to outlive `'2`
--> $DIR/scope_callback_inner.rs:7:17
|
7 | let f = scope
| _________________^
8 | | .create_function_mut(|_, t: Table| {
... |
13 | | Ok(())
14 | | });
| |______^
9 | | inner = Some(t);
10 | | Ok(())
11 | | })?;
| |______________^
help: to force the closure to take ownership of `inner` (and any other referenced variables), use the `move` keyword
|
8 | .create_function_mut(move |_, t: Table| {
| ^^^^^^^^^^^^^^^^^^
+30 -11
View File
@@ -1,11 +1,30 @@
error: borrowed data cannot be stored outside of its closure
--> $DIR/scope_callback_outer.rs:7:17
|
5 | let mut outer: Option<Table> = None;
| --------- ...so that variable is valid at time of its declaration
6 | lua.scope(|scope| {
| ------- borrowed data cannot outlive this closure
7 | let f = scope
| ^^^^^ cannot be stored outside of its closure
8 | .create_function_mut(|_, t: Table| {
| ------------------- cannot infer an appropriate lifetime...
error[E0521]: borrowed data escapes outside of closure
--> $DIR/scope_callback_outer.rs:7:17
|
6 | lua.scope(|scope| {
| -----
| |
| `scope` declared here, outside of the closure body
| `scope` is a reference that is only valid in the closure body
7 | let f = scope
| _________________^
8 | | .create_function_mut(|_, t: Table| {
9 | | outer = Some(t);
10 | | Ok(())
11 | | })?;
| |______________^ `scope` escapes the closure body here
error[E0597]: `outer` does not live long enough
--> $DIR/scope_callback_outer.rs:9:17
|
6 | lua.scope(|scope| {
| ------- value captured here
...
9 | outer = Some(t);
| ^^^^^ borrowed value does not live long enough
...
15 | }
| -
| |
| `outer` dropped here while still borrowed
| borrow might be used here, when `outer` is dropped and runs the destructor for type `Option<LuaTable<'_>>`
+1 -1
View File
@@ -2,7 +2,7 @@ error[E0373]: closure may outlive the current function, but it borrows `test`, w
--> $DIR/scope_invariance.rs:14:38
|
9 | lua.scope(|scope| {
| ----- has type `&mlua::scope::Scope<'_, '1>`
| ----- has type `&Scope<'_, '1>`
...
14 | .create_function_mut(|_, ()| {
| ^^^^^^^ may outlive borrowed value `test`
+1 -1
View File
@@ -2,7 +2,7 @@ error[E0597]: `ibad` does not live long enough
--> $DIR/scope_userdata_borrow.rs:15:56
|
11 | lua.scope(|scope| {
| ----- has type `&mlua::scope::Scope<'_, '1>`
| ----- has type `&Scope<'_, '1>`
...
15 | scope.create_nonstatic_userdata(MyUserData(&ibad)).unwrap();
| -------------------------------------------^^^^^--
+14
View File
@@ -87,3 +87,17 @@ fn test_rust_function() -> Result<()> {
Ok(())
}
#[test]
fn test_dump() -> Result<()> {
let lua = unsafe { Lua::unsafe_new() };
let concat_lua = lua
.load(r#"function(arg1, arg2) return arg1 .. arg2 end"#)
.eval::<Function>()?;
let concat = lua.load(&concat_lua.dump(false)?).into_function()?;
assert_eq!(concat.call::<_, String>(("foo", "bar"))?, "foobar");
Ok(())
}
+341
View File
@@ -0,0 +1,341 @@
#![cfg(feature = "serialize")]
#![cfg_attr(
all(feature = "luajit", target_os = "macos", target_arch = "x86_64"),
feature(link_args)
)]
#[cfg_attr(
all(feature = "luajit", target_os = "macos", target_arch = "x86_64"),
link_args = "-pagezero_size 10000 -image_base 100000000",
allow(unused_attributes)
)]
extern "system" {}
use mlua::{Error, Lua, LuaSerdeExt, Result as LuaResult, UserData, Value};
use serde::{Deserialize, Serialize};
#[test]
fn test_serialize() -> Result<(), Box<dyn std::error::Error>> {
#[derive(Serialize)]
struct MyUserData(i64, String);
impl UserData for MyUserData {};
let lua = Lua::new();
let globals = lua.globals();
let ud = lua.create_ser_userdata(MyUserData(123, "test userdata".into()))?;
globals.set("ud", ud)?;
globals.set("null", lua.null()?)?;
let empty_array = lua.create_table()?;
empty_array.set_metatable(Some(lua.array_metatable()?));
globals.set("empty_array", empty_array)?;
let val = lua
.load(
r#"
{
_bool = true,
_integer = 123,
_number = 321.99,
_string = "test string serialization",
_table_arr = {nil, "value 1", nil, "value 2", {}},
_table_map = {["table"] = "map", ["null"] = null},
_bytes = "\240\040\140\040",
_userdata = ud,
_null = null,
_empty_map = {},
_empty_array = empty_array,
}
"#,
)
.eval::<Value>()?;
let json = serde_json::json!({
"_bool": true,
"_integer": 123,
"_number": 321.99,
"_string": "test string serialization",
"_table_arr": [null, "value 1", null, "value 2", {}],
"_table_map": {"table": "map", "null": null},
"_bytes": [240, 40, 140, 40],
"_userdata": [123, "test userdata"],
"_null": null,
"_empty_map": {},
"_empty_array": [],
});
assert_eq!(serde_json::to_value(&val)?, json);
// Test to-from loop
let val = lua.to_value(&json)?;
let expected_json = lua.from_value::<serde_json::Value>(val)?;
assert_eq!(expected_json, json);
Ok(())
}
#[test]
fn test_serialize_in_scope() -> LuaResult<()> {
#[derive(Serialize, Clone)]
struct MyUserData(i64, String);
impl UserData for MyUserData {};
let lua = Lua::new();
lua.scope(|scope| {
let ud = scope.create_ser_userdata(MyUserData(-5, "test userdata".into()))?;
assert_eq!(
serde_json::to_value(&ud).unwrap(),
serde_json::json!((-5, "test userdata"))
);
Ok(())
})?;
lua.scope(|scope| {
let ud = scope.create_ser_userdata(MyUserData(-5, "test userdata".into()))?;
lua.globals().set("ud", ud)
})?;
let val = lua.load("ud").eval::<Value>()?;
match serde_json::to_value(&val) {
Ok(v) => panic!("expected destructed error, got {}", v),
Err(e) if e.to_string().contains("destructed") => {}
Err(e) => panic!("expected destructed error, got {}", e),
}
Ok(())
}
#[test]
fn test_serialize_failure() -> Result<(), Box<dyn std::error::Error>> {
#[derive(Serialize)]
struct MyUserData(i64);
impl UserData for MyUserData {};
let lua = Lua::new();
let ud = Value::UserData(lua.create_userdata(MyUserData(123))?);
match serde_json::to_value(&ud) {
Ok(v) => panic!("expected serialization error, got {}", v),
Err(serde_json::Error { .. }) => {}
}
let func = lua.create_function(|_, _: ()| Ok(()))?;
match serde_json::to_value(&Value::Function(func.clone())) {
Ok(v) => panic!("expected serialization error, got {}", v),
Err(serde_json::Error { .. }) => {}
}
let thr = lua.create_thread(func)?;
match serde_json::to_value(&Value::Thread(thr)) {
Ok(v) => panic!("expected serialization error, got {}", v),
Err(serde_json::Error { .. }) => {}
}
Ok(())
}
#[test]
fn test_to_value_struct() -> LuaResult<()> {
let lua = Lua::new();
let globals = lua.globals();
globals.set("null", lua.null()?)?;
#[derive(Serialize)]
struct Test {
name: String,
key: i64,
data: Option<bool>,
};
let test = Test {
name: "alex".to_string(),
key: -16,
data: None,
};
globals.set("value", lua.to_value(&test)?)?;
lua.load(
r#"
assert(value["name"] == "alex")
assert(value["key"] == -16)
assert(value["data"] == null)
"#,
)
.exec()
}
#[test]
fn test_to_value_enum() -> LuaResult<()> {
let lua = Lua::new();
let globals = lua.globals();
globals.set("null", lua.null()?)?;
#[derive(Serialize)]
struct Test {
name: String,
key: i64,
data: Option<bool>,
};
let test = Test {
name: "alex".to_string(),
key: -16,
data: None,
};
globals.set("value", lua.to_value(&test)?)?;
lua.load(
r#"
assert(value["name"] == "alex")
assert(value["key"] == -16)
assert(value["data"] == null)
"#,
)
.exec()?;
#[derive(Serialize)]
enum E {
Unit,
Integer(u32),
Tuple(u32, u32),
Struct { a: u32 },
}
let u = E::Unit;
globals.set("value", lua.to_value(&u)?)?;
lua.load(r#"assert(value == "Unit")"#).exec()?;
let n = E::Integer(1);
globals.set("value", lua.to_value(&n)?)?;
lua.load(r#"assert(value["Integer"] == 1)"#).exec()?;
let t = E::Tuple(1, 2);
globals.set("value", lua.to_value(&t)?)?;
lua.load(
r#"
assert(value["Tuple"][1] == 1)
assert(value["Tuple"][2] == 2)
"#,
)
.exec()?;
let s = E::Struct { a: 1 };
globals.set("value", lua.to_value(&s)?)?;
lua.load(r#"assert(value["Struct"]["a"] == 1)"#).exec()?;
Ok(())
}
#[test]
fn test_from_value_struct() -> Result<(), Box<dyn std::error::Error>> {
let lua = Lua::new();
#[derive(Deserialize, PartialEq, Debug)]
struct Test {
int: u32,
seq: Vec<String>,
map: std::collections::HashMap<i32, i32>,
empty: Vec<()>,
tuple: (u8, u8, u8),
}
let value = lua
.load(
r#"
{
int = 1,
seq = {"a", "b"},
map = {2, [4] = 1},
empty = {},
tuple = {10, 20, 30},
}
"#,
)
.eval::<Value>()?;
let got = lua.from_value(value)?;
assert_eq!(
Test {
int: 1,
seq: vec!["a".into(), "b".into()],
map: vec![(1, 2), (4, 1)].into_iter().collect(),
empty: vec![],
tuple: (10, 20, 30),
},
got
);
Ok(())
}
#[test]
fn test_from_value_enum() -> Result<(), Box<dyn std::error::Error>> {
let lua = Lua::new();
#[derive(Deserialize, PartialEq, Debug)]
enum E {
Unit,
Integer(u32),
Tuple(u32, u32),
Struct { a: u32 },
}
let value = lua.load(r#""Unit""#).eval()?;
let got = lua.from_value(value)?;
assert_eq!(E::Unit, got);
let value = lua.load(r#"{Integer = 1}"#).eval()?;
let got = lua.from_value(value)?;
assert_eq!(E::Integer(1), got);
let value = lua.load(r#"{Tuple = {1, 2}}"#).eval()?;
let got = lua.from_value(value)?;
assert_eq!(E::Tuple(1, 2), got);
let value = lua.load(r#"{Struct = {a = 3}}"#).eval()?;
let got = lua.from_value(value)?;
assert_eq!(E::Struct { a: 3 }, got);
Ok(())
}
#[test]
fn test_from_value_enum_untagged() -> Result<(), Box<dyn std::error::Error>> {
let lua = Lua::new();
lua.globals().set("null", lua.null()?)?;
#[derive(Deserialize, PartialEq, Debug)]
#[serde(untagged)]
enum Eut {
Unit,
Integer(u64),
Tuple(u32, u32),
Struct { a: u32 },
}
let value = lua.load(r#"null"#).eval()?;
let got = lua.from_value(value)?;
assert_eq!(Eut::Unit, got);
let value = lua.load(r#"1"#).eval()?;
let got = lua.from_value(value)?;
assert_eq!(Eut::Integer(1), got);
let value = lua.load(r#"{3, 1}"#).eval()?;
let got = lua.from_value(value)?;
assert_eq!(Eut::Tuple(3, 1), got);
let value = lua.load(r#"{a = 10}"#).eval()?;
let got = lua.from_value(value)?;
assert_eq!(Eut::Struct { a: 10 }, got);
let value = lua.load(r#"{b = 12}"#).eval()?;
match lua.from_value::<Eut>(value) {
Ok(v) => panic!("expected Error::DeserializeError, got {:?}", v),
Err(Error::DeserializeError(_)) => {}
Err(e) => panic!("expected Error::DeserializeError, got {}", e),
}
Ok(())
}
+71 -3
View File
@@ -16,8 +16,8 @@ use std::sync::Arc;
use std::{error, f32, f64, fmt};
use mlua::{
Error, ExternalError, Function, Lua, Nil, Result, StdLib, String, Table, UserData, Value,
Variadic,
ChunkMode, Error, ExternalError, Function, Lua, Nil, Result, StdLib, String, Table, UserData,
Value, Variadic,
};
#[test]
@@ -56,6 +56,37 @@ fn test_safety() -> Result<()> {
Ok(_) => panic!("expected RuntimeError, got no error"),
}
match lua.load("1 + 1").set_mode(ChunkMode::Binary).exec() {
Err(Error::SafetyError(msg)) => {
assert!(msg.contains("binary chunks are disabled in safe mode"))
}
Err(e) => panic!("expected SafetyError, got {:?}", e),
Ok(_) => panic!("expected SafetyError, got no error"),
}
let bytecode = lua.load("return 1 + 1").into_function()?.dump(true)?;
match lua.load(&bytecode).exec() {
Err(Error::SafetyError(msg)) => {
assert!(msg.contains("binary chunks are disabled in safe mode"))
}
Err(e) => panic!("expected SafetyError, got {:?}", e),
Ok(_) => panic!("expected SafetyError, got no error"),
}
drop(lua);
// Test safety rules after dynamically loading `package` library
let lua = Lua::new_with(StdLib::NONE)?;
assert!(lua.globals().get::<_, Option<Value>>("require")?.is_none());
lua.load_from_std_lib(StdLib::PACKAGE)?;
match lua.load(r#"package.loadlib()"#).exec() {
Err(Error::CallbackError { ref cause, .. }) => match cause.as_ref() {
Error::SafetyError(_) => {}
e => panic!("expected SafetyError cause, got {:?}", e),
},
Err(e) => panic!("expected CallbackError, got {:?}", e),
Ok(_) => panic!("expected CallbackError, got no error"),
};
Ok(())
}
@@ -127,6 +158,41 @@ fn test_eval() -> Result<()> {
Ok(())
}
#[test]
fn test_load_mode() -> Result<()> {
let lua = unsafe { Lua::unsafe_new() };
assert_eq!(
lua.load("1 + 1").set_mode(ChunkMode::Text).eval::<i32>()?,
2
);
match lua.load("1 + 1").set_mode(ChunkMode::Binary).exec() {
Ok(_) => panic!("expected SyntaxError, got no error"),
Err(Error::SyntaxError { message: msg, .. }) => {
assert!(msg.contains("attempt to load a text chunk"))
}
Err(e) => panic!("expected SyntaxError, got {:?}", e),
};
let bytecode = lua.load("return 1 + 1").into_function()?.dump(true)?;
assert_eq!(lua.load(&bytecode).eval::<i32>()?, 2);
assert_eq!(
lua.load(&bytecode)
.set_mode(ChunkMode::Binary)
.eval::<i32>()?,
2
);
match lua.load(&bytecode).set_mode(ChunkMode::Text).exec() {
Ok(_) => panic!("expected SyntaxError, got no error"),
Err(Error::SyntaxError { message: msg, .. }) => {
assert!(msg.contains("attempt to load a binary chunk"))
}
Err(e) => panic!("expected SyntaxError, got {:?}", e),
};
Ok(())
}
#[test]
fn test_lua_multi() -> Result<()> {
let lua = Lua::new();
@@ -457,7 +523,9 @@ fn test_num_conversion() -> Result<()> {
assert!(lua.load("math.huge").eval::<i64>().is_err());
assert_eq!(lua.unpack::<f64>(lua.pack(f32::MAX)?)?, f32::MAX as f64);
assert!(lua.unpack::<f32>(lua.pack(f64::MAX)?).is_err());
assert_eq!(lua.unpack::<f64>(lua.pack(f32::MIN)?)?, f32::MIN as f64);
assert_eq!(lua.unpack::<f32>(lua.pack(f64::MAX)?)?, f32::INFINITY);
assert_eq!(lua.unpack::<f32>(lua.pack(f64::MIN)?)?, f32::NEG_INFINITY);
assert_eq!(lua.unpack::<i128>(lua.pack(1i128 << 64)?)?, 1i128 << 64);