Compare commits

..

59 Commits

Author SHA1 Message Date
Alex Orlenko 03b6dfb2c3 v0.9.0-beta.3 2023-06-07 14:40:49 +01:00
Alex Orlenko cf1cb31150 mlua_derive: v0.9.0-beta.2 2023-06-07 13:08:02 +01:00
Alex Orlenko 7d586f52f2 mlua-sys: v0.2.0 2023-06-07 13:06:51 +01:00
Alex Orlenko e85818e199 mlua-sys: include luau to luau-codegen feature flag 2023-06-07 13:05:16 +01:00
Alex Orlenko 16bec29274 Fix setting Luau version number in _VERSION env var 2023-06-07 12:35:45 +01:00
Alex Orlenko 5a135a331a Add UserDataFields::add_field() method to add static fields to UserData.
Plus `UserDataFields::add_meta_field()` for static meta fields.
Fix propagating fields to wrapped UserData types.
2023-06-07 12:18:24 +01:00
Alex Orlenko e7b712e29f Rely on LUA_MINSTACK in callbacks 2023-06-06 21:38:45 +01:00
Alex Orlenko d5483988d2 Optimize polling async functions (40% performance win) 2023-06-04 20:53:12 +01:00
Alex Orlenko 0f5c68dcf8 Update criterion to 0.5 2023-06-04 18:31:10 +01:00
Alex Orlenko 8ab0ccf11c Don't keep poll function in environment globals when polling async functions.
This is redundant after deprecating scoped async.
Closes #281
2023-06-04 02:31:29 +01:00
Alex Orlenko 9596f2e9ee Fix warning when compiling for lua51 2023-06-03 14:24:34 +01:00
Alex Orlenko 1dc32452e6 Support getting and setting environment for Lua functions.
Closes #218
2023-06-03 12:56:49 +01:00
Alex Orlenko 9785722d61 Require environment to be a Table instead of Value in Chunks.
In addition to that, `AsChunk::env()` renamed to `AsChunk::environment()`.
2023-06-03 00:00:53 +01:00
Alex Orlenko c905a34b1d (Luau) Set thread globals to main thread globals when resetting the thread 2023-06-02 21:13:23 +01:00
Alex Orlenko a1089dbf95 Optimize a bit calling Rust functions from Lua.
This is done by reusing upvalue as a captured variable.
Also in async functions upvalue is always set (async cannot be scoped) so extra checks removed.
2023-05-30 15:15:20 +01:00
Alex Orlenko baf25e263f Change _mlua to __mlua prefix for internal chunk names 2023-05-30 00:36:09 +01:00
Alex Orlenko 3abf73dee5 Update FunctionInfo doc and interface 2023-05-30 00:33:57 +01:00
Alex Orlenko 4adebd31f9 Update .gitignore 2023-05-30 00:33:16 +01:00
Alex Orlenko cea2d7fd15 Refactor application data container.
Now it's allowed at the same time mutably and immutably borrow different types.
Each value in the application data container is stored in it's own `RefCell` wrapper.
Also added new function `Lua::try_set_app_data()`.
2023-05-29 00:30:31 +01:00
Alex Orlenko e0224ab159 Use futures-core and futures-tasks via futures-util.
Just to reduce number of dependencies.
2023-05-29 00:30:29 +01:00
Alex Orlenko 6dee339783 Use lua_closethread in AsyncThread::drop() 2023-05-28 01:43:15 +01:00
Alex Orlenko b674d7906d Include (known) userdata type to Value debug pretty-print 2023-05-28 01:12:42 +01:00
Alex Orlenko 3d7796de55 clippy 2023-05-27 22:08:42 +01:00
Alex Orlenko 4306e6e978 Add Value::to_string() method similar to luaL_tolstring.
It uses `__tostring` metamethod if set.
Closes #279
2023-05-27 01:29:08 +01:00
Alex Orlenko 68e65a8ffe Set __name field in userdata metatable if not provided 2023-05-25 10:59:46 +01:00
Alex Orlenko 2efc637ab9 Move util into mod and add short_type_name function 2023-05-25 10:23:52 +01:00
Alex Orlenko 22e748557c Add new feature flag luau-jit to enable experimental Luau codegen backend 2023-05-24 23:13:12 +01:00
Alex Orlenko 77effb5055 Refactor callback_error_ext
Also it makes rust-analyzer happy
2023-05-24 00:46:59 +01:00
Alex Orlenko 1c66a02878 Update to Lua 5.4.6 2023-05-21 00:55:06 +01:00
Alex Orlenko 1ac98e7d16 - Allow downcasting error wrapped into Error::WithContext
- Overwrite error context when called multiple times
2023-05-08 23:39:53 +01:00
Alex Orlenko bbd2fe06e1 Use DeserializeOwned and remove lifetime from LuaSerdeExt trait 2023-05-06 23:07:34 +01:00
Alex Orlenko d951cb503f Add Value::NULL constant 2023-05-06 22:53:40 +01:00
Alex Orlenko bbd2488f79 Add OwnedAnyUserData::take() 2023-05-03 23:29:33 +01:00
Alex Orlenko 3253ae8f4a v0.9.0-beta.2 2023-04-27 23:13:32 +01:00
Alex Orlenko fdda0d3724 Update compile tests 2023-04-27 23:06:38 +01:00
Alex Orlenko a1d385c7b7 Add OwnedString 2023-04-26 23:17:27 +01:00
Alex Orlenko 21b834decc Update coverage calculation (exclude mlua-sys) 2023-04-26 22:22:11 +01:00
Alex Orlenko 4daa7de997 Various improvements for owned types, including:
- tests
- shortcuts for `OwnedFunction` and `OwnedAnyUserData`
2023-04-26 15:40:48 +01:00
Alex Orlenko bd68a155c0 Replace ManuallyDrop with MaybeUninit 2023-04-26 15:40:47 +01:00
Alex Orlenko 993aaf44c7 mlua-sys: v0.1.1 2023-04-24 11:25:22 +01:00
Alex Orlenko 54cbc62727 mlua-sys: use luau-src v0.5.6+luau573 2023-04-24 11:24:32 +01:00
Alex Orlenko 2d6a0fdf9c Check for reference leak detection only in mlua integratin tests.
This is not necessary an error and should not be enforced by default.
Fixes #268.
2023-04-23 10:56:26 +01:00
Alex Orlenko d0cbd32ad2 (clippy) mlua_derive: don't clone function name 2023-04-22 11:07:33 +01:00
Alex Orlenko be64706cff Missing doc for OwnedAnyUserData 2023-04-14 10:29:34 +01:00
Alex Orlenko c178bc0a55 cargo fmt 2023-04-14 10:29:12 +01:00
Alex Orlenko 0fccdfed5c Fix feature flags for owned types 2023-04-14 01:17:26 +01:00
Alex Orlenko aaf0a5e44a Remove usage of crate::ffi (clippy) 2023-04-14 00:36:31 +01:00
Alex Orlenko 98888883bc mlua-sys: clippy 2023-04-14 00:31:06 +01:00
Alex Orlenko 65e72f39ae Remove unstable feature flag from Function::wrap/wrap_mut/wrap_async 2023-04-14 00:21:18 +01:00
Alex Orlenko f9d1213c4b Don't set html_root_url (it's not recommended) 2023-04-13 22:44:40 +01:00
Alex Orlenko dc10c80e61 Add documentation url to mlua-sys 2023-04-13 22:32:27 +01:00
Alex Orlenko 23b9cefdca Add missing doc_cfg feature to mlua-sys 2023-04-13 21:55:37 +01:00
Alex Orlenko 15dc0e9f23 Move ffi module into mlua-sys crate 2023-04-12 23:23:34 +01:00
Alex Orlenko 0c53e09e30 Update tarpaulin settings 2023-04-12 01:03:33 +01:00
Alex Orlenko cdbf04f50c Add pretty-print to the Debug formatting to Value to Table.
This would allow dumping any Lua variable in human readable form.
2023-04-11 20:36:41 +01:00
Alex Orlenko ba324b4f54 Allow deserializing Lua null into unit(()) or unit struct. See #264 2023-04-10 01:49:51 +01:00
Alex Orlenko 3e83753466 Add Thread::set_hook() function 2023-04-08 23:53:48 +01:00
Alex Orlenko 288934c82c Refactor HookTriggers (make it const) 2023-04-05 01:02:27 +01:00
Alex Orlenko 483bc80fc4 Add must_use to AsyncThread 2023-04-05 00:23:13 +01:00
86 changed files with 2319 additions and 940 deletions
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
- name: Generate coverage report
run: |
cargo tarpaulin --verbose --features lua54,vendored,async,send,serialize,macros --out xml --exclude-files benches --exclude-files build --exclude-files mlua_derive --exclude-files src/ffi --exclude-files tests
cargo tarpaulin --out xml --tests --exclude-files benches/* --exclude-files mlua-sys/src/*/*
- name: Upload report to codecov.io
uses: codecov/codecov-action@v3
+11 -9
View File
@@ -9,7 +9,7 @@ jobs:
matrix:
os: [ubuntu-22.04, macos-latest, windows-latest]
rust: [stable]
lua: [lua54, lua53, lua52, lua51, luajit, luau]
lua: [lua54, lua53, lua52, lua51, luajit, luau, luau-jit]
include:
- os: ubuntu-22.04
target: x86_64-unknown-linux-gnu
@@ -104,7 +104,7 @@ jobs:
matrix:
os: [ubuntu-22.04, macos-latest, windows-latest]
rust: [stable, nightly]
lua: [lua54, lua53, lua52, lua51, luajit, luajit52, luau]
lua: [lua54, lua53, lua52, lua51, luajit, luajit52, luau, luau-jit]
include:
- os: ubuntu-22.04
target: x86_64-unknown-linux-gnu
@@ -118,11 +118,12 @@ jobs:
with:
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
- uses: Swatinem/rust-cache@v1
- uses: Swatinem/rust-cache@v2
- name: Run ${{ matrix.lua }} tests
run: |
cargo test --features "${{ matrix.lua }},vendored"
cargo test --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot,unstable"
cargo test --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot"
cargo test --features "${{ matrix.lua }},vendored,async,serialize,macros,parking_lot,unstable"
shell: bash
- name: Run compile tests (macos lua54)
if: ${{ matrix.os == 'macos-latest' && matrix.lua == 'lua54' }}
@@ -139,7 +140,7 @@ jobs:
matrix:
os: [ubuntu-22.04]
rust: [nightly]
lua: [lua54, lua53, lua52, lua51, luajit, luau]
lua: [lua54, lua53, lua52, lua51, luajit, luau, luau-jit]
include:
- os: ubuntu-22.04
target: x86_64-unknown-linux-gnu
@@ -149,12 +150,13 @@ jobs:
with:
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
- uses: Swatinem/rust-cache@v1
- uses: Swatinem/rust-cache@v2
- name: Run ${{ matrix.lua }} tests with address sanitizer
run: |
RUSTFLAGS="-Z sanitizer=address" \
cargo test --tests --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot,unstable" --target x86_64-unknown-linux-gnu -- --skip test_too_many_recursions
shell: bash
env:
RUSTFLAGS: -Z sanitizer=address
test_modules:
name: Test modules
@@ -176,7 +178,7 @@ jobs:
with:
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
- uses: Swatinem/rust-cache@v1
- uses: Swatinem/rust-cache@v2
- name: Run ${{ matrix.lua }} module tests
run: |
(cd tests/module && cargo build --release --features "${{ matrix.lua }}")
@@ -220,7 +222,7 @@ jobs:
runs-on: ubuntu-22.04
strategy:
matrix:
lua: [lua54, lua53, lua52, lua51, luajit, luau]
lua: [lua54, lua53, lua52, lua51, luajit, luau, luau-jit]
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@stable
+1
View File
@@ -4,3 +4,4 @@ Cargo.lock
.vscode/
.DS_Store
.stignore
+29
View File
@@ -1,3 +1,32 @@
## v0.9.0-beta.3
- Added `OwnedAnyUserData::take()`
- Switch to `DeserializeOwned`
- Overwrite error context when called multiple times
- New feature flag `luau-jit` to enable (experimental) Luau codegen backend
- Set `__name` field in userdata metatable
- Added `Value::to_string()` method similar to `luaL_tolstring`
- Lua 5.4.6
- Application data container now allows to mutably and immutably borrow different types at the same time
- Performance optimizations
- Support getting and setting environment for Lua functions.
- Added `UserDataFields::add_field()` method to add static fields to UserData
Breaking changes:
- Require environment to be a `Table` instead of `Value` in Chunks.
- `AsChunk::env()` renamed to `AsChunk::environment()`
## v0.9.0-beta.2
New features:
- Added `Thread::set_hook()` function to set hook on threads
- Added pretty print to the Debug formatting to Lua `Value` and `Table`
- ffi layer moved to `mlua-sys` crate
- Added OwnedString (unstable)
Breaking changes:
- Refactor `HookTriggers` (make it const)
## v0.9.0-beta.1
New features:
+17 -24
View File
@@ -1,6 +1,6 @@
[package]
name = "mlua"
version = "0.9.0-beta.1" # remember to update html_root_url and mlua_derive
version = "0.9.0-beta.3" # remember to update mlua_derive
authors = ["Aleksandr Orlenko <zxteam@pm.me>", "kyren <catherine@chucklefish.org>"]
edition = "2021"
repository = "https://github.com/khvzak/mlua"
@@ -9,8 +9,6 @@ readme = "README.md"
keywords = ["lua", "luajit", "luau", "async", "scripting"]
categories = ["api-bindings", "asynchronous"]
license = "MIT"
links = "lua"
build = "build/main.rs"
description = """
High level bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT) and Roblox Luau
with async/await features and support of writing native Lua modules in Rust.
@@ -23,48 +21,43 @@ rustdoc-args = ["--cfg", "docsrs"]
[workspace]
members = [
"mlua_derive",
"mlua-sys",
]
[features]
lua54 = []
lua53 = []
lua52 = []
lua51 = []
luajit = []
luajit52 = ["luajit"]
luau = ["luau0-src"]
vendored = ["lua-src", "luajit-src"]
module = ["mlua_derive"]
async = ["futures-core", "futures-task", "futures-util"]
lua54 = ["ffi/lua54"]
lua53 = ["ffi/lua53"]
lua52 = ["ffi/lua52"]
lua51 = ["ffi/lua51"]
luajit = ["ffi/luajit"]
luajit52 = ["luajit", "ffi/luajit52"]
luau = ["ffi/luau"]
luau-jit = ["luau", "ffi/luau-codegen"]
vendored = ["ffi/vendored"]
module = ["mlua_derive", "ffi/module"]
async = ["futures-util"]
send = []
serialize = ["serde", "erased-serde", "serde-value"]
macros = ["mlua_derive/macros"]
unstable = []
[dependencies]
mlua_derive = { version = "=0.9.0-beta.1", optional = true, path = "mlua_derive" }
mlua_derive = { version = "=0.9.0-beta.2", optional = true, path = "mlua_derive" }
bstr = { version = "1.0", features = ["std"], default_features = false }
once_cell = { version = "1.0" }
num-traits = { version = "0.2.14" }
rustc-hash = "1.0"
futures-core = { version = "0.3.5", optional = true }
futures-task = { version = "0.3.5", optional = true }
futures-util = { version = "0.3.5", optional = true }
futures-util = { version = "0.3", optional = true, default-features = false, features = ["std"] }
serde = { version = "1.0", optional = true }
erased-serde = { version = "0.3", optional = true }
serde-value = { version = "0.7", optional = true }
parking_lot = { version = "0.12", optional = true }
[build-dependencies]
cc = { version = "1.0" }
pkg-config = { version = "0.3.17" }
lua-src = { version = ">= 544.0.0, < 550.0.0", optional = true }
luajit-src = { version = ">= 210.4.0, < 220.0.0", optional = true }
luau0-src = { version = "0.5.0", optional = true }
ffi = { package = "mlua-sys", version = "0.2.0", path = "mlua-sys" }
[dev-dependencies]
rustyline = "11.0"
criterion = { version = "0.4", features = ["html_reports", "async_tokio"] }
criterion = { version = "0.5", features = ["async_tokio"] }
trybuild = "1.0"
futures = "0.3.5"
hyper = { version = "0.14", features = ["client", "server"] }
+1
View File
@@ -45,6 +45,7 @@ Below is a list of the available feature flags. By default `mlua` does not enabl
* `luajit`: activate [LuaJIT] support
* `luajit52`: activate [LuaJIT] support with partial compatibility with Lua 5.2
* `luau`: activate [Luau] support (auto vendored mode)
* `luau-jit`: activate [Luau] support with experimental jit backend. This is unstable feature and not recommended to use.
* `vendored`: build static Lua(JIT) library from sources during `mlua` compilation using [lua-src] or [luajit-src] crates
* `module`: enable module mode (building loadable `cdylib` library for Lua)
* `async`: enable async/await support (any executor can be used, eg. [tokio] or [async-std])
-5
View File
@@ -1,5 +0,0 @@
use std::path::PathBuf;
pub fn probe_lua() -> Option<PathBuf> {
None
}
-115
View File
@@ -1,115 +0,0 @@
#[cfg_attr(
any(
feature = "luau",
all(
feature = "vendored",
any(
feature = "lua54",
feature = "lua53",
feature = "lua52",
feature = "lua51",
feature = "luajit"
)
)
),
path = "find_vendored.rs"
)]
#[cfg_attr(
all(
not(feature = "vendored"),
any(
feature = "lua54",
feature = "lua53",
feature = "lua52",
feature = "lua51",
feature = "luajit"
)
),
path = "find_normal.rs"
)]
#[cfg_attr(
not(any(
feature = "lua54",
feature = "lua53",
feature = "lua52",
feature = "lua51",
feature = "luajit",
feature = "luau"
)),
path = "find_dummy.rs"
)]
mod find;
fn main() {
#[cfg(not(any(
feature = "lua54",
feature = "lua53",
feature = "lua52",
feature = "lua51",
feature = "luajit",
feature = "luau"
)))]
compile_error!(
"You must enable one of the features: lua54, lua53, lua52, lua51, luajit, luajit52, luau"
);
#[cfg(all(
feature = "lua54",
any(
feature = "lua53",
feature = "lua52",
feature = "lua51",
feature = "luajit",
feature = "luau"
)
))]
compile_error!(
"You can enable only one of the features: lua54, lua53, lua52, lua51, luajit, luajit52, luau"
);
#[cfg(all(
feature = "lua53",
any(
feature = "lua52",
feature = "lua51",
feature = "luajit",
feature = "luau"
)
))]
compile_error!(
"You can enable only one of the features: lua54, lua53, lua52, lua51, luajit, luajit52, luau"
);
#[cfg(all(
feature = "lua52",
any(feature = "lua51", feature = "luajit", feature = "luau")
))]
compile_error!(
"You can enable only one of the features: lua54, lua53, lua52, lua51, luajit, luajit52, luau"
);
#[cfg(all(feature = "lua51", any(feature = "luajit", feature = "luau")))]
compile_error!(
"You can enable only one of the features: lua54, lua53, lua52, lua51, luajit, luajit52, luau"
);
#[cfg(all(feature = "luajit", feature = "luau"))]
compile_error!(
"You can enable only one of the features: lua54, lua53, lua52, lua51, luajit, luajit52, luau"
);
// We don't support "vendored module" mode on windows
#[cfg(all(feature = "vendored", feature = "module", target_os = "windows"))]
compile_error!(
"Vendored (static) builds are not supported for modules on Windows.\n"
+ "Please, use `pkg-config` or custom mode to link to a Lua dll."
);
#[cfg(all(feature = "luau", feature = "module"))]
compile_error!("Luau does not support module mode");
#[cfg(any(not(feature = "module"), target_os = "windows"))]
find::probe_lua();
println!("cargo:rerun-if-changed=build");
}
+41
View File
@@ -0,0 +1,41 @@
[package]
name = "mlua-sys"
version = "0.2.0"
authors = ["Aleksandr Orlenko <zxteam@pm.me>"]
edition = "2021"
repository = "https://github.com/khvzak/mlua"
documentation = "https://docs.rs/mlua-sys"
readme = "README.md"
categories = ["external-ffi-bindings"]
license = "MIT"
links = "lua"
build = "build/main.rs"
description = """
Low level (FFI) bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT) and Roblox Luau
"""
[package.metadata.docs.rs]
features = ["lua54", "vendored"]
rustdoc-args = ["--cfg", "docsrs"]
[features]
lua54 = []
lua53 = []
lua52 = []
lua51 = []
luajit = []
luajit52 = ["luajit"]
luau = ["luau0-src"]
luau-codegen = ["luau"]
vendored = ["lua-src", "luajit-src"]
module = []
[dependencies]
[build-dependencies]
cc = "1.0"
cfg-if = "1.0"
pkg-config = "0.3.17"
lua-src = { version = ">= 546.0.0, < 550.0.0", optional = true }
luajit-src = { version = ">= 210.4.0, < 220.0.0", optional = true }
luau0-src = { version = "0.5.8", optional = true }
+8
View File
@@ -0,0 +1,8 @@
# mlua-sys
Low level (FFI) bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT) and Roblox [Luau].
Intended to be consumed by the [mlua] crate.
[Luau]: https://github.com/Roblox/luau
[mlua]: https://crates.io/crates/mlua
@@ -5,22 +5,25 @@ use std::path::PathBuf;
pub fn probe_lua() -> Option<PathBuf> {
#[cfg(feature = "lua54")]
let artifacts = lua_src::Build::new().build(lua_src::Lua54);
#[cfg(feature = "lua53")]
let artifacts = lua_src::Build::new().build(lua_src::Lua53);
#[cfg(feature = "lua52")]
let artifacts = lua_src::Build::new().build(lua_src::Lua52);
#[cfg(feature = "lua51")]
let artifacts = lua_src::Build::new().build(lua_src::Lua51);
#[cfg(feature = "luajit")]
let artifacts = {
let mut builder = luajit_src::Build::new();
if cfg!(feature = "luajit52") {
builder.lua52compat(true);
}
builder.build()
};
let artifacts = luajit_src::Build::new()
.lua52compat(cfg!(feature = "luajit52"))
.build();
#[cfg(feature = "luau")]
let artifacts = luau0_src::Build::new().build();
let artifacts = luau0_src::Build::new()
.enable_codegen(cfg!(feature = "luau-codegen"))
.build();
artifacts.print_cargo_metadata();
+19
View File
@@ -0,0 +1,19 @@
cfg_if::cfg_if! {
if #[cfg(all(feature = "lua54", not(any(feature = "lua53", feature = "lua52", feature = "lua51", feature = "luajit", feature = "luau"))))] {
include!("main_inner.rs");
} else if #[cfg(all(feature = "lua53", not(any(feature = "lua54", feature = "lua52", feature = "lua51", feature = "luajit", feature = "luau"))))] {
include!("main_inner.rs");
} else if #[cfg(all(feature = "lua52", not(any(feature = "lua54", feature = "lua53", feature = "lua51", feature = "luajit", feature = "luau"))))] {
include!("main_inner.rs");
} else if #[cfg(all(feature = "lua51", not(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luajit", feature = "luau"))))] {
include!("main_inner.rs");
} else if #[cfg(all(feature = "luajit", not(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "lua51", feature = "luau"))))] {
include!("main_inner.rs");
} else if #[cfg(all(feature = "luau", not(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "lua51", feature = "luajit"))))] {
include!("main_inner.rs");
} else {
fn main() {
compile_error!("You can enable only one of the features: lua54, lua53, lua52, lua51, luajit, luajit52, luau");
}
}
}
+26
View File
@@ -0,0 +1,26 @@
cfg_if::cfg_if! {
if #[cfg(any(feature = "luau", feature = "vendored"))] {
#[path = "find_vendored.rs"]
mod find;
} else {
#[path = "find_normal.rs"]
mod find;
}
}
fn main() {
// We don't support "vendored module" mode on windows
#[cfg(all(feature = "vendored", feature = "module", target_os = "windows"))]
compile_error!(
"Vendored (static) builds are not supported for modules on Windows.\n"
+ "Please, use `pkg-config` or custom mode to link to a Lua dll."
);
#[cfg(all(feature = "luau", feature = "module"))]
compile_error!("Luau does not support module mode");
#[cfg(any(not(feature = "module"), target_os = "windows"))]
find::probe_lua();
println!("cargo:rerun-if-changed=build");
}
+35 -35
View File
@@ -1,44 +1,52 @@
//! Low level bindings to Lua 5.4/5.3/5.2/5.1 including LuaJIT.
//! Low level bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT) and Roblox Luau.
#![allow(non_camel_case_types, non_snake_case, dead_code)]
#![allow(clippy::missing_safety_doc)]
#![doc(test(attr(deny(warnings))))]
#![cfg_attr(docsrs, feature(doc_cfg))]
use std::os::raw::c_int;
#[cfg(feature = "lua54")]
#[cfg(any(feature = "lua54", doc))]
pub use lua54::*;
#[cfg(feature = "lua53")]
#[cfg(any(feature = "lua53", doc))]
pub use lua53::*;
#[cfg(feature = "lua52")]
#[cfg(any(feature = "lua52", doc))]
pub use lua52::*;
#[cfg(any(feature = "lua51", feature = "luajit"))]
#[cfg(any(feature = "lua51", feature = "luajit", doc))]
pub use lua51::*;
#[cfg(feature = "luau")]
#[cfg(any(feature = "luau", doc))]
pub use luau::*;
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
#[doc(hidden)]
pub const LUA_MAX_UPVALUES: c_int = 255;
#[cfg(any(feature = "lua51", all(feature = "luajit", not(feature = "vendored"))))]
#[doc(hidden)]
pub const LUA_MAX_UPVALUES: c_int = 60;
#[cfg(all(feature = "luajit", feature = "vendored"))]
#[doc(hidden)]
pub const LUA_MAX_UPVALUES: c_int = 120;
#[cfg(feature = "luau")]
#[doc(hidden)]
pub const LUA_MAX_UPVALUES: c_int = 200;
// I believe `luaL_traceback` < 5.4 requires this much free stack to not error.
// 5.4 uses `luaL_Buffer`
#[doc(hidden)]
pub const LUA_TRACEBACK_STACK: c_int = 11;
// The minimum alignment guaranteed by the architecture. This value is used to
// add fast paths for low alignment values.
// Copied from https://github.com/rust-lang/rust/blob/master/library/std/src/sys/common/alloc.rs
#[cfg(all(any(
#[cfg(any(
target_arch = "x86",
target_arch = "arm",
target_arch = "mips",
@@ -50,9 +58,10 @@ pub const LUA_TRACEBACK_STACK: c_int = 11;
target_arch = "hexagon",
all(target_arch = "riscv32", not(target_os = "espidf")),
all(target_arch = "xtensa", not(target_os = "espidf")),
)))]
))]
#[doc(hidden)]
pub const SYS_MIN_ALIGN: usize = 8;
#[cfg(all(any(
#[cfg(any(
target_arch = "x86_64",
target_arch = "aarch64",
target_arch = "mips64",
@@ -60,45 +69,36 @@ pub const SYS_MIN_ALIGN: usize = 8;
target_arch = "sparc64",
target_arch = "riscv64",
target_arch = "wasm64",
)))]
))]
#[doc(hidden)]
pub const SYS_MIN_ALIGN: usize = 16;
// The allocator on the esp-idf platform guarentees 4 byte alignment.
#[cfg(all(any(
#[cfg(any(
all(target_arch = "riscv32", target_os = "espidf"),
all(target_arch = "xtensa", target_os = "espidf"),
)))]
))]
#[doc(hidden)]
pub const SYS_MIN_ALIGN: usize = 4;
// Hack to avoid stripping a few unused Lua symbols that could be imported
// by C modules in unsafe mode
#[cfg(not(feature = "luau"))]
pub(crate) fn keep_lua_symbols() {
let mut _symbols: Vec<*const extern "C" fn()> = vec![
lua_atpanic as _,
lua_isuserdata as _,
lua_tocfunction as _,
luaL_loadstring as _,
luaL_openlibs as _,
];
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
{
_symbols.push(lua_getglobal as _);
_symbols.push(lua_setglobal as _);
_symbols.push(luaL_setfuncs as _);
}
}
#[macro_use]
mod macros;
#[cfg(feature = "lua54")]
#[cfg(any(feature = "lua54", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "lua54")))]
pub mod lua54;
#[cfg(feature = "lua53")]
#[cfg(any(feature = "lua53", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "lua53")))]
pub mod lua53;
#[cfg(feature = "lua52")]
#[cfg(any(feature = "lua52", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "lua52")))]
pub mod lua52;
#[cfg(any(feature = "lua51", feature = "luajit"))]
#[cfg(any(feature = "lua51", feature = "luajit", doc))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua51", feature = "luajit"))))]
pub mod lua51;
#[cfg(feature = "luau")]
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub mod luau;
@@ -486,10 +486,10 @@ pub unsafe fn luaL_traceback(
lua_concat(L, lua_gettop(L) - top);
}
pub unsafe fn luaL_tolstring(L: *mut lua_State, idx: c_int, len: *mut usize) -> *const c_char {
pub unsafe fn luaL_tolstring(L: *mut lua_State, mut idx: c_int, len: *mut usize) -> *const c_char {
idx = lua_absindex(L, idx);
if luaL_callmeta(L, idx, cstr!("__tostring")) == 0 {
let t = lua_type(L, idx);
match t {
match lua_type(L, idx) {
LUA_TNIL => {
lua_pushliteral(L, "nil");
}
@@ -503,7 +503,7 @@ pub unsafe fn luaL_tolstring(L: *mut lua_State, idx: c_int, len: *mut usize) ->
lua_pushliteral(L, "true");
}
}
_ => {
t => {
let tt = luaL_getmetafield(L, idx, cstr!("__name"));
let name = if tt == LUA_TSTRING {
lua_tostring(L, -1)
@@ -512,7 +512,7 @@ pub unsafe fn luaL_tolstring(L: *mut lua_State, idx: c_int, len: *mut usize) ->
};
lua_pushfstring(L, cstr!("%s: %p"), name, lua_topointer(L, idx));
if tt != LUA_TNIL {
lua_replace(L, -2);
lua_replace(L, -2); // remove '__name'
}
}
};
@@ -205,10 +205,10 @@ pub unsafe fn luaL_newmetatable(L: *mut lua_State, tname: *const c_char) -> c_in
}
}
pub unsafe fn luaL_tolstring(L: *mut lua_State, idx: c_int, len: *mut usize) -> *const c_char {
pub unsafe fn luaL_tolstring(L: *mut lua_State, mut idx: c_int, len: *mut usize) -> *const c_char {
idx = lua_absindex(L, idx);
if luaL_callmeta(L, idx, cstr!("__tostring")) == 0 {
let t = lua_type(L, idx);
match t {
match lua_type(L, idx) {
LUA_TNIL => {
lua_pushliteral(L, "nil");
}
@@ -222,7 +222,7 @@ pub unsafe fn luaL_tolstring(L: *mut lua_State, idx: c_int, len: *mut usize) ->
lua_pushliteral(L, "true");
}
}
_ => {
t => {
let tt = luaL_getmetafield(L, idx, cstr!("__name"));
let name = if tt == LUA_TSTRING {
lua_tostring(L, -1)
@@ -231,7 +231,7 @@ pub unsafe fn luaL_tolstring(L: *mut lua_State, idx: c_int, len: *mut usize) ->
};
lua_pushfstring(L, cstr!("%s: %p"), name, lua_topointer(L, idx));
if tt != LUA_TNIL {
lua_replace(L, -2);
lua_replace(L, -2); // remove '__name'
}
}
};
@@ -1,4 +1,4 @@
//! MLua compatibility layer for Lua 5.2
//! MLua compatibility layer for Lua 5.3
use std::os::raw::c_int;
@@ -25,7 +25,8 @@ extern "C" {
pub fn luaL_getmetafield(L: *mut lua_State, obj: c_int, e: *const c_char) -> c_int;
pub fn luaL_callmeta(L: *mut lua_State, obj: c_int, e: *const c_char) -> c_int;
pub fn luaL_tolstring(L: *mut lua_State, idx: c_int, len: *mut usize) -> *const c_char;
#[link_name = "luaL_tolstring"]
pub fn luaL_tolstring_(L: *mut lua_State, idx: c_int, len: *mut usize) -> *const c_char;
pub fn luaL_argerror(L: *mut lua_State, arg: c_int, extramsg: *const c_char) -> c_int;
pub fn luaL_checklstring(L: *mut lua_State, arg: c_int, l: *mut usize) -> *const c_char;
pub fn luaL_optlstring(
@@ -167,6 +168,11 @@ pub unsafe fn luaL_getmetatable(L: *mut lua_State, n: *const c_char) {
lua::lua_getfield(L, lua::LUA_REGISTRYINDEX, n);
}
#[inline(always)]
pub unsafe fn luaL_tolstring(L: *mut lua_State, idx: c_int, len: *mut usize) -> *const c_char {
luaL_tolstring_(L, lua::lua_absindex(L, idx), len)
}
// luaL_opt would be implemented here but it is undocumented, so it's omitted
#[inline(always)]
@@ -112,7 +112,10 @@ extern "C" {
pub fn lua_newstate(f: lua_Alloc, ud: *mut c_void) -> *mut lua_State;
pub fn lua_close(L: *mut lua_State);
pub fn lua_newthread(L: *mut lua_State) -> *mut lua_State;
// Deprecated in Lua 5.4.6
pub fn lua_resetthread(L: *mut lua_State) -> c_int;
#[cfg(feature = "vendored")]
pub fn lua_closethread(L: *mut lua_State, from: *mut lua_State) -> c_int;
pub fn lua_atpanic(L: *mut lua_State, panicf: lua_CFunction) -> lua_CFunction;
@@ -436,10 +436,10 @@ pub unsafe fn luaL_traceback(
lua_concat(L, lua_gettop(L) - top);
}
pub unsafe fn luaL_tolstring(L: *mut lua_State, idx: c_int, len: *mut usize) -> *const c_char {
pub unsafe fn luaL_tolstring(L: *mut lua_State, mut idx: c_int, len: *mut usize) -> *const c_char {
idx = lua_absindex(L, idx);
if luaL_callmeta(L, idx, cstr!("__tostring")) == 0 {
let t = lua_type(L, idx);
match t {
match lua_type(L, idx) {
LUA_TNIL => {
lua_pushliteral(L, "nil");
}
@@ -453,7 +453,7 @@ pub unsafe fn luaL_tolstring(L: *mut lua_State, idx: c_int, len: *mut usize) ->
lua_pushliteral(L, "true");
}
}
_ => {
t => {
let tt = luaL_getmetafield(L, idx, cstr!("__name"));
let name = if tt == LUA_TSTRING {
lua_tostring(L, -1)
@@ -462,7 +462,7 @@ pub unsafe fn luaL_tolstring(L: *mut lua_State, idx: c_int, len: *mut usize) ->
};
lua_pushfstring(L, cstr!("%s: %p"), name, lua_topointer(L, idx));
if tt != LUA_TNIL {
lua_replace(L, -2);
lua_replace(L, -2); // remove '__name'
}
}
};
@@ -74,6 +74,7 @@ pub type lua_Continuation = unsafe extern "C" fn(L: *mut lua_State, status: c_in
/// Type for userdata destructor functions.
pub type lua_Udestructor = unsafe extern "C" fn(*mut c_void);
pub type lua_Destructor = unsafe extern "C" fn(L: *mut lua_State, *mut c_void);
/// Type for memory-allocation functions.
pub type lua_Alloc = unsafe extern "C" fn(
@@ -83,6 +84,11 @@ pub type lua_Alloc = unsafe extern "C" fn(
nsize: usize,
) -> *mut c_void;
/// Returns Luau release version (eg. `0.xxx`).
pub const fn luau_version() -> Option<&'static str> {
option_env!("LUAU_VERSION")
}
extern "C" {
//
// State manipulation
@@ -265,11 +271,8 @@ extern "C" {
// TODO: lua_encodepointer
pub fn lua_clock() -> c_double;
pub fn lua_setuserdatatag(L: *mut lua_State, idx: c_int, tag: c_int);
pub fn lua_setuserdatadtor(
L: *mut lua_State,
tag: c_int,
dtor: Option<unsafe extern "C" fn(*mut lua_State, *mut c_void)>,
);
pub fn lua_setuserdatadtor(L: *mut lua_State, tag: c_int, dtor: Option<lua_Destructor>);
pub fn lua_getuserdatadtor(L: *mut lua_State, tag: c_int) -> Option<lua_Destructor>;
pub fn lua_clonefunction(L: *mut lua_State, idx: c_int);
pub fn lua_cleartable(L: *mut lua_State, idx: c_int);
}
+11
View File
@@ -0,0 +1,11 @@
//! Contains definitions from `luacodegen.h`.
use std::os::raw::c_int;
use super::lua::lua_State;
extern "C" {
pub fn luau_codegen_supported() -> c_int;
pub fn luau_codegen_create(state: *mut lua_State);
pub fn luau_codegen_compile(state: *mut lua_State, idx: c_int);
}
@@ -4,10 +4,12 @@ pub use compat::*;
pub use lauxlib::*;
pub use lua::*;
pub use luacode::*;
pub use luacodegen::*;
pub use lualib::*;
pub mod compat;
pub mod lauxlib;
pub mod lua;
pub mod luacode;
pub mod luacodegen;
pub mod lualib;
+7
View File
@@ -0,0 +1,7 @@
#[allow(unused_macros)]
macro_rules! cstr {
($s:expr) => {
concat!($s, "\0") as *const str as *const [::std::os::raw::c_char]
as *const ::std::os::raw::c_char
};
}
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "mlua_derive"
version = "0.9.0-beta.1"
version = "0.9.0-beta.2"
authors = ["Aleksandr Orlenko <zxteam@pm.me>"]
edition = "2021"
description = "Procedural macros for the mlua crate."
+10 -10
View File
@@ -42,7 +42,7 @@ pub fn lua_module(attr: TokenStream, item: TokenStream) -> TokenStream {
}
let func = parse_macro_input!(item as ItemFn);
let func_name = func.sig.ident.clone();
let func_name = &func.sig.ident;
let module_name = args.name.unwrap_or_else(|| func_name.clone());
let ext_entrypoint_name = Ident::new(&format!("luaopen_{module_name}"), Span::call_site());
@@ -84,26 +84,26 @@ pub fn chunk(input: TokenStream) -> TokenStream {
});
let wrapped_code = quote! {{
use ::mlua::{AsChunk, ChunkMode, Lua, Result, Value};
use ::mlua::{AsChunk, ChunkMode, Lua, Result, Table};
use ::std::borrow::Cow;
use ::std::io::Result as IoResult;
use ::std::sync::Mutex;
struct InnerChunk<F: for <'a> FnOnce(&'a Lua) -> Result<Value<'a>>>(Mutex<Option<F>>);
struct InnerChunk<F: for <'a> FnOnce(&'a Lua) -> Result<Table<'a>>>(Mutex<Option<F>>);
impl<F> AsChunk<'static> for InnerChunk<F>
where
F: for <'a> FnOnce(&'a Lua) -> Result<Value<'a>>,
F: for <'a> FnOnce(&'a Lua) -> Result<Table<'a>>,
{
fn env<'lua>(&self, lua: &'lua Lua) -> Result<Value<'lua>> {
fn environment<'lua>(&self, lua: &'lua Lua) -> Result<Option<Table<'lua>>> {
if #caps_len > 0 {
if let Ok(mut make_env) = self.0.lock() {
if let Some(make_env) = make_env.take() {
return make_env(lua);
return make_env(lua).map(Some);
}
}
}
Ok(Value::Nil)
Ok(None)
}
fn mode(&self) -> Option<ChunkMode> {
@@ -115,9 +115,9 @@ pub fn chunk(input: TokenStream) -> TokenStream {
}
}
fn annotate<F: for<'a> FnOnce(&'a Lua) -> Result<Value<'a>>>(f: F) -> F { f }
fn annotate<F: for<'a> FnOnce(&'a Lua) -> Result<Table<'a>>>(f: F) -> F { f }
let make_env = annotate(move |lua: &Lua| -> Result<Value> {
let make_env = annotate(move |lua: &Lua| -> Result<Table> {
let globals = lua.globals();
let env = lua.create_table()?;
let meta = lua.create_table()?;
@@ -128,7 +128,7 @@ pub fn chunk(input: TokenStream) -> TokenStream {
#(#caps)*
env.set_metatable(Some(meta));
Ok(Value::Table(env))
Ok(env)
});
InnerChunk(Mutex::new(Some(make_env)))
+15 -12
View File
@@ -5,14 +5,14 @@ use std::io::Result as IoResult;
use std::path::{Path, PathBuf};
use std::string::String as StdString;
use crate::error::{Error, Result};
use crate::ffi;
use crate::error::{Error, ErrorContext, Result};
use crate::function::Function;
use crate::lua::Lua;
use crate::value::{FromLuaMulti, IntoLua, IntoLuaMulti, Value};
use crate::table::Table;
use crate::value::{FromLuaMulti, IntoLua, IntoLuaMulti};
#[cfg(feature = "async")]
use {futures_core::future::LocalBoxFuture, futures_util::future};
use futures_util::future::{self, LocalBoxFuture};
/// Trait for types [loadable by Lua] and convertible to a [`Chunk`]
///
@@ -27,9 +27,9 @@ pub trait AsChunk<'a> {
/// Returns optional chunk [environment]
///
/// [environment]: https://www.lua.org/manual/5.4/manual.html#2.2
fn env<'lua>(&self, lua: &'lua Lua) -> Result<Value<'lua>> {
fn environment<'lua>(&self, lua: &'lua Lua) -> Result<Option<Table<'lua>>> {
let _lua = lua; // suppress warning
Ok(Value::Nil)
Ok(None)
}
/// Returns optional chunk mode (text or binary)
@@ -104,7 +104,7 @@ impl AsChunk<'static> for PathBuf {
pub struct Chunk<'lua, 'a> {
pub(crate) lua: &'lua Lua,
pub(crate) name: StdString,
pub(crate) env: Result<Value<'lua>>,
pub(crate) env: Result<Option<Table<'lua>>>,
pub(crate) mode: Option<ChunkMode>,
pub(crate) source: IoResult<Cow<'a, [u8]>>,
#[cfg(feature = "luau")]
@@ -255,9 +255,9 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
self
}
/// Sets the first upvalue (`_ENV`) of the loaded chunk to the given value.
/// Sets the environment of the loaded chunk to the given value.
///
/// Lua main chunks always have exactly one upvalue, and this upvalue is used as the `_ENV`
/// In Lua >=5.2 main chunks always have exactly one upvalue, and this upvalue is used as the `_ENV`
/// variable inside the chunk. By default this value is set to the global environment.
///
/// Calling this method changes the `_ENV` upvalue to the value provided, and variables inside
@@ -267,7 +267,10 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
/// necessary to populate the environment in order for scripts using custom environments to be
/// useful.
pub fn set_environment<V: IntoLua<'lua>>(mut self, env: V) -> Self {
self.env = env.into_lua(self.lua);
self.env = env
.into_lua(self.lua)
.and_then(|val| self.lua.unpack(val))
.context("bad environment value");
self
}
@@ -415,7 +418,7 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
self.mode = Some(ChunkMode::Binary);
}
#[cfg(not(feature = "luau"))]
if let Ok(func) = self.lua.load_chunk(None, Value::Nil, None, source.as_ref()) {
if let Ok(func) = self.lua.load_chunk(None, None, None, source.as_ref()) {
let data = func.dump(false);
self.source = Ok(Cow::Owned(data));
self.mode = Some(ChunkMode::Binary);
@@ -455,7 +458,7 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
} else {
let mut cache = ChunksCache(HashMap::new());
cache.0.insert(text_source, binary_source.as_ref().to_vec());
self.lua.set_app_data(cache);
let _ = self.lua.try_set_app_data(cache);
}
}
}
+17 -16
View File
@@ -9,7 +9,7 @@ use bstr::{BStr, BString};
use num_traits::cast;
use crate::error::{Error, Result};
use crate::function::Function;
use crate::function::{Function, WrappedFunction};
use crate::lua::Lua;
use crate::string::String;
use crate::table::Table;
@@ -18,14 +18,10 @@ use crate::types::{LightUserData, MaybeSend};
use crate::userdata::{AnyUserData, UserData, UserDataRef, UserDataRefMut};
use crate::value::{FromLua, IntoLua, Nil, Value};
#[cfg(feature = "unstable")]
use crate::{
function::{OwnedFunction, WrappedFunction},
table::OwnedTable,
userdata::OwnedAnyUserData,
};
#[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))]
use crate::{function::OwnedFunction, table::OwnedTable, userdata::OwnedAnyUserData};
#[cfg(all(feature = "async", feature = "unstable"))]
#[cfg(feature = "async")]
use crate::function::WrappedAsyncFunction;
impl<'lua> IntoLua<'lua> for Value<'lua> {
@@ -83,7 +79,8 @@ impl<'lua> FromLua<'lua> for Table<'lua> {
}
}
#[cfg(feature = "unstable")]
#[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))]
impl<'lua> IntoLua<'lua> for OwnedTable {
#[inline]
fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
@@ -91,7 +88,8 @@ impl<'lua> IntoLua<'lua> for OwnedTable {
}
}
#[cfg(feature = "unstable")]
#[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))]
impl<'lua> FromLua<'lua> for OwnedTable {
#[inline]
fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> Result<OwnedTable> {
@@ -120,7 +118,8 @@ impl<'lua> FromLua<'lua> for Function<'lua> {
}
}
#[cfg(feature = "unstable")]
#[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))]
impl<'lua> IntoLua<'lua> for OwnedFunction {
#[inline]
fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
@@ -128,7 +127,8 @@ impl<'lua> IntoLua<'lua> for OwnedFunction {
}
}
#[cfg(feature = "unstable")]
#[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))]
impl<'lua> FromLua<'lua> for OwnedFunction {
#[inline]
fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> Result<OwnedFunction> {
@@ -136,7 +136,6 @@ impl<'lua> FromLua<'lua> for OwnedFunction {
}
}
#[cfg(feature = "unstable")]
impl<'lua> IntoLua<'lua> for WrappedFunction<'lua> {
#[inline]
fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
@@ -144,7 +143,7 @@ impl<'lua> IntoLua<'lua> for WrappedFunction<'lua> {
}
}
#[cfg(all(feature = "async", feature = "unstable"))]
#[cfg(feature = "async")]
impl<'lua> IntoLua<'lua> for WrappedAsyncFunction<'lua> {
#[inline]
fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
@@ -194,7 +193,8 @@ impl<'lua> FromLua<'lua> for AnyUserData<'lua> {
}
}
#[cfg(feature = "unstable")]
#[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))]
impl<'lua> IntoLua<'lua> for OwnedAnyUserData {
#[inline]
fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
@@ -202,7 +202,8 @@ impl<'lua> IntoLua<'lua> for OwnedAnyUserData {
}
}
#[cfg(feature = "unstable")]
#[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))]
impl<'lua> FromLua<'lua> for OwnedAnyUserData {
#[inline]
fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> Result<OwnedAnyUserData> {
+24 -14
View File
@@ -335,6 +335,10 @@ impl StdError for Error {
// Given that we include source to fmt::Display implementation for `CallbackError`, this call returns nothing.
Error::CallbackError { .. } => None,
Error::ExternalError(ref err) => err.source(),
Error::WithContext { ref cause, .. } => match cause.as_ref() {
Error::ExternalError(err) => err.source(),
_ => None,
},
_ => None,
}
}
@@ -353,6 +357,10 @@ impl Error {
{
match self {
Error::ExternalError(err) => err.downcast_ref(),
Error::WithContext { cause, .. } => match cause.as_ref() {
Error::ExternalError(err) => err.downcast_ref(),
_ => None,
},
_ => None,
}
}
@@ -414,33 +422,35 @@ pub trait ErrorContext: Sealed {
impl ErrorContext for Error {
fn context<C: fmt::Display>(self, context: C) -> Self {
Error::WithContext {
context: context.to_string(),
cause: Arc::new(self),
let context = context.to_string();
match self {
Error::WithContext { cause, .. } => Error::WithContext { context, cause },
_ => Error::WithContext {
context,
cause: Arc::new(self),
},
}
}
fn with_context<C: fmt::Display>(self, f: impl FnOnce(&Error) -> C) -> Self {
Error::WithContext {
context: f(&self).to_string(),
cause: Arc::new(self),
let context = f(&self).to_string();
match self {
Error::WithContext { cause, .. } => Error::WithContext { context, cause },
_ => Error::WithContext {
context,
cause: Arc::new(self),
},
}
}
}
impl<T> ErrorContext for StdResult<T, Error> {
fn context<C: fmt::Display>(self, context: C) -> Self {
self.map_err(|err| Error::WithContext {
context: context.to_string(),
cause: Arc::new(err),
})
self.map_err(|err| err.context(context))
}
fn with_context<C: fmt::Display>(self, f: impl FnOnce(&Error) -> C) -> Self {
self.map_err(|err| Error::WithContext {
context: f(&err).to_string(),
cause: Arc::new(err),
})
self.map_err(|err| err.with_context(f))
}
}
+187 -36
View File
@@ -1,36 +1,36 @@
use std::cell::RefCell;
use std::mem;
use std::os::raw::{c_int, c_void};
use std::ptr;
use std::slice;
use crate::error::{Error, Result};
use crate::ffi;
use crate::lua::Lua;
use crate::memory::MemoryState;
use crate::types::LuaRef;
use crate::table::Table;
use crate::types::{Callback, LuaRef, MaybeSend};
use crate::util::{
assert_stack, check_stack, error_traceback, pop_error, ptr_to_cstr_bytes, StackGuard,
};
use crate::value::{FromLuaMulti, IntoLuaMulti};
#[cfg(feature = "unstable")]
use {
crate::lua::Lua,
crate::types::{Callback, MaybeSend},
crate::value::IntoLua,
std::cell::RefCell,
};
use crate::value::{FromLuaMulti, IntoLua, IntoLuaMulti};
#[cfg(feature = "async")]
use {futures_core::future::LocalBoxFuture, futures_util::future};
#[cfg(all(feature = "async", feature = "unstable"))]
use {crate::types::AsyncCallback, futures_core::Future, futures_util::TryFutureExt};
use {
crate::types::AsyncCallback,
futures_util::future::{self, Future, LocalBoxFuture, TryFutureExt},
};
/// Handle to an internal Lua function.
#[derive(Clone, Debug)]
pub struct Function<'lua>(pub(crate) LuaRef<'lua>);
/// Owned handle to an internal Lua function.
///
/// The owned handle holds a *strong* reference to the current Lua instance.
/// Be warned, if you place it into a Lua type (eg. [`UserData`] or a Rust callback), it is *very easy*
/// to accidentally cause reference cycles that would prevent destroying Lua instance.
///
/// [`UserData`]: crate::UserData
#[cfg(feature = "unstable")]
#[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
#[derive(Clone, Debug)]
@@ -39,20 +39,36 @@ pub struct OwnedFunction(pub(crate) crate::types::LuaOwnedRef);
#[cfg(feature = "unstable")]
impl OwnedFunction {
/// Get borrowed handle to the underlying Lua function.
#[cfg_attr(feature = "send", allow(unused))]
pub const fn to_ref(&self) -> Function {
Function(self.0.to_ref())
}
}
/// Contains information about a function.
///
/// Please refer to the [`Lua Debug Interface`] for more information.
///
/// [`Lua Debug Interface`]: https://www.lua.org/manual/5.4/manual.html#4.7
#[derive(Clone, Debug)]
pub struct FunctionInfo {
pub name: Option<Vec<u8>>,
pub name_what: Option<Vec<u8>>,
pub what: Option<Vec<u8>>,
/// A (reasonable) name of the function.
pub name: Option<String>,
/// Explains the `name` field ("global", "local", "method", "field", "upvalue", or "").
///
/// Always `None` for Luau.
pub name_what: Option<String>,
/// A string "Lua" if the function is a Lua function, "C" if it is a C function, "main" if it is the main part of a chunk.
pub what: Option<String>,
/// The source of the chunk that created the function.
pub source: Option<Vec<u8>>,
/// A "printable" version of source, to be used in error messages.
pub short_src: Option<Vec<u8>>,
/// The line number where the definition of the function starts.
pub line_defined: i32,
#[cfg(not(feature = "luau"))]
/// The line number where the definition of the function ends.
///
/// Always `-1` for Luau.
pub last_line_defined: i32,
}
@@ -270,10 +286,113 @@ impl<'lua> Function<'lua> {
"#,
)
.try_cache()
.set_name("_mlua_bind")
.set_name("__mlua_bind")
.call((self.clone(), args_wrapper))
}
/// Returns the environment of the Lua function.
///
/// By default Lua functions shares a global environment.
///
/// This function always returns `None` for Rust/C functions.
pub fn environment(&self) -> Option<Table> {
let lua = self.0.lua;
let state = lua.state();
unsafe {
let _sg = StackGuard::new(state);
assert_stack(state, 1);
lua.push_ref(&self.0);
let mut ar: ffi::lua_Debug = mem::zeroed();
#[cfg(not(feature = "luau"))]
{
ffi::lua_pushvalue(state, -1);
ffi::lua_getinfo(state, cstr!(">S"), &mut ar);
}
#[cfg(feature = "luau")]
ffi::lua_getinfo(state, -1, cstr!("s"), &mut ar);
if ptr_to_cstr_bytes(ar.what) == Some(b"C") {
return None;
}
#[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))]
ffi::lua_getfenv(state, -1);
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
for i in 1..=255 {
// Traverse upvalues until we find the _ENV one
match ffi::lua_getupvalue(state, -1, i) {
s if s.is_null() => break,
s if std::ffi::CStr::from_ptr(s as _).to_bytes() == b"_ENV" => break,
_ => ffi::lua_pop(state, 1),
}
}
if ffi::lua_type(state, -1) != ffi::LUA_TTABLE {
return None;
}
Some(Table(lua.pop_ref()))
}
}
/// Sets the environment of the Lua function.
///
/// The environment is a table that is used as the global environment for the function.
/// Returns `true` if environment successfully changed, `false` otherwise.
///
/// This function does nothing for Rust/C functions.
pub fn set_environment(&self, env: Table) -> Result<bool> {
let lua = self.0.lua;
let state = lua.state();
unsafe {
let _sg = StackGuard::new(state);
check_stack(state, 2)?;
lua.push_ref(&self.0);
let mut ar: ffi::lua_Debug = mem::zeroed();
#[cfg(not(feature = "luau"))]
{
ffi::lua_pushvalue(state, -1);
ffi::lua_getinfo(state, cstr!(">S"), &mut ar);
}
#[cfg(feature = "luau")]
ffi::lua_getinfo(state, -1, cstr!("s"), &mut ar);
if ptr_to_cstr_bytes(ar.what) == Some(b"C") {
return Ok(false);
}
#[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))]
{
lua.push_ref(&env.0);
ffi::lua_setfenv(state, -2);
}
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
for i in 1..=255 {
match ffi::lua_getupvalue(state, -1, i) {
s if s.is_null() => return Ok(false),
s if std::ffi::CStr::from_ptr(s as _).to_bytes() == b"_ENV" => {
ffi::lua_pop(state, 1);
// Create an anonymous function with the new environment
let f_with_env = lua
.load("return _ENV")
.set_environment(env)
.try_cache()
.into_function()?;
lua.push_ref(&f_with_env.0);
ffi::lua_upvaluejoin(state, -2, i, -1, 1);
break;
}
_ => ffi::lua_pop(state, 1),
}
}
Ok(true)
}
}
/// Returns information about the function.
///
/// Corresponds to the `>Sn` what mask for [`lua_getinfo`] when applied to the function.
@@ -295,12 +414,13 @@ impl<'lua> Function<'lua> {
mlua_assert!(res != 0, "lua_getinfo failed with `>Sn`");
FunctionInfo {
name: ptr_to_cstr_bytes(ar.name).map(|s| s.to_vec()),
name: ptr_to_cstr_bytes(ar.name).map(|s| String::from_utf8_lossy(s).into_owned()),
#[cfg(not(feature = "luau"))]
name_what: ptr_to_cstr_bytes(ar.namewhat).map(|s| s.to_vec()),
name_what: ptr_to_cstr_bytes(ar.namewhat)
.map(|s| String::from_utf8_lossy(s).into_owned()),
#[cfg(feature = "luau")]
name_what: None,
what: ptr_to_cstr_bytes(ar.what).map(|s| s.to_vec()),
what: ptr_to_cstr_bytes(ar.what).map(|s| String::from_utf8_lossy(s).into_owned()),
source: ptr_to_cstr_bytes(ar.source).map(|s| s.to_vec()),
#[cfg(not(feature = "luau"))]
short_src: ptr_to_cstr_bytes(ar.short_src.as_ptr()).map(|s| s.to_vec()),
@@ -309,6 +429,8 @@ impl<'lua> Function<'lua> {
line_defined: ar.linedefined,
#[cfg(not(feature = "luau"))]
last_line_defined: ar.lastlinedefined,
#[cfg(feature = "luau")]
last_line_defined: -1,
}
}
}
@@ -406,8 +528,8 @@ impl<'lua> Function<'lua> {
}
/// Convert this handle to owned version.
#[cfg(feature = "unstable")]
#[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
#[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))]
#[inline]
pub fn into_owned(self) -> OwnedFunction {
OwnedFunction(self.0.into_owned())
@@ -420,22 +542,50 @@ impl<'lua> PartialEq for Function<'lua> {
}
}
// Additional shortcuts
#[cfg(feature = "unstable")]
impl OwnedFunction {
/// Calls the function, passing `args` as function arguments.
///
/// This is a shortcut for [`Function::call()`].
#[inline]
pub fn call<'lua, A, R>(&'lua self, args: A) -> Result<R>
where
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua>,
{
self.to_ref().call(args)
}
/// Returns a future that, when polled, calls `self`, passing `args` as function arguments,
/// and drives the execution.
///
/// This is a shortcut for [`Function::call_async()`].
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
#[inline]
pub fn call_async<'lua, A, R>(&'lua self, args: A) -> LocalBoxFuture<'lua, Result<R>>
where
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua> + 'lua,
{
self.to_ref().call_async(args)
}
}
pub(crate) struct WrappedFunction<'lua>(pub(crate) Callback<'lua, 'static>);
#[cfg(all(feature = "async", feature = "unstable"))]
#[cfg(feature = "async")]
pub(crate) struct WrappedAsyncFunction<'lua>(pub(crate) AsyncCallback<'lua, 'static>);
#[cfg(feature = "unstable")]
#[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
impl<'lua> Function<'lua> {
/// Wraps a Rust function or closure, returning an opaque type that implements [`IntoLua`] trait.
#[inline]
pub fn wrap<F, A, R>(func: F) -> impl IntoLua<'lua>
pub fn wrap<A, R, F>(func: F) -> impl IntoLua<'lua>
where
F: Fn(&'lua Lua, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
R: IntoLuaMulti<'lua>,
F: Fn(&'lua Lua, A) -> Result<R> + MaybeSend + 'static,
{
WrappedFunction(Box::new(move |lua, args| {
func(lua, A::from_lua_multi(args, lua)?)?.into_lua_multi(lua)
@@ -444,11 +594,11 @@ impl<'lua> Function<'lua> {
/// Wraps a Rust mutable closure, returning an opaque type that implements [`IntoLua`] trait.
#[inline]
pub fn wrap_mut<F, A, R>(func: F) -> impl IntoLua<'lua>
pub fn wrap_mut<A, R, F>(func: F) -> impl IntoLua<'lua>
where
F: FnMut(&'lua Lua, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
R: IntoLuaMulti<'lua>,
F: FnMut(&'lua Lua, A) -> Result<R> + MaybeSend + 'static,
{
let func = RefCell::new(func);
WrappedFunction(Box::new(move |lua, args| {
@@ -459,14 +609,15 @@ impl<'lua> Function<'lua> {
}))
}
/// Wraps a Rust async function or closure, returning an opaque type that implements [`IntoLua`] trait.
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub fn wrap_async<F, A, FR, R>(func: F) -> impl IntoLua<'lua>
pub fn wrap_async<A, R, F, FR>(func: F) -> impl IntoLua<'lua>
where
F: Fn(&'lua Lua, A) -> FR + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
FR: Future<Output = Result<R>> + 'lua,
R: IntoLuaMulti<'lua>,
F: Fn(&'lua Lua, A) -> FR + MaybeSend + 'static,
FR: Future<Output = Result<R>> + 'lua,
{
WrappedAsyncFunction(Box::new(move |lua, args| {
let args = match A::from_lua_multi(args, lua) {
@@ -484,6 +635,6 @@ mod assertions {
static_assertions::assert_not_impl_any!(Function: Send);
#[cfg(feature = "unstable")]
#[cfg(all(feature = "unstable", not(feature = "send")))]
static_assertions::assert_not_impl_any!(OwnedFunction: Send);
}
+41 -28
View File
@@ -3,7 +3,8 @@ use std::cell::UnsafeCell;
use std::ops::{BitOr, BitOrAssign};
use std::os::raw::c_int;
use crate::ffi::{self, lua_Debug};
use ffi::lua_Debug;
use crate::lua::Lua;
use crate::util::ptr_to_cstr_bytes;
@@ -266,48 +267,59 @@ pub struct HookTriggers {
#[cfg(not(feature = "luau"))]
impl HookTriggers {
/// Returns a new instance of `HookTriggers` with [`on_calls`] trigger set.
/// An instance of `HookTriggers` with `on_calls` trigger set.
pub const ON_CALLS: Self = HookTriggers::new().on_calls();
/// An instance of `HookTriggers` with `on_returns` trigger set.
pub const ON_RETURNS: Self = HookTriggers::new().on_returns();
/// An instance of `HookTriggers` with `every_line` trigger set.
pub const EVERY_LINE: Self = HookTriggers::new().every_line();
/// Returns a new instance of `HookTriggers` with all triggers disabled.
pub const fn new() -> Self {
HookTriggers {
on_calls: false,
on_returns: false,
every_line: false,
every_nth_instruction: None,
}
}
/// Returns an instance of `HookTriggers` with [`on_calls`] trigger set.
///
/// [`on_calls`]: #structfield.on_calls
pub fn on_calls() -> Self {
HookTriggers {
on_calls: true,
..Default::default()
}
pub const fn on_calls(mut self) -> Self {
self.on_calls = true;
self
}
/// Returns a new instance of `HookTriggers` with [`on_returns`] trigger set.
/// Returns an instance of `HookTriggers` with [`on_returns`] trigger set.
///
/// [`on_returns`]: #structfield.on_returns
pub fn on_returns() -> Self {
HookTriggers {
on_returns: true,
..Default::default()
}
pub const fn on_returns(mut self) -> Self {
self.on_returns = true;
self
}
/// Returns a new instance of `HookTriggers` with [`every_line`] trigger set.
/// Returns an instance of `HookTriggers` with [`every_line`] trigger set.
///
/// [`every_line`]: #structfield.every_line
pub fn every_line() -> Self {
HookTriggers {
every_line: true,
..Default::default()
}
pub const fn every_line(mut self) -> Self {
self.every_line = true;
self
}
/// Returns a new instance of `HookTriggers` with [`every_nth_instruction`] trigger set.
/// Returns an instance of `HookTriggers` with [`every_nth_instruction`] trigger set.
///
/// [`every_nth_instruction`]: #structfield.every_nth_instruction
pub fn every_nth_instruction(n: u32) -> Self {
HookTriggers {
every_nth_instruction: Some(n),
..Default::default()
}
pub const fn every_nth_instruction(mut self, n: u32) -> Self {
self.every_nth_instruction = Some(n);
self
}
// Compute the mask to pass to `lua_sethook`.
pub(crate) fn mask(&self) -> c_int {
pub(crate) const fn mask(&self) -> c_int {
let mut mask: c_int = 0;
if self.on_calls {
mask |= ffi::LUA_MASKCALL
@@ -326,8 +338,9 @@ impl HookTriggers {
// Returns the `count` parameter to pass to `lua_sethook`, if applicable. Otherwise, zero is
// returned.
pub(crate) fn count(&self) -> c_int {
self.every_nth_instruction.unwrap_or(0) as c_int
pub(crate) const fn count(&self) -> c_int {
let Some(n) = self.every_nth_instruction else { return 0 };
n as c_int
}
}
+8 -9
View File
@@ -71,8 +71,6 @@
//! [`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.9.0-beta.1")]
// Deny warnings inside doc tests / examples. When this isn't present, rustdoc doesn't show *any*
// warnings at all.
#![doc(test(attr(deny(warnings))))]
@@ -84,7 +82,6 @@ mod macros;
mod chunk;
mod conversion;
mod error;
mod ffi;
mod function;
mod hook;
mod lua;
@@ -106,7 +103,7 @@ mod value;
pub mod prelude;
pub use crate::{ffi::lua_CFunction, ffi::lua_State};
pub use ffi::{lua_CFunction, lua_State};
pub use crate::chunk::{AsChunk, Chunk, ChunkMode};
pub use crate::error::{Error, ErrorContext, ExternalError, ExternalResult, Result};
@@ -119,7 +116,7 @@ pub use crate::stdlib::StdLib;
pub use crate::string::String;
pub use crate::table::{Table, TableExt, TablePairs, TableSequence};
pub use crate::thread::{Thread, ThreadStatus};
pub use crate::types::{Integer, LightUserData, Number, RegistryKey};
pub use crate::types::{AppDataRef, AppDataRefMut, Integer, LightUserData, Number, RegistryKey};
pub use crate::userdata::{
AnyUserData, MetaMethod, UserData, UserDataFields, UserDataMetatable, UserDataMethods,
UserDataRef, UserDataRefMut,
@@ -148,14 +145,16 @@ pub use crate::serde::{
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
pub mod serde;
#[cfg(any(feature = "mlua_derive"))]
#[cfg(feature = "mlua_derive")]
#[allow(unused_imports)]
#[macro_use]
extern crate mlua_derive;
// Unstable features
#[cfg(all(feature = "unstable", not(feature = "send")))]
pub use crate::{function::OwnedFunction, table::OwnedTable, userdata::OwnedAnyUserData};
#[cfg(feature = "unstable")]
pub use crate::{
function::OwnedFunction, string::OwnedString, table::OwnedTable, userdata::OwnedAnyUserData,
};
/// Create a type that implements [`AsChunk`] and can capture Rust variables.
///
@@ -209,7 +208,7 @@ pub use crate::{function::OwnedFunction, table::OwnedTable, userdata::OwnedAnyUs
/// [`AsChunk`]: crate::AsChunk
/// [`UserData`]: crate::UserData
/// [`IntoLua`]: crate::IntoLua
#[cfg(any(feature = "macros"))]
#[cfg(feature = "macros")]
#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
pub use mlua_derive::chunk;
+346 -240
View File
File diff suppressed because it is too large Load Diff
+1 -2
View File
@@ -4,7 +4,6 @@ use std::string::String as StdString;
use crate::chunk::ChunkMode;
use crate::error::{Error, Result};
use crate::ffi;
use crate::lua::Lua;
use crate::table::Table;
use crate::util::{check_stack, StackGuard};
@@ -25,7 +24,7 @@ impl Lua {
// Set `_VERSION` global to include version number
// The environment variable `LUAU_VERSION` set by the build script
if let Some(version) = option_env!("LUAU_VERSION") {
if let Some(version) = ffi::luau_version() {
globals.raw_set("_VERSION", format!("Luau {version}"))?;
}
+6 -1
View File
@@ -103,7 +103,12 @@ macro_rules! protect_lua {
($state:expr, $nargs:expr, $nresults:expr, fn($state_inner:ident) $code:expr) => {{
unsafe extern "C" fn do_call($state_inner: *mut ffi::lua_State) -> ::std::os::raw::c_int {
$code;
$nresults
let nresults = $nresults;
if nresults == ::ffi::LUA_MULTRET {
ffi::lua_gettop($state_inner)
} else {
nresults
}
}
crate::util::protect_lua_call($state, $nargs, do_call)
-1
View File
@@ -2,7 +2,6 @@ use std::alloc::{self, Layout};
use std::os::raw::c_void;
use std::ptr;
use crate::ffi;
#[cfg(feature = "luau")]
use crate::lua::ExtraData;
+2 -2
View File
@@ -36,9 +36,9 @@ pub use crate::{
SerializeOptions as LuaSerializeOptions,
};
#[cfg(all(feature = "unstable", not(feature = "send")))]
#[cfg(feature = "unstable")]
#[doc(no_inline)]
pub use crate::{
OwnedAnyUserData as LuaOwnedAnyUserData, OwnedFunction as LuaOwnedFunction,
OwnedTable as LuaOwnedTable,
OwnedString as LuaOwnedString, OwnedTable as LuaOwnedTable,
};
+38 -23
View File
@@ -8,13 +8,13 @@ use std::os::raw::c_int;
use serde::Serialize;
use crate::error::{Error, Result};
use crate::ffi;
use crate::function::Function;
use crate::lua::Lua;
use crate::types::{Callback, CallbackUpvalue, LuaRef, MaybeSend};
use crate::userdata::{
AnyUserData, MetaMethod, UserData, UserDataCell, UserDataFields, UserDataMethods,
};
use crate::userdata_impl::UserDataRegistrar;
use crate::util::{
assert_stack, check_stack, get_userdata, init_userdata_metatable, push_table, rawset_field,
take_userdata, StackGuard,
@@ -25,7 +25,7 @@ use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, MultiValue, Val
use crate::userdata::USER_VALUE_MAXSLOT;
#[cfg(feature = "async")]
use futures_core::future::Future;
use std::future::Future;
/// Constructed by the [`Lua::scope`] method, allows temporarily creating Lua userdata and
/// callbacks that are not required to be Send or 'static.
@@ -33,7 +33,10 @@ use futures_core::future::Future;
/// See [`Lua::scope`] for more details.
///
/// [`Lua::scope`]: crate::Lua.html::scope
pub struct Scope<'lua, 'scope> {
pub struct Scope<'lua, 'scope>
where
'lua: 'scope,
{
lua: &'lua Lua,
destructors: RefCell<Vec<(LuaRef<'lua>, DestructorCallback<'lua>)>>,
_scope_invariant: PhantomData<Cell<&'scope ()>>,
@@ -394,7 +397,7 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
rawset_field(state, -2, MetaMethod::validate(&k)?)?;
}
for (k, f) in ud_fields.meta_fields {
lua.push_value(f(mem::transmute(lua))?)?;
lua.push_value(f(lua, MultiValue::new())?.pop_front().unwrap())?;
rawset_field(state, -2, MetaMethod::validate(&k)?)?;
}
let metatable_index = ffi::lua_absindex(state, -1);
@@ -735,15 +738,16 @@ impl<'lua, T: UserData> UserDataMethods<'lua, T> for NonStaticUserDataMethods<'l
}
struct NonStaticUserDataFields<'lua, T: UserData> {
fields: Vec<(String, Callback<'lua, 'static>)>,
field_getters: Vec<(String, NonStaticMethod<'lua, T>)>,
field_setters: Vec<(String, NonStaticMethod<'lua, T>)>,
#[allow(clippy::type_complexity)]
meta_fields: Vec<(String, Box<dyn Fn(&'lua Lua) -> Result<Value<'lua>>>)>,
meta_fields: Vec<(String, Callback<'lua, 'static>)>,
}
impl<'lua, T: UserData> Default for NonStaticUserDataFields<'lua, T> {
fn default() -> NonStaticUserDataFields<'lua, T> {
NonStaticUserDataFields {
fields: Vec::new(),
field_getters: Vec::new(),
field_setters: Vec::new(),
meta_fields: Vec::new(),
@@ -752,6 +756,17 @@ impl<'lua, T: UserData> Default for NonStaticUserDataFields<'lua, T> {
}
impl<'lua, T: UserData> UserDataFields<'lua, T> for NonStaticUserDataFields<'lua, T> {
fn add_field<V>(&mut self, name: impl AsRef<str>, value: V)
where
V: IntoLua<'lua> + Clone + 'static,
{
let name = name.as_ref().to_string();
self.fields.push((
name,
Box::new(move |lua, _| value.clone().into_lua_multi(lua)),
));
}
fn add_field_method_get<M, R>(&mut self, name: impl AsRef<str>, method: M)
where
M: Fn(&'lua Lua, &T) -> Result<R> + MaybeSend + 'static,
@@ -797,30 +812,30 @@ impl<'lua, T: UserData> UserDataFields<'lua, T> for NonStaticUserDataFields<'lua
self.field_setters.push((name.as_ref().into(), func));
}
fn add_meta_field<V>(&mut self, name: impl AsRef<str>, value: V)
where
V: IntoLua<'lua> + Clone + 'static,
{
let name = name.as_ref().to_string();
let name2 = name.clone();
self.meta_fields.push((
name,
Box::new(move |lua, _| {
UserDataRegistrar::<()>::check_meta_field(lua, &name2, value.clone())
}),
));
}
fn add_meta_field_with<F, R>(&mut self, name: impl AsRef<str>, f: F)
where
F: Fn(&'lua Lua) -> Result<R> + MaybeSend + 'static,
R: IntoLua<'lua>,
{
let name = name.as_ref().to_string();
let name2 = name.clone();
self.meta_fields.push((
name.clone(),
Box::new(move |lua| {
let value = f(lua)?.into_lua(lua)?;
if name == MetaMethod::Index || name == MetaMethod::NewIndex {
match value {
Value::Nil | Value::Table(_) | Value::Function(_) => {}
_ => {
return Err(Error::MetaMethodTypeError {
method: name.clone(),
type_name: value.type_name(),
message: Some("expected nil, table or function".to_string()),
})
}
}
}
Ok(value)
}),
name,
Box::new(move |lua, _| UserDataRegistrar::<()>::check_meta_field(lua, &name2, f(lua)?)),
));
}
}
+23 -1
View File
@@ -345,9 +345,31 @@ impl<'lua, 'de> serde::Deserializer<'de> for Deserializer<'lua> {
}
}
#[inline]
fn deserialize_unit<V>(self, visitor: V) -> Result<V::Value>
where
V: de::Visitor<'de>,
{
match self.value {
Value::LightUserData(ud) if ud.0.is_null() => visitor.visit_unit(),
_ => self.deserialize_any(visitor),
}
}
#[inline]
fn deserialize_unit_struct<V>(self, _name: &'static str, visitor: V) -> Result<V::Value>
where
V: de::Visitor<'de>,
{
match self.value {
Value::LightUserData(ud) if ud.0.is_null() => visitor.visit_unit(),
_ => self.deserialize_any(visitor),
}
}
serde::forward_to_deserialize_any! {
bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string bytes
byte_buf unit unit_struct identifier ignored_any
byte_buf identifier ignored_any
}
}
+19 -25
View File
@@ -1,22 +1,19 @@
//! (De)Serialization support using serde.
use std::os::raw::c_void;
use std::ptr;
use serde::{Deserialize, Serialize};
use serde::{de::DeserializeOwned, ser::Serialize};
use crate::error::Result;
use crate::ffi;
use crate::lua::Lua;
use crate::private::Sealed;
use crate::table::Table;
use crate::types::LightUserData;
use crate::util::check_stack;
use crate::value::Value;
/// Trait for serializing/deserializing Lua values using Serde.
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
pub trait LuaSerdeExt<'lua>: Sealed {
pub trait LuaSerdeExt: Sealed {
/// A special value (lightuserdata) to encode/decode optional (none) values.
///
/// Requires `feature = "serialize"`
@@ -38,7 +35,7 @@ pub trait LuaSerdeExt<'lua>: Sealed {
/// Ok(())
/// }
/// ```
fn null(&'lua self) -> Value<'lua>;
fn null(&self) -> Value;
/// 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
@@ -69,7 +66,7 @@ pub trait LuaSerdeExt<'lua>: Sealed {
/// Ok(())
/// }
/// ```
fn array_metatable(&'lua self) -> Table<'lua>;
fn array_metatable(&self) -> Table;
/// Converts `T` into a [`Value`] instance.
///
@@ -102,7 +99,7 @@ pub trait LuaSerdeExt<'lua>: Sealed {
/// "#).exec()
/// }
/// ```
fn to_value<T: Serialize + ?Sized>(&'lua self, t: &T) -> Result<Value<'lua>>;
fn to_value<'lua, T: Serialize + ?Sized>(&'lua self, t: &T) -> Result<Value<'lua>>;
/// Converts `T` into a [`Value`] instance with options.
///
@@ -127,7 +124,7 @@ pub trait LuaSerdeExt<'lua>: Sealed {
/// "#).exec()
/// }
/// ```
fn to_value_with<T>(&'lua self, t: &T, options: ser::Options) -> Result<Value<'lua>>
fn to_value_with<'lua, T>(&'lua self, t: &T, options: ser::Options) -> Result<Value<'lua>>
where
T: Serialize + ?Sized;
@@ -160,7 +157,7 @@ pub trait LuaSerdeExt<'lua>: Sealed {
/// }
/// ```
#[allow(clippy::wrong_self_convention)]
fn from_value<T: Deserialize<'lua>>(&'lua self, value: Value<'lua>) -> Result<T>;
fn from_value<T: DeserializeOwned>(&self, value: Value) -> Result<T>;
/// Deserializes a [`Value`] into any serde deserializable object with options.
///
@@ -192,49 +189,46 @@ pub trait LuaSerdeExt<'lua>: Sealed {
/// }
/// ```
#[allow(clippy::wrong_self_convention)]
fn from_value_with<T: Deserialize<'lua>>(
&'lua self,
value: Value<'lua>,
options: de::Options,
) -> Result<T>;
fn from_value_with<T: DeserializeOwned>(&self, value: Value, options: de::Options)
-> Result<T>;
}
impl<'lua> LuaSerdeExt<'lua> for Lua {
fn null(&'lua self) -> Value<'lua> {
Value::LightUserData(LightUserData(ptr::null_mut()))
impl LuaSerdeExt for Lua {
fn null(&self) -> Value {
Value::NULL
}
fn array_metatable(&'lua self) -> Table<'lua> {
fn array_metatable(&self) -> Table {
unsafe {
push_array_metatable(self.ref_thread());
Table(self.pop_ref_thread())
}
}
fn to_value<T>(&'lua self, t: &T) -> Result<Value<'lua>>
fn to_value<'lua, T>(&'lua self, t: &T) -> Result<Value<'lua>>
where
T: Serialize + ?Sized,
{
t.serialize(ser::Serializer::new(self))
}
fn to_value_with<T>(&'lua self, t: &T, options: ser::Options) -> Result<Value<'lua>>
fn to_value_with<'lua, T>(&'lua self, t: &T, options: ser::Options) -> Result<Value<'lua>>
where
T: Serialize + ?Sized,
{
t.serialize(ser::Serializer::new_with_options(self, options))
}
fn from_value<T>(&'lua self, value: Value<'lua>) -> Result<T>
fn from_value<T>(&self, value: Value) -> Result<T>
where
T: Deserialize<'lua>,
T: DeserializeOwned,
{
T::deserialize(de::Deserializer::new(value))
}
fn from_value_with<T>(&'lua self, value: Value<'lua>, options: de::Options) -> Result<T>
fn from_value_with<T>(&self, value: Value, options: de::Options) -> Result<T>
where
T: Deserialize<'lua>,
T: DeserializeOwned,
{
T::deserialize(de::Deserializer::new_with_options(value, options))
}
-1
View File
@@ -4,7 +4,6 @@ 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;
+60 -1
View File
@@ -11,7 +11,6 @@ use {
};
use crate::error::{Error, Result};
use crate::ffi;
use crate::types::LuaRef;
/// Handle to an internal Lua string.
@@ -20,6 +19,27 @@ use crate::types::LuaRef;
#[derive(Clone)]
pub struct String<'lua>(pub(crate) LuaRef<'lua>);
/// Owned handle to an internal Lua string.
///
/// The owned handle holds a *strong* reference to the current Lua instance.
/// Be warned, if you place it into a Lua type (eg. [`UserData`] or a Rust callback), it is *very easy*
/// to accidentally cause reference cycles that would prevent destroying Lua instance.
///
/// [`UserData`]: crate::UserData
#[cfg(feature = "unstable")]
#[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
#[derive(Clone)]
pub struct OwnedString(pub(crate) crate::types::LuaOwnedRef);
#[cfg(feature = "unstable")]
impl OwnedString {
/// Get borrowed handle to the underlying Lua string.
#[cfg_attr(feature = "send", allow(unused))]
pub const fn to_ref(&self) -> String {
String(self.0.to_ref())
}
}
impl<'lua> String<'lua> {
/// Get a `&str` slice if the Lua string is valid UTF-8.
///
@@ -122,6 +142,14 @@ impl<'lua> String<'lua> {
let ref_thread = self.0.lua.ref_thread();
unsafe { ffi::lua_topointer(ref_thread, self.0.index) }
}
/// Convert this handle to owned version.
#[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))]
#[inline]
pub fn into_owned(self) -> OwnedString {
OwnedString(self.0.into_owned())
}
}
impl<'lua> fmt::Debug for String<'lua> {
@@ -203,6 +231,37 @@ impl<'lua> Serialize for String<'lua> {
}
}
// Additional shortcuts
#[cfg(feature = "unstable")]
impl OwnedString {
/// Get a `&str` slice if the Lua string is valid UTF-8.
///
/// This is a shortcut for [`String::to_str()`].
#[inline]
pub fn to_str(&self) -> Result<&str> {
let s = self.to_ref();
// Reattach lifetime to &self
unsafe { std::mem::transmute(s.to_str()) }
}
/// Get the bytes that make up this string.
///
/// This is a shortcut for [`String::as_bytes()`].
#[inline]
pub fn as_bytes(&self) -> &[u8] {
let s = self.to_ref();
// Reattach lifetime to &self
unsafe { std::mem::transmute(s.as_bytes()) }
}
}
#[cfg(feature = "unstable")]
impl fmt::Debug for OwnedString {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.to_ref().fmt(f)
}
}
#[cfg(test)]
mod assertions {
use super::*;
+50 -6
View File
@@ -1,3 +1,5 @@
use std::collections::HashSet;
use std::fmt;
use std::marker::PhantomData;
use std::os::raw::c_void;
@@ -9,7 +11,6 @@ use {
};
use crate::error::{Error, Result};
use crate::ffi;
use crate::function::Function;
use crate::private::Sealed;
use crate::types::{Integer, LuaRef};
@@ -17,13 +18,19 @@ use crate::util::{assert_stack, check_stack, StackGuard};
use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, Nil, Value};
#[cfg(feature = "async")]
use {futures_core::future::LocalBoxFuture, futures_util::future};
use futures_util::future::{self, LocalBoxFuture};
/// Handle to an internal Lua table.
#[derive(Clone, Debug)]
#[derive(Clone)]
pub struct Table<'lua>(pub(crate) LuaRef<'lua>);
/// Owned handle to an internal Lua table.
///
/// The owned handle holds a *strong* reference to the current Lua instance.
/// Be warned, if you place it into a Lua type (eg. [`UserData`] or a Rust callback), it is *very easy*
/// to accidentally cause reference cycles that would prevent destroying Lua instance.
///
/// [`UserData`]: crate::UserData
#[cfg(feature = "unstable")]
#[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
#[derive(Clone, Debug)]
@@ -32,6 +39,7 @@ pub struct OwnedTable(pub(crate) crate::types::LuaOwnedRef);
#[cfg(feature = "unstable")]
impl OwnedTable {
/// Get borrowed handle to the underlying Lua table.
#[cfg_attr(feature = "send", allow(unused))]
pub const fn to_ref(&self) -> Table {
Table(self.0.to_ref())
}
@@ -583,8 +591,8 @@ impl<'lua> Table<'lua> {
}
/// Convert this handle to owned version.
#[cfg(feature = "unstable")]
#[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
#[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))]
#[inline]
pub fn into_owned(self) -> OwnedTable {
OwnedTable(self.0.into_owned())
@@ -697,7 +705,7 @@ impl<'lua> Table<'lua> {
}
}
#[cfg(any(feature = "serialize"))]
#[cfg(feature = "serialize")]
pub(crate) fn raw_sequence_values_by_len<V: FromLua<'lua>>(
self,
len: Option<Integer>,
@@ -738,6 +746,42 @@ impl<'lua> Table<'lua> {
}
Ok(())
}
pub(crate) fn fmt_pretty(
&self,
fmt: &mut fmt::Formatter,
ident: usize,
visited: &mut HashSet<*const c_void>,
) -> fmt::Result {
visited.insert(self.to_pointer());
let t = self.clone();
// Collect key/value pairs into a vector so we can sort them
let mut pairs = t.pairs::<Value, Value>().flatten().collect::<Vec<_>>();
// Sort keys
pairs.sort_by(|(a, _), (b, _)| a.cmp(b));
if pairs.is_empty() {
return write!(fmt, "{{}}");
}
writeln!(fmt, "{{")?;
for (key, value) in pairs {
write!(fmt, "{}[", " ".repeat(ident + 2))?;
key.fmt_pretty(fmt, false, ident + 2, visited)?;
write!(fmt, "] = ")?;
value.fmt_pretty(fmt, true, ident + 2, visited)?;
writeln!(fmt, ",")?;
}
write!(fmt, "{}}}", " ".repeat(ident))
}
}
impl fmt::Debug for Table<'_> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
if fmt.alternate() {
return self.fmt_pretty(fmt, 0, &mut HashSet::new());
}
fmt.write_fmt(format_args!("Table({:?})", self.0))
}
}
impl<'lua> PartialEq for Table<'lua> {
+38 -10
View File
@@ -2,7 +2,8 @@ use std::cmp;
use std::os::raw::c_int;
use crate::error::{Error, Result};
use crate::ffi;
#[allow(unused)]
use crate::lua::Lua;
use crate::types::LuaRef;
use crate::util::{check_stack, error_traceback_thread, pop_error, StackGuard};
use crate::value::{FromLuaMulti, IntoLuaMulti};
@@ -14,14 +15,21 @@ use crate::value::{FromLuaMulti, IntoLuaMulti};
))]
use crate::function::Function;
#[cfg(not(feature = "luau"))]
use crate::{
hook::{Debug, HookTriggers},
types::MaybeSend,
};
#[cfg(feature = "async")]
use {
crate::{
lua::{Lua, ASYNC_POLL_PENDING},
lua::ASYNC_POLL_PENDING,
value::{MultiValue, Value},
},
futures_core::{future::Future, stream::Stream},
futures_util::stream::Stream,
std::{
future::Future,
marker::PhantomData,
pin::Pin,
ptr::NonNull,
@@ -56,6 +64,7 @@ pub struct Thread<'lua>(pub(crate) LuaRef<'lua>);
/// [`Stream`]: futures_core::stream::Stream
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct AsyncThread<'lua, R> {
thread: Thread<'lua>,
args0: Option<Result<MultiValue<'lua>>>,
@@ -177,6 +186,23 @@ impl<'lua> Thread<'lua> {
}
}
/// Sets a 'hook' function that will periodically be called as Lua code executes.
///
/// This function is similar or [`Lua::set_hook()`] except that it sets for the thread.
/// To remove a hook call [`Lua::remove_hook()`].
#[cfg(not(feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
pub fn set_hook<F>(&self, triggers: HookTriggers, callback: F)
where
F: Fn(&Lua, Debug) -> Result<()> + MaybeSend + 'static,
{
let lua = self.0.lua;
unsafe {
let thread_state = ffi::lua_tothread(lua.ref_thread(), self.0.index);
lua.set_thread_hook(thread_state, triggers, callback);
}
}
/// Resets a thread
///
/// In [Lua 5.4]: cleans its call stack and closes all pending to-be-closed variables.
@@ -207,8 +233,10 @@ impl<'lua> Thread<'lua> {
lua.push_ref(&self.0);
let thread_state = ffi::lua_tothread(state, -1);
#[cfg(feature = "lua54")]
#[cfg(all(feature = "lua54", not(feature = "vendored")))]
let status = ffi::lua_resetthread(thread_state);
#[cfg(all(feature = "lua54", feature = "vendored"))]
let status = ffi::lua_closethread(thread_state, state);
#[cfg(feature = "lua54")]
if status != ffi::LUA_OK {
return Err(pop_error(thread_state, status));
@@ -223,8 +251,8 @@ impl<'lua> Thread<'lua> {
#[cfg(feature = "luau")]
{
// Inherit `LUA_GLOBALSINDEX` from the caller
ffi::lua_xpush(state, thread_state, ffi::LUA_GLOBALSINDEX);
// Inherit `LUA_GLOBALSINDEX` from the main thread
ffi::lua_xpush(lua.main_state(), thread_state, ffi::LUA_GLOBALSINDEX);
ffi::lua_replace(thread_state, ffi::LUA_GLOBALSINDEX);
}
@@ -332,11 +360,8 @@ impl<'lua> Thread<'lua> {
let state = lua.state();
unsafe {
let thread = ffi::lua_tothread(lua.ref_thread(), self.0.index);
check_stack(thread, 1)?;
check_stack(thread, 3)?;
check_stack(state, 3)?;
// Inherit `LUA_GLOBALSINDEX` from the caller
ffi::lua_xpush(state, thread, ffi::LUA_GLOBALSINDEX);
ffi::lua_replace(thread, ffi::LUA_GLOBALSINDEX);
protect_lua!(state, 0, 0, |_| ffi::luaL_sandboxthread(thread))
}
}
@@ -372,7 +397,10 @@ impl<'lua, R> Drop for AsyncThread<'lua, R> {
#[cfg(feature = "lua54")]
if self.thread.status() == ThreadStatus::Error {
let thread_state = ffi::lua_tothread(lua.ref_thread(), self.thread.0.index);
#[cfg(not(feature = "vendored"))]
ffi::lua_resetthread(thread_state);
#[cfg(feature = "vendored")]
ffi::lua_closethread(thread_state, lua.state());
}
}
}
+162 -18
View File
@@ -1,6 +1,9 @@
use std::cell::UnsafeCell;
use std::any::{Any, TypeId};
use std::cell::{Cell, Ref, RefCell, RefMut, UnsafeCell};
use std::hash::{Hash, Hasher};
use std::ops::{Deref, DerefMut};
use std::os::raw::{c_int, c_void};
use std::result::Result as StdResult;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use std::{fmt, mem, ptr};
@@ -8,17 +11,21 @@ use std::{fmt, mem, ptr};
#[cfg(feature = "lua54")]
use std::ffi::CStr;
use rustc_hash::FxHashMap;
#[cfg(feature = "async")]
use futures_core::future::LocalBoxFuture;
use futures_util::future::LocalBoxFuture;
use crate::error::Result;
use crate::ffi;
#[cfg(not(feature = "luau"))]
use crate::hook::Debug;
use crate::lua::{ExtraData, Lua};
use crate::util::{assert_stack, StackGuard};
use crate::value::MultiValue;
#[cfg(feature = "unstable")]
use {crate::lua::LuaInner, std::marker::PhantomData};
/// Type of Lua integer numbers.
pub type Integer = ffi::lua_Integer;
/// Type of Lua floating point numbers.
@@ -238,9 +245,9 @@ impl<'lua> PartialEq for LuaRef<'lua> {
#[cfg(feature = "unstable")]
pub(crate) struct LuaOwnedRef {
pub(crate) lua: Lua,
pub(crate) inner: Arc<LuaInner>,
pub(crate) index: c_int,
_non_send: std::marker::PhantomData<*const ()>,
_non_send: PhantomData<*const ()>,
}
#[cfg(feature = "unstable")]
@@ -260,37 +267,174 @@ impl Clone for LuaOwnedRef {
#[cfg(feature = "unstable")]
impl Drop for LuaOwnedRef {
fn drop(&mut self) {
self.lua.drop_ref_index(self.index);
let lua: &Lua = unsafe { mem::transmute(&self.inner) };
lua.drop_ref_index(self.index);
}
}
#[cfg(feature = "unstable")]
impl LuaOwnedRef {
pub(crate) const fn new(lua: Lua, index: c_int) -> Self {
#[cfg(feature = "send")]
{
let _lua = lua;
let _index = index;
panic!("mlua must be compiled without \"send\" feature to use Owned types");
}
#[cfg(not(feature = "send"))]
pub(crate) const fn new(inner: Arc<LuaInner>, index: c_int) -> Self {
LuaOwnedRef {
lua,
inner,
index,
_non_send: std::marker::PhantomData,
_non_send: PhantomData,
}
}
pub(crate) const fn to_ref(&self) -> LuaRef {
LuaRef {
lua: &self.lua,
lua: unsafe { mem::transmute(&self.inner) },
index: self.index,
drop: false,
}
}
}
#[derive(Debug, Default)]
pub(crate) struct AppData {
#[cfg(not(feature = "send"))]
container: UnsafeCell<FxHashMap<TypeId, RefCell<Box<dyn Any>>>>,
#[cfg(feature = "send")]
container: UnsafeCell<FxHashMap<TypeId, RefCell<Box<dyn Any + Send>>>>,
borrow: Cell<usize>,
}
impl AppData {
#[track_caller]
pub(crate) fn insert<T: MaybeSend + 'static>(&self, data: T) -> Option<T> {
match self.try_insert(data) {
Ok(data) => data,
Err(_) => panic!("cannot mutably borrow app data container"),
}
}
pub(crate) fn try_insert<T: MaybeSend + 'static>(&self, data: T) -> StdResult<Option<T>, T> {
if self.borrow.get() != 0 {
return Err(data);
}
// SAFETY: we checked that there are no other references to the container
Ok(unsafe { &mut *self.container.get() }
.insert(TypeId::of::<T>(), RefCell::new(Box::new(data)))
.and_then(|data| data.into_inner().downcast::<T>().ok().map(|data| *data)))
}
#[track_caller]
pub(crate) fn borrow<T: 'static>(&self) -> Option<AppDataRef<T>> {
let data = unsafe { &*self.container.get() }
.get(&TypeId::of::<T>())?
.borrow();
self.borrow.set(self.borrow.get() + 1);
Some(AppDataRef {
data: Ref::filter_map(data, |data| data.downcast_ref()).ok()?,
borrow: &self.borrow,
})
}
#[track_caller]
pub(crate) fn borrow_mut<T: 'static>(&self) -> Option<AppDataRefMut<T>> {
let data = unsafe { &*self.container.get() }
.get(&TypeId::of::<T>())?
.borrow_mut();
self.borrow.set(self.borrow.get() + 1);
Some(AppDataRefMut {
data: RefMut::filter_map(data, |data| data.downcast_mut()).ok()?,
borrow: &self.borrow,
})
}
#[track_caller]
pub(crate) fn remove<T: 'static>(&self) -> Option<T> {
if self.borrow.get() != 0 {
panic!("cannot mutably borrow app data container");
}
// SAFETY: we checked that there are no other references to the container
unsafe { &mut *self.container.get() }
.remove(&TypeId::of::<T>())?
.into_inner()
.downcast::<T>()
.ok()
.map(|data| *data)
}
}
/// A wrapper type for an immutably borrowed value from an app data container.
///
/// This type is similar to [`Ref`].
pub struct AppDataRef<'a, T: ?Sized + 'a> {
data: Ref<'a, T>,
borrow: &'a Cell<usize>,
}
impl<T: ?Sized> Drop for AppDataRef<'_, T> {
fn drop(&mut self) {
self.borrow.set(self.borrow.get() - 1);
}
}
impl<T: ?Sized> Deref for AppDataRef<'_, T> {
type Target = T;
#[inline]
fn deref(&self) -> &Self::Target {
&self.data
}
}
impl<T: ?Sized + fmt::Display> fmt::Display for AppDataRef<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
(**self).fmt(f)
}
}
impl<T: ?Sized + fmt::Debug> fmt::Debug for AppDataRef<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
(**self).fmt(f)
}
}
/// A wrapper type for a mutably borrowed value from an app data container.
///
/// This type is similar to [`RefMut`].
pub struct AppDataRefMut<'a, T: ?Sized + 'a> {
data: RefMut<'a, T>,
borrow: &'a Cell<usize>,
}
impl<T: ?Sized> Drop for AppDataRefMut<'_, T> {
fn drop(&mut self) {
self.borrow.set(self.borrow.get() - 1);
}
}
impl<T: ?Sized> Deref for AppDataRefMut<'_, T> {
type Target = T;
#[inline]
fn deref(&self) -> &Self::Target {
&self.data
}
}
impl<T: ?Sized> DerefMut for AppDataRefMut<'_, T> {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.data
}
}
impl<T: ?Sized + fmt::Display> fmt::Display for AppDataRefMut<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
(**self).fmt(f)
}
}
impl<T: ?Sized + fmt::Debug> fmt::Debug for AppDataRefMut<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
(**self).fmt(f)
}
}
#[cfg(test)]
mod assertions {
use super::*;
+105 -29
View File
@@ -17,16 +17,14 @@ use {
};
use crate::error::{Error, Result};
use crate::ffi;
use crate::function::Function;
use crate::lua::Lua;
use crate::string::String;
use crate::table::{Table, TablePairs};
use crate::types::{Callback, LuaRef, MaybeSend};
use crate::types::{LuaRef, MaybeSend};
use crate::util::{check_stack, get_userdata, take_userdata, StackGuard};
use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, Value};
#[cfg(feature = "async")]
use crate::types::AsyncCallback;
use crate::UserDataRegistrar;
#[cfg(feature = "lua54")]
pub(crate) const USER_VALUE_MAXSLOT: usize = 8;
@@ -140,7 +138,8 @@ pub enum MetaMethod {
/// Requires `feature = "lua54"`
///
/// [lua_doc]: https://www.lua.org/manual/5.4/manual.html#3.3.8
#[cfg(any(feature = "lua54"))]
#[cfg(feature = "lua54")]
#[cfg_attr(docsrs, doc(cfg(feature = "lua54")))]
Close,
}
@@ -150,7 +149,7 @@ impl PartialEq<MetaMethod> for &str {
}
}
impl PartialEq<MetaMethod> for String {
impl PartialEq<MetaMethod> for StdString {
fn eq(&self, other: &MetaMethod) -> bool {
self == other.name()
}
@@ -411,24 +410,26 @@ pub trait UserDataMethods<'lua, T> {
//
#[doc(hidden)]
fn add_callback(&mut self, _name: String, _callback: Callback<'lua, 'static>) {}
#[doc(hidden)]
#[cfg(feature = "async")]
fn add_async_callback(&mut self, _name: String, _callback: AsyncCallback<'lua, 'static>) {}
#[doc(hidden)]
fn add_meta_callback(&mut self, _name: String, _callback: Callback<'lua, 'static>) {}
#[doc(hidden)]
#[cfg(feature = "async")]
fn add_async_meta_callback(&mut self, _name: String, _callback: AsyncCallback<'lua, 'static>) {}
fn append_methods_from<S>(&mut self, _other: UserDataRegistrar<'lua, S>) {}
}
/// Field registry for [`UserData`] implementors.
///
/// [`UserData`]: crate::UserData
pub trait UserDataFields<'lua, T> {
/// Add a static field to the `UserData`.
///
/// Static fields are implemented by updating the `__index` metamethod and returning the
/// accessed field. This allows them to be used with the expected `userdata.field` syntax.
///
/// Static fields are usually shared between all instances of the `UserData` of the same type.
///
/// If `add_meta_method` is used to set the `__index` metamethod, it will
/// be used as a fall-back if no regular field or method are found.
fn add_field<V>(&mut self, name: impl AsRef<str>, value: V)
where
V: IntoLua<'lua> + Clone + 'static;
/// Add a regular field getter as a method which accepts a `&T` as the parameter.
///
/// Regular field getters are implemented by overriding the `__index` metamethod and returning the
@@ -477,9 +478,21 @@ pub trait UserDataFields<'lua, T> {
F: FnMut(&'lua Lua, AnyUserData<'lua>, A) -> Result<()> + MaybeSend + 'static,
A: FromLua<'lua>;
/// Add a metamethod value computed from `f`.
/// Add a metatable field.
///
/// This will initialize the metamethod value from `f` on `UserData` creation.
/// This will initialize the metatable field with `value` on `UserData` creation.
///
/// # Note
///
/// `mlua` will trigger an error on an attempt to define a protected metamethod,
/// like `__gc` or `__metatable`.
fn add_meta_field<V>(&mut self, name: impl AsRef<str>, value: V)
where
V: IntoLua<'lua> + Clone + 'static;
/// Add a metatable field computed from `f`.
///
/// This will initialize the metatable field from `f` on `UserData` creation.
///
/// # Note
///
@@ -495,10 +508,7 @@ pub trait UserDataFields<'lua, T> {
//
#[doc(hidden)]
fn add_field_getter(&mut self, _name: String, _callback: Callback<'lua, 'static>) {}
#[doc(hidden)]
fn add_field_setter(&mut self, _name: String, _callback: Callback<'lua, 'static>) {}
fn append_fields_from<S>(&mut self, _other: UserDataRegistrar<'lua, S>) {}
}
/// Trait for custom userdata types.
@@ -740,6 +750,11 @@ impl Serialize for UserDataSerializeError {
#[derive(Clone, Debug)]
pub struct AnyUserData<'lua>(pub(crate) LuaRef<'lua>);
/// Owned handle to an internal Lua userdata.
///
/// The owned handle holds a *strong* reference to the current Lua instance.
/// Be warned, if you place it into a Lua type (eg. [`UserData`] or a Rust callback), it is *very easy*
/// to accidentally cause reference cycles that would prevent destroying Lua instance.
#[cfg(feature = "unstable")]
#[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
#[derive(Clone, Debug)]
@@ -747,6 +762,8 @@ pub struct OwnedAnyUserData(pub(crate) crate::types::LuaOwnedRef);
#[cfg(feature = "unstable")]
impl OwnedAnyUserData {
/// Get borrowed handle to the underlying Lua userdata.
#[cfg_attr(feature = "send", allow(unused))]
pub const fn to_ref(&self) -> AnyUserData {
AnyUserData(self.0.to_ref())
}
@@ -1030,13 +1047,38 @@ impl<'lua> AnyUserData<'lua> {
}
}
#[cfg(feature = "unstable")]
#[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
/// Convert this handle to owned version.
#[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))]
#[inline]
pub fn into_owned(self) -> OwnedAnyUserData {
OwnedAnyUserData(self.0.into_owned())
}
/// Returns a type name of this `UserData` (from `__name` metatable field).
pub(crate) fn type_name(&self) -> Result<Option<StdString>> {
let lua = self.0.lua;
let state = lua.state();
unsafe {
let _sg = StackGuard::new(state);
check_stack(state, 3)?;
lua.push_userdata_ref(&self.0)?;
let protect = !lua.unlikely_memory_error();
let name_type = if protect {
protect_lua!(state, 1, 1, |state| {
ffi::luaL_getmetafield(state, -1, cstr!("__name"))
})?
} else {
ffi::luaL_getmetafield(state, -1, cstr!("__name"))
};
match name_type {
ffi::LUA_TSTRING => Ok(Some(String(lua.pop_ref()).to_str()?.to_owned())),
_ => Ok(None),
}
}
}
pub(crate) fn equals<T: AsRef<Self>>(&self, other: T) -> Result<bool> {
let other = other.as_ref();
// Uses lua_rawequal() under the hood
@@ -1121,6 +1163,40 @@ unsafe fn getuservalue_table(state: *mut ffi::lua_State, idx: c_int) -> c_int {
return ffi::lua_getuservalue(state, idx);
}
// Additional shortcuts
#[cfg(feature = "unstable")]
impl OwnedAnyUserData {
/// Borrow this userdata immutably if it is of type `T`.
///
/// This is a shortcut for [`AnyUserData::borrow()`]
#[inline]
pub fn borrow<T: 'static>(&self) -> Result<Ref<T>> {
let ud = self.to_ref();
let t = ud.borrow::<T>()?;
// Reattach lifetime to &self
Ok(unsafe { mem::transmute::<Ref<T>, Ref<T>>(t) })
}
/// Borrow this userdata mutably if it is of type `T`.
///
/// This is a shortcut for [`AnyUserData::borrow_mut()`]
#[inline]
pub fn borrow_mut<T: 'static>(&self) -> Result<RefMut<T>> {
let ud = self.to_ref();
let t = ud.borrow_mut::<T>()?;
// Reattach lifetime to &self
Ok(unsafe { mem::transmute::<RefMut<T>, RefMut<T>>(t) })
}
/// Takes the value out of this userdata.
///
/// This is a shortcut for [`AnyUserData::take()`]
#[inline]
pub fn take<T: 'static>(&self) -> Result<T> {
self.to_ref().take()
}
}
/// Handle to a `UserData` metatable.
#[derive(Clone, Debug)]
pub struct UserDataMetatable<'lua>(pub(crate) Table<'lua>);
@@ -1178,7 +1254,7 @@ impl<'lua, V> Iterator for UserDataMetatablePairs<'lua, V>
where
V: FromLua<'lua>,
{
type Item = Result<(String, V)>;
type Item = Result<(StdString, V)>;
fn next(&mut self) -> Option<Self::Item> {
loop {
@@ -1286,6 +1362,6 @@ mod assertions {
static_assertions::assert_not_impl_any!(AnyUserData: Send);
#[cfg(feature = "unstable")]
#[cfg(all(feature = "unstable", not(feature = "send")))]
static_assertions::assert_not_impl_any!(OwnedAnyUserData: Send);
}
+1 -1
View File
@@ -4,7 +4,7 @@ use crate::userdata::{AnyUserData, MetaMethod};
use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, Value};
#[cfg(feature = "async")]
use {futures_core::future::LocalBoxFuture, futures_util::future};
use futures_util::future::{self, LocalBoxFuture};
/// An extension trait for [`AnyUserData`] that provides a variety of convenient functionality.
pub trait AnyUserDataExt<'lua>: Sealed {
+72 -71
View File
@@ -1,18 +1,17 @@
use std::any::{self, TypeId};
use std::any::TypeId;
use std::cell::{Ref, RefCell, RefMut};
use std::marker::PhantomData;
use std::string::String as StdString;
use std::sync::{Arc, Mutex, RwLock};
use crate::error::{Error, Result};
use crate::ffi;
use crate::lua::Lua;
use crate::types::{Callback, MaybeSend};
use crate::userdata::{
AnyUserData, MetaMethod, UserData, UserDataCell, UserDataFields, UserDataMethods,
};
use crate::util::{check_stack, get_userdata, StackGuard};
use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, Value};
use crate::util::{check_stack, get_userdata, short_type_name, StackGuard};
use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, MultiValue, Value};
#[cfg(not(feature = "send"))]
use std::rc::Rc;
@@ -26,13 +25,10 @@ use {
pub struct UserDataRegistrar<'lua, T: 'static> {
// Fields
pub(crate) fields: Vec<(String, Callback<'lua, 'static>)>,
pub(crate) field_getters: Vec<(String, Callback<'lua, 'static>)>,
pub(crate) field_setters: Vec<(String, Callback<'lua, 'static>)>,
#[allow(clippy::type_complexity)]
pub(crate) meta_fields: Vec<(
String,
Box<dyn Fn(&'lua Lua) -> Result<Value<'lua>> + 'static>,
)>,
pub(crate) meta_fields: Vec<(String, Callback<'lua, 'static>)>,
// Methods
pub(crate) methods: Vec<(String, Callback<'lua, 'static>)>,
@@ -48,6 +44,7 @@ pub struct UserDataRegistrar<'lua, T: 'static> {
impl<'lua, T: 'static> UserDataRegistrar<'lua, T> {
pub(crate) const fn new() -> Self {
UserDataRegistrar {
fields: Vec::new(),
field_getters: Vec::new(),
field_setters: Vec::new(),
meta_fields: Vec::new(),
@@ -361,15 +358,49 @@ impl<'lua, T: 'static> UserDataRegistrar<'lua, T> {
)
})
}
pub(crate) fn check_meta_field<V>(
lua: &'lua Lua,
name: &str,
value: V,
) -> Result<MultiValue<'lua>>
where
V: IntoLua<'lua>,
{
let value = value.into_lua(lua)?;
if name == MetaMethod::Index || name == MetaMethod::NewIndex {
match value {
Value::Nil | Value::Table(_) | Value::Function(_) => {}
_ => {
return Err(Error::MetaMethodTypeError {
method: name.to_string(),
type_name: value.type_name(),
message: Some("expected nil, table or function".to_string()),
})
}
}
}
value.into_lua_multi(lua)
}
}
// Returns function name for the type `T`, without the module path
fn get_function_name<T: 'static>(name: &str) -> StdString {
let type_name = any::type_name::<T>().rsplit("::").next().unwrap();
format!("{type_name}.{name}",)
fn get_function_name<T>(name: &str) -> StdString {
format!("{}.{name}", short_type_name::<T>())
}
impl<'lua, T: 'static> UserDataFields<'lua, T> for UserDataRegistrar<'lua, T> {
fn add_field<V>(&mut self, name: impl AsRef<str>, value: V)
where
V: IntoLua<'lua> + Clone + 'static,
{
let name = name.as_ref().to_string();
self.fields.push((
name,
Box::new(move |lua, _| value.clone().into_lua_multi(lua)),
));
}
fn add_field_method_get<M, R>(&mut self, name: impl AsRef<str>, method: M)
where
M: Fn(&'lua Lua, &T) -> Result<R> + MaybeSend + 'static,
@@ -410,41 +441,38 @@ impl<'lua, T: 'static> UserDataFields<'lua, T> for UserDataRegistrar<'lua, T> {
self.field_setters.push((name.into(), func));
}
fn add_meta_field<V>(&mut self, name: impl AsRef<str>, value: V)
where
V: IntoLua<'lua> + Clone + 'static,
{
let name = name.as_ref().to_string();
let name2 = name.clone();
self.meta_fields.push((
name,
Box::new(move |lua, _| Self::check_meta_field(lua, &name2, value.clone())),
));
}
fn add_meta_field_with<F, R>(&mut self, name: impl AsRef<str>, f: F)
where
F: Fn(&'lua Lua) -> Result<R> + MaybeSend + 'static,
R: IntoLua<'lua>,
{
let name = name.as_ref().to_string();
let name2 = name.clone();
self.meta_fields.push((
name.clone(),
Box::new(move |lua| {
let value = f(lua)?.into_lua(lua)?;
if name == MetaMethod::Index || name == MetaMethod::NewIndex {
match value {
Value::Nil | Value::Table(_) | Value::Function(_) => {}
_ => {
return Err(Error::MetaMethodTypeError {
method: name.clone(),
type_name: value.type_name(),
message: Some("expected nil, table or function".to_string()),
})
}
}
}
Ok(value)
}),
name,
Box::new(move |lua, _| Self::check_meta_field(lua, &name2, f(lua)?)),
));
}
// Below are internal methods
fn add_field_getter(&mut self, name: String, callback: Callback<'lua, 'static>) {
self.field_getters.push((name, callback));
}
fn add_field_setter(&mut self, name: String, callback: Callback<'lua, 'static>) {
self.field_setters.push((name, callback));
fn append_fields_from<S>(&mut self, other: UserDataRegistrar<'lua, S>) {
self.fields.extend(other.fields);
self.field_getters.extend(other.field_getters);
self.field_setters.extend(other.field_setters);
self.meta_fields.extend(other.meta_fields);
}
}
@@ -593,22 +621,13 @@ impl<'lua, T: 'static> UserDataMethods<'lua, T> for UserDataRegistrar<'lua, T> {
// Below are internal methods used in generated code
fn add_callback(&mut self, name: String, callback: Callback<'lua, 'static>) {
self.methods.push((name, callback));
}
#[cfg(feature = "async")]
fn add_async_callback(&mut self, name: String, callback: AsyncCallback<'lua, 'static>) {
self.async_methods.push((name, callback));
}
fn add_meta_callback(&mut self, name: String, callback: Callback<'lua, 'static>) {
self.meta_methods.push((name, callback));
}
#[cfg(feature = "async")]
fn add_async_meta_callback(&mut self, meta: String, callback: AsyncCallback<'lua, 'static>) {
self.async_meta_methods.push((meta, callback))
fn append_methods_from<S>(&mut self, other: UserDataRegistrar<'lua, S>) {
self.methods.extend(other.methods);
#[cfg(feature = "async")]
self.async_methods.extend(other.async_methods);
self.meta_methods.extend(other.meta_methods);
#[cfg(feature = "async")]
self.async_meta_methods.extend(other.async_meta_methods);
}
}
@@ -628,31 +647,13 @@ macro_rules! lua_userdata_impl {
fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) {
let mut orig_fields = UserDataRegistrar::new();
T::add_fields(&mut orig_fields);
for (name, callback) in orig_fields.field_getters {
fields.add_field_getter(name, callback);
}
for (name, callback) in orig_fields.field_setters {
fields.add_field_setter(name, callback);
}
fields.append_fields_from(orig_fields);
}
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
let mut orig_methods = UserDataRegistrar::new();
T::add_methods(&mut orig_methods);
for (name, callback) in orig_methods.methods {
methods.add_callback(name, callback);
}
#[cfg(feature = "async")]
for (name, callback) in orig_methods.async_methods {
methods.add_async_callback(name, callback);
}
for (meta, callback) in orig_methods.meta_methods {
methods.add_meta_callback(meta, callback);
}
#[cfg(feature = "async")]
for (meta, callback) in orig_methods.async_meta_methods {
methods.add_async_meta_callback(meta, callback);
}
methods.append_methods_from(orig_methods);
}
}
};
+32 -10
View File
@@ -11,9 +11,10 @@ use once_cell::sync::Lazy;
use rustc_hash::FxHashMap;
use crate::error::{Error, Result};
use crate::ffi;
use crate::memory::MemoryState;
pub(crate) use short_names::short_type_name;
static METATABLE_CACHE: Lazy<FxHashMap<TypeId, u8>> = Lazy::new(|| {
let mut map = FxHashMap::with_capacity_and_hasher(32, Default::default());
crate::lua::init_metatable_cache(&mut map);
@@ -401,9 +402,12 @@ unsafe extern "C" fn lua_error_impl(state: *mut ffi::lua_State) -> c_int {
}
unsafe extern "C" fn lua_isfunction_impl(state: *mut ffi::lua_State) -> c_int {
let t = ffi::lua_type(state, -1);
ffi::lua_pop(state, 1);
ffi::lua_pushboolean(state, (t == ffi::LUA_TFUNCTION) as c_int);
ffi::lua_pushboolean(state, ffi::lua_isfunction(state, -1));
1
}
unsafe extern "C" fn lua_istable_impl(state: *mut ffi::lua_State) -> c_int {
ffi::lua_pushboolean(state, ffi::lua_istable(state, -1));
1
}
@@ -417,14 +421,19 @@ unsafe fn init_userdata_metatable_index(state: *mut ffi::lua_State) -> Result<()
// Create and cache `__index` generator
let code = cstr!(
r#"
local error, isfunction = ...
local error, isfunction, istable = ...
return function (__index, field_getters, methods)
-- Fastpath to return methods table for index access
if __index == nil and field_getters == nil then
return methods
-- Common case: has field getters and index is a table
if field_getters ~= nil and methods == nil and istable(__index) then
return function (self, key)
local field_getter = field_getters[key]
if field_getter ~= nil then
return field_getter(self)
end
return __index[key]
end
end
-- Alternatively return a function for index access
return function (self, key)
if field_getters ~= nil then
local field_getter = field_getters[key]
@@ -459,7 +468,13 @@ unsafe fn init_userdata_metatable_index(state: *mut ffi::lua_State) -> Result<()
}
ffi::lua_pushcfunction(state, lua_error_impl);
ffi::lua_pushcfunction(state, lua_isfunction_impl);
ffi::lua_call(state, 2, 1);
ffi::lua_pushcfunction(state, lua_istable_impl);
ffi::lua_call(state, 3, 1);
#[cfg(feature = "luau-jit")]
if ffi::luau_codegen_supported() != 0 {
ffi::luau_codegen_compile(state, -1);
}
// Store in the registry
ffi::lua_pushvalue(state, -1);
@@ -509,6 +524,11 @@ pub unsafe fn init_userdata_metatable_newindex(state: *mut ffi::lua_State) -> Re
ffi::lua_pushcfunction(state, lua_isfunction_impl);
ffi::lua_call(state, 2, 1);
#[cfg(feature = "luau-jit")]
if ffi::luau_codegen_supported() != 0 {
ffi::luau_codegen_compile(state, -1);
}
// Store in the registry
ffi::lua_pushvalue(state, -1);
ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, newindex_key);
@@ -1057,3 +1077,5 @@ static DESTRUCTED_USERDATA_METATABLE: u8 = 0;
static ERROR_PRINT_BUFFER_KEY: u8 = 0;
static USERDATA_METATABLE_INDEX: u8 = 0;
static USERDATA_METATABLE_NEWINDEX: u8 = 0;
mod short_names;
+84
View File
@@ -0,0 +1,84 @@
//! Mostly copied from bevy_utils
//! https://github.com/bevyengine/bevy/blob/main/crates/bevy_utils/src/short_names.rs
use std::any::type_name;
/// Returns a short version of a type name `T` without all module paths.
///
/// The short name of a type is its full name as returned by
/// [`std::any::type_name`], but with the prefix of all paths removed. For
/// example, the short name of `alloc::vec::Vec<core::option::Option<u32>>`
/// would be `Vec<Option<u32>>`.
pub(crate) fn short_type_name<T: ?Sized>() -> String {
let full_name = type_name::<T>();
// Generics result in nested paths within <..> blocks.
// Consider "core::option::Option<alloc::string::String>".
// To tackle this, we parse the string from left to right, collapsing as we go.
let mut index: usize = 0;
let end_of_string = full_name.len();
let mut parsed_name = String::new();
while index < end_of_string {
let rest_of_string = full_name.get(index..end_of_string).unwrap_or_default();
// Collapse everything up to the next special character,
// then skip over it
if let Some(special_character_index) = rest_of_string
.find(|c: char| [' ', '<', '>', '(', ')', '[', ']', ',', ';'].contains(&c))
{
let segment_to_collapse = rest_of_string
.get(0..special_character_index)
.unwrap_or_default();
parsed_name += collapse_type_name(segment_to_collapse);
// Insert the special character
let special_character =
&rest_of_string[special_character_index..=special_character_index];
parsed_name.push_str(special_character);
match special_character {
">" | ")" | "]"
if rest_of_string[special_character_index + 1..].starts_with("::") =>
{
parsed_name.push_str("::");
// Move the index past the "::"
index += special_character_index + 3;
}
// Move the index just past the special character
_ => index += special_character_index + 1,
}
} else {
// If there are no special characters left, we're done!
parsed_name += collapse_type_name(rest_of_string);
index = end_of_string;
}
}
parsed_name
}
#[inline(always)]
fn collapse_type_name(string: &str) -> &str {
string.rsplit("::").next().unwrap()
}
#[cfg(test)]
mod tests {
use super::short_type_name;
use std::collections::HashMap;
#[test]
fn tests() {
assert_eq!(short_type_name::<String>(), "String");
assert_eq!(short_type_name::<Option<String>>(), "Option<String>");
assert_eq!(short_type_name::<(String, &str)>(), "(String, &str)");
assert_eq!(short_type_name::<[i32; 3]>(), "[i32; 3]");
assert_eq!(
short_type_name::<HashMap<String, Option<[i32; 3]>>>(),
"HashMap<String, Option<[i32; 3]>>"
);
assert_eq!(
short_type_name::<dyn Fn(i32) -> i32>(),
"dyn Fn(i32) -> i32"
);
}
}
+140 -3
View File
@@ -1,8 +1,11 @@
use std::cmp::Ordering;
use std::collections::HashSet;
use std::iter::{self, FromIterator};
use std::ops::Index;
use std::os::raw::c_void;
use std::string::String as StdString;
use std::sync::Arc;
use std::{ptr, slice, str, vec};
use std::{fmt, ptr, slice, str, vec};
#[cfg(feature = "serialize")]
use {
@@ -12,7 +15,6 @@ use {
};
use crate::error::{Error, Result};
use crate::ffi;
use crate::function::Function;
use crate::lua::Lua;
use crate::string::String;
@@ -20,11 +22,12 @@ use crate::table::Table;
use crate::thread::Thread;
use crate::types::{Integer, LightUserData, Number};
use crate::userdata::AnyUserData;
use crate::util::{check_stack, StackGuard};
/// A dynamically typed Lua value. The `String`, `Table`, `Function`, `Thread`, and `UserData`
/// variants contain handle types into the internal Lua state. It is a logic error to mix handle
/// types between separate `Lua` instances, and doing so will result in a panic.
#[derive(Debug, Clone)]
#[derive(Clone)]
pub enum Value<'lua> {
/// The Lua value `nil`.
Nil,
@@ -62,6 +65,12 @@ pub enum Value<'lua> {
pub use self::Value::Nil;
impl<'lua> Value<'lua> {
/// A special value (lightuserdata) to represent null value.
///
/// It can be used in Lua tables without downsides of `nil`.
pub const NULL: Value<'static> = Value::LightUserData(LightUserData(ptr::null_mut()));
/// Returns type name of this value.
pub const fn type_name(&self) -> &'static str {
match *self {
Value::Nil => "nil",
@@ -121,6 +130,134 @@ impl<'lua> Value<'lua> {
}
}
}
/// Converts the value to a string.
///
/// If the value has a metatable with a `__tostring` method, then it will be called to get the result.
pub fn to_string(&self) -> Result<StdString> {
match self {
Value::Nil => Ok("nil".to_string()),
Value::Boolean(b) => Ok(b.to_string()),
Value::LightUserData(ud) if ud.0.is_null() => Ok("null".to_string()),
Value::LightUserData(ud) => Ok(format!("lightuserdata: {:p}", ud.0)),
Value::Integer(i) => Ok(i.to_string()),
Value::Number(n) => Ok(n.to_string()),
#[cfg(feature = "luau")]
Value::Vector(x, y, z) => Ok(format!("vector({x}, {y}, {z})")),
Value::String(s) => Ok(s.to_str()?.to_string()),
Value::Table(Table(r))
| Value::Function(Function(r))
| Value::Thread(Thread(r))
| Value::UserData(AnyUserData(r)) => unsafe {
let state = r.lua.state();
let _guard = StackGuard::new(state);
check_stack(state, 3)?;
r.lua.push_ref(r);
protect_lua!(state, 1, 1, fn(state) {
ffi::luaL_tolstring(state, -1, ptr::null_mut());
})?;
Ok(String(r.lua.pop_ref()).to_str()?.to_string())
},
Value::Error(err) => Ok(err.to_string()),
}
}
// Compares two values.
// Used to sort values for Debug printing.
pub(crate) fn cmp(&self, other: &Self) -> Ordering {
fn cmp_num(a: Number, b: Number) -> Ordering {
match (a, b) {
_ if a < b => Ordering::Less,
_ if a > b => Ordering::Greater,
_ => Ordering::Equal,
}
}
match (self, other) {
// Nil
(Value::Nil, Value::Nil) => Ordering::Equal,
(Value::Nil, _) => Ordering::Less,
(_, Value::Nil) => Ordering::Greater,
// Null (a special case)
(Value::LightUserData(ud1), Value::LightUserData(ud2)) if ud1 == ud2 => Ordering::Equal,
(Value::LightUserData(ud1), _) if ud1.0.is_null() => Ordering::Less,
(_, Value::LightUserData(ud2)) if ud2.0.is_null() => Ordering::Greater,
// Boolean
(Value::Boolean(a), Value::Boolean(b)) => a.cmp(b),
(Value::Boolean(_), _) => Ordering::Less,
(_, Value::Boolean(_)) => Ordering::Greater,
// Integer && Number
(Value::Integer(a), Value::Integer(b)) => a.cmp(b),
(&Value::Integer(a), &Value::Number(b)) => cmp_num(a as Number, b),
(&Value::Number(a), &Value::Integer(b)) => cmp_num(a, b as Number),
(&Value::Number(a), &Value::Number(b)) => cmp_num(a, b),
(Value::Integer(_) | Value::Number(_), _) => Ordering::Less,
(_, Value::Integer(_) | Value::Number(_)) => Ordering::Greater,
// String
(Value::String(a), Value::String(b)) => a.as_bytes().cmp(b.as_bytes()),
(Value::String(_), _) => Ordering::Less,
(_, Value::String(_)) => Ordering::Greater,
// Other variants can be randomly ordered
(a, b) => a.to_pointer().cmp(&b.to_pointer()),
}
}
pub(crate) fn fmt_pretty(
&self,
fmt: &mut fmt::Formatter,
recursive: bool,
ident: usize,
visited: &mut HashSet<*const c_void>,
) -> fmt::Result {
match self {
Value::Nil => write!(fmt, "nil"),
Value::Boolean(b) => write!(fmt, "{b}"),
Value::LightUserData(ud) if ud.0.is_null() => write!(fmt, "null"),
Value::LightUserData(ud) => write!(fmt, "lightuserdata: {:?}", ud.0),
Value::Integer(i) => write!(fmt, "{i}"),
Value::Number(n) => write!(fmt, "{n}"),
#[cfg(feature = "luau")]
Value::Vector(x, y, z) => write!(fmt, "vector({x}, {y}, {z})"),
Value::String(s) => write!(fmt, "{s:?}"),
Value::Table(t) if recursive && !visited.contains(&t.to_pointer()) => {
t.fmt_pretty(fmt, ident, visited)
}
t @ Value::Table(_) => write!(fmt, "table: {:?}", t.to_pointer()),
f @ Value::Function(_) => write!(fmt, "function: {:?}", f.to_pointer()),
t @ Value::Thread(_) => write!(fmt, "thread: {:?}", t.to_pointer()),
u @ Value::UserData(ud) => {
let name = ud.type_name().ok().flatten();
let name = name.unwrap_or_else(|| "userdata".to_string());
write!(fmt, "{name}: {:?}", u.to_pointer())
}
Value::Error(e) if recursive => write!(fmt, "{e:?}"),
Value::Error(_) => write!(fmt, "error"),
}
}
}
impl fmt::Debug for Value<'_> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
if fmt.alternate() {
return self.fmt_pretty(fmt, true, 0, &mut HashSet::new());
}
match self {
Value::Nil => write!(fmt, "Nil"),
Value::Boolean(b) => write!(fmt, "Boolean({b})"),
Value::LightUserData(ud) => write!(fmt, "{ud:?}"),
Value::Integer(i) => write!(fmt, "Integer({i})"),
Value::Number(n) => write!(fmt, "Number({n})"),
#[cfg(feature = "luau")]
Value::Vector(x, y, z) => write!(fmt, "Vector({x}, {y}, {z})"),
Value::String(s) => write!(fmt, "String({s:?})"),
Value::Table(t) => write!(fmt, "{t:?}"),
Value::Function(f) => write!(fmt, "{f:?}"),
Value::Thread(t) => write!(fmt, "{t:?}"),
Value::UserData(ud) => write!(fmt, "{ud:?}"),
Value::Error(e) => write!(fmt, "Error({e:?})"),
}
}
}
impl<'lua> PartialEq for Value<'lua> {
+8
View File
@@ -0,0 +1,8 @@
[lua54_coverage]
features = "lua54,vendored,async,serialize,macros,unstable"
[lua51_coverage]
features = "lua51,vendored,async,serialize,macros,unstable"
[luau_coverage]
features = "luau,async,serialize,macros,unstable"
+60 -3
View File
@@ -1,7 +1,7 @@
#![cfg(feature = "async")]
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use futures_timer::Delay;
@@ -9,7 +9,7 @@ use futures_util::stream::TryStreamExt;
use mlua::{
AnyUserDataExt, Error, Function, Lua, LuaOptions, Result, StdLib, Table, TableExt, UserData,
UserDataMethods,
UserDataMethods, Value,
};
#[tokio::test]
@@ -26,7 +26,6 @@ async fn test_async_function() -> Result<()> {
Ok(())
}
#[cfg(feature = "unstable")]
#[tokio::test]
async fn test_async_function_wrap() -> Result<()> {
let lua = Lua::new();
@@ -271,6 +270,24 @@ async fn test_async_thread() -> Result<()> {
Ok(())
}
#[test]
fn test_async_thread_capture() -> Result<()> {
let lua = Lua::new();
let f = lua.create_async_function(move |_lua, v: Value| async move {
tokio::task::yield_now().await;
drop(v);
Ok(())
})?;
let thread = lua.create_thread(f)?;
// After first resume, `v: Value` is captured in the coroutine
thread.resume::<_, ()>("abc").unwrap();
drop(thread);
Ok(())
}
#[tokio::test]
async fn test_async_table() -> Result<()> {
let options = LuaOptions::new().thread_pool_size(4);
@@ -467,3 +484,43 @@ async fn test_async_thread_error() -> Result<()> {
Ok(())
}
#[cfg(all(feature = "unstable", not(feature = "send")))]
#[tokio::test]
async fn test_owned_async_call() -> Result<()> {
let lua = Lua::new();
let hello = lua
.create_async_function(|_, name: String| async move {
Delay::new(Duration::from_millis(10)).await;
Ok(format!("hello, {}!", name))
})?
.into_owned();
drop(lua);
assert_eq!(hello.call_async::<_, String>("alex").await?, "hello, alex!");
Ok(())
}
#[tokio::test]
async fn test_async_terminate() -> Result<()> {
let lua = Lua::new();
let mutex = Arc::new(Mutex::new(0u32));
let mutex2 = mutex.clone();
let func = lua.create_async_function(move |_, ()| {
let mutex = mutex2.clone();
async move {
let _guard = mutex.lock();
Delay::new(Duration::from_millis(100)).await;
Ok(())
}
})?;
let _ = tokio::time::timeout(Duration::from_millis(30), func.call_async::<_, ()>(())).await;
lua.gc_collect()?;
assert!(mutex.try_lock().is_ok());
Ok(())
}
+26 -20
View File
@@ -1,22 +1,28 @@
error[E0277]: the type `UnsafeCell<mlua::lua::LuaInner>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
--> tests/compile/lua_norefunwindsafe.rs:7:5
|
7 | catch_unwind(|| lua.create_table().unwrap());
| ^^^^^^^^^^^^ `UnsafeCell<mlua::lua::LuaInner>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
|
= help: within `Lua`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<mlua::lua::LuaInner>`
= note: required because it appears within the type `alloc::sync::ArcInner<UnsafeCell<mlua::lua::LuaInner>>`
= note: required because it appears within the type `PhantomData<alloc::sync::ArcInner<UnsafeCell<mlua::lua::LuaInner>>>`
= note: required because it appears within the type `Arc<UnsafeCell<mlua::lua::LuaInner>>`
= note: required because it appears within the type `Lua`
= note: required because of the requirements on the impl of `UnwindSafe` for `&Lua`
error[E0277]: the type `UnsafeCell<mlua::lua::ExtraData>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
--> tests/compile/lua_norefunwindsafe.rs:7:18
|
7 | catch_unwind(|| lua.create_table().unwrap());
| ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<mlua::lua::ExtraData>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
| |
| required by a bound introduced by this call
|
= help: within `Lua`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<mlua::lua::ExtraData>`
= note: required because it appears within the type `ArcInner<UnsafeCell<ExtraData>>`
= note: required because it appears within the type `PhantomData<ArcInner<UnsafeCell<ExtraData>>>`
= note: required because it appears within the type `Arc<UnsafeCell<ExtraData>>`
= note: required because it appears within the type `LuaInner`
= note: required because it appears within the type `ArcInner<LuaInner>`
= note: required because it appears within the type `PhantomData<ArcInner<LuaInner>>`
= note: required because it appears within the type `Arc<LuaInner>`
= note: required because it appears within the type `Lua`
= note: required for `&Lua` to implement `UnwindSafe`
note: required because it's used within this closure
--> tests/compile/lua_norefunwindsafe.rs:7:18
|
7 | catch_unwind(|| lua.create_table().unwrap());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
--> tests/compile/lua_norefunwindsafe.rs:7:18
|
7 | catch_unwind(|| lua.create_table().unwrap());
| ^^
note: required by a bound in `catch_unwind`
--> $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`
--> $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`
+22 -23
View File
@@ -1,26 +1,25 @@
error[E0277]: `Rc<Cell<i32>>` cannot be sent between threads safely
--> tests/compile/non_send.rs:11:9
|
11 | lua.create_function(move |_, ()| {
| _________^^^^^^^^^^^^^^^_-
| | |
| | `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]`
|
= help: within `[closure@$DIR/tests/compile/non_send.rs:11:25: 13:6]`, the trait `Send` is not implemented for `Rc<Cell<i32>>`
--> tests/compile/non_send.rs:11:25
|
11 | lua.create_function(move |_, ()| {
| --------------- ^-----------
| | |
| _________|_______________within this `[closure@$DIR/tests/compile/non_send.rs:11:25: 11:37]`
| | |
| | required by a bound introduced by this call
12 | | Ok(data.get())
13 | | })?
| |_____^ `Rc<Cell<i32>>` cannot be sent between threads safely
|
= help: within `[closure@$DIR/tests/compile/non_send.rs:11:25: 11:37]`, the trait `Send` is not implemented for `Rc<Cell<i32>>`
note: required because it's used within this closure
--> tests/compile/non_send.rs:11:25
|
11 | lua.create_function(move |_, ()| {
| _________________________^
12 | | Ok(data.get())
13 | | })?
| |_____^
= 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]`
--> tests/compile/non_send.rs:11:25
|
11 | lua.create_function(move |_, ()| {
| ^^^^^^^^^^^^
= note: required for `[closure@$DIR/tests/compile/non_send.rs:11:25: 11:37]` to implement `mlua::types::MaybeSend`
note: required by a bound in `Lua::create_function`
--> src/lua.rs
|
| F: 'static + MaybeSend + Fn(&'lua Lua, A) -> Result<R>,
| ^^^^^^^^^ required by this bound in `Lua::create_function`
--> src/lua.rs
|
| F: 'static + MaybeSend + Fn(&'lua Lua, A) -> Result<R>,
| ^^^^^^^^^ required by this bound in `Lua::create_function`
+28 -22
View File
@@ -1,24 +1,30 @@
error[E0277]: the type `UnsafeCell<mlua::lua::LuaInner>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
--> tests/compile/ref_nounwindsafe.rs:8:5
|
8 | catch_unwind(move || table.set("a", "b").unwrap());
| ^^^^^^^^^^^^ `UnsafeCell<mlua::lua::LuaInner>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
|
= help: within `Lua`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<mlua::lua::LuaInner>`
= note: required because it appears within the type `alloc::sync::ArcInner<UnsafeCell<mlua::lua::LuaInner>>`
= note: required because it appears within the type `PhantomData<alloc::sync::ArcInner<UnsafeCell<mlua::lua::LuaInner>>>`
= note: required because it appears within the type `Arc<UnsafeCell<mlua::lua::LuaInner>>`
= 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 `LuaTable<'_>`
error[E0277]: the type `UnsafeCell<mlua::lua::ExtraData>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
--> tests/compile/ref_nounwindsafe.rs:8:18
|
8 | catch_unwind(move || table.set("a", "b").unwrap());
| ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<mlua::lua::ExtraData>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
| |
| required by a bound introduced by this call
|
= help: within `Lua`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<mlua::lua::ExtraData>`
= note: required because it appears within the type `ArcInner<UnsafeCell<ExtraData>>`
= note: required because it appears within the type `PhantomData<ArcInner<UnsafeCell<ExtraData>>>`
= note: required because it appears within the type `Arc<UnsafeCell<ExtraData>>`
= note: required because it appears within the type `LuaInner`
= note: required because it appears within the type `ArcInner<LuaInner>`
= note: required because it appears within the type `PhantomData<ArcInner<LuaInner>>`
= note: required because it appears within the type `Arc<LuaInner>`
= note: required because it appears within the type `Lua`
= note: required for `&Lua` to implement `UnwindSafe`
= note: required because it appears within the type `LuaRef<'_>`
= note: required because it appears within the type `Table<'_>`
note: required because it's used within this closure
--> tests/compile/ref_nounwindsafe.rs:8:18
|
8 | catch_unwind(move || table.set("a", "b").unwrap());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
--> tests/compile/ref_nounwindsafe.rs:8:18
|
8 | catch_unwind(move || table.set("a", "b").unwrap());
| ^^^^^^^
note: required by a bound in `catch_unwind`
--> $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`
--> $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`
+16 -12
View File
@@ -1,15 +1,17 @@
error[E0597]: `lua` does not live long enough
--> tests/compile/static_callback_args.rs:12:5
|
12 | / lua.create_function(|_, table: Table| {
13 | | BAD_TIME.with(|bt| {
| |_________-
10 | let lua = Lua::new();
| --- binding `lua` declared here
11 |
12 | / lua.create_function(|_, table: Table| {
13 | |/ BAD_TIME.with(|bt| {
14 | || *bt.borrow_mut() = Some(table);
15 | || });
| ||__________- argument requires that `lua` is borrowed for `'static`
16 | | Ok(())
17 | | })?
| |______^ borrowed value does not live long enough
16 | | Ok(())
17 | | })?
| |_______^ borrowed value does not live long enough
...
32 | }
| - `lua` dropped here while still borrowed
@@ -17,15 +19,17 @@ error[E0597]: `lua` does not live long enough
error[E0505]: cannot move out of `lua` because it is borrowed
--> tests/compile/static_callback_args.rs:22:10
|
12 | / lua.create_function(|_, table: Table| {
13 | | BAD_TIME.with(|bt| {
| |_________-
10 | let lua = Lua::new();
| --- binding `lua` declared here
11 |
12 | / lua.create_function(|_, table: Table| {
13 | |/ BAD_TIME.with(|bt| {
14 | || *bt.borrow_mut() = Some(table);
15 | || });
| ||__________- argument requires that `lua` is borrowed for `'static`
16 | | Ok(())
17 | | })?
| |______- borrow of `lua` occurs here
16 | | Ok(())
17 | | })?
| |_______- borrow of `lua` occurs here
...
22 | drop(lua);
| ^^^ move out of `lua` occurs here
+15
View File
@@ -0,0 +1,15 @@
use mlua::{Lua, Result};
#[test]
fn test_debug_format() -> Result<()> {
let lua = Lua::new();
// Globals
let globals = lua.globals();
let dump = format!("{globals:#?}");
assert!(dump.starts_with("{\n [\"_G\"] = table:"));
// TODO: Other cases
Ok(())
}
+17
View File
@@ -1,3 +1,5 @@
use std::io;
use mlua::{Error, ErrorContext, Lua, Result};
#[test]
@@ -29,5 +31,20 @@ fn test_error_context() -> Result<()> {
println!("{msg2}");
assert!(msg2.contains("error converting Lua nil to String"));
// Rewrite context message and test `downcast_ref`
let func3 = lua.create_function(|_, ()| {
Err::<(), _>(Error::external(io::Error::new(
io::ErrorKind::Other,
"other",
)))
.context("some context")
.context("some new context")
})?;
let res = func3.call::<_, ()>(()).err().unwrap();
let Error::CallbackError { cause, .. } = &res else { unreachable!() };
assert!(!res.to_string().contains("some context"));
assert!(res.to_string().contains("some new context"));
assert!(cause.downcast_ref::<io::Error>().is_some());
Ok(())
}
+117 -13
View File
@@ -1,4 +1,4 @@
use mlua::{Function, Lua, Result, String};
use mlua::{Function, Lua, Result, String, Table};
#[test]
fn test_function() -> Result<()> {
@@ -114,6 +114,66 @@ fn test_dump() -> Result<()> {
Ok(())
}
#[test]
fn test_function_environment() -> Result<()> {
let lua = Lua::new();
// We must not get or set environment for C functions
let rust_func = lua.create_function(|_, ()| Ok("hello"))?;
assert_eq!(rust_func.environment(), None);
assert_eq!(rust_func.set_environment(lua.globals()).ok(), Some(false));
// Test getting Lua function environment
lua.globals().set("hello", "global")?;
let lua_func = lua
.load(
r#"
local t = ""
return function()
-- two upvalues
return t .. hello
end
"#,
)
.eval::<Function>()?;
let lua_func2 = lua.load("return hello").into_function()?;
assert_eq!(lua_func.call::<_, String>(())?, "global");
assert_eq!(lua_func.environment(), Some(lua.globals()));
// Test changing the environment
let env = lua.create_table_from([("hello", "local")])?;
assert!(lua_func.set_environment(env.clone())?);
assert_eq!(lua_func.call::<_, String>(())?, "local");
assert_eq!(lua_func2.call::<_, String>(())?, "global");
// More complex case
lua.load(
r#"
local number = 15
function lucky() return tostring("number is "..number) end
new_env = {
tostring = function() return tostring(number) end,
}
"#,
)
.exec()?;
let lucky = lua.globals().get::<_, Function>("lucky")?;
assert_eq!(lucky.call::<_, String>(())?, "number is 15");
let new_env = lua.globals().get::<_, Table>("new_env")?;
lucky.set_environment(new_env)?;
assert_eq!(lucky.call::<_, String>(())?, "15");
// Test inheritance
let lua_func2 = lua
.load(r#"return function() return (function() return hello end)() end"#)
.eval::<Function>()?;
assert!(lua_func2.set_environment(env.clone())?);
lua.gc_collect()?;
assert_eq!(lua_func2.call::<_, String>(())?, "local");
Ok(())
}
#[test]
fn test_function_info() -> Result<()> {
let lua = Lua::new();
@@ -135,40 +195,42 @@ fn test_function_info() -> Result<()> {
let function1_info = function1.info();
#[cfg(feature = "luau")]
assert_eq!(function1_info.name, Some(b"function1".to_vec()));
assert_eq!(function1_info.source, Some(b"source1".to_vec()));
assert_eq!(function1_info.name.as_deref(), Some("function1"));
assert_eq!(function1_info.source.as_deref(), Some(b"source1".as_ref()));
assert_eq!(function1_info.line_defined, 2);
#[cfg(not(feature = "luau"))]
assert_eq!(function1_info.last_line_defined, 4);
assert_eq!(function1_info.what, Some(b"Lua".to_vec()));
#[cfg(feature = "luau")]
assert_eq!(function1_info.last_line_defined, -1);
assert_eq!(function1_info.what.as_deref(), Some("Lua"));
let function2_info = function2.info();
assert_eq!(function2_info.name, None);
assert_eq!(function2_info.source, Some(b"source1".to_vec()));
assert_eq!(function2_info.source.as_deref(), Some(b"source1".as_ref()));
assert_eq!(function2_info.line_defined, 3);
#[cfg(not(feature = "luau"))]
assert_eq!(function2_info.last_line_defined, 3);
assert_eq!(function2_info.what, Some(b"Lua".to_vec()));
#[cfg(feature = "luau")]
assert_eq!(function2_info.last_line_defined, -1);
assert_eq!(function2_info.what.as_deref(), Some("Lua"));
let function3_info = function3.info();
assert_eq!(function3_info.name, None);
assert_eq!(function3_info.source, Some(b"=[C]".to_vec()));
assert_eq!(function3_info.source.as_deref(), Some(b"=[C]".as_ref()));
assert_eq!(function3_info.line_defined, -1);
#[cfg(not(feature = "luau"))]
assert_eq!(function3_info.last_line_defined, -1);
assert_eq!(function3_info.what, Some(b"C".to_vec()));
assert_eq!(function3_info.what.as_deref(), Some("C"));
let print_info = globals.get::<_, Function>("print")?.info();
#[cfg(feature = "luau")]
assert_eq!(print_info.name, Some(b"print".to_vec()));
assert_eq!(print_info.source, Some(b"=[C]".to_vec()));
assert_eq!(print_info.what, Some(b"C".to_vec()));
assert_eq!(print_info.name.as_deref(), Some("print"));
assert_eq!(print_info.source.as_deref(), Some(b"=[C]".as_ref()));
assert_eq!(print_info.what.as_deref(), Some("C"));
assert_eq!(print_info.line_defined, -1);
Ok(())
}
#[cfg(feature = "unstable")]
#[test]
fn test_function_wrap() -> Result<()> {
use mlua::Error;
@@ -200,3 +262,45 @@ fn test_function_wrap() -> Result<()> {
Ok(())
}
#[cfg(all(feature = "unstable", not(feature = "send")))]
#[test]
fn test_owned_function() -> Result<()> {
let lua = Lua::new();
let f = lua
.create_function(|_, ()| Ok("hello, world!"))?
.into_owned();
drop(lua);
// We still should be able to call the function despite Lua is dropped
let s = f.call::<_, String>(())?;
assert_eq!(s.to_string_lossy(), "hello, world!");
Ok(())
}
#[cfg(all(feature = "unstable", not(feature = "send")))]
#[test]
fn test_owned_function_drop() -> Result<()> {
let rc = std::sync::Arc::new(());
{
let lua = Lua::new();
lua.set_app_data(rc.clone());
let f1 = lua
.create_function(|_, ()| Ok("hello, world!"))?
.into_owned();
let f2 =
lua.create_function(move |_, ()| f1.to_ref().call::<_, std::string::String>(()))?;
assert_eq!(f2.call::<_, String>(())?.to_string_lossy(), "hello, world!");
}
// Check that Lua is properly destroyed
// It works because we collect garbage when Lua goes out of scope
assert_eq!(std::sync::Arc::strong_count(&rc), 1);
Ok(())
}
+53 -16
View File
@@ -9,11 +9,9 @@ use std::sync::{Arc, Mutex};
use mlua::{DebugEvent, Error, HookTriggers, Lua, Result, Value};
#[test]
fn test_hook_triggers_bitor() {
let trigger = HookTriggers::on_calls()
| HookTriggers::on_returns()
| HookTriggers::every_line()
| HookTriggers::every_nth_instruction(5);
fn test_hook_triggers() {
let trigger = HookTriggers::new().on_calls().on_returns()
| HookTriggers::new().every_line().every_nth_instruction(5);
assert!(trigger.on_calls);
assert!(trigger.on_returns);
@@ -27,7 +25,7 @@ fn test_line_counts() -> Result<()> {
let hook_output = output.clone();
let lua = Lua::new();
lua.set_hook(HookTriggers::every_line(), move |_lua, debug| {
lua.set_hook(HookTriggers::EVERY_LINE, move |_lua, debug| {
assert_eq!(debug.event(), DebugEvent::Line);
hook_output.lock().unwrap().push(debug.curr_line());
Ok(())
@@ -59,7 +57,7 @@ fn test_function_calls() -> Result<()> {
let hook_output = output.clone();
let lua = Lua::new();
lua.set_hook(HookTriggers::on_calls(), move |_lua, debug| {
lua.set_hook(HookTriggers::ON_CALLS, move |_lua, debug| {
assert_eq!(debug.event(), DebugEvent::Call);
let names = debug.names();
let source = debug.source();
@@ -104,7 +102,7 @@ fn test_function_calls() -> Result<()> {
fn test_error_within_hook() -> Result<()> {
let lua = Lua::new();
lua.set_hook(HookTriggers::every_line(), |_lua, _debug| {
lua.set_hook(HookTriggers::EVERY_LINE, |_lua, _debug| {
Err(Error::RuntimeError(
"Something happened in there!".to_string(),
))
@@ -136,7 +134,7 @@ fn test_limit_execution_instructions() -> Result<()> {
let max_instructions = AtomicI64::new(10000);
lua.set_hook(
HookTriggers::every_nth_instruction(30),
HookTriggers::new().every_nth_instruction(30),
move |_lua, debug| {
assert_eq!(debug.event(), DebugEvent::Count);
if max_instructions.fetch_sub(30, Ordering::Relaxed) <= 30 {
@@ -166,11 +164,14 @@ fn test_limit_execution_instructions() -> Result<()> {
fn test_hook_removal() -> Result<()> {
let lua = Lua::new();
lua.set_hook(HookTriggers::every_nth_instruction(1), |_lua, _debug| {
Err(Error::RuntimeError(
"this hook should've been removed by this time".to_string(),
))
})?;
lua.set_hook(
HookTriggers::new().every_nth_instruction(1),
|_lua, _debug| {
Err(Error::RuntimeError(
"this hook should've been removed by this time".to_string(),
))
},
)?;
assert!(lua.load("local x = 1").exec().is_err());
lua.remove_hook();
@@ -193,11 +194,11 @@ fn test_hook_swap_within_hook() -> Result<()> {
tl.borrow()
.as_ref()
.unwrap()
.set_hook(HookTriggers::every_line(), move |lua, _debug| {
.set_hook(HookTriggers::EVERY_LINE, move |lua, _debug| {
lua.globals().set("ok", 1i64)?;
TL_LUA.with(|tl| {
tl.borrow().as_ref().unwrap().set_hook(
HookTriggers::every_line(),
HookTriggers::EVERY_LINE,
move |lua, _debug| {
lua.load(
r#"
@@ -233,3 +234,39 @@ fn test_hook_swap_within_hook() -> Result<()> {
Ok(())
})
}
#[test]
fn test_hook_threads() -> Result<()> {
let lua = Lua::new();
let func = lua
.load(
r#"
local x = 2 + 3
local y = x * 63
local z = string.len(x..", "..y)
"#,
)
.into_function()?;
let co = lua.create_thread(func)?;
let output = Arc::new(Mutex::new(Vec::new()));
let hook_output = output.clone();
co.set_hook(HookTriggers::EVERY_LINE, move |_lua, debug| {
assert_eq!(debug.event(), DebugEvent::Line);
hook_output.lock().unwrap().push(debug.curr_line());
Ok(())
});
co.resume(())?;
lua.remove_hook();
let output = output.lock().unwrap();
if cfg!(feature = "luajit") && lua.load("jit.version_num").eval::<i64>()? >= 20100 {
assert_eq!(*output, vec![2, 3, 4, 0, 4]);
} else {
assert_eq!(*output, vec![2, 3, 4]);
}
Ok(())
}
+10
View File
@@ -9,6 +9,16 @@ use std::sync::Arc;
use mlua::{Compiler, CoverageInfo, Error, Lua, Result, Table, ThreadStatus, Value, VmState};
#[test]
fn test_version() -> Result<()> {
let lua = Lua::new();
assert!(lua
.globals()
.get::<_, String>("_VERSION")?
.starts_with("Luau 0."));
Ok(())
}
#[test]
fn test_require() -> Result<()> {
let lua = Lua::new();
+18 -5
View File
@@ -392,31 +392,44 @@ fn test_from_value_newtype_struct() -> Result<(), Box<dyn StdError>> {
#[test]
fn test_from_value_enum() -> Result<(), Box<dyn StdError>> {
let lua = Lua::new();
lua.globals().set("null", lua.null())?;
#[derive(Deserialize, PartialEq, Debug)]
enum E {
struct UnitStruct;
#[derive(Deserialize, PartialEq, Debug)]
enum E<T = ()> {
Unit,
Integer(u32),
Tuple(u32, u32),
Struct { a: u32 },
Wrap(T),
}
let value = lua.load(r#""Unit""#).eval()?;
let got = lua.from_value(value)?;
let got: E = lua.from_value(value)?;
assert_eq!(E::Unit, got);
let value = lua.load(r#"{Integer = 1}"#).eval()?;
let got = lua.from_value(value)?;
let got: E = 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)?;
let got: E = 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)?;
let got: E = lua.from_value(value)?;
assert_eq!(E::Struct { a: 3 }, got);
let value = lua.load(r#"{Wrap = null}"#).eval()?;
let got = lua.from_value(value)?;
assert_eq!(E::Wrap(UnitStruct), got);
let value = lua.load(r#"{Wrap = null}"#).eval()?;
let got = lua.from_value(value)?;
assert_eq!(E::Wrap(()), got);
Ok(())
}
+19
View File
@@ -98,3 +98,22 @@ fn test_string_debug() -> Result<()> {
Ok(())
}
#[cfg(all(feature = "unstable", not(feature = "send")))]
#[test]
fn test_owned_string() -> Result<()> {
let lua = Lua::new();
let s = lua.create_string("hello, world!")?.into_owned();
drop(lua);
// Shortcuts
assert_eq!(s.as_bytes(), b"hello, world!");
assert_eq!(s.to_str()?, "hello, world!");
assert_eq!(format!("{s:?}"), "\"hello, world!\"");
// Access via reference
assert_eq!(s.to_ref().to_string_lossy(), "hello, world!");
Ok(())
}
+14
View File
@@ -392,3 +392,17 @@ fn test_table_call() -> Result<()> {
Ok(())
}
#[cfg(all(feature = "unstable", not(feature = "send")))]
#[test]
fn test_owned_table() -> Result<()> {
let lua = Lua::new();
let table = lua.create_table()?.into_owned();
drop(lua);
table.to_ref().set("abc", 123)?;
assert_eq!(table.to_ref().get::<_, i64>("abc")?, 123);
Ok(())
}
+37 -9
View File
@@ -887,19 +887,47 @@ fn test_application_data() -> Result<()> {
lua.set_app_data("test1");
lua.set_app_data(vec!["test2"]);
// Borrow &str immutably and Vec<&str> mutably
let s = lua.app_data_ref::<&str>().unwrap();
let mut v = lua.app_data_mut::<Vec<&str>>().unwrap();
v.push("test3");
// Insert of new data or removal should fail now
assert!(lua.try_set_app_data::<i32>(123).is_err());
match catch_unwind(AssertUnwindSafe(|| lua.set_app_data::<i32>(123))) {
Ok(_) => panic!("expected panic"),
Err(_) => {}
}
match catch_unwind(AssertUnwindSafe(|| lua.remove_app_data::<i32>())) {
Ok(_) => panic!("expected panic"),
Err(_) => {}
}
// Check display and debug impls
assert_eq!(format!("{s}"), "test1");
assert_eq!(format!("{s:?}"), "\"test1\"");
// Borrowing immutably and mutably of the same type is not allowed
match catch_unwind(AssertUnwindSafe(|| lua.app_data_mut::<&str>().unwrap())) {
Ok(_) => panic!("expected panic"),
Err(_) => {}
}
drop((s, v));
// Test that application data is accessible from anywhere
let f = lua.create_function(|lua, ()| {
{
let data1 = lua.app_data_ref::<&str>().unwrap();
assert_eq!(*data1, "test1");
}
let mut data2 = lua.app_data_mut::<Vec<&str>>().unwrap();
assert_eq!(*data2, vec!["test2"]);
data2.push("test3");
let mut data1 = lua.app_data_mut::<&str>().unwrap();
assert_eq!(*data1, "test1");
*data1 = "test4";
let data2 = lua.app_data_ref::<Vec<&str>>().unwrap();
assert_eq!(*data2, vec!["test2", "test3"]);
Ok(())
})?;
f.call(())?;
assert_eq!(*lua.app_data_ref::<&str>().unwrap(), "test1");
assert_eq!(*lua.app_data_ref::<&str>().unwrap(), "test4");
assert_eq!(
*lua.app_data_ref::<Vec<&str>>().unwrap(),
vec!["test2", "test3"]
@@ -991,7 +1019,7 @@ fn test_ref_stack_exhaustion() {
match catch_unwind(AssertUnwindSafe(|| -> Result<()> {
let lua = Lua::new();
let mut vals = Vec::new();
for _ in 0..1000000 {
for _ in 0..10000000 {
vals.push(lua.create_table()?);
}
Ok(())
+50 -26
View File
@@ -1,3 +1,4 @@
use std::collections::HashMap;
use std::string::String as StdString;
use std::sync::Arc;
#[cfg(not(feature = "parking_lot"))]
@@ -486,6 +487,7 @@ fn test_fields() -> Result<()> {
impl UserData for MyUserData {
fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) {
fields.add_field("static", "constant");
fields.add_field_method_get("val", |_, data| Ok(data.0));
fields.add_field_method_set("val", |_, data, val| {
data.0 = val;
@@ -497,11 +499,7 @@ fn test_fields() -> Result<()> {
fields
.add_field_function_set("uval", |_, ud, s| ud.set_user_value::<Option<String>>(s));
fields.add_meta_field_with(MetaMethod::Index, |lua| {
let index = lua.create_table()?;
index.set("f", 321)?;
Ok(index)
});
fields.add_meta_field(MetaMethod::Index, HashMap::from([("f", 321)]));
fields.add_meta_field_with(MetaMethod::NewIndex, |lua| {
lua.create_function(|lua, (_, field, val): (AnyUserData, String, Value)| {
lua.globals().set(field, val)?;
@@ -516,6 +514,7 @@ fn test_fields() -> Result<()> {
globals.set("ud", MyUserData(7))?;
lua.load(
r#"
assert(ud.static == "constant")
assert(ud.val == 7)
ud.val = 10
assert(ud.val == 10)
@@ -538,30 +537,22 @@ fn test_fields() -> Result<()> {
#[test]
fn test_metatable() -> Result<()> {
#[derive(Copy, Clone)]
struct MyUserData(i64);
struct MyUserData;
impl UserData for MyUserData {
fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) {
fields.add_meta_field_with("__type_name", |_| Ok("MyUserData"));
}
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_function("my_type_name", |_, data: AnyUserData| {
let metatable = data.get_metatable()?;
metatable.get::<String>("__type_name")
metatable.get::<String>("__name")
});
}
}
let lua = Lua::new();
let globals = lua.globals();
globals.set("ud", MyUserData(7))?;
lua.load(
r#"
assert(ud:my_type_name() == "MyUserData")
"#,
)
.exec()?;
globals.set("ud", MyUserData)?;
lua.load(r#"assert(ud:my_type_name() == "MyUserData")"#)
.exec()?;
let ud: AnyUserData = globals.get("ud")?;
let metatable = ud.get_metatable()?;
@@ -583,10 +574,10 @@ fn test_metatable() -> Result<()> {
.map(|kv: Result<(_, Value)>| Ok(kv?.0))
.collect::<Result<Vec<_>>>()?;
methods.sort();
assert_eq!(methods, vec!["__index", "__type_name"]);
assert_eq!(methods, vec!["__index", "__name"]);
#[derive(Copy, Clone)]
struct MyUserData2(i64);
struct MyUserData2;
impl UserData for MyUserData2 {
fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) {
@@ -594,12 +585,25 @@ fn test_metatable() -> Result<()> {
}
}
match lua.create_userdata(MyUserData2(1)) {
match lua.create_userdata(MyUserData2) {
Ok(_) => panic!("expected MetaMethodTypeError, got no error"),
Err(Error::MetaMethodTypeError { .. }) => {}
Err(e) => panic!("expected MetaMethodTypeError, got {:?}", e),
}
#[derive(Copy, Clone)]
struct MyUserData3;
impl UserData for MyUserData3 {
fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) {
fields.add_meta_field_with("__name", |_| Ok("CustomName"));
}
}
let ud = lua.create_userdata(MyUserData3)?;
let metatable = ud.get_metatable()?;
assert_eq!(metatable.get::<String>("__name")?.to_str()?, "CustomName");
Ok(())
}
@@ -609,6 +613,7 @@ fn test_userdata_wrapped() -> Result<()> {
impl UserData for MyUserData {
fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) {
fields.add_field("static", "constant");
fields.add_field_method_get("data", |_, this| Ok(this.0));
fields.add_field_method_set("data", |_, this, val| {
this.0 = val;
@@ -626,6 +631,7 @@ fn test_userdata_wrapped() -> Result<()> {
globals.set("rc_refcell_ud", ud1.clone())?;
lua.load(
r#"
assert(rc_refcell_ud.static == "constant")
rc_refcell_ud.data = rc_refcell_ud.data + 1
assert(rc_refcell_ud.data == 2)
"#,
@@ -641,6 +647,7 @@ fn test_userdata_wrapped() -> Result<()> {
globals.set("arc_mutex_ud", ud2.clone())?;
lua.load(
r#"
assert(arc_mutex_ud.static == "constant")
arc_mutex_ud.data = arc_mutex_ud.data + 1
assert(arc_mutex_ud.data == 3)
"#,
@@ -655,6 +662,7 @@ fn test_userdata_wrapped() -> Result<()> {
globals.set("arc_rwlock_ud", ud3.clone())?;
lua.load(
r#"
assert(arc_rwlock_ud.static == "constant")
arc_rwlock_ud.data = arc_rwlock_ud.data + 1
assert(arc_rwlock_ud.data == 4)
"#,
@@ -681,7 +689,7 @@ fn test_userdata_proxy() -> Result<()> {
impl UserData for MyUserData {
fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) {
fields.add_field_function_get("static_field", |_, _| Ok(123));
fields.add_field("static_field", 123);
fields.add_field_method_get("n", |_, this| Ok(this.0));
}
@@ -775,10 +783,7 @@ fn test_userdata_ext() -> Result<()> {
assert_eq!(ud.get::<_, u32>("n")?, 123);
ud.set("n", 321)?;
assert_eq!(ud.get::<_, u32>("n")?, 321);
match ud.get::<_, u32>("non-existent") {
Err(Error::RuntimeError(_)) => {}
r => panic!("expected RuntimeError, got {r:?}"),
}
assert_eq!(ud.get::<_, Option<u32>>("non-existent")?, None);
match ud.set::<_, u32>("non-existent", 123) {
Err(Error::RuntimeError(_)) => {}
r => panic!("expected RuntimeError, got {r:?}"),
@@ -820,3 +825,22 @@ fn test_userdata_method_errors() -> Result<()> {
Ok(())
}
#[cfg(all(feature = "unstable", not(feature = "send")))]
#[test]
fn test_owned_userdata() -> Result<()> {
let lua = Lua::new();
let ud = lua.create_any_userdata("abc")?.into_owned();
drop(lua);
assert_eq!(*ud.borrow::<&str>()?, "abc");
*ud.borrow_mut()? = "cba";
assert!(matches!(
ud.borrow::<i64>(),
Err(Error::UserDataTypeMismatch)
));
assert_eq!(ud.take::<&str>()?, "cba");
Ok(())
}
+73 -1
View File
@@ -1,6 +1,9 @@
use std::collections::HashMap;
use std::os::raw::c_void;
use std::ptr;
use std::string::String as StdString;
use mlua::{Lua, MultiValue, Result, Value};
use mlua::{Error, LightUserData, Lua, MultiValue, Result, UserData, UserDataMethods, Value};
#[test]
fn test_value_eq() -> Result<()> {
@@ -28,6 +31,7 @@ fn test_value_eq() -> Result<()> {
"#,
)
.exec()?;
globals.set("null", Value::NULL)?;
let table1: Value = globals.get("table1")?;
let table2: Value = globals.get("table2")?;
@@ -41,6 +45,7 @@ fn test_value_eq() -> Result<()> {
let func3: Value = globals.get("func3")?;
let thread1: Value = globals.get("thread1")?;
let thread2: Value = globals.get("thread2")?;
let null: Value = globals.get("null")?;
assert!(table1 != table2);
assert!(table1.equals(&table2)?);
@@ -54,6 +59,7 @@ fn test_value_eq() -> Result<()> {
assert!(!func1.equals(&func3)?);
assert!(thread1 == thread2);
assert!(thread1.equals(&thread2)?);
assert!(null == Value::NULL);
assert!(!table1.to_pointer().is_null());
assert!(!ptr::eq(table1.to_pointer(), table2.to_pointer()));
@@ -81,3 +87,69 @@ fn test_multi_value() {
multi_value.clear();
assert!(multi_value.is_empty());
}
#[test]
fn test_value_to_string() -> Result<()> {
let lua = Lua::new();
assert_eq!(Value::Nil.to_string()?, "nil");
assert_eq!(Value::Boolean(true).to_string()?, "true");
assert_eq!(Value::NULL.to_string()?, "null");
assert_eq!(
Value::LightUserData(LightUserData(0x1 as *const c_void as *mut _)).to_string()?,
"lightuserdata: 0x1"
);
assert_eq!(Value::Integer(1).to_string()?, "1");
assert_eq!(Value::Number(34.59).to_string()?, "34.59");
#[cfg(feature = "luau")]
assert_eq!(
Value::Vector(10.0, 11.1, 12.2).to_string()?,
"vector(10, 11.1, 12.2)"
);
assert_eq!(
Value::String(lua.create_string("hello")?).to_string()?,
"hello"
);
let table: Value = lua.load("{}").eval()?;
assert!(table.to_string()?.starts_with("table:"));
let table: Value = lua
.load("setmetatable({}, {__tostring = function() return 'test table' end})")
.eval()?;
assert_eq!(table.to_string()?, "test table");
let func: Value = lua.load("function() end").eval()?;
assert!(func.to_string()?.starts_with("function:"));
let thread: Value = lua.load("coroutine.create(function() end)").eval()?;
assert!(thread.to_string()?.starts_with("thread:"));
lua.register_userdata_type::<StdString>(|reg| {
reg.add_meta_method("__tostring", |_, this, ()| Ok(this.clone()));
})?;
let ud: Value = Value::UserData(lua.create_any_userdata(String::from("string userdata"))?);
assert_eq!(ud.to_string()?, "string userdata");
struct MyUserData;
impl UserData for MyUserData {}
let ud: Value = Value::UserData(lua.create_userdata(MyUserData)?);
assert!(ud.to_string()?.starts_with("MyUserData:"));
let err = Value::Error(Error::RuntimeError("test error".to_string()));
assert_eq!(err.to_string()?, "runtime error: test error");
Ok(())
}
#[test]
fn test_debug_format() -> Result<()> {
let lua = Lua::new();
lua.register_userdata_type::<HashMap<i32, StdString>>(|_| {})?;
let ud = lua
.create_any_userdata::<HashMap<i32, StdString>>(HashMap::new())
.map(Value::UserData)?;
assert!(format!("{ud:#?}").starts_with("HashMap<i32, String>:"));
Ok(())
}