Compare commits

..

125 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
Alex Orlenko 354c69acc4 Update README 2023-03-30 23:22:01 +01:00
Alex Orlenko 6fa0a78ec0 Prepare v0.9.0-beta.1 2023-03-30 23:18:10 +01:00
Alex Orlenko f66932e8d2 Revert "Rename Owned*::to_ref() to as_ref()"
This reverts commit 5394faca16.
2023-03-30 22:35:43 +01:00
Alex Orlenko da6d5a93c9 Update rustyline dependency 2023-03-30 22:34:41 +01:00
Alex Orlenko 1be927bc5d Don't call error handler for memory errors in coroutines 2023-03-30 17:29:57 +01:00
Alex Orlenko 0848ddcdf7 Clippy 2023-03-28 20:53:22 +01:00
Alex Orlenko e182d474e0 More user-friendly error message about missing value for name attribute in module macro
Update the `[lua_module]` doc
2023-03-28 16:27:16 +01:00
Alex Orlenko 0f937b0a03 Switch to syn 2.0 2023-03-28 13:56:59 +01:00
Alex Orlenko cd9fc1d08f Update Error doc 2023-03-26 20:58:43 +01:00
Alex Orlenko d9aac08b81 Support setting memory limit for Lua 5.1/JIT/Luau
Other versions already support this feature.
Closes #119
2023-03-26 00:06:52 +00:00
Alex Orlenko 9c1669020b Clippy 2023-03-25 23:38:57 +00:00
Alex Orlenko 742307a267 Add &Lua to luau interrupt callback (fixes #197) 2023-03-25 16:30:31 +00:00
Alex Orlenko 781ded573a Seal LuaSerdeExt/TableExt/AnyUserDataExt 2023-03-19 02:38:21 +00:00
Alex Orlenko 6a647f58be Add Error::downcast_ref() method 2023-03-19 00:22:51 +00:00
Alex Orlenko 4bc6501d2e Tests for the ErrorContext trait 2023-03-17 01:31:07 +00:00
Alex Orlenko 8d80321738 Add ErrorContext extension trait to attach additional context to Error 2023-03-17 01:20:57 +00:00
Alex Orlenko a0d37fd182 Rename error to cause in Error::BadArgument 2023-03-16 23:48:54 +00:00
Alex Orlenko 03787668fd Improve error reporting when calling Rust functions from Lua.
In particular new error type `Error::BadArgument` added to help identify bad argument position or name (eg `self` for userdata).
2023-03-14 23:23:46 +00:00
Alex Orlenko 3059f82552 Update README and CHANGELOG 2023-03-12 23:54:19 +00:00
Alex Orlenko 22fbcfd9fb Update edition everywhere 2023-03-12 01:03:31 +00:00
Alex Orlenko 304880bfd1 Fast metatable check for Lua::push_userdata_ref 2023-03-10 10:34:41 +00:00
Alex Orlenko 33c276d0b4 Optimize userdata methods call when __index and fields_getters are nil 2023-03-04 11:58:34 +00:00
Alex Orlenko 87d027e8ac Rename rust_module in tests to test_module
This causes warning during compilation
2023-03-03 18:58:13 +00:00
Alex Orlenko d7db1b7f2e Add Waker lifetime to WakerGuard 2023-03-03 18:29:04 +00:00
Alex Orlenko 22c973af02 Async: Don't clone function when executing it from recycled thread 2023-03-03 18:22:39 +00:00
Alex Orlenko 8c18fa1764 Async: store pointer to Waker in extra data rather than in Lua ref thread 2023-03-03 15:19:54 +00:00
Alex Orlenko 5394faca16 Rename Owned*::to_ref() to as_ref() 2023-03-02 15:36:11 +00:00
Alex Orlenko 4a3f6d60ad Update exports in libs and prelude 2023-03-02 15:34:42 +00:00
Alex Orlenko 68b60e2a0a Add UserDataRef and UserDataRefMut types that implement FromLua
and can be used as accessors to underlying `AnyUserData` type.
2023-02-26 21:52:28 +00:00
Alex Orlenko 888bd77e60 Add AnyUserDataExt to prelude 2023-02-25 23:59:29 +00:00
Alex Orlenko 949906f9f7 Fix potential deadlock when trying to reuse dropped RegistryKey.
If no free registry id found, we call protect_lua! macro while keeping mutex guard to the unref list.
Protected calls can trigger garbage collection and if RegistryKey is placed in userdata being collected, this can lead to deadlock.
The solution is drop mutex guard as soon as possible.
Also this commit includes optimization in creating reference in Lua registry.
2023-02-22 20:15:40 +00:00
Alex Orlenko 94f01e597c Add AnyUserDataExt trait with auxiliary functions for AnyUserData 2023-02-18 23:40:28 +00:00
Alex Orlenko b8e3290f35 Update LuaRef/LuaOwnedRef
Move content of `Lua::make_owned_ref` into `LuaRef::into_owned`
Add crate-visible `Lua::clone` function (not trait)
2023-02-15 09:20:03 +00:00
Alex Orlenko 03ab828342 Return evicted value to Lua::set_app_data if it's already present.
Add "Panics" section to `Lua::*_app_data` docs.
2023-02-12 23:38:09 +00:00
Alex Orlenko b66bff9155 Drop Lua::async_scope as it's unsound 2023-02-12 22:25:10 +00:00
Alex Orlenko f52abf919e Add create_userdata_ref/create_userdata_ref_mut for scope (#206)
New methods would allow creating userdata objects from (mutable) reference
to a UserData of registered type.
2023-02-12 16:37:02 +00:00
Alex Orlenko b790b525c1 Fix clippy warnings 2023-02-07 22:43:48 +00:00
Alex Orlenko f5182e0584 Force protected mode for long enough strings 2023-02-06 23:46:31 +00:00
Alex Orlenko 47c8300ccf Allow registering and creating custom userdata types that don't necessary implement the UserData trait.
This is useful to register 3rd party types that cannot implement `UserData` due to Rust orphan rules.
See #206
2023-02-03 23:46:04 +00:00
Alex Orlenko 8339621f9c Rename to_lua_err -> into_lua_err 2023-01-06 21:35:15 +00:00
Alex Orlenko 16cbb442e5 Fix clippy warnings 2023-01-06 20:50:29 +00:00
Alex Orlenko 88da28a68d Allow deserializing values from serializable UserData using Lua::from_value() method.
Closes #240
2023-01-06 10:41:17 +00:00
Alex Orlenko a62061f453 Update README 2023-01-04 20:58:03 +00:00
Alex Orlenko cb3d58b1c7 Better Debug for String 2023-01-04 15:56:57 +00:00
Alex Orlenko fa46720f5a Add Table::clear() method 2023-01-02 15:50:21 +00:00
Alex Orlenko 72b9209ae9 Rename wrapped_failure/thread cache to pool 2022-12-30 21:48:12 +00:00
Alex Orlenko 0cb7058476 Rename MultiValue cache to pool 2022-12-30 14:50:39 +00:00
Alex Orlenko ac8b3424d1 Update criterion 2022-12-30 13:45:59 +00:00
Alex Orlenko 2cb4b49d0d Don't use unstable features in examples 2022-12-30 13:39:27 +00:00
Alex Orlenko d67332a3ac Refactor userdata::MetaMethod.
Take `impl AsRef<str>` as name of metamethod (instead of `Into<MetaMethod>`) in UserDataMethods/UserDataFields.
Change position of generic args in userdata.
2022-12-24 19:16:58 +00:00
Alex Orlenko 22c6e9a7e7 Enable unstable feature flag in CI 2022-12-22 16:30:14 +00:00
Alex Orlenko 1d4a135e8e Add Function::wrap/Function::wrap_mut/Function::wrap_async to wrap functions into a type that implements IntoLua trait.
This is useful to avoid calling `lua.create_function*` every time when `Function` handle is needed.
2022-12-22 16:24:35 +00:00
Alex Orlenko 9d28b790e7 Fix module tests 2022-12-21 21:05:23 +00:00
Alex Orlenko 61c285746c Remove FromLua impl for UserData+Clone 2022-12-20 23:30:18 +00:00
Alex Orlenko a13c188de3 Rename ToLua/ToLuaMulti -> IntoLua/IntoLuaMulti 2022-12-19 23:00:47 +00:00
Alex Orlenko c60f633a62 Add "unstable" feature flag.
Hide owned types under the new feature flag.
Drop OwnedString/OwnedThread types (unlikely they are useful).
2022-12-19 22:26:44 +00:00
Alex Orlenko 56abc4a700 Refactor AsChunk trait 2022-12-19 21:12:36 +00:00
Alex Orlenko 9b4e3a1598 Refactor LuaInner state
Add static_assertions to check for auto traits impl on compilation stage
Bump MSRV to 1.63 (required for `Ref::filter_map`)
2022-12-19 16:09:05 +00:00
Alex Orlenko 0aa30226df Check for invalid args when parsing #[lua_module(...)] proc macro 2022-12-18 00:35:41 +00:00
Alex Orlenko fdb5724053 Add ?Sized to PartialEq for Lua String 2022-12-18 00:35:41 +00:00
rise0chen b532f86674 feat: Support custom name of lua_module 2022-12-18 00:35:41 +00:00
Alex Orlenko c44463f030 Replace impl AsRef<str> with simple &str 2022-12-18 00:35:41 +00:00
Alex Orlenko de38e299fc Use impl AsRef<str> for userdata methods/fields instead of generic param.
Use `impl AsRef<str>` for module names and named registry values.
2022-12-18 00:35:41 +00:00
Alex Orlenko 7526773a90 Do not clear usevalues when taking value out of userdata.
It has big performance penalty.
Lua GC can collect uservalues when userdata is not referenced anymore.
2022-12-18 00:35:41 +00:00
Alex Orlenko bf79d9e75d Initial implementation of owned Lua types 2022-12-18 00:35:41 +00:00
Joel Natividad 02c08c6350 upgrade bstr from 0.2 to 1.0 2022-12-18 00:35:40 +00:00
101 changed files with 5930 additions and 3162 deletions
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
- name: Generate coverage report
run: |
cargo tarpaulin --out xml --tests --exclude-files benches/* --exclude-files src/ffi/*/*
cargo tarpaulin --out xml --tests --exclude-files benches/* --exclude-files mlua-sys/src/*/*
- name: Upload report to codecov.io
uses: codecov/codecov-action@v3
+17 -15
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
@@ -27,7 +27,7 @@ jobs:
- name: Build ${{ matrix.lua }} vendored
run: |
cargo build --features "${{ matrix.lua }},vendored"
cargo build --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot"
cargo build --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot,unstable"
shell: bash
- name: Build ${{ matrix.lua }} pkg-config
if: ${{ matrix.os == 'ubuntu-22.04' }}
@@ -50,7 +50,7 @@ jobs:
toolchain: stable
target: aarch64-apple-darwin
- name: Cross-compile
run: cargo build --target aarch64-apple-darwin --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot"
run: cargo build --target aarch64-apple-darwin --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot,unstable"
build_aarch64_cross_ubuntu:
name: Cross-compile to aarch64-unknown-linux-gnu
@@ -71,7 +71,7 @@ jobs:
sudo apt-get install -y --no-install-recommends gcc-aarch64-linux-gnu libc6-dev-arm64-cross
shell: bash
- name: Cross-compile
run: cargo build --target aarch64-unknown-linux-gnu --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot"
run: cargo build --target aarch64-unknown-linux-gnu --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot,unstable"
shell: bash
build_armv7_cross_ubuntu:
@@ -93,7 +93,7 @@ jobs:
sudo apt-get install -y --no-install-recommends gcc-arm-linux-gnueabihf libc-dev-armhf-cross
shell: bash
- name: Cross-compile
run: cargo build --target armv7-unknown-linux-gnueabihf --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot"
run: cargo build --target armv7-unknown-linux-gnueabihf --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot,unstable"
shell: bash
test:
@@ -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,17 +118,18 @@ 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"
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' }}
run: |
TRYBUILD=overwrite cargo test --features "${{ matrix.lua }},vendored" -- --ignored
TRYBUILD=overwrite cargo test --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot" -- --ignored
TRYBUILD=overwrite cargo test --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot,unstable" -- --ignored
shell: bash
test_with_sanitizer:
@@ -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" --target x86_64-unknown-linux-gnu -- --skip test_too_many_recursions
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
@@ -230,4 +232,4 @@ jobs:
- uses: actions-rs/clippy-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot"
args: --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot,unstable"
+1
View File
@@ -4,3 +4,4 @@ Cargo.lock
.vscode/
.DS_Store
.stignore
+57 -8
View File
@@ -1,14 +1,63 @@
## v0.8.10
## v0.9.0-beta.3
- Update to Luau 0.590 (luau0-src to 0.7.x)
- Fix loading luau code starting with \t
- Pin lua-src and luajit-src versions
- 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
## v0.8.9
Breaking changes:
- Require environment to be a `Table` instead of `Value` in Chunks.
- `AsChunk::env()` renamed to `AsChunk::environment()`
- Update minimal (vendored) Lua 5.4 to 5.4.6
- Use `lua_closethread` instead of `lua_resetthread` in vendored mode (Lua 5.4.6)
- Allow deserializing Lua null into unit (`()`) or unit struct.
## 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:
- Owned Lua types (unstable feature flag)
- New functions `Function::wrap`/`Function::wrap_mut`/`Function::wrap_async`
- `Lua::register_userdata_type()` to register a custom userdata types (without requiring `UserData` trait)
- `Lua::create_any_userdata()`
- Added `create_userdata_ref`/`create_userdata_ref_mut` for scopes
- Added `AnyUserDataExt` trait with auxiliary functions for `AnyUserData`
- Added `UserDataRef` and `UserDataRefMut` type wrapped that implement `FromLua`
- Improved error handling:
* Improved error reporting when calling Rust functions from Lua.
* Added `Error::BadArgument` to help identify bad argument position or name
* Added `ErrorContext` extension trait to attach additional context to `Error`
Breaking changes:
- Refactored `AsChunk` trait
- `ToLua`/`ToLuaMulti` renamed to `IntoLua`/`IntoLuaMulti`
- Renamed `to_lua_err` to `into_lua_err`
- Removed `FromLua` impl for `T: UserData+Clone`
- Removed `Lua::async_scope`
- Added `&Lua` arg to Luau interrupt callback
Other:
- Better Debug for String
- Allow deserializing values from serializable UserData using `Lua::from_value()` method
- Added `Table::clear()` method
- Added `Error::downcast_ref()` method
- Support setting memory limit for Lua 5.1/JIT/Luau
- Support setting module name in `#[lua_module(name = "...")]` macro
- Minor fixes and improvements
## v0.8.8
+24 -28
View File
@@ -1,6 +1,6 @@
[package]
name = "mlua"
version = "0.8.10" # remember to update 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,60 +9,55 @@ 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.
"""
[package.metadata.docs.rs]
features = ["lua54", "vendored", "async", "send", "serialize", "macros", "parking_lot"]
features = ["lua54", "vendored", "async", "send", "serialize", "macros", "parking_lot", "unstable"]
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"]
serialize = ["serde", "erased-serde", "serde-value"]
macros = ["mlua_derive/macros"]
unstable = []
[dependencies]
mlua_derive = { version = "=0.8.0", optional = true, path = "mlua_derive" }
bstr = { version = "0.2", features = ["std"], default_features = false }
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 = ">= 546.0.0, < 546.1.0", optional = true }
luajit-src = { version = ">= 210.4.0, < 210.5.0", optional = true }
luau0-src = { version = "0.7.0", optional = true }
ffi = { package = "mlua-sys", version = "0.2.0", path = "mlua-sys" }
[dev-dependencies]
rustyline = "10.0"
criterion = { version = "0.3.4", features = ["html_reports", "async_tokio"] }
rustyline = "11.0"
criterion = { version = "0.5", features = ["async_tokio"] }
trybuild = "1.0"
futures = "0.3.5"
hyper = { version = "0.14", features = ["client", "server"] }
@@ -73,6 +68,7 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
maplit = "1.0"
tempfile = "3"
static_assertions = "1.0"
[[bench]]
name = "benchmark"
+8 -2
View File
@@ -7,9 +7,9 @@
[crates.io]: https://crates.io/crates/mlua
[API Documentation]: https://docs.rs/mlua/badge.svg
[docs.rs]: https://docs.rs/mlua
[Coverage Status]: https://codecov.io/gh/khvzak/mlua/branch/v0.8/graph/badge.svg?token=99339FS1CG
[Coverage Status]: https://codecov.io/gh/khvzak/mlua/branch/master/graph/badge.svg?token=99339FS1CG
[codecov.io]: https://codecov.io/gh/khvzak/mlua
[MSRV]: https://img.shields.io/badge/rust-1.56+-brightgreen.svg?&logo=rust
[MSRV]: https://img.shields.io/badge/rust-1.63+-brightgreen.svg?&logo=rust
[Guided Tour] | [Benchmarks] | [FAQ]
@@ -17,6 +17,10 @@
[Benchmarks]: https://github.com/khvzak/script-bench-rs
[FAQ]: FAQ.md
> **Note**
>
> Please see the [v0.8](https://github.com/khvzak/mlua/tree/v0.8) branch for the stable versions of `mlua` released to crates.io.
`mlua` is bindings to [Lua](https://www.lua.org) programming language for Rust with a goal to provide
_safe_ (as far as it's possible), high level, easy to use, practical and flexible API.
@@ -41,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])
@@ -48,6 +53,7 @@ Below is a list of the available feature flags. By default `mlua` does not enabl
* `serialize`: add serialization and deserialization support to `mlua` types using [serde] framework
* `macros`: enable procedural macros (such as `chunk!`)
* `parking_lot`: support UserData types wrapped in [parking_lot]'s primitives (`Arc<Mutex>` and `Arc<RwLock>`)
* `unstable`: enable **unstable** features. The public API of these features may break between releases.
[5.4]: https://www.lua.org/manual/5.4/manual.html
[5.3]: https://www.lua.org/manual/5.3/manual.html
+2 -2
View File
@@ -120,7 +120,7 @@ fn call_sum_callback(c: &mut Criterion) {
}
fn call_async_sum_callback(c: &mut Criterion) {
let options = LuaOptions::new().thread_cache_size(1024);
let options = LuaOptions::new().thread_pool_size(1024);
let lua = Lua::new_with(LuaStdLib::ALL_SAFE, options).unwrap();
let callback = lua
.create_async_function(|_, (a, b, c): (i64, i64, i64)| async move {
@@ -272,7 +272,7 @@ fn call_async_userdata_method(c: &mut Criterion) {
}
}
let options = LuaOptions::new().thread_cache_size(1024);
let options = LuaOptions::new().thread_pool_size(1024);
let lua = Lua::new_with(LuaStdLib::ALL_SAFE, options).unwrap();
lua.globals().set("userdata", UserData(10)).unwrap();
-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");
}
+4 -4
View File
@@ -12,7 +12,7 @@ impl UserData for BodyReader {
methods.add_async_function("read", |lua, reader: AnyUserData| async move {
let mut reader = reader.borrow_mut::<Self>()?;
if let Some(bytes) = reader.0.data().await {
let bytes = bytes.to_lua_err()?;
let bytes = bytes.into_lua_err()?;
return Some(lua.create_string(&bytes)).transpose();
}
Ok(None)
@@ -26,8 +26,8 @@ async fn main() -> Result<()> {
let fetch_url = lua.create_async_function(|lua, uri: String| async move {
let client = HyperClient::new();
let uri = uri.parse().to_lua_err()?;
let resp = client.get(uri).await.to_lua_err()?;
let uri = uri.parse().into_lua_err()?;
let resp = client.get(uri).await.into_lua_err()?;
let lua_resp = lua.create_table()?;
lua_resp.set("status", resp.status().as_u16())?;
@@ -37,7 +37,7 @@ async fn main() -> Result<()> {
headers
.entry(key.as_str())
.or_insert(Vec::new())
.push(value.to_str().to_lua_err()?);
.push(value.to_str().into_lua_err()?);
}
lua_resp.set("headers", headers)?;
+2 -2
View File
@@ -10,8 +10,8 @@ async fn main() -> Result<()> {
let resp = reqwest::get(&uri)
.await
.and_then(|resp| resp.error_for_status())
.to_lua_err()?;
let json = resp.json::<serde_json::Value>().await.to_lua_err()?;
.into_lua_err()?;
let json = resp.json::<serde_json::Value>().await.into_lua_err()?;
lua.to_value(&json)
})?;
+14 -2
View File
@@ -1,7 +1,9 @@
use std::f32;
use std::iter::FromIterator;
use mlua::{chunk, Function, Lua, MetaMethod, Result, UserData, UserDataMethods, Variadic};
use mlua::{
chunk, FromLua, Function, Lua, MetaMethod, Result, UserData, UserDataMethods, Value, Variadic,
};
fn main() -> Result<()> {
// You can create a new Lua state with `Lua::new()`. This loads the default Lua std library
@@ -30,7 +32,7 @@ fn main() -> Result<()> {
global = 'foo'..'bar'
"#,
)
.set_name("example code")?
.set_name("example code")
.exec()?;
assert_eq!(globals.get::<_, String>("global")?, "foobar");
@@ -151,6 +153,16 @@ fn main() -> Result<()> {
#[derive(Copy, Clone)]
struct Vec2(f32, f32);
// We can implement `FromLua` trait for our `Vec2` to return a copy
impl<'lua> FromLua<'lua> for Vec2 {
fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result<Self> {
match value {
Value::UserData(ud) => Ok(*ud.borrow::<Self>()?),
_ => unreachable!(),
}
}
}
impl UserData for Vec2 {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_method("magnitude", |_, vec, ()| {
+1 -1
View File
@@ -2,7 +2,7 @@
name = "rust_module"
version = "0.0.0"
authors = ["Aleksandr Orlenko <zxteam@pm.me>"]
edition = "2018"
edition = "2021"
[lib]
crate-type = ["cdylib"]
+2 -2
View File
@@ -5,7 +5,7 @@ use rustyline::Editor;
fn main() {
let lua = Lua::new();
let mut editor = Editor::<()>::new().expect("Failed to make rustyline editor");
let mut editor = Editor::<(), _>::new().expect("Failed to make rustyline editor");
loop {
let mut prompt = "> ";
@@ -19,7 +19,7 @@ fn main() {
match lua.load(&line).eval::<MultiValue>() {
Ok(values) => {
editor.add_history_entry(line);
editor.add_history_entry(line).unwrap();
println!(
"{}",
values
+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
@@ -8,7 +8,7 @@ fn get_env_var(name: &str) -> String {
match env::var(name) {
Ok(val) => val,
Err(env::VarError::NotPresent) => String::new(),
Err(err) => panic!("cannot get {}: {}", name, err),
Err(err) => panic!("cannot get {name}: {err}"),
}
}
@@ -37,8 +37,8 @@ pub fn probe_lua() -> Option<PathBuf> {
if get_env_var("LUA_LINK") == "static" {
link_lib = "static=";
};
println!("cargo:rustc-link-search=native={}", lib_dir);
println!("cargo:rustc-link-lib={}{}", link_lib, lua_lib);
println!("cargo:rustc-link-search=native={lib_dir}");
println!("cargo:rustc-link-lib={link_lib}{lua_lib}");
}
return Some(PathBuf::from(include_dir));
}
@@ -72,7 +72,7 @@ pub fn probe_lua() -> Option<PathBuf> {
.probe(alt_probe);
}
lua.unwrap_or_else(|_| panic!("cannot find Lua {} using `pkg-config`", ver))
lua.unwrap_or_else(|_| panic!("cannot find Lua {ver} using `pkg-config`"))
.include_paths
.get(0)
.cloned()
@@ -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 -34
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,44 +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::new();
symbols.push(lua_atpanic as _);
symbols.push(lua_isuserdata as _);
symbols.push(lua_tocfunction as _);
symbols.push(luaL_loadstring as _);
symbols.push(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)]
@@ -341,7 +341,7 @@ pub unsafe fn luaL_loadbufferx(
fn free(p: *mut c_void);
}
let chunk_is_text = size == 0 || (*data as u8) >= b'\t';
let chunk_is_text = size == 0 || (*data as u8) >= b'\n';
if !mode.is_null() {
let modeb = CStr::from_ptr(mode).to_bytes();
if !chunk_is_text && !modeb.contains(&b'b') {
@@ -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);
}
@@ -10,8 +10,7 @@ pub struct lua_CompileOptions {
pub coverageLevel: c_int,
pub vectorLib: *const c_char,
pub vectorCtor: *const c_char,
pub vectorType: *const c_char,
pub mutableGlobals: *const *const c_char,
pub mutableGlobals: *mut *const c_char,
}
extern "C" {
+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
};
}
+3 -3
View File
@@ -1,8 +1,8 @@
[package]
name = "mlua_derive"
version = "0.8.0"
version = "0.9.0-beta.2"
authors = ["Aleksandr Orlenko <zxteam@pm.me>"]
edition = "2018"
edition = "2021"
description = "Procedural macros for the mlua crate."
repository = "https://github.com/khvzak/mlua"
keywords = ["lua", "mlua"]
@@ -18,7 +18,7 @@ macros = ["proc-macro-error", "itertools", "regex", "once_cell"]
quote = "1.0"
proc-macro2 = { version = "1.0", features = ["span-locations"] }
proc-macro-error = { version = "1.0", optional = true }
syn = { version = "1.0", features = ["full"] }
syn = { version = "2.0", features = ["full"] }
itertools = { version = "0.10", optional = true }
regex = { version = "1.4", optional = true }
once_cell = { version = "1.0", optional = true }
+47 -25
View File
@@ -1,7 +1,8 @@
use proc_macro::TokenStream;
use proc_macro2::{Ident, Span};
use quote::quote;
use syn::{parse_macro_input, AttributeArgs, Error, ItemFn};
use syn::meta::ParseNestedMeta;
use syn::{parse_macro_input, ItemFn, LitStr, Result};
#[cfg(feature = "macros")]
use {
@@ -9,19 +10,41 @@ use {
proc_macro_error::proc_macro_error,
};
#[derive(Default)]
struct ModuleAttributes {
name: Option<Ident>,
}
impl ModuleAttributes {
fn parse(&mut self, meta: ParseNestedMeta) -> Result<()> {
if meta.path.is_ident("name") {
match meta.value() {
Ok(value) => {
self.name = Some(value.parse::<LitStr>()?.parse()?);
}
Err(_) => {
return Err(meta.error("`name` attribute must have a value"));
}
}
} else {
return Err(meta.error("unsupported module attribute"));
}
Ok(())
}
}
#[proc_macro_attribute]
pub fn lua_module(attr: TokenStream, item: TokenStream) -> TokenStream {
let args = parse_macro_input!(attr as AttributeArgs);
let func = parse_macro_input!(item as ItemFn);
if !args.is_empty() {
let err = Error::new(Span::call_site(), "the macro does not support arguments")
.to_compile_error();
return err.into();
let mut args = ModuleAttributes::default();
if !attr.is_empty() {
let args_parser = syn::meta::parser(|meta| args.parse(meta));
parse_macro_input!(attr with args_parser);
}
let func_name = func.sig.ident.clone();
let ext_entrypoint_name = Ident::new(&format!("luaopen_{}", func_name), Span::call_site());
let func = parse_macro_input!(item as ItemFn);
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());
let wrapped = quote! {
::mlua::require_module_feature!();
@@ -61,25 +84,18 @@ 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::marker::PhantomData;
use ::std::sync::Mutex;
fn annotate<'a, F: FnOnce(&'a Lua) -> Result<Value<'a>>>(f: F) -> F { f }
struct InnerChunk<F: for <'a> FnOnce(&'a Lua) -> Result<Table<'a>>>(Mutex<Option<F>>);
struct InnerChunk<'a, F: FnOnce(&'a Lua) -> Result<Value<'a>>>(Mutex<Option<F>>, PhantomData<&'a ()>);
impl<'lua, F> AsChunk<'lua> for InnerChunk<'lua, F>
impl<F> AsChunk<'static> for InnerChunk<F>
where
F: FnOnce(&'lua Lua) -> Result<Value<'lua>>,
F: for <'a> FnOnce(&'a Lua) -> Result<Table<'a>>,
{
fn source(&self) -> IoResult<Cow<[u8]>> {
Ok(Cow::Borrowed((#source).as_bytes()))
}
fn env(&self, lua: &'lua Lua) -> Result<Option<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() {
@@ -93,9 +109,15 @@ pub fn chunk(input: TokenStream) -> TokenStream {
fn mode(&self) -> Option<ChunkMode> {
Some(ChunkMode::Text)
}
fn source(self) -> IoResult<Cow<'static, [u8]>> {
Ok(Cow::Borrowed((#source).as_bytes()))
}
}
let make_env = annotate(move |lua: &Lua| -> Result<Value> {
fn annotate<F: for<'a> FnOnce(&'a Lua) -> Result<Table<'a>>>(f: F) -> F { f }
let make_env = annotate(move |lua: &Lua| -> Result<Table> {
let globals = lua.globals();
let env = lua.create_table()?;
let meta = lua.create_table()?;
@@ -106,10 +128,10 @@ pub fn chunk(input: TokenStream) -> TokenStream {
#(#caps)*
env.set_metatable(Some(meta));
Ok(Value::Table(env))
Ok(env)
});
&InnerChunk(Mutex::new(Some(make_env)), PhantomData)
InnerChunk(Mutex::new(Some(make_env)))
}};
wrapped_code.into()
+1 -1
View File
@@ -59,7 +59,7 @@ fn parse_pos(span: &Span) -> Option<(usize, usize)> {
static RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"bytes\(([0-9]+)\.\.([0-9]+)\)").unwrap());
match RE.captures(&format!("{:?}", span)) {
match RE.captures(&format!("{span:?}")) {
Some(caps) => match (caps.get(1), caps.get(2)) {
(Some(start), Some(end)) => Some((
match start.as_str().parse() {
+71 -66
View File
@@ -5,23 +5,20 @@ 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, ToLua, ToLuaMulti, 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`]
///
/// [loadable by Lua]: https://www.lua.org/manual/5.4/manual.html#3.3.2
/// [`Chunk`]: crate::Chunk
pub trait AsChunk<'lua> {
/// Returns chunk data (can be text or binary)
fn source(&self) -> IoResult<Cow<[u8]>>;
pub trait AsChunk<'a> {
/// Returns optional chunk name
fn name(&self) -> Option<StdString> {
None
@@ -30,7 +27,8 @@ pub trait AsChunk<'lua> {
/// Returns optional chunk [environment]
///
/// [environment]: https://www.lua.org/manual/5.4/manual.html#2.2
fn env(&self, _lua: &'lua Lua) -> Result<Option<Value<'lua>>> {
fn environment<'lua>(&self, lua: &'lua Lua) -> Result<Option<Table<'lua>>> {
let _lua = lua; // suppress warning
Ok(None)
}
@@ -38,50 +36,65 @@ pub trait AsChunk<'lua> {
fn mode(&self) -> Option<ChunkMode> {
None
}
/// Returns chunk data (can be text or binary)
fn source(self) -> IoResult<Cow<'a, [u8]>>;
}
impl<'lua> AsChunk<'lua> for str {
fn source(&self) -> IoResult<Cow<[u8]>> {
impl<'a> AsChunk<'a> for &'a str {
fn source(self) -> IoResult<Cow<'a, [u8]>> {
Ok(Cow::Borrowed(self.as_ref()))
}
}
impl<'lua> AsChunk<'lua> for StdString {
fn source(&self) -> IoResult<Cow<[u8]>> {
impl AsChunk<'static> for StdString {
fn source(self) -> IoResult<Cow<'static, [u8]>> {
Ok(Cow::Owned(self.into_bytes()))
}
}
impl<'a> AsChunk<'a> for &'a StdString {
fn source(self) -> IoResult<Cow<'a, [u8]>> {
Ok(Cow::Borrowed(self.as_bytes()))
}
}
impl<'a> AsChunk<'a> for &'a [u8] {
fn source(self) -> IoResult<Cow<'a, [u8]>> {
Ok(Cow::Borrowed(self))
}
}
impl AsChunk<'static> for Vec<u8> {
fn source(self) -> IoResult<Cow<'static, [u8]>> {
Ok(Cow::Owned(self))
}
}
impl<'a> AsChunk<'a> for &'a Vec<u8> {
fn source(self) -> IoResult<Cow<'a, [u8]>> {
Ok(Cow::Borrowed(self.as_ref()))
}
}
impl<'lua> AsChunk<'lua> for [u8] {
fn source(&self) -> IoResult<Cow<[u8]>> {
Ok(Cow::Borrowed(self))
}
}
impl<'lua> AsChunk<'lua> for Vec<u8> {
fn source(&self) -> IoResult<Cow<[u8]>> {
Ok(Cow::Borrowed(self))
}
}
impl<'lua> AsChunk<'lua> for Path {
fn source(&self) -> IoResult<Cow<[u8]>> {
std::fs::read(self).map(Cow::Owned)
}
impl AsChunk<'static> for &Path {
fn name(&self) -> Option<StdString> {
Some(format!("@{}", self.display()))
}
}
impl<'lua> AsChunk<'lua> for PathBuf {
fn source(&self) -> IoResult<Cow<[u8]>> {
fn source(self) -> IoResult<Cow<'static, [u8]>> {
std::fs::read(self).map(Cow::Owned)
}
}
impl AsChunk<'static> for PathBuf {
fn name(&self) -> Option<StdString> {
Some(format!("@{}", self.display()))
}
fn source(self) -> IoResult<Cow<'static, [u8]>> {
std::fs::read(self).map(Cow::Owned)
}
}
/// Returned from [`Lua::load`] and is used to finalize loading and executing Lua main chunks.
@@ -90,10 +103,10 @@ impl<'lua> AsChunk<'lua> for PathBuf {
#[must_use = "`Chunk`s do nothing unless one of `exec`, `eval`, `call`, or `into_function` are called on them"]
pub struct Chunk<'lua, 'a> {
pub(crate) lua: &'lua Lua,
pub(crate) source: IoResult<Cow<'a, [u8]>>,
pub(crate) name: Option<StdString>,
pub(crate) env: Result<Option<Value<'lua>>>,
pub(crate) name: StdString,
pub(crate) env: Result<Option<Table<'lua>>>,
pub(crate) mode: Option<ChunkMode>,
pub(crate) source: IoResult<Cow<'a, [u8]>>,
#[cfg(feature = "luau")]
pub(crate) compiler: Option<Compiler>,
}
@@ -228,7 +241,6 @@ impl Compiler {
coverageLevel: self.coverage_level as c_int,
vectorLib: vector_lib.map_or(ptr::null(), |s| s.as_ptr()),
vectorCtor: vector_ctor.map_or(ptr::null(), |s| s.as_ptr()),
vectorType: ptr::null(),
mutableGlobals: mutable_globals_ptr,
};
ffi::luau_compile(source.as_ref(), options)
@@ -238,16 +250,14 @@ impl Compiler {
impl<'lua, 'a> Chunk<'lua, 'a> {
/// Sets the name of this chunk, which results in more informative error traces.
pub fn set_name(mut self, name: impl AsRef<str>) -> Result<Self> {
self.name = Some(name.as_ref().to_string());
// Do extra validation
let _ = self.convert_name()?;
Ok(self)
pub fn set_name(mut self, name: impl Into<String>) -> Self {
self.name = name.into();
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
@@ -256,10 +266,12 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
/// All global variables (including the standard library!) are looked up in `_ENV`, so it may be
/// necessary to populate the environment in order for scripts using custom environments to be
/// useful.
pub fn set_environment<V: ToLua<'lua>>(mut self, env: V) -> Result<Self> {
// Prefer to propagate errors here and wrap to `Ok`
self.env = Ok(Some(env.to_lua(self.lua)?));
Ok(self)
pub fn set_environment<V: IntoLua<'lua>>(mut self, env: V) -> Self {
self.env = env
.into_lua(self.lua)
.and_then(|val| self.lua.unpack(val))
.context("bad environment value");
self
}
/// Sets whether the chunk is text or binary (autodetected by default).
@@ -300,10 +312,7 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
/// [`exec`]: #method.exec
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub fn exec_async<'fut>(self) -> LocalBoxFuture<'fut, Result<()>>
where
'lua: 'fut,
{
pub fn exec_async(self) -> LocalBoxFuture<'lua, Result<()>> {
self.call_async(())
}
@@ -352,7 +361,7 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
/// Load the chunk function and call it with the given arguments.
///
/// This is equivalent to `into_function` and calling the resulting function.
pub fn call<A: ToLuaMulti<'lua>, R: FromLuaMulti<'lua>>(self, args: A) -> Result<R> {
pub fn call<A: IntoLuaMulti<'lua>, R: FromLuaMulti<'lua>>(self, args: A) -> Result<R> {
self.into_function()?.call(args)
}
@@ -368,7 +377,7 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
pub fn call_async<'fut, A, R>(self, args: A) -> LocalBoxFuture<'fut, Result<R>>
where
'lua: 'fut,
A: ToLuaMulti<'lua>,
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua> + 'fut,
{
match self.into_function() {
@@ -388,9 +397,9 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
self.compile();
}
let name = self.convert_name()?;
let name = Self::convert_name(self.name)?;
self.lua
.load_chunk(self.source?.as_ref(), name.as_deref(), self.env?, self.mode)
.load_chunk(Some(&name), self.env?, self.mode, self.source?.as_ref())
}
/// Compiles the chunk and changes mode to binary.
@@ -409,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(source.as_ref(), None, None, None) {
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);
@@ -449,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);
}
}
}
@@ -471,9 +480,9 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
.map(|c| c.compile(&source))
.unwrap_or(source);
let name = self.convert_name()?;
let name = Self::convert_name(self.name.clone())?;
self.lua
.load_chunk(&source, name.as_deref(), self.env.clone()?, None)
.load_chunk(Some(&name), self.env.clone()?, None, &source)
}
fn detect_mode(&self) -> ChunkMode {
@@ -494,12 +503,8 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
}
}
fn convert_name(&self) -> Result<Option<CString>> {
self.name
.clone()
.map(CString::new)
.transpose()
.map_err(|err| Error::RuntimeError(format!("invalid name: {err}")))
fn convert_name(name: String) -> Result<CString> {
CString::new(name).map_err(|err| Error::RuntimeError(format!("invalid name: {err}")))
}
fn expression_source(source: &[u8]) -> Vec<u8> {
+151 -78
View File
@@ -1,5 +1,3 @@
#![allow(clippy::wrong_self_convention)]
use std::borrow::Cow;
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
use std::convert::TryInto;
@@ -11,18 +9,24 @@ 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;
use crate::thread::Thread;
use crate::types::{LightUserData, MaybeSend};
use crate::userdata::{AnyUserData, UserData};
use crate::value::{FromLua, Nil, ToLua, Value};
use crate::userdata::{AnyUserData, UserData, UserDataRef, UserDataRefMut};
use crate::value::{FromLua, IntoLua, Nil, Value};
impl<'lua> ToLua<'lua> for Value<'lua> {
#[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))]
use crate::{function::OwnedFunction, table::OwnedTable, userdata::OwnedAnyUserData};
#[cfg(feature = "async")]
use crate::function::WrappedAsyncFunction;
impl<'lua> IntoLua<'lua> for Value<'lua> {
#[inline]
fn to_lua(self, _: &'lua Lua) -> Result<Value<'lua>> {
fn into_lua(self, _: &'lua Lua) -> Result<Value<'lua>> {
Ok(self)
}
}
@@ -34,9 +38,9 @@ impl<'lua> FromLua<'lua> for Value<'lua> {
}
}
impl<'lua> ToLua<'lua> for String<'lua> {
impl<'lua> IntoLua<'lua> for String<'lua> {
#[inline]
fn to_lua(self, _: &'lua Lua) -> Result<Value<'lua>> {
fn into_lua(self, _: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::String(self))
}
}
@@ -54,9 +58,9 @@ impl<'lua> FromLua<'lua> for String<'lua> {
}
}
impl<'lua> ToLua<'lua> for Table<'lua> {
impl<'lua> IntoLua<'lua> for Table<'lua> {
#[inline]
fn to_lua(self, _: &'lua Lua) -> Result<Value<'lua>> {
fn into_lua(self, _: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::Table(self))
}
}
@@ -75,9 +79,27 @@ impl<'lua> FromLua<'lua> for Table<'lua> {
}
}
impl<'lua> ToLua<'lua> for Function<'lua> {
#[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 to_lua(self, _: &'lua Lua) -> Result<Value<'lua>> {
fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::Table(Table(lua.adopt_owned_ref(self.0))))
}
}
#[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> {
Table::from_lua(value, lua).map(|s| s.into_owned())
}
}
impl<'lua> IntoLua<'lua> for Function<'lua> {
#[inline]
fn into_lua(self, _: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::Function(self))
}
}
@@ -96,9 +118,42 @@ impl<'lua> FromLua<'lua> for Function<'lua> {
}
}
impl<'lua> ToLua<'lua> for Thread<'lua> {
#[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 to_lua(self, _: &'lua Lua) -> Result<Value<'lua>> {
fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::Function(Function(lua.adopt_owned_ref(self.0))))
}
}
#[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> {
Function::from_lua(value, lua).map(|s| s.into_owned())
}
}
impl<'lua> IntoLua<'lua> for WrappedFunction<'lua> {
#[inline]
fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
lua.create_callback(self.0).map(Value::Function)
}
}
#[cfg(feature = "async")]
impl<'lua> IntoLua<'lua> for WrappedAsyncFunction<'lua> {
#[inline]
fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
lua.create_async_callback(self.0).map(Value::Function)
}
}
impl<'lua> IntoLua<'lua> for Thread<'lua> {
#[inline]
fn into_lua(self, _: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::Thread(self))
}
}
@@ -117,9 +172,9 @@ impl<'lua> FromLua<'lua> for Thread<'lua> {
}
}
impl<'lua> ToLua<'lua> for AnyUserData<'lua> {
impl<'lua> IntoLua<'lua> for AnyUserData<'lua> {
#[inline]
fn to_lua(self, _: &'lua Lua) -> Result<Value<'lua>> {
fn into_lua(self, _: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::UserData(self))
}
}
@@ -138,30 +193,48 @@ impl<'lua> FromLua<'lua> for AnyUserData<'lua> {
}
}
impl<'lua, T: 'static + MaybeSend + UserData> ToLua<'lua> for T {
#[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 to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::UserData(AnyUserData(lua.adopt_owned_ref(self.0))))
}
}
#[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> {
AnyUserData::from_lua(value, lua).map(|s| s.into_owned())
}
}
impl<'lua, T: 'static + MaybeSend + UserData> IntoLua<'lua> for T {
#[inline]
fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::UserData(lua.create_userdata(self)?))
}
}
impl<'lua, T: 'static + UserData + Clone> FromLua<'lua> for T {
impl<'lua, T: 'static> FromLua<'lua> for UserDataRef<'lua, T> {
#[inline]
fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result<T> {
match value {
Value::UserData(ud) => Ok(ud.borrow::<T>()?.clone()),
_ => Err(Error::FromLuaConversionError {
from: value.type_name(),
to: "userdata",
message: None,
}),
}
fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result<Self> {
Self::from_value(value)
}
}
impl<'lua> ToLua<'lua> for Error {
impl<'lua, T: 'static> FromLua<'lua> for UserDataRefMut<'lua, T> {
#[inline]
fn to_lua(self, _: &'lua Lua) -> Result<Value<'lua>> {
fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result<Self> {
Self::from_value(value)
}
}
impl<'lua> IntoLua<'lua> for Error {
#[inline]
fn into_lua(self, _: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::Error(self))
}
}
@@ -180,9 +253,9 @@ impl<'lua> FromLua<'lua> for Error {
}
}
impl<'lua> ToLua<'lua> for bool {
impl<'lua> IntoLua<'lua> for bool {
#[inline]
fn to_lua(self, _: &'lua Lua) -> Result<Value<'lua>> {
fn into_lua(self, _: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::Boolean(self))
}
}
@@ -198,9 +271,9 @@ impl<'lua> FromLua<'lua> for bool {
}
}
impl<'lua> ToLua<'lua> for LightUserData {
impl<'lua> IntoLua<'lua> for LightUserData {
#[inline]
fn to_lua(self, _: &'lua Lua) -> Result<Value<'lua>> {
fn into_lua(self, _: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::LightUserData(self))
}
}
@@ -219,9 +292,9 @@ impl<'lua> FromLua<'lua> for LightUserData {
}
}
impl<'lua> ToLua<'lua> for StdString {
impl<'lua> IntoLua<'lua> for StdString {
#[inline]
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::String(lua.create_string(&self)?))
}
}
@@ -242,23 +315,23 @@ impl<'lua> FromLua<'lua> for StdString {
}
}
impl<'lua> ToLua<'lua> for &str {
impl<'lua> IntoLua<'lua> for &str {
#[inline]
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::String(lua.create_string(self)?))
}
}
impl<'lua> ToLua<'lua> for Cow<'_, str> {
impl<'lua> IntoLua<'lua> for Cow<'_, str> {
#[inline]
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::String(lua.create_string(self.as_bytes())?))
}
}
impl<'lua> ToLua<'lua> for Box<str> {
impl<'lua> IntoLua<'lua> for Box<str> {
#[inline]
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::String(lua.create_string(&*self)?))
}
}
@@ -280,9 +353,9 @@ impl<'lua> FromLua<'lua> for Box<str> {
}
}
impl<'lua> ToLua<'lua> for CString {
impl<'lua> IntoLua<'lua> for CString {
#[inline]
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::String(lua.create_string(self.as_bytes())?))
}
}
@@ -310,23 +383,23 @@ impl<'lua> FromLua<'lua> for CString {
}
}
impl<'lua> ToLua<'lua> for &CStr {
impl<'lua> IntoLua<'lua> for &CStr {
#[inline]
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::String(lua.create_string(self.to_bytes())?))
}
}
impl<'lua> ToLua<'lua> for Cow<'_, CStr> {
impl<'lua> IntoLua<'lua> for Cow<'_, CStr> {
#[inline]
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::String(lua.create_string(self.to_bytes())?))
}
}
impl<'lua> ToLua<'lua> for BString {
impl<'lua> IntoLua<'lua> for BString {
#[inline]
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::String(lua.create_string(&self)?))
}
}
@@ -348,18 +421,18 @@ impl<'lua> FromLua<'lua> for BString {
}
}
impl<'lua> ToLua<'lua> for &BStr {
impl<'lua> IntoLua<'lua> for &BStr {
#[inline]
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::String(lua.create_string(self)?))
}
}
macro_rules! lua_convert_int {
($x:ty) => {
impl<'lua> ToLua<'lua> for $x {
impl<'lua> IntoLua<'lua> for $x {
#[inline]
fn to_lua(self, _: &'lua Lua) -> Result<Value<'lua>> {
fn into_lua(self, _: &'lua Lua) -> Result<Value<'lua>> {
cast(self)
.map(Value::Integer)
.or_else(|| cast(self).map(Value::Number))
@@ -420,9 +493,9 @@ lua_convert_int!(usize);
macro_rules! lua_convert_float {
($x:ty) => {
impl<'lua> ToLua<'lua> for $x {
impl<'lua> IntoLua<'lua> for $x {
#[inline]
fn to_lua(self, _: &'lua Lua) -> Result<Value<'lua>> {
fn into_lua(self, _: &'lua Lua) -> Result<Value<'lua>> {
cast(self)
.ok_or_else(|| Error::ToLuaConversionError {
from: stringify!($x),
@@ -458,24 +531,24 @@ macro_rules! lua_convert_float {
lua_convert_float!(f32);
lua_convert_float!(f64);
impl<'lua, T> ToLua<'lua> for &[T]
impl<'lua, T> IntoLua<'lua> for &[T]
where
T: Clone + ToLua<'lua>,
T: Clone + IntoLua<'lua>,
{
#[inline]
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::Table(
lua.create_sequence_from(self.iter().cloned())?,
))
}
}
impl<'lua, T, const N: usize> ToLua<'lua> for [T; N]
impl<'lua, T, const N: usize> IntoLua<'lua> for [T; N]
where
T: ToLua<'lua>,
T: IntoLua<'lua>,
{
#[inline]
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::Table(lua.create_sequence_from(self)?))
}
}
@@ -516,9 +589,9 @@ where
}
}
impl<'lua, T: ToLua<'lua>> ToLua<'lua> for Box<[T]> {
impl<'lua, T: IntoLua<'lua>> IntoLua<'lua> for Box<[T]> {
#[inline]
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::Table(lua.create_sequence_from(self.into_vec())?))
}
}
@@ -530,9 +603,9 @@ impl<'lua, T: FromLua<'lua>> FromLua<'lua> for Box<[T]> {
}
}
impl<'lua, T: ToLua<'lua>> ToLua<'lua> for Vec<T> {
impl<'lua, T: IntoLua<'lua>> IntoLua<'lua> for Vec<T> {
#[inline]
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::Table(lua.create_sequence_from(self)?))
}
}
@@ -557,11 +630,11 @@ impl<'lua, T: FromLua<'lua>> FromLua<'lua> for Vec<T> {
}
}
impl<'lua, K: Eq + Hash + ToLua<'lua>, V: ToLua<'lua>, S: BuildHasher> ToLua<'lua>
impl<'lua, K: Eq + Hash + IntoLua<'lua>, V: IntoLua<'lua>, S: BuildHasher> IntoLua<'lua>
for HashMap<K, V, S>
{
#[inline]
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::Table(lua.create_table_from(self)?))
}
}
@@ -583,9 +656,9 @@ impl<'lua, K: Eq + Hash + FromLua<'lua>, V: FromLua<'lua>, S: BuildHasher + Defa
}
}
impl<'lua, K: Ord + ToLua<'lua>, V: ToLua<'lua>> ToLua<'lua> for BTreeMap<K, V> {
impl<'lua, K: Ord + IntoLua<'lua>, V: IntoLua<'lua>> IntoLua<'lua> for BTreeMap<K, V> {
#[inline]
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::Table(lua.create_table_from(self)?))
}
}
@@ -605,9 +678,9 @@ impl<'lua, K: Ord + FromLua<'lua>, V: FromLua<'lua>> FromLua<'lua> for BTreeMap<
}
}
impl<'lua, T: Eq + Hash + ToLua<'lua>, S: BuildHasher> ToLua<'lua> for HashSet<T, S> {
impl<'lua, T: Eq + Hash + IntoLua<'lua>, S: BuildHasher> IntoLua<'lua> for HashSet<T, S> {
#[inline]
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::Table(lua.create_table_from(
self.into_iter().map(|val| (val, true)),
)?))
@@ -632,9 +705,9 @@ impl<'lua, T: Eq + Hash + FromLua<'lua>, S: BuildHasher + Default> FromLua<'lua>
}
}
impl<'lua, T: Ord + ToLua<'lua>> ToLua<'lua> for BTreeSet<T> {
impl<'lua, T: Ord + IntoLua<'lua>> IntoLua<'lua> for BTreeSet<T> {
#[inline]
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::Table(lua.create_table_from(
self.into_iter().map(|val| (val, true)),
)?))
@@ -659,11 +732,11 @@ impl<'lua, T: Ord + FromLua<'lua>> FromLua<'lua> for BTreeSet<T> {
}
}
impl<'lua, T: ToLua<'lua>> ToLua<'lua> for Option<T> {
impl<'lua, T: IntoLua<'lua>> IntoLua<'lua> for Option<T> {
#[inline]
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
match self {
Some(val) => val.to_lua(lua),
Some(val) => val.into_lua(lua),
None => Ok(Nil),
}
}
+152 -28
View File
@@ -1,5 +1,3 @@
#![allow(clippy::wrong_self_convention)]
use std::error::Error as StdError;
use std::fmt;
use std::io::Error as IoError;
@@ -9,6 +7,8 @@ use std::str::Utf8Error;
use std::string::String as StdString;
use std::sync::Arc;
use crate::private::Sealed;
/// Error type returned by `mlua` methods.
#[derive(Debug, Clone)]
#[non_exhaustive]
@@ -71,6 +71,20 @@ pub enum Error {
StackError,
/// Too many arguments to `Function::bind`
BindError,
/// Bad argument received from Lua (usually when calling a function).
///
/// This error can help to identify the argument that caused the error
/// (which is stored in the corresponding field).
BadArgument {
/// Function that was called.
to: Option<StdString>,
/// Argument position (usually starts from 1).
pos: usize,
/// Argument name.
name: Option<StdString>,
/// Underlying error returned when converting argument to a Lua value.
cause: Arc<Error>,
},
/// A Rust value could not be converted to a Lua value.
ToLuaConversionError {
/// Name of the Rust type that could not be converted.
@@ -142,8 +156,11 @@ pub enum Error {
///
/// [`MetaMethod`]: crate::MetaMethod
MetaMethodTypeError {
/// Name of the metamethod.
method: StdString,
/// Passed value type.
type_name: &'static str,
/// A string containing more detailed error information.
message: Option<StdString>,
},
/// A [`RegistryKey`] produced from a different Lua state was used.
@@ -178,6 +195,13 @@ pub enum Error {
/// error. The Rust code that originally invoked the Lua code then receives a `CallbackError`,
/// from which the original error (and a stack traceback) can be recovered.
ExternalError(Arc<dyn StdError + Send + Sync>),
/// An error with additional context.
WithContext {
/// A string containing additional context.
context: StdString,
/// Underlying error.
cause: Arc<Error>,
},
}
/// A specialized `Result` type used by `mlua`'s API.
@@ -187,17 +211,17 @@ pub type Result<T> = StdResult<T, Error>;
impl fmt::Display for Error {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match *self {
Error::SyntaxError { ref message, .. } => write!(fmt, "syntax error: {}", message),
Error::RuntimeError(ref msg) => write!(fmt, "runtime error: {}", msg),
Error::SyntaxError { ref message, .. } => write!(fmt, "syntax error: {message}"),
Error::RuntimeError(ref msg) => write!(fmt, "runtime error: {msg}"),
Error::MemoryError(ref msg) => {
write!(fmt, "memory error: {}", msg)
write!(fmt, "memory error: {msg}")
}
#[cfg(any(feature = "lua53", feature = "lua52"))]
Error::GarbageCollectorError(ref msg) => {
write!(fmt, "garbage collector error: {}", msg)
write!(fmt, "garbage collector error: {msg}")
}
Error::SafetyError(ref msg) => {
write!(fmt, "safety error: {}", msg)
write!(fmt, "safety error: {msg}")
},
Error::MemoryLimitNotAvailable => {
write!(fmt, "setting memory limit is not available")
@@ -218,18 +242,29 @@ impl fmt::Display for Error {
fmt,
"too many arguments to Function::bind"
),
Error::BadArgument { ref to, pos, ref name, ref cause } => {
if let Some(name) = name {
write!(fmt, "bad argument `{name}`")?;
} else {
write!(fmt, "bad argument #{pos}")?;
}
if let Some(to) = to {
write!(fmt, " to `{to}`")?;
}
write!(fmt, ": {cause}")
},
Error::ToLuaConversionError { from, to, ref message } => {
write!(fmt, "error converting {} to Lua {}", from, to)?;
write!(fmt, "error converting {from} to Lua {to}")?;
match *message {
None => Ok(()),
Some(ref message) => write!(fmt, " ({})", message),
Some(ref message) => write!(fmt, " ({message})"),
}
}
Error::FromLuaConversionError { from, to, ref message } => {
write!(fmt, "error converting Lua {} to {}", from, to)?;
write!(fmt, "error converting Lua {from} to {to}")?;
match *message {
None => Ok(()),
Some(ref message) => write!(fmt, " ({})", message),
Some(ref message) => write!(fmt, " ({message})"),
}
}
Error::CoroutineInactive => write!(fmt, "cannot resume inactive coroutine"),
@@ -237,25 +272,25 @@ impl fmt::Display for Error {
Error::UserDataDestructed => write!(fmt, "userdata has been destructed"),
Error::UserDataBorrowError => write!(fmt, "userdata already mutably borrowed"),
Error::UserDataBorrowMutError => write!(fmt, "userdata already borrowed"),
Error::MetaMethodRestricted(ref method) => write!(fmt, "metamethod {} is restricted", method),
Error::MetaMethodRestricted(ref method) => write!(fmt, "metamethod {method} is restricted"),
Error::MetaMethodTypeError { ref method, type_name, ref message } => {
write!(fmt, "metamethod {} has unsupported type {}", method, type_name)?;
write!(fmt, "metamethod {method} has unsupported type {type_name}")?;
match *message {
None => Ok(()),
Some(ref message) => write!(fmt, " ({})", message),
Some(ref message) => write!(fmt, " ({message})"),
}
}
Error::MismatchedRegistryKey => {
write!(fmt, "RegistryKey used from different Lua state")
}
Error::CallbackError { ref cause, ref traceback } => {
writeln!(fmt, "callback error")?;
// Trace errors down to the root
let (mut cause, mut full_traceback) = (cause, None);
while let Error::CallbackError { cause: ref cause2, traceback: ref traceback2 } = **cause {
cause = cause2;
full_traceback = Some(traceback2);
}
writeln!(fmt, "{cause}")?;
if let Some(full_traceback) = full_traceback {
let traceback = traceback.trim_start_matches("stack traceback:");
let traceback = traceback.trim_start().trim_end();
@@ -269,20 +304,24 @@ impl fmt::Display for Error {
} else {
writeln!(fmt, "{}", traceback.trim_end())?;
}
write!(fmt, "caused by: {}", cause)
Ok(())
}
Error::PreviouslyResumedPanic => {
write!(fmt, "previously resumed panic returned again")
}
#[cfg(feature = "serialize")]
Error::SerializeError(ref err) => {
write!(fmt, "serialize error: {}", err)
write!(fmt, "serialize error: {err}")
},
#[cfg(feature = "serialize")]
Error::DeserializeError(ref err) => {
write!(fmt, "deserialize error: {}", err)
write!(fmt, "deserialize error: {err}")
},
Error::ExternalError(ref err) => write!(fmt, "{}", err),
Error::ExternalError(ref err) => write!(fmt, "{err}"),
Error::WithContext { ref context, ref cause } => {
writeln!(fmt, "{context}")?;
write!(fmt, "{cause}")
}
}
}
}
@@ -296,53 +335,138 @@ 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,
}
}
}
impl Error {
pub fn external<T: Into<Box<dyn StdError + Send + Sync>>>(err: T) -> Error {
/// Wraps an external error object.
pub fn external<T: Into<Box<dyn StdError + Send + Sync>>>(err: T) -> Self {
Error::ExternalError(err.into().into())
}
/// Attempts to downcast the external error object to a concrete type by reference.
pub fn downcast_ref<T>(&self) -> Option<&T>
where
T: StdError + 'static,
{
match self {
Error::ExternalError(err) => err.downcast_ref(),
Error::WithContext { cause, .. } => match cause.as_ref() {
Error::ExternalError(err) => err.downcast_ref(),
_ => None,
},
_ => None,
}
}
pub(crate) fn bad_self_argument(to: &str, cause: Error) -> Self {
Error::BadArgument {
to: Some(to.to_string()),
pos: 1,
name: Some("self".to_string()),
cause: Arc::new(cause),
}
}
pub(crate) fn from_lua_conversion<'a>(
from: &'static str,
to: &'static str,
message: impl Into<Option<&'a str>>,
) -> Self {
Error::FromLuaConversionError {
from,
to,
message: message.into().map(|s| s.into()),
}
}
}
pub trait ExternalError {
fn to_lua_err(self) -> Error;
fn into_lua_err(self) -> Error;
}
impl<E: Into<Box<dyn StdError + Send + Sync>>> ExternalError for E {
fn to_lua_err(self) -> Error {
fn into_lua_err(self) -> Error {
Error::external(self)
}
}
pub trait ExternalResult<T> {
fn to_lua_err(self) -> Result<T>;
fn into_lua_err(self) -> Result<T>;
}
impl<T, E> ExternalResult<T> for StdResult<T, E>
where
E: ExternalError,
{
fn to_lua_err(self) -> Result<T> {
self.map_err(|e| e.to_lua_err())
fn into_lua_err(self) -> Result<T> {
self.map_err(|e| e.into_lua_err())
}
}
impl std::convert::From<AddrParseError> for Error {
/// Provides the `context` method for [`Error`] and `Result<T, Error>`.
pub trait ErrorContext: Sealed {
/// Wraps the error value with additional context.
fn context<C: fmt::Display>(self, context: C) -> Self;
/// Wrap the error value with additional context that is evaluated lazily
/// only once an error does occur.
fn with_context<C: fmt::Display>(self, f: impl FnOnce(&Error) -> C) -> Self;
}
impl ErrorContext for Error {
fn context<C: fmt::Display>(self, context: C) -> 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 {
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| err.context(context))
}
fn with_context<C: fmt::Display>(self, f: impl FnOnce(&Error) -> C) -> Self {
self.map_err(|err| err.with_context(f))
}
}
impl From<AddrParseError> for Error {
fn from(err: AddrParseError) -> Self {
Error::external(err)
}
}
impl std::convert::From<IoError> for Error {
impl From<IoError> for Error {
fn from(err: IoError) -> Self {
Error::external(err)
}
}
impl std::convert::From<Utf8Error> for Error {
impl From<Utf8Error> for Error {
fn from(err: Utf8Error) -> Self {
Error::external(err)
}
+300 -42
View File
@@ -1,32 +1,74 @@
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::types::LuaRef;
use crate::lua::Lua;
use crate::memory::MemoryState;
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, ToLuaMulti};
use crate::value::{FromLuaMulti, IntoLua, IntoLuaMulti};
#[cfg(feature = "async")]
use {futures_core::future::LocalBoxFuture, futures_util::future};
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)]
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,
}
@@ -82,33 +124,34 @@ impl<'lua> Function<'lua> {
/// # Ok(())
/// # }
/// ```
pub fn call<A: ToLuaMulti<'lua>, R: FromLuaMulti<'lua>>(&self, args: A) -> Result<R> {
pub fn call<A: IntoLuaMulti<'lua>, R: FromLuaMulti<'lua>>(&self, args: A) -> Result<R> {
let lua = self.0.lua;
let state = lua.state();
let mut args = args.to_lua_multi(lua)?;
let mut args = args.into_lua_multi(lua)?;
let nargs = args.len() as c_int;
let results = unsafe {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, nargs + 3)?;
let _sg = StackGuard::new(state);
check_stack(state, nargs + 3)?;
ffi::lua_pushcfunction(lua.state, error_traceback);
let stack_start = ffi::lua_gettop(lua.state);
MemoryState::relax_limit_with(state, || ffi::lua_pushcfunction(state, error_traceback));
let stack_start = ffi::lua_gettop(state);
lua.push_ref(&self.0);
for arg in args.drain_all() {
lua.push_value(arg)?;
}
let ret = ffi::lua_pcall(lua.state, nargs, ffi::LUA_MULTRET, stack_start);
let ret = ffi::lua_pcall(state, nargs, ffi::LUA_MULTRET, stack_start);
if ret != ffi::LUA_OK {
return Err(pop_error(lua.state, ret));
return Err(pop_error(state, ret));
}
let nresults = ffi::lua_gettop(lua.state) - stack_start;
let nresults = ffi::lua_gettop(state) - stack_start;
let mut results = args; // Reuse MultiValue container
assert_stack(lua.state, 2);
assert_stack(state, 2);
for _ in 0..nresults {
results.push_front(lua.pop_value());
}
ffi::lua_pop(lua.state, 1);
ffi::lua_pop(state, 1);
results
};
R::from_lua_multi(results, lua)
@@ -148,11 +191,11 @@ impl<'lua> Function<'lua> {
pub fn call_async<'fut, A, R>(&self, args: A) -> LocalBoxFuture<'fut, Result<R>>
where
'lua: 'fut,
A: ToLuaMulti<'lua>,
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua> + 'fut,
{
let lua = self.0.lua;
match lua.create_recycled_thread(self.clone()) {
match lua.create_recycled_thread(self) {
Ok(t) => {
let mut t = t.into_async(args);
t.set_recyclable(true);
@@ -189,7 +232,7 @@ impl<'lua> Function<'lua> {
/// # Ok(())
/// # }
/// ```
pub fn bind<A: ToLuaMulti<'lua>>(&self, args: A) -> Result<Function<'lua>> {
pub fn bind<A: IntoLuaMulti<'lua>>(&self, args: A) -> Result<Function<'lua>> {
unsafe extern "C" fn args_wrapper_impl(state: *mut ffi::lua_State) -> c_int {
let nargs = ffi::lua_gettop(state);
let nbinds = ffi::lua_tointeger(state, ffi::lua_upvalueindex(1)) as c_int;
@@ -206,8 +249,9 @@ impl<'lua> Function<'lua> {
}
let lua = self.0.lua;
let state = lua.state();
let args = args.to_lua_multi(lua)?;
let args = args.into_lua_multi(lua)?;
let nargs = args.len() as c_int;
if nargs == 0 {
@@ -219,14 +263,14 @@ impl<'lua> Function<'lua> {
}
let args_wrapper = unsafe {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, nargs + 3)?;
let _sg = StackGuard::new(state);
check_stack(state, nargs + 3)?;
ffi::lua_pushinteger(lua.state, nargs as ffi::lua_Integer);
ffi::lua_pushinteger(state, nargs as ffi::lua_Integer);
for arg in args {
lua.push_value(arg)?;
}
protect_lua!(lua.state, nargs + 1, 1, fn(state) {
protect_lua!(state, nargs + 1, 1, fn(state) {
ffi::lua_pushcclosure(state, args_wrapper_impl, ffi::lua_gettop(state));
})?;
@@ -242,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.
@@ -253,25 +400,27 @@ impl<'lua> Function<'lua> {
/// [`lua_getinfo`]: https://www.lua.org/manual/5.4/manual.html#lua_getinfo
pub fn info(&self) -> FunctionInfo {
let lua = self.0.lua;
let state = lua.state();
unsafe {
let _sg = StackGuard::new(lua.state);
assert_stack(lua.state, 1);
let _sg = StackGuard::new(state);
assert_stack(state, 1);
let mut ar: ffi::lua_Debug = mem::zeroed();
lua.push_ref(&self.0);
#[cfg(not(feature = "luau"))]
let res = ffi::lua_getinfo(lua.state, cstr!(">Sn"), &mut ar);
let res = ffi::lua_getinfo(state, cstr!(">Sn"), &mut ar);
#[cfg(feature = "luau")]
let res = ffi::lua_getinfo(lua.state, -1, cstr!("sn"), &mut ar);
let res = ffi::lua_getinfo(state, -1, cstr!("sn"), &mut ar);
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()),
@@ -280,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,
}
}
}
@@ -308,15 +459,16 @@ impl<'lua> Function<'lua> {
}
let lua = self.0.lua;
let state = lua.state();
let mut data: Vec<u8> = Vec::new();
unsafe {
let _sg = StackGuard::new(lua.state);
assert_stack(lua.state, 1);
let _sg = StackGuard::new(state);
assert_stack(state, 1);
lua.push_ref(&self.0);
let data_ptr = &mut data as *mut Vec<u8> as *mut c_void;
ffi::lua_dump(lua.state, writer, data_ptr, strip as i32);
ffi::lua_pop(lua.state, 1);
ffi::lua_dump(state, writer, data_ptr, strip as i32);
ffi::lua_pop(state, 1);
}
data
@@ -364,15 +516,24 @@ impl<'lua> Function<'lua> {
}
let lua = self.0.lua;
let state = lua.state();
unsafe {
let _sg = StackGuard::new(lua.state);
assert_stack(lua.state, 1);
let _sg = StackGuard::new(state);
assert_stack(state, 1);
lua.push_ref(&self.0);
let func_ptr = &mut func as *mut F as *mut c_void;
ffi::lua_getcoverage(lua.state, -1, func_ptr, callback::<F>);
ffi::lua_getcoverage(state, -1, func_ptr, callback::<F>);
}
}
/// 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) -> OwnedFunction {
OwnedFunction(self.0.into_owned())
}
}
impl<'lua> PartialEq for Function<'lua> {
@@ -380,3 +541,100 @@ impl<'lua> PartialEq for Function<'lua> {
self.0 == other.0
}
}
// 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(feature = "async")]
pub(crate) struct WrappedAsyncFunction<'lua>(pub(crate) AsyncCallback<'lua, 'static>);
impl<'lua> Function<'lua> {
/// Wraps a Rust function or closure, returning an opaque type that implements [`IntoLua`] trait.
#[inline]
pub fn wrap<A, R, F>(func: F) -> impl IntoLua<'lua>
where
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)
}))
}
/// Wraps a Rust mutable closure, returning an opaque type that implements [`IntoLua`] trait.
#[inline]
pub fn wrap_mut<A, R, F>(func: F) -> impl IntoLua<'lua>
where
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| {
let mut func = func
.try_borrow_mut()
.map_err(|_| Error::RecursiveMutCallback)?;
func(lua, A::from_lua_multi(args, lua)?)?.into_lua_multi(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<A, R, F, FR>(func: F) -> impl IntoLua<'lua>
where
A: FromLuaMulti<'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) {
Ok(args) => args,
Err(e) => return Box::pin(future::err(e)),
};
Box::pin(func(lua, args).and_then(move |ret| future::ready(ret.into_lua_multi(lua))))
}))
}
}
#[cfg(test)]
mod assertions {
use super::*;
static_assertions::assert_not_impl_any!(Function: Send);
#[cfg(all(feature = "unstable", not(feature = "send")))]
static_assertions::assert_not_impl_any!(OwnedFunction: Send);
}
+52 -39
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;
@@ -67,12 +68,12 @@ impl<'lua> Debug<'lua> {
unsafe {
#[cfg(not(feature = "luau"))]
mlua_assert!(
ffi::lua_getinfo(self.lua.state, cstr!("n"), self.ar.get()) != 0,
ffi::lua_getinfo(self.lua.state(), cstr!("n"), self.ar.get()) != 0,
"lua_getinfo failed with `n`"
);
#[cfg(feature = "luau")]
mlua_assert!(
ffi::lua_getinfo(self.lua.state, self.level, cstr!("n"), self.ar.get()) != 0,
ffi::lua_getinfo(self.lua.state(), self.level, cstr!("n"), self.ar.get()) != 0,
"lua_getinfo failed with `n`"
);
@@ -91,12 +92,12 @@ impl<'lua> Debug<'lua> {
unsafe {
#[cfg(not(feature = "luau"))]
mlua_assert!(
ffi::lua_getinfo(self.lua.state, cstr!("S"), self.ar.get()) != 0,
ffi::lua_getinfo(self.lua.state(), cstr!("S"), self.ar.get()) != 0,
"lua_getinfo failed with `S`"
);
#[cfg(feature = "luau")]
mlua_assert!(
ffi::lua_getinfo(self.lua.state, self.level, cstr!("s"), self.ar.get()) != 0,
ffi::lua_getinfo(self.lua.state(), self.level, cstr!("s"), self.ar.get()) != 0,
"lua_getinfo failed with `s`"
);
@@ -119,12 +120,12 @@ impl<'lua> Debug<'lua> {
unsafe {
#[cfg(not(feature = "luau"))]
mlua_assert!(
ffi::lua_getinfo(self.lua.state, cstr!("l"), self.ar.get()) != 0,
ffi::lua_getinfo(self.lua.state(), cstr!("l"), self.ar.get()) != 0,
"lua_getinfo failed with `l`"
);
#[cfg(feature = "luau")]
mlua_assert!(
ffi::lua_getinfo(self.lua.state, self.level, cstr!("l"), self.ar.get()) != 0,
ffi::lua_getinfo(self.lua.state(), self.level, cstr!("l"), self.ar.get()) != 0,
"lua_getinfo failed with `l`"
);
@@ -139,7 +140,7 @@ impl<'lua> Debug<'lua> {
pub fn is_tail_call(&self) -> bool {
unsafe {
mlua_assert!(
ffi::lua_getinfo(self.lua.state, cstr!("t"), self.ar.get()) != 0,
ffi::lua_getinfo(self.lua.state(), cstr!("t"), self.ar.get()) != 0,
"lua_getinfo failed with `t`"
);
(*self.ar.get()).currentline != 0
@@ -151,20 +152,20 @@ impl<'lua> Debug<'lua> {
unsafe {
#[cfg(not(feature = "luau"))]
mlua_assert!(
ffi::lua_getinfo(self.lua.state, cstr!("u"), self.ar.get()) != 0,
ffi::lua_getinfo(self.lua.state(), cstr!("u"), self.ar.get()) != 0,
"lua_getinfo failed with `u`"
);
#[cfg(feature = "luau")]
mlua_assert!(
ffi::lua_getinfo(self.lua.state, self.level, cstr!("a"), self.ar.get()) != 0,
ffi::lua_getinfo(self.lua.state(), self.level, cstr!("a"), self.ar.get()) != 0,
"lua_getinfo failed with `a`"
);
#[cfg(not(feature = "luau"))]
let stack = DebugStack {
num_ups: (*self.ar.get()).nups as i32,
num_ups: (*self.ar.get()).nups as _,
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
num_params: (*self.ar.get()).nparams as i32,
num_params: (*self.ar.get()).nparams as _,
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
is_vararg: (*self.ar.get()).isvararg != 0,
};
@@ -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
}
}
+46 -13
View File
@@ -10,10 +10,10 @@
//!
//! # Converting data
//!
//! The [`ToLua`] and [`FromLua`] traits allow conversion from Rust types to Lua values and vice
//! The [`IntoLua`] and [`FromLua`] traits allow conversion from Rust types to Lua values and vice
//! versa. They are implemented for many data structures found in Rust's standard library.
//!
//! For more general conversions, the [`ToLuaMulti`] and [`FromLuaMulti`] traits allow converting
//! For more general conversions, the [`IntoLuaMulti`] and [`FromLuaMulti`] traits allow converting
//! between Rust types and *any number* of Lua values.
//!
//! Most code in `mlua` is generic over implementors of those traits, so in most places the normal
@@ -54,9 +54,9 @@
//! [executing]: crate::Chunk::exec
//! [evaluating]: crate::Chunk::eval
//! [globals]: crate::Lua::globals
//! [`ToLua`]: crate::ToLua
//! [`IntoLua`]: crate::IntoLua
//! [`FromLua`]: crate::FromLua
//! [`ToLuaMulti`]: crate::ToLuaMulti
//! [`IntoLuaMulti`]: crate::IntoLuaMulti
//! [`FromLuaMulti`]: crate::FromLuaMulti
//! [`Function`]: crate::Function
//! [`UserData`]: crate::UserData
@@ -82,12 +82,12 @@ mod macros;
mod chunk;
mod conversion;
mod error;
mod ffi;
mod function;
mod hook;
mod lua;
#[cfg(feature = "luau")]
mod luau;
mod memory;
mod multi;
mod scope;
mod stdlib;
@@ -96,16 +96,17 @@ mod table;
mod thread;
mod types;
mod userdata;
mod userdata_ext;
mod userdata_impl;
mod util;
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, ExternalError, ExternalResult, Result};
pub use crate::error::{Error, ErrorContext, ExternalError, ExternalResult, Result};
pub use crate::function::{Function, FunctionInfo};
pub use crate::hook::{Debug, DebugEvent, DebugNames, DebugSource, DebugStack};
pub use crate::lua::{GCMode, Lua, LuaOptions};
@@ -115,11 +116,14 @@ 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,
};
pub use crate::value::{FromLua, FromLuaMulti, MultiValue, Nil, ToLua, ToLuaMulti, Value};
pub use crate::userdata_ext::AnyUserDataExt;
pub use crate::userdata_impl::UserDataRegistrar;
pub use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, MultiValue, Nil, Value};
#[cfg(not(feature = "luau"))]
pub use crate::hook::HookTriggers;
@@ -141,17 +145,23 @@ 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(feature = "unstable")]
pub use crate::{
function::OwnedFunction, string::OwnedString, table::OwnedTable, userdata::OwnedAnyUserData,
};
/// Create a type that implements [`AsChunk`] and can capture Rust variables.
///
/// This macro allows to write Lua code directly in Rust code.
///
/// Rust variables can be referenced from Lua using `$` prefix, as shown in the example below.
/// User's Rust types needs to implement [`UserData`] or [`ToLua`] traits.
/// User's Rust types needs to implement [`UserData`] or [`IntoLua`] traits.
///
/// Captured variables are **moved** into the chunk.
///
@@ -197,8 +207,8 @@ extern crate mlua_derive;
///
/// [`AsChunk`]: crate::AsChunk
/// [`UserData`]: crate::UserData
/// [`ToLua`]: crate::ToLua
#[cfg(any(feature = "macros"))]
/// [`IntoLua`]: crate::IntoLua
#[cfg(feature = "macros")]
#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
pub use mlua_derive::chunk;
@@ -219,6 +229,29 @@ pub use mlua_derive::chunk;
///
/// Internally in the code above the compiler defines C function `luaopen_my_module`.
///
/// You can also pass options to the attribute:
///
/// name - name of the module, defaults to the name of the function
///
/// ```ignore
/// #[mlua::lua_module(name = "alt_module")]
/// fn my_module(lua: &Lua) -> Result<Table> {
/// ...
/// }
/// ```
///
#[cfg(any(feature = "module", docsrs))]
#[cfg_attr(docsrs, doc(cfg(feature = "module")))]
pub use mlua_derive::lua_module;
pub(crate) mod private {
use super::*;
pub trait Sealed {}
impl Sealed for Error {}
impl<T> Sealed for std::result::Result<T, Error> {}
impl Sealed for Lua {}
impl Sealed for Table<'_> {}
impl Sealed for AnyUserData<'_> {}
}
+951 -842
View File
File diff suppressed because it is too large Load Diff
+9 -8
View File
@@ -1,9 +1,9 @@
use std::ffi::CStr;
use std::os::raw::{c_float, c_int};
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};
@@ -24,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}"))?;
}
@@ -69,14 +69,15 @@ unsafe extern "C" fn lua_collectgarbage(state: *mut ffi::lua_State) -> c_int {
}
}
fn lua_require(lua: &Lua, name: Option<std::string::String>) -> Result<Value> {
fn lua_require(lua: &Lua, name: Option<StdString>) -> Result<Value> {
let name = name.ok_or_else(|| Error::RuntimeError("invalid module name".into()))?;
// Find module in the cache
let state = lua.state();
let loaded = unsafe {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, 2)?;
protect_lua!(lua.state, 0, 1, fn(state) {
let _sg = StackGuard::new(state);
check_stack(state, 2)?;
protect_lua!(state, 0, 1, fn(state) {
ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, cstr!("_LOADED"));
})?;
Table(lua.pop_ref())
@@ -100,11 +101,11 @@ fn lua_require(lua: &Lua, name: Option<std::string::String>) -> Result<Value> {
break;
}
}
let source = source.ok_or_else(|| Error::RuntimeError(format!("cannot find '{}'", name)))?;
let source = source.ok_or_else(|| Error::RuntimeError(format!("cannot find '{name}'")))?;
let value = lua
.load(&source)
.set_name(&format!("={}", source_name))?
.set_name(&format!("={source_name}"))
.set_mode(ChunkMode::Text)
.call::<_, Value>(())?;
+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)
+152
View File
@@ -0,0 +1,152 @@
use std::alloc::{self, Layout};
use std::os::raw::c_void;
use std::ptr;
#[cfg(feature = "luau")]
use crate::lua::ExtraData;
pub(crate) static ALLOCATOR: ffi::lua_Alloc = allocator;
#[derive(Default)]
pub(crate) struct MemoryState {
used_memory: isize,
memory_limit: isize,
// Can be set to temporary ignore the memory limit.
// This is used when calling `lua_pushcfunction` for lua5.1/jit/luau.
ignore_limit: bool,
// Indicates that the memory limit was reached on the last allocation.
#[cfg(feature = "luau")]
limit_reached: bool,
}
impl MemoryState {
#[inline]
pub(crate) fn used_memory(&self) -> usize {
self.used_memory as usize
}
#[inline]
pub(crate) fn memory_limit(&self) -> usize {
self.memory_limit as usize
}
#[inline]
pub(crate) fn set_memory_limit(&mut self, limit: usize) -> usize {
let prev_limit = self.memory_limit;
self.memory_limit = limit as isize;
prev_limit as usize
}
// This function is used primarily for calling `lua_pushcfunction` in lua5.1/jit
// to bypass the memory limit (if set).
#[cfg(any(feature = "lua51", feature = "luajit"))]
#[inline]
pub(crate) unsafe fn relax_limit_with(state: *mut ffi::lua_State, f: impl FnOnce()) {
let mut mem_state: *mut c_void = ptr::null_mut();
if ffi::lua_getallocf(state, &mut mem_state) == ALLOCATOR {
(*(mem_state as *mut MemoryState)).ignore_limit = true;
f();
(*(mem_state as *mut MemoryState)).ignore_limit = false;
} else {
f();
}
}
// Same as the above but for Luau
// It does not have `lua_getallocf` function, so instead we use `lua_callbacks`
#[cfg(feature = "luau")]
#[inline]
pub(crate) unsafe fn relax_limit_with(state: *mut ffi::lua_State, f: impl FnOnce()) {
let extra = (*ffi::lua_callbacks(state)).userdata as *mut ExtraData;
if extra.is_null() {
return f();
}
let mem_state = (*extra).mem_state();
(*mem_state.as_ptr()).ignore_limit = true;
f();
(*mem_state.as_ptr()).ignore_limit = false;
}
// Does nothing apart from calling `f()`, we don't need to bypass any limits
#[cfg(any(feature = "lua52", feature = "lua53", feature = "lua54"))]
#[inline]
pub(crate) unsafe fn relax_limit_with(_state: *mut ffi::lua_State, f: impl FnOnce()) {
f();
}
// Returns `true` if the memory limit was reached on the last memory operation
#[cfg(feature = "luau")]
pub(crate) unsafe fn limit_reached(state: *mut ffi::lua_State) -> bool {
let extra = (*ffi::lua_callbacks(state)).userdata as *mut ExtraData;
if extra.is_null() {
return false;
}
(*(*extra).mem_state().as_ptr()).limit_reached
}
}
unsafe extern "C" fn allocator(
extra: *mut c_void,
ptr: *mut c_void,
osize: usize,
nsize: usize,
) -> *mut c_void {
let mem_state = &mut *(extra as *mut MemoryState);
#[cfg(feature = "luau")]
{
// Reset the flag
mem_state.limit_reached = false;
}
if nsize == 0 {
// Free memory
if !ptr.is_null() {
let layout = Layout::from_size_align_unchecked(osize, ffi::SYS_MIN_ALIGN);
alloc::dealloc(ptr as *mut u8, layout);
mem_state.used_memory -= osize as isize;
}
return ptr::null_mut();
}
// Do not allocate more than isize::MAX
if nsize > isize::MAX as usize {
return ptr::null_mut();
}
// Are we fit to the memory limits?
let mut mem_diff = nsize as isize;
if !ptr.is_null() {
mem_diff -= osize as isize;
}
let mem_limit = mem_state.memory_limit;
let new_used_memory = mem_state.used_memory + mem_diff;
if mem_limit > 0 && new_used_memory > mem_limit && !mem_state.ignore_limit {
#[cfg(feature = "luau")]
{
mem_state.limit_reached = true;
}
return ptr::null_mut();
}
mem_state.used_memory += mem_diff;
if ptr.is_null() {
// Allocate new memory
let new_layout = match Layout::from_size_align(nsize, ffi::SYS_MIN_ALIGN) {
Ok(layout) => layout,
Err(_) => return ptr::null_mut(),
};
let new_ptr = alloc::alloc(new_layout) as *mut c_void;
if new_ptr.is_null() {
alloc::handle_alloc_error(new_layout);
}
return new_ptr;
}
// Reallocate memory
let old_layout = Layout::from_size_align_unchecked(osize, ffi::SYS_MIN_ALIGN);
let new_ptr = alloc::realloc(ptr as *mut u8, old_layout, nsize) as *mut c_void;
if new_ptr.is_null() {
alloc::handle_alloc_error(old_layout);
}
new_ptr
}
+54 -32
View File
@@ -1,23 +1,21 @@
#![allow(clippy::wrong_self_convention)]
use std::iter::FromIterator;
use std::ops::{Deref, DerefMut};
use std::result::Result as StdResult;
use crate::error::Result;
use crate::lua::Lua;
use crate::value::{FromLua, FromLuaMulti, MultiValue, Nil, ToLua, ToLuaMulti};
use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, MultiValue, Nil};
/// Result is convertible to `MultiValue` following the common Lua idiom of returning the result
/// on success, or in the case of an error, returning `nil` and an error message.
impl<'lua, T: ToLua<'lua>, E: ToLua<'lua>> ToLuaMulti<'lua> for StdResult<T, E> {
impl<'lua, T: IntoLua<'lua>, E: IntoLua<'lua>> IntoLuaMulti<'lua> for StdResult<T, E> {
#[inline]
fn to_lua_multi(self, lua: &'lua Lua) -> Result<MultiValue<'lua>> {
let mut result = MultiValue::new_or_cached(lua);
fn into_lua_multi(self, lua: &'lua Lua) -> Result<MultiValue<'lua>> {
let mut result = MultiValue::new_or_pooled(lua);
match self {
Ok(v) => result.push_front(v.to_lua(lua)?),
Ok(v) => result.push_front(v.into_lua(lua)?),
Err(e) => {
result.push_front(e.to_lua(lua)?);
result.push_front(e.into_lua(lua)?);
result.push_front(Nil);
}
}
@@ -25,11 +23,11 @@ impl<'lua, T: ToLua<'lua>, E: ToLua<'lua>> ToLuaMulti<'lua> for StdResult<T, E>
}
}
impl<'lua, T: ToLua<'lua>> ToLuaMulti<'lua> for T {
impl<'lua, T: IntoLua<'lua>> IntoLuaMulti<'lua> for T {
#[inline]
fn to_lua_multi(self, lua: &'lua Lua) -> Result<MultiValue<'lua>> {
let mut v = MultiValue::new_or_cached(lua);
v.push_front(self.to_lua(lua)?);
fn into_lua_multi(self, lua: &'lua Lua) -> Result<MultiValue<'lua>> {
let mut v = MultiValue::new_or_pooled(lua);
v.push_front(self.into_lua(lua)?);
Ok(v)
}
}
@@ -38,14 +36,26 @@ impl<'lua, T: FromLua<'lua>> FromLuaMulti<'lua> for T {
#[inline]
fn from_lua_multi(mut values: MultiValue<'lua>, lua: &'lua Lua) -> Result<Self> {
let res = T::from_lua(values.pop_front().unwrap_or(Nil), lua);
lua.cache_multivalue(values);
MultiValue::return_to_pool(values, lua);
res
}
#[inline]
fn from_lua_multi_args(
mut values: MultiValue<'lua>,
i: usize,
to: Option<&str>,
lua: &'lua Lua,
) -> Result<Self> {
let res = T::from_lua_arg(values.pop_front().unwrap_or(Nil), i, to, lua);
MultiValue::return_to_pool(values, lua);
res
}
}
impl<'lua> ToLuaMulti<'lua> for MultiValue<'lua> {
impl<'lua> IntoLuaMulti<'lua> for MultiValue<'lua> {
#[inline]
fn to_lua_multi(self, _: &'lua Lua) -> Result<MultiValue<'lua>> {
fn into_lua_multi(self, _: &'lua Lua) -> Result<MultiValue<'lua>> {
Ok(self)
}
}
@@ -128,11 +138,11 @@ impl<T> DerefMut for Variadic<T> {
}
}
impl<'lua, T: ToLua<'lua>> ToLuaMulti<'lua> for Variadic<T> {
impl<'lua, T: IntoLua<'lua>> IntoLuaMulti<'lua> for Variadic<T> {
#[inline]
fn to_lua_multi(self, lua: &'lua Lua) -> Result<MultiValue<'lua>> {
let mut values = MultiValue::new_or_cached(lua);
values.refill(self.0.into_iter().map(|e| e.to_lua(lua)))?;
fn into_lua_multi(self, lua: &'lua Lua) -> Result<MultiValue<'lua>> {
let mut values = MultiValue::new_or_pooled(lua);
values.refill(self.0.into_iter().map(|e| e.into_lua(lua)))?;
Ok(values)
}
}
@@ -145,42 +155,42 @@ impl<'lua, T: FromLua<'lua>> FromLuaMulti<'lua> for Variadic<T> {
.map(|e| T::from_lua(e, lua))
.collect::<Result<Vec<T>>>()
.map(Variadic);
lua.cache_multivalue(values);
MultiValue::return_to_pool(values, lua);
res
}
}
macro_rules! impl_tuple {
() => (
impl<'lua> ToLuaMulti<'lua> for () {
impl<'lua> IntoLuaMulti<'lua> for () {
#[inline]
fn to_lua_multi(self, lua: &'lua Lua) -> Result<MultiValue<'lua>> {
Ok(MultiValue::new_or_cached(lua))
fn into_lua_multi(self, lua: &'lua Lua) -> Result<MultiValue<'lua>> {
Ok(MultiValue::new_or_pooled(lua))
}
}
impl<'lua> FromLuaMulti<'lua> for () {
#[inline]
fn from_lua_multi(values: MultiValue<'lua>, lua: &'lua Lua) -> Result<Self> {
lua.cache_multivalue(values);
MultiValue::return_to_pool(values, lua);
Ok(())
}
}
);
($last:ident $($name:ident)*) => (
impl<'lua, $($name,)* $last> ToLuaMulti<'lua> for ($($name,)* $last,)
where $($name: ToLua<'lua>,)*
$last: ToLuaMulti<'lua>
impl<'lua, $($name,)* $last> IntoLuaMulti<'lua> for ($($name,)* $last,)
where $($name: IntoLua<'lua>,)*
$last: IntoLuaMulti<'lua>
{
#[allow(unused_mut)]
#[allow(non_snake_case)]
#[inline]
fn to_lua_multi(self, lua: &'lua Lua) -> Result<MultiValue<'lua>> {
fn into_lua_multi(self, lua: &'lua Lua) -> Result<MultiValue<'lua>> {
let ($($name,)* $last,) = self;
let mut results = $last.to_lua_multi(lua)?;
push_reverse!(results, $($name.to_lua(lua)?,)*);
let mut results = $last.into_lua_multi(lua)?;
push_reverse!(results, $($name.into_lua(lua)?,)*);
Ok(results)
}
}
@@ -193,9 +203,21 @@ macro_rules! impl_tuple {
#[allow(non_snake_case)]
#[inline]
fn from_lua_multi(mut values: MultiValue<'lua>, lua: &'lua Lua) -> Result<Self> {
$(let $name = values.pop_front().unwrap_or(Nil);)*
$(let $name = FromLua::from_lua(values.pop_front().unwrap_or(Nil), lua)?;)*
let $last = FromLuaMulti::from_lua_multi(values, lua)?;
Ok(($(FromLua::from_lua($name, lua)?,)* $last,))
Ok(($($name,)* $last,))
}
#[allow(unused_mut)]
#[allow(non_snake_case)]
#[inline]
fn from_lua_multi_args(mut values: MultiValue<'lua>, mut i: usize, to: Option<&str>, lua: &'lua Lua) -> Result<Self> {
$(
let $name = FromLua::from_lua_arg(values.pop_front().unwrap_or(Nil), i, to, lua)?;
i += 1;
)*
let $last = FromLuaMulti::from_lua_multi_args(values, i, to, lua)?;
Ok(($($name,)* $last,))
}
}
);
+19 -10
View File
@@ -2,16 +2,18 @@
#[doc(no_inline)]
pub use crate::{
AnyUserData as LuaAnyUserData, Chunk as LuaChunk, Error as LuaError,
ExternalError as LuaExternalError, ExternalResult as LuaExternalResult, FromLua, FromLuaMulti,
Function as LuaFunction, FunctionInfo as LuaFunctionInfo, GCMode as LuaGCMode,
Integer as LuaInteger, LightUserData as LuaLightUserData, Lua, LuaOptions,
MetaMethod as LuaMetaMethod, MultiValue as LuaMultiValue, Nil as LuaNil, Number as LuaNumber,
RegistryKey as LuaRegistryKey, Result as LuaResult, StdLib as LuaStdLib, String as LuaString,
Table as LuaTable, TableExt as LuaTableExt, TablePairs as LuaTablePairs,
TableSequence as LuaTableSequence, Thread as LuaThread, ThreadStatus as LuaThreadStatus, ToLua,
ToLuaMulti, UserData as LuaUserData, UserDataFields as LuaUserDataFields,
UserDataMetatable as LuaUserDataMetatable, UserDataMethods as LuaUserDataMethods,
AnyUserData as LuaAnyUserData, AnyUserDataExt as LuaAnyUserDataExt, Chunk as LuaChunk,
Error as LuaError, ErrorContext as LuaErrorContext, ExternalError as LuaExternalError,
ExternalResult as LuaExternalResult, FromLua, FromLuaMulti, Function as LuaFunction,
FunctionInfo as LuaFunctionInfo, GCMode as LuaGCMode, Integer as LuaInteger, IntoLua,
IntoLuaMulti, LightUserData as LuaLightUserData, Lua, LuaOptions, MetaMethod as LuaMetaMethod,
MultiValue as LuaMultiValue, Nil as LuaNil, Number as LuaNumber, RegistryKey as LuaRegistryKey,
Result as LuaResult, StdLib as LuaStdLib, String as LuaString, Table as LuaTable,
TableExt as LuaTableExt, TablePairs as LuaTablePairs, TableSequence as LuaTableSequence,
Thread as LuaThread, ThreadStatus as LuaThreadStatus, UserData as LuaUserData,
UserDataFields as LuaUserDataFields, UserDataMetatable as LuaUserDataMetatable,
UserDataMethods as LuaUserDataMethods, UserDataRef as LuaUserDataRef,
UserDataRefMut as LuaUserDataRefMut, UserDataRegistrar as LuaUserDataRegistrar,
Value as LuaValue,
};
@@ -33,3 +35,10 @@ pub use crate::{
DeserializeOptions as LuaDeserializeOptions, LuaSerdeExt,
SerializeOptions as LuaSerializeOptions,
};
#[cfg(feature = "unstable")]
#[doc(no_inline)]
pub use crate::{
OwnedAnyUserData as LuaOwnedAnyUserData, OwnedFunction as LuaOwnedFunction,
OwnedString as LuaOwnedString, OwnedTable as LuaOwnedTable,
};
+326 -389
View File
File diff suppressed because it is too large Load Diff
+31 -4
View File
@@ -9,6 +9,7 @@ use serde::de::{self, IntoDeserializer};
use crate::error::{Error, Result};
use crate::table::{Table, TablePairs, TableSequence};
use crate::userdata::AnyUserData;
use crate::value::Value;
/// A struct for deserializing Lua values into Rust values.
@@ -131,6 +132,9 @@ impl<'lua, 'de> serde::Deserializer<'de> for Deserializer<'lua> {
Value::Table(ref t) if t.raw_len() > 0 || t.is_array() => self.deserialize_seq(visitor),
Value::Table(_) => self.deserialize_map(visitor),
Value::LightUserData(ud) if ud.0.is_null() => visitor.visit_none(),
Value::UserData(ud) if ud.is_serializable() => {
serde_userdata(ud, |value| value.deserialize_any(visitor))
}
Value::Function(_)
| Value::Thread(_)
| Value::UserData(_)
@@ -163,8 +167,8 @@ impl<'lua, 'de> serde::Deserializer<'de> for Deserializer<'lua> {
#[inline]
fn deserialize_enum<V>(
self,
_name: &str,
_variants: &'static [&'static str],
name: &'static str,
variants: &'static [&'static str],
visitor: V,
) -> Result<V::Value>
where
@@ -198,6 +202,9 @@ impl<'lua, 'de> serde::Deserializer<'de> for Deserializer<'lua> {
(variant, Some(value), Some(_guard))
}
Value::String(variant) => (variant.to_str()?.to_owned(), None, None),
Value::UserData(ud) if ud.is_serializable() => {
return serde_userdata(ud, |value| value.deserialize_enum(name, variants, visitor));
}
_ => return Err(de::Error::custom("bad enum value")),
};
@@ -244,6 +251,9 @@ impl<'lua, 'de> serde::Deserializer<'de> for Deserializer<'lua> {
))
}
}
Value::UserData(ud) if ud.is_serializable() => {
serde_userdata(ud, |value| value.deserialize_seq(visitor))
}
value => Err(de::Error::invalid_type(
de::Unexpected::Other(value.type_name()),
&"table",
@@ -299,6 +309,9 @@ impl<'lua, 'de> serde::Deserializer<'de> for Deserializer<'lua> {
))
}
}
Value::UserData(ud) if ud.is_serializable() => {
serde_userdata(ud, |value| value.deserialize_map(visitor))
}
value => Err(de::Error::invalid_type(
de::Unexpected::Other(value.type_name()),
&"table",
@@ -320,11 +333,16 @@ impl<'lua, 'de> serde::Deserializer<'de> for Deserializer<'lua> {
}
#[inline]
fn deserialize_newtype_struct<V>(self, _name: &'static str, visitor: V) -> Result<V::Value>
fn deserialize_newtype_struct<V>(self, name: &'static str, visitor: V) -> Result<V::Value>
where
V: de::Visitor<'de>,
{
visitor.visit_newtype_struct(self)
match self.value {
Value::UserData(ud) if ud.is_serializable() => {
serde_userdata(ud, |value| value.deserialize_newtype_struct(name, visitor))
}
_ => visitor.visit_newtype_struct(self),
}
}
#[inline]
@@ -612,6 +630,7 @@ fn check_value_if_skip(
return Ok(true); // skip
}
}
Value::UserData(ud) if ud.is_serializable() => {}
Value::Function(_)
| Value::Thread(_)
| Value::UserData(_)
@@ -625,3 +644,11 @@ fn check_value_if_skip(
}
Ok(false) // do not skip
}
fn serde_userdata<V>(
ud: AnyUserData,
f: impl FnOnce(serde_value::Value) -> std::result::Result<V, serde_value::DeserializerError>,
) -> Result<V> {
let value = serde_value::to_value(ud).map_err(|err| Error::SerializeError(err.to_string()))?;
f(value).map_err(|err| Error::DeserializeError(err.to_string()))
}
+20 -25
View File
@@ -1,21 +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> {
pub trait LuaSerdeExt: Sealed {
/// A special value (lightuserdata) to encode/decode optional (none) values.
///
/// Requires `feature = "serialize"`
@@ -37,7 +35,7 @@ pub trait LuaSerdeExt<'lua> {
/// 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
@@ -68,7 +66,7 @@ pub trait LuaSerdeExt<'lua> {
/// Ok(())
/// }
/// ```
fn array_metatable(&'lua self) -> Table<'lua>;
fn array_metatable(&self) -> Table;
/// Converts `T` into a [`Value`] instance.
///
@@ -101,7 +99,7 @@ pub trait LuaSerdeExt<'lua> {
/// "#).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.
///
@@ -126,7 +124,7 @@ pub trait LuaSerdeExt<'lua> {
/// "#).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;
@@ -159,7 +157,7 @@ pub trait LuaSerdeExt<'lua> {
/// }
/// ```
#[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.
///
@@ -191,49 +189,46 @@ pub trait LuaSerdeExt<'lua> {
/// }
/// ```
#[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))
}
+9 -9
View File
@@ -4,13 +4,12 @@ use serde::{ser, Serialize};
use super::LuaSerdeExt;
use crate::error::{Error, Result};
use crate::ffi;
use crate::lua::Lua;
use crate::string::String;
use crate::table::Table;
use crate::types::Integer;
use crate::util::{check_stack, StackGuard};
use crate::value::{ToLua, Value};
use crate::value::{IntoLua, Value};
/// A struct for serializing Rust values into Lua values.
#[derive(Debug)]
@@ -110,7 +109,7 @@ macro_rules! lua_serialize_number {
($name:ident, $t:ty) => {
#[inline]
fn $name(self, value: $t) -> Result<Value<'lua>> {
value.to_lua(self.lua)
value.into_lua(self.lua)
}
};
}
@@ -320,20 +319,21 @@ impl<'lua> ser::SerializeSeq for SerializeVec<'lua> {
T: Serialize + ?Sized,
{
let lua = self.table.0.lua;
let state = lua.state();
let value = lua.to_value_with(value, self.options)?;
unsafe {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, 4)?;
let _sg = StackGuard::new(state);
check_stack(state, 4)?;
lua.push_ref(&self.table.0);
lua.push_value(value)?;
if lua.unlikely_memory_error() {
let len = ffi::lua_rawlen(lua.state, -2) as Integer;
ffi::lua_rawseti(lua.state, -2, len + 1);
ffi::lua_pop(lua.state, 1);
let len = ffi::lua_rawlen(state, -2) as Integer;
ffi::lua_rawseti(state, -2, len + 1);
ffi::lua_pop(state, 1);
Ok(())
} else {
protect_lua!(lua.state, 2, 0, fn(state) {
protect_lua!(state, 2, 0, fn(state) {
let len = ffi::lua_rawlen(state, -2) as Integer;
ffi::lua_rawseti(state, -2, len + 1);
})
+99 -4
View File
@@ -2,7 +2,7 @@ use std::borrow::{Borrow, Cow};
use std::hash::{Hash, Hasher};
use std::os::raw::c_void;
use std::string::String as StdString;
use std::{slice, str};
use std::{fmt, slice, str};
#[cfg(feature = "serialize")]
use {
@@ -11,15 +11,35 @@ use {
};
use crate::error::{Error, Result};
use crate::ffi;
use crate::types::LuaRef;
/// Handle to an internal Lua string.
///
/// Unlike Rust strings, Lua strings may not be valid UTF-8.
#[derive(Clone, Debug)]
#[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,43 @@ 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> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let bytes = self.as_bytes();
// Check if the string is valid utf8
if let Ok(s) = str::from_utf8(bytes) {
return s.fmt(f);
}
// Format as bytes
write!(f, "b\"")?;
for &b in bytes {
// https://doc.rust-lang.org/reference/tokens.html#byte-escapes
match b {
b'\n' => write!(f, "\\n")?,
b'\r' => write!(f, "\\r")?,
b'\t' => write!(f, "\\t")?,
b'\\' | b'"' => write!(f, "\\{}", b as char)?,
b'\0' => write!(f, "\\0")?,
// ASCII printable
0x20..=0x7e => write!(f, "{}", b as char)?,
_ => write!(f, "\\x{b:02x}")?,
}
}
write!(f, "\"")?;
Ok(())
}
}
impl<'lua> AsRef<[u8]> for String<'lua> {
@@ -146,7 +203,7 @@ impl<'lua> Borrow<[u8]> for String<'lua> {
// in other ways.
impl<'lua, T> PartialEq<T> for String<'lua>
where
T: AsRef<[u8]>,
T: AsRef<[u8]> + ?Sized,
{
fn eq(&self, other: &T) -> bool {
self.as_bytes() == other.as_ref()
@@ -173,3 +230,41 @@ 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::*;
static_assertions::assert_not_impl_any!(String: Send);
}
+240 -105
View File
@@ -1,3 +1,5 @@
use std::collections::HashSet;
use std::fmt;
use std::marker::PhantomData;
use std::os::raw::c_void;
@@ -9,19 +11,40 @@ use {
};
use crate::error::{Error, Result};
use crate::ffi;
use crate::function::Function;
use crate::private::Sealed;
use crate::types::{Integer, LuaRef};
use crate::util::{assert_stack, check_stack, StackGuard};
use crate::value::{FromLua, FromLuaMulti, Nil, ToLua, ToLuaMulti, Value};
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)]
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())
}
}
#[allow(clippy::len_without_is_empty)]
impl<'lua> Table<'lua> {
/// Sets a key-value pair in the table.
@@ -57,24 +80,25 @@ impl<'lua> Table<'lua> {
/// ```
///
/// [`raw_set`]: #method.raw_set
pub fn set<K: ToLua<'lua>, V: ToLua<'lua>>(&self, key: K, value: V) -> Result<()> {
pub fn set<K: IntoLua<'lua>, V: IntoLua<'lua>>(&self, key: K, value: V) -> Result<()> {
// Fast track
if !self.has_metatable() {
return self.raw_set(key, value);
}
let lua = self.0.lua;
let key = key.to_lua(lua)?;
let value = value.to_lua(lua)?;
let key = key.into_lua(lua)?;
let value = value.into_lua(lua)?;
let state = lua.state();
unsafe {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, 5)?;
let _sg = StackGuard::new(state);
check_stack(state, 5)?;
lua.push_ref(&self.0);
lua.push_value(key)?;
lua.push_value(value)?;
protect_lua!(lua.state, 3, 0, fn(state) ffi::lua_settable(state, -3))
protect_lua!(state, 3, 0, fn(state) ffi::lua_settable(state, -3))
}
}
@@ -102,22 +126,23 @@ impl<'lua> Table<'lua> {
/// ```
///
/// [`raw_get`]: #method.raw_get
pub fn get<K: ToLua<'lua>, V: FromLua<'lua>>(&self, key: K) -> Result<V> {
pub fn get<K: IntoLua<'lua>, V: FromLua<'lua>>(&self, key: K) -> Result<V> {
// Fast track
if !self.has_metatable() {
return self.raw_get(key);
}
let lua = self.0.lua;
let key = key.to_lua(lua)?;
let state = lua.state();
let key = key.into_lua(lua)?;
let value = unsafe {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, 4)?;
let _sg = StackGuard::new(state);
check_stack(state, 4)?;
lua.push_ref(&self.0);
lua.push_value(key)?;
protect_lua!(lua.state, 2, 1, fn(state) ffi::lua_gettable(state, -2))?;
protect_lua!(state, 2, 1, fn(state) ffi::lua_gettable(state, -2))?;
lua.pop_value()
};
@@ -125,26 +150,27 @@ impl<'lua> Table<'lua> {
}
/// Checks whether the table contains a non-nil value for `key`.
pub fn contains_key<K: ToLua<'lua>>(&self, key: K) -> Result<bool> {
pub fn contains_key<K: IntoLua<'lua>>(&self, key: K) -> Result<bool> {
Ok(self.get::<_, Value>(key)? != Value::Nil)
}
/// Appends a value to the back of the table.
pub fn push<V: ToLua<'lua>>(&self, value: V) -> Result<()> {
pub fn push<V: IntoLua<'lua>>(&self, value: V) -> Result<()> {
// Fast track
if !self.has_metatable() {
return self.raw_push(value);
}
let lua = self.0.lua;
let value = value.to_lua(lua)?;
let state = lua.state();
let value = value.into_lua(lua)?;
unsafe {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, 4)?;
let _sg = StackGuard::new(state);
check_stack(state, 4)?;
lua.push_ref(&self.0);
lua.push_value(value)?;
protect_lua!(lua.state, 2, 0, fn(state) {
protect_lua!(state, 2, 0, fn(state) {
let len = ffi::luaL_len(state, -2) as Integer;
ffi::lua_seti(state, -2, len + 1);
})?
@@ -160,12 +186,13 @@ impl<'lua> Table<'lua> {
}
let lua = self.0.lua;
let state = lua.state();
let value = unsafe {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, 4)?;
let _sg = StackGuard::new(state);
check_stack(state, 4)?;
lua.push_ref(&self.0);
protect_lua!(lua.state, 1, 1, fn(state) {
protect_lua!(state, 1, 1, fn(state) {
let len = ffi::luaL_len(state, -1) as Integer;
ffi::lua_geti(state, -1, len);
ffi::lua_pushnil(state);
@@ -233,44 +260,46 @@ impl<'lua> Table<'lua> {
}
/// Sets a key-value pair without invoking metamethods.
pub fn raw_set<K: ToLua<'lua>, V: ToLua<'lua>>(&self, key: K, value: V) -> Result<()> {
pub fn raw_set<K: IntoLua<'lua>, V: IntoLua<'lua>>(&self, key: K, value: V) -> Result<()> {
#[cfg(feature = "luau")]
self.check_readonly_write()?;
let lua = self.0.lua;
let key = key.to_lua(lua)?;
let value = value.to_lua(lua)?;
let state = lua.state();
let key = key.into_lua(lua)?;
let value = value.into_lua(lua)?;
unsafe {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, 5)?;
let _sg = StackGuard::new(state);
check_stack(state, 5)?;
lua.push_ref(&self.0);
lua.push_value(key)?;
lua.push_value(value)?;
if lua.unlikely_memory_error() {
ffi::lua_rawset(lua.state, -3);
ffi::lua_pop(lua.state, 1);
ffi::lua_rawset(state, -3);
ffi::lua_pop(state, 1);
Ok(())
} else {
protect_lua!(lua.state, 3, 0, fn(state) ffi::lua_rawset(state, -3))
protect_lua!(state, 3, 0, fn(state) ffi::lua_rawset(state, -3))
}
}
}
/// Gets the value associated to `key` without invoking metamethods.
pub fn raw_get<K: ToLua<'lua>, V: FromLua<'lua>>(&self, key: K) -> Result<V> {
pub fn raw_get<K: IntoLua<'lua>, V: FromLua<'lua>>(&self, key: K) -> Result<V> {
let lua = self.0.lua;
let key = key.to_lua(lua)?;
let state = lua.state();
let key = key.into_lua(lua)?;
let value = unsafe {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, 3)?;
let _sg = StackGuard::new(state);
check_stack(state, 3)?;
lua.push_ref(&self.0);
lua.push_value(key)?;
ffi::lua_rawget(lua.state, -2);
ffi::lua_rawget(state, -2);
lua.pop_value()
};
@@ -279,21 +308,23 @@ impl<'lua> Table<'lua> {
/// Inserts element value at position `idx` to the table, shifting up the elements from `table[idx]`.
/// The worst case complexity is O(n), where n is the table length.
pub fn raw_insert<V: ToLua<'lua>>(&self, idx: Integer, value: V) -> Result<()> {
pub fn raw_insert<V: IntoLua<'lua>>(&self, idx: Integer, value: V) -> Result<()> {
let lua = self.0.lua;
let state = lua.state();
let size = self.raw_len();
if idx < 1 || idx > size + 1 {
return Err(Error::RuntimeError("index out of bounds".to_string()));
}
let value = value.to_lua(lua)?;
let value = value.into_lua(lua)?;
unsafe {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, 5)?;
let _sg = StackGuard::new(state);
check_stack(state, 5)?;
lua.push_ref(&self.0);
lua.push_value(value)?;
protect_lua!(lua.state, 2, 0, |state| {
protect_lua!(state, 2, 0, |state| {
for i in (idx..=size).rev() {
// table[i+1] = table[i]
ffi::lua_rawgeti(state, -2, i);
@@ -305,16 +336,17 @@ impl<'lua> Table<'lua> {
}
/// Appends a value to the back of the table without invoking metamethods.
pub fn raw_push<V: ToLua<'lua>>(&self, value: V) -> Result<()> {
pub fn raw_push<V: IntoLua<'lua>>(&self, value: V) -> Result<()> {
#[cfg(feature = "luau")]
self.check_readonly_write()?;
let lua = self.0.lua;
let value = value.to_lua(lua)?;
let state = lua.state();
let value = value.into_lua(lua)?;
unsafe {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, 4)?;
let _sg = StackGuard::new(state);
check_stack(state, 4)?;
lua.push_ref(&self.0);
lua.push_value(value)?;
@@ -325,9 +357,9 @@ impl<'lua> Table<'lua> {
}
if lua.unlikely_memory_error() {
callback(lua.state);
callback(state);
} else {
protect_lua!(lua.state, 2, 0, fn(state) callback(state))?;
protect_lua!(state, 2, 0, fn(state) callback(state))?;
}
}
Ok(())
@@ -339,16 +371,17 @@ impl<'lua> Table<'lua> {
self.check_readonly_write()?;
let lua = self.0.lua;
let state = lua.state();
let value = unsafe {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, 3)?;
let _sg = StackGuard::new(state);
check_stack(state, 3)?;
lua.push_ref(&self.0);
let len = ffi::lua_rawlen(lua.state, -1) as Integer;
ffi::lua_rawgeti(lua.state, -1, len);
let len = ffi::lua_rawlen(state, -1) as Integer;
ffi::lua_rawgeti(state, -1, len);
// Set slot to nil (it must be safe to do)
ffi::lua_pushnil(lua.state);
ffi::lua_rawseti(lua.state, -3, len);
ffi::lua_pushnil(state);
ffi::lua_rawseti(state, -3, len);
lua.pop_value()
};
V::from_lua(value, lua)
@@ -361,9 +394,10 @@ impl<'lua> Table<'lua> {
/// where n is the table length.
///
/// For other key types this is equivalent to setting `table[key] = nil`.
pub fn raw_remove<K: ToLua<'lua>>(&self, key: K) -> Result<()> {
pub fn raw_remove<K: IntoLua<'lua>>(&self, key: K) -> Result<()> {
let lua = self.0.lua;
let key = key.to_lua(lua)?;
let state = lua.state();
let key = key.into_lua(lua)?;
match key {
Value::Integer(idx) => {
let size = self.raw_len();
@@ -371,11 +405,11 @@ impl<'lua> Table<'lua> {
return Err(Error::RuntimeError("index out of bounds".to_string()));
}
unsafe {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, 4)?;
let _sg = StackGuard::new(state);
check_stack(state, 4)?;
lua.push_ref(&self.0);
protect_lua!(lua.state, 1, 0, |state| {
protect_lua!(state, 1, 0, |state| {
for i in idx..size {
ffi::lua_rawgeti(state, -1, i + 1);
ffi::lua_rawseti(state, -2, i);
@@ -389,6 +423,47 @@ impl<'lua> Table<'lua> {
}
}
/// Clears the table, removing all keys and values from array and hash parts,
/// without invoking metamethods.
///
/// This method is useful to clear the table while keeping its capacity.
pub fn clear(&self) -> Result<()> {
#[cfg(feature = "luau")]
self.check_readonly_write()?;
let lua = self.0.lua;
unsafe {
#[cfg(feature = "luau")]
ffi::lua_cleartable(lua.ref_thread(), self.0.index);
#[cfg(not(feature = "luau"))]
{
let state = lua.state();
check_stack(state, 4)?;
lua.push_ref(&self.0);
// Clear array part
for i in 1..=ffi::lua_rawlen(state, -1) {
ffi::lua_pushnil(state);
ffi::lua_rawseti(state, -2, i as Integer);
}
// Clear hash part
// It must be safe as long as we don't use invalid keys
ffi::lua_pushnil(state);
while ffi::lua_next(state, -2) != 0 {
ffi::lua_pop(state, 1); // pop value
ffi::lua_pushvalue(state, -1); // copy key
ffi::lua_pushnil(state);
ffi::lua_rawset(state, -4);
}
}
}
Ok(())
}
/// Returns the result of the Lua `#` operator.
///
/// This might invoke the `__len` metamethod. Use the [`raw_len`] method if that is not desired.
@@ -401,12 +476,13 @@ impl<'lua> Table<'lua> {
}
let lua = self.0.lua;
let state = lua.state();
unsafe {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, 4)?;
let _sg = StackGuard::new(state);
check_stack(state, 4)?;
lua.push_ref(&self.0);
protect_lua!(lua.state, 1, 0, |state| ffi::luaL_len(state, -1))
protect_lua!(state, 1, 0, |state| ffi::luaL_len(state, -1))
}
}
@@ -421,12 +497,13 @@ impl<'lua> Table<'lua> {
/// Unlike the `getmetatable` Lua function, this method ignores the `__metatable` field.
pub fn get_metatable(&self) -> Option<Table<'lua>> {
let lua = self.0.lua;
let state = lua.state();
unsafe {
let _sg = StackGuard::new(lua.state);
assert_stack(lua.state, 2);
let _sg = StackGuard::new(state);
assert_stack(state, 2);
lua.push_ref(&self.0);
if ffi::lua_getmetatable(lua.state, -1) == 0 {
if ffi::lua_getmetatable(state, -1) == 0 {
None
} else {
Some(Table(lua.pop_ref()))
@@ -446,17 +523,18 @@ impl<'lua> Table<'lua> {
}
let lua = self.0.lua;
let state = lua.state();
unsafe {
let _sg = StackGuard::new(lua.state);
assert_stack(lua.state, 2);
let _sg = StackGuard::new(state);
assert_stack(state, 2);
lua.push_ref(&self.0);
if let Some(metatable) = metatable {
lua.push_ref(&metatable.0);
} else {
ffi::lua_pushnil(lua.state);
ffi::lua_pushnil(state);
}
ffi::lua_setmetatable(lua.state, -2);
ffi::lua_setmetatable(state, -2);
}
}
@@ -512,6 +590,14 @@ impl<'lua> Table<'lua> {
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) -> OwnedTable {
OwnedTable(self.0.into_owned())
}
/// Consume this table and return an iterator over the pairs of the table.
///
/// This works like the Lua `pairs` function, but does not invoke the `__pairs` metamethod.
@@ -619,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>,
@@ -637,16 +723,17 @@ impl<'lua> Table<'lua> {
#[cfg(feature = "serialize")]
pub(crate) fn is_array(&self) -> bool {
let lua = self.0.lua;
let state = lua.state();
unsafe {
let _sg = StackGuard::new(lua.state);
assert_stack(lua.state, 3);
let _sg = StackGuard::new(state);
assert_stack(state, 3);
lua.push_ref(&self.0);
if ffi::lua_getmetatable(lua.state, -1) == 0 {
if ffi::lua_getmetatable(state, -1) == 0 {
return false;
}
crate::serde::push_array_metatable(lua.state);
ffi::lua_rawequal(lua.state, -1, -2) != 0
crate::serde::push_array_metatable(state);
ffi::lua_rawequal(state, -1, -2) != 0
}
}
@@ -659,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> {
@@ -675,13 +798,13 @@ impl<'lua> AsRef<Table<'lua>> for Table<'lua> {
}
/// An extension trait for `Table`s that provides a variety of convenient functionality.
pub trait TableExt<'lua> {
pub trait TableExt<'lua>: Sealed {
/// Calls the table as function assuming it has `__call` metamethod.
///
/// The metamethod is called with the table as its first argument, followed by the passed arguments.
fn call<A, R>(&self, args: A) -> Result<R>
where
A: ToLuaMulti<'lua>,
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua>;
/// Asynchronously calls the table as function assuming it has `__call` metamethod.
@@ -692,7 +815,7 @@ pub trait TableExt<'lua> {
fn call_async<'fut, A, R>(&self, args: A) -> LocalBoxFuture<'fut, Result<R>>
where
'lua: 'fut,
A: ToLuaMulti<'lua>,
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua> + 'fut;
/// Gets the function associated to `key` from the table and executes it,
@@ -704,8 +827,8 @@ pub trait TableExt<'lua> {
/// This might invoke the `__index` metamethod.
fn call_method<K, A, R>(&self, key: K, args: A) -> Result<R>
where
K: ToLua<'lua>,
A: ToLuaMulti<'lua>,
K: IntoLua<'lua>,
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua>;
/// Gets the function associated to `key` from the table and executes it,
@@ -717,8 +840,8 @@ pub trait TableExt<'lua> {
/// This might invoke the `__index` metamethod.
fn call_function<K, A, R>(&self, key: K, args: A) -> Result<R>
where
K: ToLua<'lua>,
A: ToLuaMulti<'lua>,
K: IntoLua<'lua>,
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua>;
/// Gets the function associated to `key` from the table and asynchronously executes it,
@@ -732,8 +855,8 @@ pub trait TableExt<'lua> {
fn call_async_method<'fut, K, A, R>(&self, key: K, args: A) -> LocalBoxFuture<'fut, Result<R>>
where
'lua: 'fut,
K: ToLua<'lua>,
A: ToLuaMulti<'lua>,
K: IntoLua<'lua>,
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua> + 'fut;
/// Gets the function associated to `key` from the table and asynchronously executes it,
@@ -751,15 +874,15 @@ pub trait TableExt<'lua> {
) -> LocalBoxFuture<'fut, Result<R>>
where
'lua: 'fut,
K: ToLua<'lua>,
A: ToLuaMulti<'lua>,
K: IntoLua<'lua>,
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua> + 'fut;
}
impl<'lua> TableExt<'lua> for Table<'lua> {
fn call<A, R>(&self, args: A) -> Result<R>
where
A: ToLuaMulti<'lua>,
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua>,
{
// Convert table to a function and call via pcall that respects the `__call` metamethod.
@@ -770,7 +893,7 @@ impl<'lua> TableExt<'lua> for Table<'lua> {
fn call_async<'fut, A, R>(&self, args: A) -> LocalBoxFuture<'fut, Result<R>>
where
'lua: 'fut,
A: ToLuaMulti<'lua>,
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua> + 'fut,
{
Function(self.0.clone()).call_async(args)
@@ -778,20 +901,20 @@ impl<'lua> TableExt<'lua> for Table<'lua> {
fn call_method<K, A, R>(&self, key: K, args: A) -> Result<R>
where
K: ToLua<'lua>,
A: ToLuaMulti<'lua>,
K: IntoLua<'lua>,
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua>,
{
let lua = self.0.lua;
let mut args = args.to_lua_multi(lua)?;
let mut args = args.into_lua_multi(lua)?;
args.push_front(Value::Table(self.clone()));
self.get::<_, Function>(key)?.call(args)
}
fn call_function<K, A, R>(&self, key: K, args: A) -> Result<R>
where
K: ToLua<'lua>,
A: ToLuaMulti<'lua>,
K: IntoLua<'lua>,
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua>,
{
self.get::<_, Function>(key)?.call(args)
@@ -801,12 +924,12 @@ impl<'lua> TableExt<'lua> for Table<'lua> {
fn call_async_method<'fut, K, A, R>(&self, key: K, args: A) -> LocalBoxFuture<'fut, Result<R>>
where
'lua: 'fut,
K: ToLua<'lua>,
A: ToLuaMulti<'lua>,
K: IntoLua<'lua>,
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua> + 'fut,
{
let lua = self.0.lua;
let mut args = match args.to_lua_multi(lua) {
let mut args = match args.into_lua_multi(lua) {
Ok(args) => args,
Err(e) => return Box::pin(future::err(e)),
};
@@ -818,8 +941,8 @@ impl<'lua> TableExt<'lua> for Table<'lua> {
fn call_async_function<'fut, K, A, R>(&self, key: K, args: A) -> LocalBoxFuture<'fut, Result<R>>
where
'lua: 'fut,
K: ToLua<'lua>,
A: ToLuaMulti<'lua>,
K: IntoLua<'lua>,
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua> + 'fut,
{
match self.get::<_, Function>(key) {
@@ -894,15 +1017,16 @@ where
fn next(&mut self) -> Option<Self::Item> {
if let Some(prev_key) = self.key.take() {
let lua = self.table.lua;
let state = lua.state();
let res = (|| unsafe {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, 5)?;
let _sg = StackGuard::new(state);
check_stack(state, 5)?;
lua.push_ref(&self.table);
lua.push_value(prev_key)?;
let next = protect_lua!(lua.state, 2, ffi::LUA_MULTRET, |state| {
let next = protect_lua!(state, 2, ffi::LUA_MULTRET, |state| {
ffi::lua_next(state, -2)
})?;
if next != 0 {
@@ -954,16 +1078,17 @@ where
fn next(&mut self) -> Option<Self::Item> {
if let Some(index) = self.index.take() {
let lua = self.table.lua;
let state = lua.state();
let res = (|| unsafe {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, 1 + if self.raw { 0 } else { 3 })?;
let _sg = StackGuard::new(state);
check_stack(state, 1 + if self.raw { 0 } else { 3 })?;
lua.push_ref(&self.table);
let res = if self.raw {
ffi::lua_rawgeti(lua.state, -1, index)
ffi::lua_rawgeti(state, -1, index)
} else {
protect_lua!(lua.state, 1, 1, |state| ffi::lua_geti(state, -1, index))?
protect_lua!(state, 1, 1, |state| ffi::lua_geti(state, -1, index))?
};
match res {
ffi::LUA_TNIL if index > self.len.unwrap_or(0) => Ok(None),
@@ -984,3 +1109,13 @@ where
}
}
}
#[cfg(test)]
mod assertions {
use super::*;
static_assertions::assert_not_impl_any!(Table: Send);
#[cfg(feature = "unstable")]
static_assertions::assert_not_impl_any!(OwnedTable: Send);
}
+103 -52
View File
@@ -2,10 +2,11 @@ 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, ToLuaMulti};
use crate::value::{FromLuaMulti, IntoLuaMulti};
#[cfg(any(
feature = "lua54",
@@ -14,17 +15,24 @@ use crate::value::{FromLuaMulti, ToLuaMulti};
))]
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::{
cell::RefCell,
future::Future,
marker::PhantomData,
pin::Pin,
ptr::NonNull,
task::{Context, Poll, Waker},
},
};
@@ -56,10 +64,10 @@ pub struct Thread<'lua>(pub(crate) LuaRef<'lua>);
/// [`Stream`]: futures_core::stream::Stream
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
#[derive(Debug)]
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct AsyncThread<'lua, R> {
thread: Thread<'lua>,
args0: RefCell<Option<Result<MultiValue<'lua>>>>,
args0: Option<Result<MultiValue<'lua>>>,
ret: PhantomData<R>,
recycle: bool,
}
@@ -108,15 +116,17 @@ impl<'lua> Thread<'lua> {
/// ```
pub fn resume<A, R>(&self, args: A) -> Result<R>
where
A: ToLuaMulti<'lua>,
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua>,
{
let lua = self.0.lua;
let mut args = args.to_lua_multi(lua)?;
let state = lua.state();
let mut args = args.into_lua_multi(lua)?;
let nargs = args.len() as c_int;
let results = unsafe {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, cmp::max(nargs + 1, 3))?;
let _sg = StackGuard::new(state);
check_stack(state, cmp::max(nargs + 1, 3))?;
let thread_state = ffi::lua_tothread(lua.ref_thread(), self.0.index);
@@ -129,23 +139,27 @@ impl<'lua> Thread<'lua> {
for arg in args.drain_all() {
lua.push_value(arg)?;
}
ffi::lua_xmove(lua.state, thread_state, nargs);
ffi::lua_xmove(state, thread_state, nargs);
let mut nresults = 0;
let ret = ffi::lua_resume(thread_state, lua.state, nargs, &mut nresults as *mut c_int);
let ret = ffi::lua_resume(thread_state, state, nargs, &mut nresults as *mut c_int);
if ret != ffi::LUA_OK && ret != ffi::LUA_YIELD {
check_stack(lua.state, 3)?;
protect_lua!(lua.state, 0, 1, |state| error_traceback_thread(
if ret == ffi::LUA_ERRMEM {
// Don't call error handler for memory errors
return Err(pop_error(thread_state, ret));
}
check_stack(state, 3)?;
protect_lua!(state, 0, 1, |state| error_traceback_thread(
state,
thread_state
))?;
return Err(pop_error(lua.state, ret));
return Err(pop_error(state, ret));
}
let mut results = args; // Reuse MultiValue container
check_stack(lua.state, nresults + 2)?; // 2 is extra for `lua.pop_value()` below
ffi::lua_xmove(thread_state, lua.state, nresults);
check_stack(state, nresults + 2)?; // 2 is extra for `lua.pop_value()` below
ffi::lua_xmove(thread_state, state, nresults);
for _ in 0..nresults {
results.push_front(lua.pop_value());
@@ -172,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.
@@ -194,33 +225,34 @@ impl<'lua> Thread<'lua> {
))]
pub fn reset(&self, func: Function<'lua>) -> Result<()> {
let lua = self.0.lua;
let state = lua.state();
unsafe {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, 2)?;
let _sg = StackGuard::new(state);
check_stack(state, 2)?;
lua.push_ref(&self.0);
let thread_state = ffi::lua_tothread(lua.state, -1);
let thread_state = ffi::lua_tothread(state, -1);
#[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, lua.state);
let status = ffi::lua_closethread(thread_state, state);
#[cfg(feature = "lua54")]
if status != ffi::LUA_OK {
return Err(pop_error(thread_state, status));
}
#[cfg(all(feature = "luajit", feature = "vendored"))]
ffi::lua_resetthread(lua.state, thread_state);
ffi::lua_resetthread(state, thread_state);
#[cfg(feature = "luau")]
ffi::lua_resetthread(thread_state);
lua.push_ref(&func.0);
ffi::lua_xmove(lua.state, thread_state, 1);
ffi::lua_xmove(state, thread_state, 1);
#[cfg(feature = "luau")]
{
// Inherit `LUA_GLOBALSINDEX` from the caller
ffi::lua_xpush(lua.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);
}
@@ -277,13 +309,13 @@ impl<'lua> Thread<'lua> {
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub fn into_async<A, R>(self, args: A) -> AsyncThread<'lua, R>
where
A: ToLuaMulti<'lua>,
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua>,
{
let args = args.to_lua_multi(self.0.lua);
let args = args.into_lua_multi(self.0.lua);
AsyncThread {
thread: self,
args0: RefCell::new(Some(args)),
args0: Some(args),
ret: PhantomData,
recycle: false,
}
@@ -325,14 +357,12 @@ impl<'lua> Thread<'lua> {
#[doc(hidden)]
pub fn sandbox(&self) -> Result<()> {
let lua = self.0.lua;
let state = lua.state();
unsafe {
let thread = ffi::lua_tothread(lua.ref_thread(), self.0.index);
check_stack(thread, 1)?;
check_stack(lua.state, 3)?;
// Inherit `LUA_GLOBALSINDEX` from the caller
ffi::lua_xpush(lua.state, thread, ffi::LUA_GLOBALSINDEX);
ffi::lua_replace(thread, ffi::LUA_GLOBALSINDEX);
protect_lua!(lua.state, 0, 0, |_| ffi::luaL_sandboxthread(thread))
check_stack(thread, 3)?;
check_stack(state, 3)?;
protect_lua!(state, 0, 0, |_| ffi::luaL_sandboxthread(thread))
}
}
}
@@ -367,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());
}
}
}
@@ -390,11 +423,14 @@ where
_ => return Poll::Ready(None),
};
let _wg = WakerGuard::new(lua, cx.waker().clone());
let ret: MultiValue = if let Some(args) = self.args0.borrow_mut().take() {
self.thread.resume(args?)?
let _wg = WakerGuard::new(lua, cx.waker());
// This is safe as we are not moving the whole struct
let this = unsafe { self.get_unchecked_mut() };
let ret: MultiValue = if let Some(args) = this.args0.take() {
this.thread.resume(args?)?
} else {
self.thread.resume(())?
this.thread.resume(())?
};
if is_poll_pending(&ret) {
@@ -421,18 +457,21 @@ where
_ => return Poll::Ready(Err(Error::CoroutineInactive)),
};
let _wg = WakerGuard::new(lua, cx.waker().clone());
let ret: MultiValue = if let Some(args) = self.args0.borrow_mut().take() {
self.thread.resume(args?)?
let _wg = WakerGuard::new(lua, cx.waker());
// This is safe as we are not moving the whole struct
let this = unsafe { self.get_unchecked_mut() };
let ret: MultiValue = if let Some(args) = this.args0.take() {
this.thread.resume(args?)?
} else {
self.thread.resume(())?
this.thread.resume(())?
};
if is_poll_pending(&ret) {
return Poll::Pending;
}
if let ThreadStatus::Resumable = self.thread.status() {
if let ThreadStatus::Resumable = this.thread.status() {
// Ignore value returned via yield()
cx.waker().wake_by_ref();
return Poll::Pending;
@@ -454,27 +493,39 @@ fn is_poll_pending(val: &MultiValue) -> bool {
}
#[cfg(feature = "async")]
struct WakerGuard<'lua> {
struct WakerGuard<'lua, 'a> {
lua: &'lua Lua,
prev: Option<Waker>,
prev: NonNull<Waker>,
_phantom: PhantomData<&'a ()>,
}
#[cfg(feature = "async")]
impl<'lua> WakerGuard<'lua> {
impl<'lua, 'a> WakerGuard<'lua, 'a> {
#[inline]
pub fn new(lua: &Lua, waker: Waker) -> Result<WakerGuard> {
pub fn new(lua: &'lua Lua, waker: &'a Waker) -> Result<WakerGuard<'lua, 'a>> {
unsafe {
let prev = lua.set_waker(Some(waker));
Ok(WakerGuard { lua, prev })
let prev = lua.set_waker(NonNull::from(waker));
Ok(WakerGuard {
lua,
prev,
_phantom: PhantomData,
})
}
}
}
#[cfg(feature = "async")]
impl<'lua> Drop for WakerGuard<'lua> {
impl<'lua, 'a> Drop for WakerGuard<'lua, 'a> {
fn drop(&mut self) {
unsafe {
self.lua.set_waker(self.prev.take());
self.lua.set_waker(self.prev);
}
}
}
#[cfg(test)]
mod assertions {
use super::*;
static_assertions::assert_not_impl_any!(Thread: Send);
}
+241 -10
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.
@@ -63,10 +70,10 @@ pub(crate) type HookCallback = Arc<dyn Fn(&Lua, Debug) -> Result<()> + Send>;
pub(crate) type HookCallback = Arc<dyn Fn(&Lua, Debug) -> Result<()>>;
#[cfg(all(feature = "luau", feature = "send"))]
pub(crate) type InterruptCallback = Arc<dyn Fn() -> Result<VmState> + Send>;
pub(crate) type InterruptCallback = Arc<dyn Fn(&Lua) -> Result<VmState> + Send>;
#[cfg(all(feature = "luau", not(feature = "send")))]
pub(crate) type InterruptCallback = Arc<dyn Fn() -> Result<VmState>>;
pub(crate) type InterruptCallback = Arc<dyn Fn(&Lua) -> Result<VmState>>;
#[cfg(all(feature = "send", feature = "lua54"))]
pub(crate) type WarnCallback = Box<dyn Fn(&Lua, &CStr, bool) -> Result<()> + Send>;
@@ -180,6 +187,26 @@ impl RegistryKey {
pub(crate) struct LuaRef<'lua> {
pub(crate) lua: &'lua Lua,
pub(crate) index: c_int,
pub(crate) drop: bool,
}
impl<'lua> LuaRef<'lua> {
pub(crate) const fn new(lua: &'lua Lua, index: c_int) -> Self {
LuaRef {
lua,
index,
drop: true,
}
}
#[cfg(feature = "unstable")]
#[inline]
pub(crate) fn into_owned(self) -> LuaOwnedRef {
assert!(self.drop, "Cannot turn non-drop reference into owned");
let owned_ref = LuaOwnedRef::new(self.lua.clone(), self.index);
mem::forget(self);
owned_ref
}
}
impl<'lua> fmt::Debug for LuaRef<'lua> {
@@ -196,8 +223,8 @@ impl<'lua> Clone for LuaRef<'lua> {
impl<'lua> Drop for LuaRef<'lua> {
fn drop(&mut self) {
if self.index > 0 {
self.lua.drop_ref(self);
if self.drop {
self.lua.drop_ref_index(self.index);
}
}
}
@@ -205,12 +232,216 @@ impl<'lua> Drop for LuaRef<'lua> {
impl<'lua> PartialEq for LuaRef<'lua> {
fn eq(&self, other: &Self) -> bool {
let lua = self.lua;
let state = lua.state();
unsafe {
let _sg = StackGuard::new(lua.state);
assert_stack(lua.state, 2);
let _sg = StackGuard::new(state);
assert_stack(state, 2);
lua.push_ref(self);
lua.push_ref(other);
ffi::lua_rawequal(lua.state, -1, -2) == 1
ffi::lua_rawequal(state, -1, -2) == 1
}
}
}
#[cfg(feature = "unstable")]
pub(crate) struct LuaOwnedRef {
pub(crate) inner: Arc<LuaInner>,
pub(crate) index: c_int,
_non_send: PhantomData<*const ()>,
}
#[cfg(feature = "unstable")]
impl fmt::Debug for LuaOwnedRef {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "OwnedRef({})", self.index)
}
}
#[cfg(feature = "unstable")]
impl Clone for LuaOwnedRef {
fn clone(&self) -> Self {
self.to_ref().clone().into_owned()
}
}
#[cfg(feature = "unstable")]
impl Drop for LuaOwnedRef {
fn drop(&mut self) {
let lua: &Lua = unsafe { mem::transmute(&self.inner) };
lua.drop_ref_index(self.index);
}
}
#[cfg(feature = "unstable")]
impl LuaOwnedRef {
pub(crate) const fn new(inner: Arc<LuaInner>, index: c_int) -> Self {
LuaOwnedRef {
inner,
index,
_non_send: PhantomData,
}
}
pub(crate) const fn to_ref(&self) -> LuaRef {
LuaRef {
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::*;
static_assertions::assert_impl_all!(RegistryKey: Send, Sync);
static_assertions::assert_not_impl_any!(LuaRef: Send);
#[cfg(feature = "unstable")]
static_assertions::assert_not_impl_any!(LuaOwnedRef: Send);
}
+421 -304
View File
File diff suppressed because it is too large Load Diff
+205
View File
@@ -0,0 +1,205 @@
use crate::error::{Error, Result};
use crate::private::Sealed;
use crate::userdata::{AnyUserData, MetaMethod};
use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, Value};
#[cfg(feature = "async")]
use futures_util::future::{self, LocalBoxFuture};
/// An extension trait for [`AnyUserData`] that provides a variety of convenient functionality.
pub trait AnyUserDataExt<'lua>: Sealed {
/// Gets the value associated to `key` from the userdata, assuming it has `__index` metamethod.
fn get<K: IntoLua<'lua>, V: FromLua<'lua>>(&self, key: K) -> Result<V>;
/// Sets the value associated to `key` in the userdata, assuming it has `__newindex` metamethod.
fn set<K: IntoLua<'lua>, V: IntoLua<'lua>>(&self, key: K, value: V) -> Result<()>;
/// Calls the userdata as a function assuming it has `__call` metamethod.
///
/// The metamethod is called with the userdata as its first argument, followed by the passed arguments.
fn call<A, R>(&self, args: A) -> Result<R>
where
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua>;
/// Asynchronously calls the userdata as a function assuming it has `__call` metamethod.
///
/// The metamethod is called with the userdata as its first argument, followed by the passed arguments.
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn call_async<'fut, A, R>(&self, args: A) -> LocalBoxFuture<'fut, Result<R>>
where
'lua: 'fut,
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua> + 'fut;
/// Calls the userdata method, assuming it has `__index` metamethod
/// and a function associated to `name`.
fn call_method<A, R>(&self, name: impl AsRef<str>, args: A) -> Result<R>
where
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua>;
/// Gets the function associated to `key` from the table and asynchronously executes it,
/// passing the table itself along with `args` as function arguments and returning Future.
///
/// Requires `feature = "async"`
///
/// This might invoke the `__index` metamethod.
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn call_async_method<'fut, A, R>(
&self,
name: impl AsRef<str>,
args: A,
) -> LocalBoxFuture<'fut, Result<R>>
where
'lua: 'fut,
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua> + 'fut;
/// Gets the function associated to `key` from the table and executes it,
/// passing `args` as function arguments.
///
/// This is a shortcut for
/// `table.get::<_, Function>(key)?.call(args)`
///
/// This might invoke the `__index` metamethod.
fn call_function<A, R>(&self, name: impl AsRef<str>, args: A) -> Result<R>
where
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua>;
/// Gets the function associated to `key` from the table and asynchronously executes it,
/// passing `args` as function arguments and returning Future.
///
/// Requires `feature = "async"`
///
/// This might invoke the `__index` metamethod.
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn call_async_function<'fut, A, R>(
&self,
name: impl AsRef<str>,
args: A,
) -> LocalBoxFuture<'fut, Result<R>>
where
'lua: 'fut,
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua> + 'fut;
}
impl<'lua> AnyUserDataExt<'lua> for AnyUserData<'lua> {
fn get<K: IntoLua<'lua>, V: FromLua<'lua>>(&self, key: K) -> Result<V> {
let metatable = self.get_metatable()?;
match metatable.get::<Value>(MetaMethod::Index)? {
Value::Table(table) => table.raw_get(key),
Value::Function(func) => func.call((self.clone(), key)),
_ => Err(Error::RuntimeError(
"attempt to index a userdata value".to_string(),
)),
}
}
fn set<K: IntoLua<'lua>, V: IntoLua<'lua>>(&self, key: K, value: V) -> Result<()> {
let metatable = self.get_metatable()?;
match metatable.get::<Value>(MetaMethod::NewIndex)? {
Value::Table(table) => table.raw_set(key, value),
Value::Function(func) => func.call((self.clone(), key, value)),
_ => Err(Error::RuntimeError(
"attempt to index a userdata value".to_string(),
)),
}
}
fn call<A, R>(&self, args: A) -> Result<R>
where
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua>,
{
let metatable = self.get_metatable()?;
match metatable.get::<Value>(MetaMethod::Call)? {
Value::Function(func) => func.call((self.clone(), args)),
_ => Err(Error::RuntimeError(
"attempt to call a userdata value".to_string(),
)),
}
}
#[cfg(feature = "async")]
fn call_async<'fut, A, R>(&self, args: A) -> LocalBoxFuture<'fut, Result<R>>
where
'lua: 'fut,
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua> + 'fut,
{
let metatable = match self.get_metatable() {
Ok(metatable) => metatable,
Err(err) => return Box::pin(future::err(err)),
};
match metatable.get::<Value>(MetaMethod::Call) {
Ok(Value::Function(func)) => func.call_async((self.clone(), args)),
Ok(_) => Box::pin(future::err(Error::RuntimeError(
"attempt to call a userdata value".to_string(),
))),
Err(err) => Box::pin(future::err(err)),
}
}
fn call_method<A, R>(&self, name: impl AsRef<str>, args: A) -> Result<R>
where
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua>,
{
self.call_function(name, (self.clone(), args))
}
#[cfg(feature = "async")]
fn call_async_method<'fut, A, R>(
&self,
name: impl AsRef<str>,
args: A,
) -> LocalBoxFuture<'fut, Result<R>>
where
'lua: 'fut,
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua> + 'fut,
{
self.call_async_function(name, (self.clone(), args))
}
fn call_function<A, R>(&self, name: impl AsRef<str>, args: A) -> Result<R>
where
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua>,
{
match self.get(name.as_ref())? {
Value::Function(func) => func.call(args),
val => Err(Error::RuntimeError(format!(
"attempt to call a {} value",
val.type_name()
))),
}
}
#[cfg(feature = "async")]
fn call_async_function<'fut, A, R>(
&self,
name: impl AsRef<str>,
args: A,
) -> LocalBoxFuture<'fut, Result<R>>
where
'lua: 'fut,
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua> + 'fut,
{
match self.get(name.as_ref()) {
Ok(Value::Function(func)) => func.call_async(args),
Ok(val) => Box::pin(future::err(Error::RuntimeError(format!(
"attempt to call a {} value",
val.type_name()
)))),
Err(err) => Box::pin(future::err(err)),
}
}
}
+464 -419
View File
File diff suppressed because it is too large Load Diff
+56 -41
View File
@@ -11,7 +11,9 @@ 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());
@@ -47,7 +49,6 @@ pub unsafe fn check_stack(state: *mut ffi::lua_State, amount: c_int) -> Result<(
pub struct StackGuard {
state: *mut ffi::lua_State,
top: c_int,
extra: c_int,
}
impl StackGuard {
@@ -59,17 +60,6 @@ impl StackGuard {
StackGuard {
state,
top: ffi::lua_gettop(state),
extra: 0,
}
}
// Similar to `new`, but checks and keeps `extra` elements from top of the stack on Drop.
#[inline]
pub unsafe fn new_extra(state: *mut ffi::lua_State, extra: c_int) -> StackGuard {
StackGuard {
state,
top: ffi::lua_gettop(state),
extra,
}
}
}
@@ -78,14 +68,11 @@ impl Drop for StackGuard {
fn drop(&mut self) {
unsafe {
let top = ffi::lua_gettop(self.state);
if top < self.top + self.extra {
if top < self.top {
mlua_panic!("{} too many stack values popped", self.top - top)
}
if top > self.top + self.extra {
if self.extra > 0 {
ffi::lua_rotate(self.state, self.top + 1, self.extra);
}
ffi::lua_settop(self.state, self.top + self.extra);
if top > self.top {
ffi::lua_settop(self.state, self.top);
}
}
}
@@ -104,8 +91,10 @@ pub unsafe fn protect_lua_call(
) -> Result<()> {
let stack_start = ffi::lua_gettop(state) - nargs;
ffi::lua_pushcfunction(state, error_traceback);
ffi::lua_pushcfunction(state, f);
MemoryState::relax_limit_with(state, || {
ffi::lua_pushcfunction(state, error_traceback);
ffi::lua_pushcfunction(state, f);
});
if nargs > 0 {
ffi::lua_rotate(state, stack_start + 1, 2);
}
@@ -162,8 +151,10 @@ where
let stack_start = ffi::lua_gettop(state) - nargs;
ffi::lua_pushcfunction(state, error_traceback);
ffi::lua_pushcfunction(state, do_call::<F, R>);
MemoryState::relax_limit_with(state, || {
ffi::lua_pushcfunction(state, error_traceback);
ffi::lua_pushcfunction(state, do_call::<F, R>);
});
if nargs > 0 {
ffi::lua_rotate(state, stack_start + 1, 2);
}
@@ -273,11 +264,7 @@ pub unsafe fn push_table(
}
// Uses 4 stack spaces, does not call checkstack.
pub unsafe fn rawset_field<S>(state: *mut ffi::lua_State, table: c_int, field: &S) -> Result<()>
where
S: AsRef<[u8]> + ?Sized,
{
let field = field.as_ref();
pub unsafe fn rawset_field(state: *mut ffi::lua_State, table: c_int, field: &str) -> Result<()> {
ffi::lua_pushvalue(state, table);
protect_lua!(state, 2, 0, |state| {
ffi::lua_pushlstring(state, field.as_ptr() as *const c_char, field.len());
@@ -415,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
}
@@ -431,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]
@@ -473,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);
@@ -523,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);
@@ -681,6 +687,13 @@ where
}
pub unsafe extern "C" fn error_traceback(state: *mut ffi::lua_State) -> c_int {
// Luau calls error handler for memory allocation errors, skip it
// See https://github.com/Roblox/luau/issues/880
#[cfg(feature = "luau")]
if MemoryState::limit_reached(state) {
return 0;
}
if ffi::lua_checkstack(state, 2) == 0 {
// If we don't have enough stack space to even check the error type, do
// nothing so we don't risk shadowing a rust panic.
@@ -869,7 +882,7 @@ pub unsafe fn init_error_registry(state: *mut ffi::lua_State) -> Result<()> {
// Depending on how the API is used and what error types scripts are given, it may
// be possible to make this consume arbitrary amounts of memory (for example, some
// kind of recursive error structure?)
let _ = write!(&mut (*err_buf), "{}", error);
let _ = write!(&mut (*err_buf), "{error}");
Ok(err_buf)
}
Some(WrappedFailure::Panic(Some(ref panic))) => {
@@ -880,9 +893,9 @@ pub unsafe fn init_error_registry(state: *mut ffi::lua_State) -> Result<()> {
ffi::lua_pop(state, 2);
if let Some(msg) = panic.downcast_ref::<&str>() {
let _ = write!(&mut (*err_buf), "{}", msg);
let _ = write!(&mut (*err_buf), "{msg}");
} else if let Some(msg) = panic.downcast_ref::<String>() {
let _ = write!(&mut (*err_buf), "{}", msg);
let _ = write!(&mut (*err_buf), "{msg}");
} else {
let _ = write!(&mut (*err_buf), "<panic>");
};
@@ -1031,7 +1044,7 @@ pub(crate) unsafe fn to_string(state: *mut ffi::lua_State, index: c_int) -> Stri
let v = ffi::lua_tovector(state, index);
mlua_debug_assert!(!v.is_null(), "vector is null");
let (x, y, z) = (*v, *v.add(1), *v.add(2));
format!("vector({},{},{})", x, y, z)
format!("vector({x},{y},{z})")
}
ffi::LUA_TSTRING => {
let mut size = 0;
@@ -1064,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"
);
}
}
+201 -15
View File
@@ -1,7 +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::{ptr, slice, str, vec};
use std::string::String as StdString;
use std::sync::Arc;
use std::{fmt, ptr, slice, str, vec};
#[cfg(feature = "serialize")]
use {
@@ -11,7 +15,6 @@ use {
};
use crate::error::{Error, Result};
use crate::ffi;
use crate::function::Function;
use crate::lua::Lua;
use crate::string::String;
@@ -19,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,
@@ -61,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",
@@ -93,7 +103,7 @@ impl<'lua> Value<'lua> {
match (self, other.as_ref()) {
(Value::Table(a), Value::Table(b)) => a.equals(b),
(Value::UserData(a), Value::UserData(b)) => a.equals(b),
_ => Ok(self == other.as_ref()),
(a, b) => Ok(a == b),
}
}
@@ -120,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> {
@@ -162,8 +300,7 @@ impl<'lua> Serialize for Value<'lua> {
Value::Boolean(b) => serializer.serialize_bool(*b),
#[allow(clippy::useless_conversion)]
Value::Integer(i) => serializer
.serialize_i64((*i).try_into().expect("cannot convert lua_Integer to i64")),
#[allow(clippy::useless_conversion)]
.serialize_i64((*i).try_into().expect("cannot convert Lua Integer to i64")),
Value::Number(n) => serializer.serialize_f64(*n),
#[cfg(feature = "luau")]
Value::Vector(x, y, z) => (x, y, z).serialize(serializer),
@@ -180,15 +317,34 @@ impl<'lua> Serialize for Value<'lua> {
}
/// Trait for types convertible to `Value`.
pub trait ToLua<'lua> {
pub trait IntoLua<'lua> {
/// Performs the conversion.
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>>;
fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>>;
}
/// Trait for types convertible from `Value`.
pub trait FromLua<'lua>: Sized {
/// Performs the conversion.
fn from_lua(lua_value: Value<'lua>, lua: &'lua Lua) -> Result<Self>;
fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> Result<Self>;
/// Performs the conversion for an argument (eg. function argument).
///
/// `i` is the argument index (position),
/// `to` is a function name that received the argument.
#[doc(hidden)]
fn from_lua_arg(
value: Value<'lua>,
i: usize,
to: Option<&str>,
lua: &'lua Lua,
) -> Result<Self> {
Self::from_lua(value, lua).map_err(|err| Error::BadArgument {
to: to.map(|s| s.to_string()),
pos: i,
name: None,
cause: Arc::new(err),
})
}
}
/// Multiple Lua values used for both argument passing and also for multiple return values.
@@ -203,8 +359,14 @@ impl<'lua> MultiValue<'lua> {
/// Similar to `new` but can return previously used container with allocated capacity.
#[inline]
pub(crate) fn new_or_cached(lua: &'lua Lua) -> MultiValue<'lua> {
lua.new_or_cached_multivalue()
pub(crate) fn new_or_pooled(lua: &'lua Lua) -> MultiValue<'lua> {
lua.new_multivalue_from_pool()
}
/// Clears and returns previously allocated multivalue container to the pool.
#[inline]
pub(crate) fn return_to_pool(multivalue: Self, lua: &Lua) {
lua.return_multivalue_to_pool(multivalue);
}
}
@@ -337,11 +499,11 @@ impl<'lua> MultiValue<'lua> {
/// Trait for types convertible to any number of Lua values.
///
/// This is a generalization of `ToLua`, allowing any number of resulting Lua values instead of just
/// one. Any type that implements `ToLua` will automatically implement this trait.
pub trait ToLuaMulti<'lua> {
/// This is a generalization of `IntoLua`, allowing any number of resulting Lua values instead of just
/// one. Any type that implements `IntoLua` will automatically implement this trait.
pub trait IntoLuaMulti<'lua> {
/// Performs the conversion.
fn to_lua_multi(self, lua: &'lua Lua) -> Result<MultiValue<'lua>>;
fn into_lua_multi(self, lua: &'lua Lua) -> Result<MultiValue<'lua>>;
}
/// Trait for types that can be created from an arbitrary number of Lua values.
@@ -356,4 +518,28 @@ pub trait FromLuaMulti<'lua>: Sized {
/// assigning values. Similarly, if not enough values are given, conversions should assume that
/// any missing values are nil.
fn from_lua_multi(values: MultiValue<'lua>, lua: &'lua Lua) -> Result<Self>;
/// Performs the conversion for a list of arguments.
///
/// `i` is an index (position) of the first argument,
/// `to` is a function name that received the arguments.
#[doc(hidden)]
#[inline]
fn from_lua_multi_args(
values: MultiValue<'lua>,
i: usize,
to: Option<&str>,
lua: &'lua Lua,
) -> Result<Self> {
let _ = (i, to);
Self::from_lua_multi(values, lua)
}
}
#[cfg(test)]
mod assertions {
use super::*;
static_assertions::assert_not_impl_any!(Value: Send);
static_assertions::assert_not_impl_any!(MultiValue: Send);
}
+3 -3
View File
@@ -1,8 +1,8 @@
[lua54_coverage]
features = "lua54,vendored,async,serialize,macros"
features = "lua54,vendored,async,serialize,macros,unstable"
[lua51_coverage]
features = "lua51,vendored,async,serialize,macros"
features = "lua51,vendored,async,serialize,macros,unstable"
[luau_coverage]
features = "luau,async,serialize,macros"
features = "luau,async,serialize,macros,unstable"
+71 -114
View File
@@ -1,18 +1,14 @@
#![cfg(feature = "async")]
use std::cell::Cell;
use std::rc::Rc;
use std::sync::{
atomic::{AtomicI64, AtomicU64, Ordering},
Arc,
};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex};
use std::time::Duration;
use futures_timer::Delay;
use futures_util::stream::TryStreamExt;
use mlua::{
Error, Function, Lua, LuaOptions, Result, StdLib, Table, TableExt, Thread, UserData,
AnyUserDataExt, Error, Function, Lua, LuaOptions, Result, StdLib, Table, TableExt, UserData,
UserDataMethods, Value,
};
@@ -30,6 +26,19 @@ async fn test_async_function() -> Result<()> {
Ok(())
}
#[tokio::test]
async fn test_async_function_wrap() -> Result<()> {
let lua = Lua::new();
let f = Function::wrap_async(|_, s: String| async move { Ok(s) });
lua.globals().set("f", f)?;
let res: String = lua.load(r#"f("hello")"#).eval_async().await?;
assert_eq!(res, "hello");
Ok(())
}
#[tokio::test]
async fn test_async_sleep() -> Result<()> {
let lua = Lua::new();
@@ -261,9 +270,27 @@ 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_cache_size(4);
let options = LuaOptions::new().thread_pool_size(4);
let lua = Lua::new_with(StdLib::ALL_SAFE, options)?;
let table = lua.create_table()?;
@@ -311,8 +338,8 @@ async fn test_async_table() -> Result<()> {
}
#[tokio::test]
async fn test_async_thread_cache() -> Result<()> {
let options = LuaOptions::new().thread_cache_size(4);
async fn test_async_thread_pool() -> Result<()> {
let options = LuaOptions::new().thread_pool_size(4);
let lua = Lua::new_with(StdLib::ALL_SAFE, options)?;
let error_f = lua.create_async_function(|_, ()| async move {
@@ -423,6 +450,14 @@ async fn test_async_userdata() -> Result<()> {
.exec_async()
.await?;
userdata.call_async_method("set_value", 24).await?;
let n: u64 = userdata.call_async_method("get_value", ()).await?;
assert_eq!(n, 24);
userdata.call_async_function("sleep", 15).await?;
#[cfg(not(any(feature = "lua51", feature = "luau")))]
assert_eq!(userdata.call_async::<_, String>(()).await?, "elapsed:24ms");
Ok(())
}
@@ -439,7 +474,7 @@ async fn test_async_thread_error() -> Result<()> {
let lua = Lua::new();
let result = lua
.load("function x(...) error(...) end x(...)")
.set_name("chunk")?
.set_name("chunk")
.call_async::<_, ()>(MyUserData)
.await;
assert!(
@@ -450,120 +485,42 @@ async fn test_async_thread_error() -> Result<()> {
Ok(())
}
#[cfg(all(feature = "unstable", not(feature = "send")))]
#[tokio::test]
async fn test_async_scope() -> Result<()> {
let ref lua = Lua::new();
async fn test_owned_async_call() -> Result<()> {
let lua = Lua::new();
let ref rc = Rc::new(Cell::new(0));
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);
let fut = lua.async_scope(|scope| async move {
let f = scope.create_async_function(move |_, n: u64| {
let rc2 = rc.clone();
async move {
rc2.set(42);
Delay::new(Duration::from_millis(n)).await;
assert_eq!(Rc::strong_count(&rc2), 2);
Ok(())
}
})?;
lua.globals().set("f", f.clone())?;
assert_eq!(Rc::strong_count(rc), 1);
let _ = f.call_async::<u64, ()>(10).await?;
assert_eq!(Rc::strong_count(rc), 1);
// Create future in partialy polled state (Poll::Pending)
let g = lua.create_thread(f)?;
g.resume::<u64, ()>(10)?;
lua.globals().set("g", g)?;
assert_eq!(Rc::strong_count(rc), 2);
Ok(())
});
assert_eq!(Rc::strong_count(rc), 1);
let _ = fut.await?;
assert_eq!(Rc::strong_count(rc), 1);
match lua
.globals()
.get::<_, Function>("f")?
.call_async::<_, ()>(10)
.await
{
Err(Error::CallbackError { ref cause, .. }) => match cause.as_ref() {
Error::CallbackDestructed => {}
e => panic!("expected `CallbackDestructed` error cause, got {:?}", e),
},
r => panic!("improper return for destructed function: {:?}", r),
};
match lua.globals().get::<_, Thread>("g")?.resume::<_, Value>(()) {
Err(Error::CallbackError { ref cause, .. }) => match cause.as_ref() {
Error::CallbackDestructed => {}
e => panic!("expected `CallbackDestructed` error cause, got {:?}", e),
},
r => panic!("improper return for destructed function: {:?}", r),
};
assert_eq!(hello.call_async::<_, String>("alex").await?, "hello, alex!");
Ok(())
}
#[tokio::test]
async fn test_async_scope_userdata() -> Result<()> {
#[derive(Clone)]
struct MyUserData(Arc<AtomicI64>);
async fn test_async_terminate() -> Result<()> {
let lua = Lua::new();
impl UserData for MyUserData {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_async_method("get_value", |_, data, ()| async move {
Delay::new(Duration::from_millis(10)).await;
Ok(data.0.load(Ordering::Relaxed))
});
methods.add_async_method("set_value", |_, data, n| async move {
Delay::new(Duration::from_millis(10)).await;
data.0.store(n, Ordering::Relaxed);
Ok(())
});
methods.add_async_function("sleep", |_, n| async move {
Delay::new(Duration::from_millis(n)).await;
Ok(format!("elapsed:{}ms", n))
});
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 ref lua = Lua::new();
let ref arc = Arc::new(AtomicI64::new(11));
lua.async_scope(|scope| async move {
let ud = scope.create_userdata(MyUserData(arc.clone()))?;
lua.globals().set("userdata", ud)?;
lua.load(
r#"
assert(userdata:get_value() == 11)
userdata:set_value(12)
assert(userdata.sleep(5) == "elapsed:5ms")
assert(userdata:get_value() == 12)
"#,
)
.exec_async()
.await
})
.await?;
assert_eq!(Arc::strong_count(arc), 1);
match lua.load("userdata:get_value()").exec_async().await {
Err(Error::CallbackError { ref cause, .. }) => match cause.as_ref() {
Error::CallbackDestructed => {}
e => panic!("expected `CallbackDestructed` error cause, got {:?}", e),
},
r => panic!("improper return for destructed userdata: {:?}", r),
};
let _ = tokio::time::timeout(Duration::from_millis(30), func.call_async::<_, ()>(())).await;
lua.gc_collect()?;
assert!(mutex.try_lock().is_ok());
Ok(())
}
+4 -5
View File
@@ -1,7 +1,7 @@
use std::fs;
use std::io;
use mlua::{Error, Lua, Result};
use mlua::{Lua, Result};
#[test]
fn test_chunk_path() -> Result<()> {
@@ -14,12 +14,11 @@ fn test_chunk_path() -> Result<()> {
return 321
"#,
)?;
let i: i32 = lua.load(&temp_dir.path().join("module.lua")).eval()?;
let i: i32 = lua.load(&*temp_dir.path().join("module.lua")).eval()?;
assert_eq!(i, 321);
match lua.load(&temp_dir.path().join("module2.lua")).exec() {
Err(Error::ExternalError(err))
if err.downcast_ref::<io::Error>().unwrap().kind() == io::ErrorKind::NotFound => {}
match lua.load(&*temp_dir.path().join("module2.lua")).exec() {
Err(err) if err.downcast_ref::<io::Error>().unwrap().kind() == io::ErrorKind::NotFound => {}
res => panic!("expected io::Error, got {:?}", res),
};
+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(())
}
+50
View File
@@ -0,0 +1,50 @@
use std::io;
use mlua::{Error, ErrorContext, Lua, Result};
#[test]
fn test_error_context() -> Result<()> {
let lua = Lua::new();
let func = lua.create_function(|_, ()| {
Err::<(), _>(Error::RuntimeError("runtime error".into())).context("some context")
})?;
lua.globals().set("func", func)?;
let msg = lua
.load("local _, err = pcall(func); return tostring(err)")
.eval::<String>()?;
assert!(msg.contains("some context"));
assert!(msg.contains("runtime error"));
let func2 = lua.create_function(|lua, ()| {
lua.globals()
.get::<_, String>("nonextant")
.with_context(|_| "failed to find global")
})?;
lua.globals().set("func2", func2)?;
let msg2 = lua
.load("local _, err = pcall(func2); return tostring(err)")
.eval::<String>()?;
assert!(msg2.contains("failed to find global"));
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(())
}
+150 -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();
@@ -126,7 +186,7 @@ fn test_function_info() -> Result<()> {
end
"#,
)
.set_name("source1")?
.set_name("source1")
.exec()?;
let function1 = globals.get::<_, Function>("function1")?;
@@ -135,35 +195,112 @@ 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(())
}
#[test]
fn test_function_wrap() -> Result<()> {
use mlua::Error;
let lua = Lua::new();
lua.globals()
.set("f", Function::wrap(|_, s: String| Ok(s)))?;
lua.load(r#"assert(f("hello") == "hello")"#).exec().unwrap();
let mut _i = false;
lua.globals().set(
"f",
Function::wrap_mut(move |lua, ()| {
_i = true;
lua.globals().get::<_, Function>("f")?.call::<_, ()>(())
}),
)?;
match lua.globals().get::<_, Function>("f")?.call::<_, ()>(()) {
Err(Error::CallbackError { ref cause, .. }) => match *cause.as_ref() {
Error::CallbackError { ref cause, .. } => match *cause.as_ref() {
Error::RecursiveMutCallback { .. } => {}
ref other => panic!("incorrect result: {other:?}"),
},
ref other => panic!("incorrect result: {other:?}"),
},
other => panic!("incorrect result: {other:?}"),
};
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(())
}
+13 -3
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();
@@ -173,7 +183,7 @@ fn test_interrupts() -> Result<()> {
let interrupts_count = Arc::new(AtomicU64::new(0));
let interrupts_count2 = interrupts_count.clone();
lua.set_interrupt(move || {
lua.set_interrupt(move |_| {
interrupts_count2.fetch_add(1, Ordering::Relaxed);
Ok(VmState::Continue)
});
@@ -195,7 +205,7 @@ fn test_interrupts() -> Result<()> {
//
let yield_count = Arc::new(AtomicU64::new(0));
let yield_count2 = yield_count.clone();
lua.set_interrupt(move || {
lua.set_interrupt(move |_| {
if yield_count2.fetch_add(1, Ordering::Relaxed) == 1 {
return Ok(VmState::Yield);
}
@@ -222,7 +232,7 @@ fn test_interrupts() -> Result<()> {
//
// Test errors in interrupts
//
lua.set_interrupt(|| Err(Error::RuntimeError("error from interrupt".into())));
lua.set_interrupt(|_| Err(Error::RuntimeError("error from interrupt".into())));
match f.call::<_, ()>(()) {
Err(Error::CallbackError { cause, .. }) => match *cause {
Error::RuntimeError(ref m) if m == "error from interrupt" => {}
+33 -5
View File
@@ -1,11 +1,7 @@
use std::sync::Arc;
use mlua::{GCMode, Lua, Result, UserData};
use mlua::{Error, GCMode, Lua, Result, UserData};
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
use mlua::Error;
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
#[test]
fn test_memory_limit() -> Result<()> {
let lua = Lua::new();
@@ -21,6 +17,15 @@ fn test_memory_limit() -> Result<()> {
.into_function()?;
f.call::<_, ()>(()).expect("should trigger no memory limit");
if cfg!(feature = "luajit") && cfg!(not(feature = "vendored")) {
// we don't support setting memory limit for non-vendored luajit
assert!(matches!(
lua.set_memory_limit(0),
Err(Error::MemoryLimitNotAvailable)
));
return Ok(());
}
lua.set_memory_limit(initial_memory + 10000)?;
match f.call::<_, ()>(()) {
Err(Error::MemoryError(_)) => {}
@@ -33,6 +38,29 @@ fn test_memory_limit() -> Result<()> {
Ok(())
}
#[test]
fn test_memory_limit_thread() -> Result<()> {
let lua = Lua::new();
let f = lua
.load("local t = {}; for i = 1,10000 do t[i] = i end")
.into_function()?;
if cfg!(feature = "luajit") && cfg!(not(feature = "vendored")) {
// we don't support setting memory limit for non-vendored luajit
return Ok(());
}
lua.set_memory_limit(lua.used_memory() + 10000)?;
let thread = lua.create_thread(f)?;
match thread.resume::<_, ()>(()) {
Err(Error::MemoryError(_)) => {}
something_else => panic!("did not trigger memory error: {:?}", something_else),
};
Ok(())
}
#[test]
fn test_gc_control() -> Result<()> {
let lua = Lua::new();
+2 -2
View File
@@ -1,8 +1,8 @@
[package]
name = "rust_module"
name = "test_module"
version = "0.0.0"
authors = ["Aleksandr Orlenko <zxteam@pm.me>"]
edition = "2018"
edition = "2021"
[lib]
crate-type = ["cdylib"]
+1 -1
View File
@@ -2,7 +2,7 @@
name = "module_loader"
version = "0.0.0"
authors = ["Aleksandr Orlenko <zxteam@pm.me>"]
edition = "2018"
edition = "2021"
[features]
lua54 = ["mlua/lua54"]
+5 -5
View File
@@ -8,7 +8,7 @@ fn test_module() -> Result<()> {
let lua = make_lua()?;
lua.load(
r#"
local mod = require("rust_module")
local mod = require("test_module")
assert(mod.sum(2,2) == 4)
"#,
)
@@ -20,8 +20,8 @@ fn test_module_multi() -> Result<()> {
let lua = make_lua()?;
lua.load(
r#"
local mod = require("rust_module")
local mod2 = require("rust_module.second")
local mod = require("test_module")
local mod2 = require("test_module.second")
assert(mod.check_userdata(mod2.userdata) == 123)
"#,
)
@@ -33,7 +33,7 @@ fn test_module_error() -> Result<()> {
let lua = make_lua()?;
lua.load(
r#"
local ok, err = pcall(require, "rust_module.error")
local ok, err = pcall(require, "test_module.error")
assert(not ok)
assert(string.find(tostring(err), "custom module error"))
"#,
@@ -55,7 +55,7 @@ fn test_module_from_thread() -> Result<()> {
local mod
local co = coroutine.create(function(a, b)
mod = require("rust_module")
mod = require("test_module")
assert(mod.sum(a, b) == a + b)
end)
+8 -8
View File
@@ -8,12 +8,12 @@ fn used_memory(lua: &Lua, _: ()) -> LuaResult<usize> {
Ok(lua.used_memory())
}
fn check_userdata(_: &Lua, ud: MyUserData) -> LuaResult<i32> {
Ok(ud.0)
fn check_userdata(_: &Lua, ud: LuaAnyUserData) -> LuaResult<i32> {
Ok(ud.borrow::<MyUserData>()?.0)
}
#[mlua::lua_module]
fn rust_module(lua: &Lua) -> LuaResult<LuaTable> {
fn test_module(lua: &Lua) -> LuaResult<LuaTable> {
let exports = lua.create_table()?;
exports.set("sum", lua.create_function(sum)?)?;
exports.set("used_memory", lua.create_function(used_memory)?)?;
@@ -26,14 +26,14 @@ struct MyUserData(i32);
impl LuaUserData for MyUserData {}
#[mlua::lua_module]
fn rust_module_second(lua: &Lua) -> LuaResult<LuaTable> {
#[mlua::lua_module(name = "test_module_second")]
fn test_module2(lua: &Lua) -> LuaResult<LuaTable> {
let exports = lua.create_table()?;
exports.set("userdata", lua.create_userdata(MyUserData(123))?)?;
exports.set("userdata", MyUserData(123))?;
Ok(exports)
}
#[mlua::lua_module]
fn rust_module_error(_: &Lua) -> LuaResult<LuaTable> {
Err("custom module error".to_lua_err())
fn test_module_error(_: &Lua) -> LuaResult<LuaTable> {
Err("custom module error".into_lua_err())
}
+104
View File
@@ -356,3 +356,107 @@ fn test_scope_nonstatic_userdata_drop() -> Result<()> {
Ok(())
}
#[test]
fn test_scope_userdata_ref() -> Result<()> {
let lua = Lua::new();
struct MyUserData(Cell<i64>);
impl UserData for MyUserData {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_method("inc", |_, data, ()| {
data.0.set(data.0.get() + 1);
Ok(())
});
methods.add_method("dec", |_, data, ()| {
data.0.set(data.0.get() - 1);
Ok(())
});
}
}
let data = MyUserData(Cell::new(1));
lua.scope(|scope| {
let ud = scope.create_userdata_ref(&data)?;
modify_userdata(&lua, ud)
})?;
assert_eq!(data.0.get(), 2);
Ok(())
}
#[test]
fn test_scope_userdata_ref_mut() -> Result<()> {
let lua = Lua::new();
struct MyUserData(i64);
impl UserData for MyUserData {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_method_mut("inc", |_, data, ()| {
data.0 += 1;
Ok(())
});
methods.add_method_mut("dec", |_, data, ()| {
data.0 -= 1;
Ok(())
});
}
}
let mut data = MyUserData(1);
lua.scope(|scope| {
let ud = scope.create_userdata_ref_mut(&mut data)?;
modify_userdata(&lua, ud)
})?;
assert_eq!(data.0, 2);
Ok(())
}
#[test]
fn test_scope_any_userdata_ref() -> Result<()> {
let lua = Lua::new();
lua.register_userdata_type::<Cell<i64>>(|reg| {
reg.add_method("inc", |_, data, ()| {
data.set(data.get() + 1);
Ok(())
});
reg.add_method("dec", |_, data, ()| {
data.set(data.get() - 1);
Ok(())
});
})?;
let data = Cell::new(1i64);
lua.scope(|scope| {
let ud = scope.create_any_userdata_ref(&data)?;
modify_userdata(&lua, ud)
})?;
assert_eq!(data.get(), 2);
Ok(())
}
fn modify_userdata(lua: &Lua, ud: AnyUserData) -> Result<()> {
let f: Function = lua
.load(
r#"
function(u)
u:inc()
u:dec()
u:inc()
end
"#,
)
.eval()?;
f.call(ud)?;
Ok(())
}
+70 -10
View File
@@ -1,6 +1,7 @@
#![cfg(feature = "serialize")]
use std::collections::HashMap;
use std::error::Error as StdError;
use mlua::{
DeserializeOptions, Error, Lua, LuaSerdeExt, Result as LuaResult, SerializeOptions, UserData,
@@ -9,7 +10,7 @@ use mlua::{
use serde::{Deserialize, Serialize};
#[test]
fn test_serialize() -> Result<(), Box<dyn std::error::Error>> {
fn test_serialize() -> Result<(), Box<dyn StdError>> {
#[derive(Serialize)]
struct MyUserData(i64, String);
@@ -115,7 +116,7 @@ fn test_serialize_in_scope() -> LuaResult<()> {
}
#[test]
fn test_serialize_failure() -> Result<(), Box<dyn std::error::Error>> {
fn test_serialize_failure() -> Result<(), Box<dyn StdError>> {
#[derive(Serialize)]
struct MyUserData(i64);
@@ -146,7 +147,7 @@ fn test_serialize_failure() -> Result<(), Box<dyn std::error::Error>> {
#[cfg(feature = "luau")]
#[test]
fn test_serialize_vector() -> Result<(), Box<dyn std::error::Error>> {
fn test_serialize_vector() -> Result<(), Box<dyn StdError>> {
let lua = Lua::new();
let globals = lua.globals();
@@ -235,7 +236,7 @@ fn test_to_value_enum() -> LuaResult<()> {
}
#[test]
fn test_to_value_with_options() -> Result<(), Box<dyn std::error::Error>> {
fn test_to_value_with_options() -> Result<(), Box<dyn StdError>> {
let lua = Lua::new();
let globals = lua.globals();
globals.set("null", lua.null())?;
@@ -305,7 +306,7 @@ fn test_to_value_with_options() -> Result<(), Box<dyn std::error::Error>> {
}
#[test]
fn test_from_value_nested_tables() -> Result<(), Box<dyn std::error::Error>> {
fn test_from_value_nested_tables() -> Result<(), Box<dyn StdError>> {
let lua = Lua::new();
let value = lua
@@ -335,7 +336,7 @@ fn test_from_value_nested_tables() -> Result<(), Box<dyn std::error::Error>> {
}
#[test]
fn test_from_value_struct() -> Result<(), Box<dyn std::error::Error>> {
fn test_from_value_struct() -> Result<(), Box<dyn StdError>> {
let lua = Lua::new();
#[derive(Deserialize, PartialEq, Debug)]
@@ -376,7 +377,7 @@ fn test_from_value_struct() -> Result<(), Box<dyn std::error::Error>> {
}
#[test]
fn test_from_value_newtype_struct() -> Result<(), Box<dyn std::error::Error>> {
fn test_from_value_newtype_struct() -> Result<(), Box<dyn StdError>> {
let lua = Lua::new();
#[derive(Deserialize, PartialEq, Debug)]
@@ -389,7 +390,7 @@ fn test_from_value_newtype_struct() -> Result<(), Box<dyn std::error::Error>> {
}
#[test]
fn test_from_value_enum() -> Result<(), Box<dyn std::error::Error>> {
fn test_from_value_enum() -> Result<(), Box<dyn StdError>> {
let lua = Lua::new();
lua.globals().set("null", lua.null())?;
@@ -433,7 +434,7 @@ fn test_from_value_enum() -> Result<(), Box<dyn std::error::Error>> {
}
#[test]
fn test_from_value_enum_untagged() -> Result<(), Box<dyn std::error::Error>> {
fn test_from_value_enum_untagged() -> Result<(), Box<dyn StdError>> {
let lua = Lua::new();
lua.globals().set("null", lua.null())?;
@@ -473,7 +474,7 @@ fn test_from_value_enum_untagged() -> Result<(), Box<dyn std::error::Error>> {
}
#[test]
fn test_from_value_with_options() -> Result<(), Box<dyn std::error::Error>> {
fn test_from_value_with_options() -> Result<(), Box<dyn StdError>> {
let lua = Lua::new();
// Deny unsupported types by default
@@ -528,3 +529,62 @@ fn test_from_value_with_options() -> Result<(), Box<dyn std::error::Error>> {
Ok(())
}
#[test]
fn test_from_value_userdata() -> Result<(), Box<dyn StdError>> {
let lua = Lua::new();
// Tuple struct
#[derive(Serialize, Deserialize)]
struct MyUserData(i64, String);
impl UserData for MyUserData {}
let ud = lua.create_ser_userdata(MyUserData(123, "test userdata".into()))?;
match lua.from_value::<MyUserData>(Value::UserData(ud)) {
Ok(_) => {}
Err(err) => panic!("expected no errors, got {err:?}"),
};
// Newtype struct
#[derive(Serialize, Deserialize)]
struct NewtypeUserdata(String);
impl UserData for NewtypeUserdata {}
let ud = lua.create_ser_userdata(NewtypeUserdata("newtype userdata".into()))?;
match lua.from_value::<NewtypeUserdata>(Value::UserData(ud)) {
Ok(_) => {}
Err(err) => panic!("expected no errors, got {err:?}"),
};
// Option
#[derive(Serialize, Deserialize)]
struct UnitUserdata;
impl UserData for UnitUserdata {}
let ud = lua.create_ser_userdata(UnitUserdata)?;
match lua.from_value::<Option<()>>(Value::UserData(ud)) {
Ok(Some(_)) => {}
Ok(_) => panic!("expected `Some`, got `None`"),
Err(err) => panic!("expected no errors, got {err:?}"),
};
// Destructed userdata with skip option
let ud = lua.create_ser_userdata(NewtypeUserdata("newtype userdata".into()))?;
let _ = ud.take::<NewtypeUserdata>()?;
match lua.from_value_with::<()>(
Value::UserData(ud),
DeserializeOptions::new().deny_unsupported_types(false),
) {
Ok(_) => {}
Err(err) => panic!("expected no errors, got {err:?}"),
};
Ok(())
}
+34
View File
@@ -83,3 +83,37 @@ fn test_string_hash() -> Result<()> {
Ok(())
}
#[test]
fn test_string_debug() -> Result<()> {
let lua = Lua::new();
// Valid utf8
let s = lua.create_string("hello")?;
assert_eq!(format!("{s:?}"), r#""hello""#);
// Invalid utf8
let s = lua.create_string(b"hello\0world\r\n\t\xF0\x90\x80")?;
assert_eq!(format!("{s:?}"), r#"b"hello\0world\r\n\t\xf0\x90\x80""#);
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(())
}
+61
View File
@@ -152,6 +152,53 @@ fn test_table_push_pop() -> Result<()> {
Ok(())
}
#[test]
fn test_table_clear() -> Result<()> {
let lua = Lua::new();
// Check readonly error
#[cfg(feature = "luau")]
{
let t = lua.create_table()?;
t.set_readonly(true);
assert!(matches!(
t.clear(),
Err(Error::RuntimeError(err)) if err.contains("attempt to modify a readonly table")
));
}
let t = lua.create_table()?;
// Set array and hash parts
t.push("abc")?;
t.push("bcd")?;
t.set("a", "1")?;
t.set("b", "2")?;
t.clear()?;
assert_eq!(t.len()?, 0);
assert_eq!(t.pairs::<Value, Value>().count(), 0);
// Test table with metamethods
let t2 = lua
.load(
r#"
setmetatable({1, 2, 3, a = "1"}, {
__index = function() error("index error") end,
__newindex = function() error("newindex error") end,
__len = function() error("len error") end,
__pairs = function() error("pairs error") end,
})
"#,
)
.eval::<Table>()?;
assert_eq!(t2.raw_len(), 3);
t2.clear()?;
assert_eq!(t2.raw_len(), 0);
assert_eq!(t2.raw_get::<_, Value>("a")?, Value::Nil);
assert_ne!(t2.get_metatable(), None);
Ok(())
}
#[test]
fn test_table_sequence_from() -> Result<()> {
let lua = Lua::new();
@@ -345,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(())
}
+46 -25
View File
@@ -69,7 +69,7 @@ fn test_safety() -> Result<()> {
fn test_load() -> Result<()> {
let lua = Lua::new();
let func = lua.load("\treturn 1+2").into_function()?;
let func = lua.load("return 1+2").into_function()?;
let result: i32 = func.call(())?;
assert_eq!(result, 3);
@@ -308,7 +308,7 @@ fn test_error() -> Result<()> {
.exec()?;
let rust_error_function =
lua.create_function(|_, ()| -> Result<()> { Err(TestError.to_lua_err()) })?;
lua.create_function(|_, ()| -> Result<()> { Err(TestError.into_lua_err()) })?;
globals.set("rust_error_function", rust_error_function)?;
let no_error = globals.get::<_, Function>("no_error")?;
@@ -506,7 +506,7 @@ fn test_result_conversions() -> Result<()> {
let err = lua.create_function(|_, ()| {
Ok(Err::<String, _>(
"only through failure can we succeed".to_lua_err(),
"only through failure can we succeed".into_lua_err(),
))
})?;
let ok = lua.create_function(|_, ()| Ok(Ok::<_, Error>("!".to_owned())))?;
@@ -730,9 +730,9 @@ fn test_set_metatable_nil() -> Result<()> {
fn test_named_registry_value() -> Result<()> {
let lua = Lua::new();
lua.set_named_registry_value::<_, i32>("test", 42)?;
lua.set_named_registry_value::<i32>("test", 42)?;
let f = lua.create_function(move |lua, ()| {
assert_eq!(lua.named_registry_value::<_, i32>("test")?, 42);
assert_eq!(lua.named_registry_value::<i32>("test")?, 42);
Ok(())
})?;
@@ -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"]
@@ -1072,7 +1100,7 @@ fn test_chunk_env() -> Result<()> {
test_var = 1
"#,
)
.set_environment(env1.clone())?
.set_environment(env1.clone())
.exec()?;
lua.load(
@@ -1081,18 +1109,11 @@ fn test_chunk_env() -> Result<()> {
test_var = 2
"#,
)
.set_environment(env2.clone())?
.set_environment(env2.clone())
.exec()?;
assert_eq!(
lua.load("test_var").set_environment(env1)?.eval::<i32>()?,
1
);
assert_eq!(
lua.load("test_var").set_environment(env2)?.eval::<i32>()?,
2
);
assert_eq!(lua.load("test_var").set_environment(env1).eval::<i32>()?, 1);
assert_eq!(lua.load("test_var").set_environment(env2).eval::<i32>()?, 2);
Ok(())
}
@@ -1227,7 +1248,7 @@ fn test_inspect_stack() -> Result<()> {
assert(logline("world") == '[string "chunk"]:12 world')
"#,
)
.set_name("chunk")?
.set_name("chunk")
.exec()?;
Ok(())
+198 -51
View File
@@ -1,3 +1,5 @@
use std::collections::HashMap;
use std::string::String as StdString;
use std::sync::Arc;
#[cfg(not(feature = "parking_lot"))]
use std::sync::{Mutex, RwLock};
@@ -12,12 +14,12 @@ use std::{cell::RefCell, rc::Rc};
use std::sync::atomic::{AtomicI64, Ordering};
use mlua::{
AnyUserData, Error, ExternalError, Function, Lua, MetaMethod, Nil, Result, String, UserData,
UserDataFields, UserDataMethods, Value,
AnyUserData, AnyUserDataExt, Error, ExternalError, Function, Lua, MetaMethod, Nil, Result,
String, UserData, UserDataFields, UserDataMethods, UserDataRef, Value,
};
#[test]
fn test_user_data() -> Result<()> {
fn test_userdata() -> Result<()> {
struct UserData1(i64);
struct UserData2(Box<i64>);
@@ -100,20 +102,25 @@ fn test_metamethods() -> Result<()> {
methods.add_method("get", |_, data, ()| Ok(data.0));
methods.add_meta_function(
MetaMethod::Add,
|_, (lhs, rhs): (MyUserData, MyUserData)| Ok(MyUserData(lhs.0 + rhs.0)),
|_, (lhs, rhs): (UserDataRef<Self>, UserDataRef<Self>)| {
Ok(MyUserData(lhs.0 + rhs.0))
},
);
methods.add_meta_function(
MetaMethod::Sub,
|_, (lhs, rhs): (MyUserData, MyUserData)| Ok(MyUserData(lhs.0 - rhs.0)),
|_, (lhs, rhs): (UserDataRef<Self>, UserDataRef<Self>)| {
Ok(MyUserData(lhs.0 - rhs.0))
},
);
methods.add_meta_function(
MetaMethod::Eq,
|_, (lhs, rhs): (UserDataRef<Self>, UserDataRef<Self>)| Ok(lhs.0 == rhs.0),
);
methods.add_meta_function(MetaMethod::Eq, |_, (lhs, rhs): (MyUserData, MyUserData)| {
Ok(lhs.0 == rhs.0)
});
methods.add_meta_method(MetaMethod::Index, |_, data, index: String| {
if index.to_str()? == "inner" {
Ok(data.0)
} else {
Err("no such custom index".to_lua_err())
Err("no such custom index".into_lua_err())
}
});
#[cfg(any(
@@ -124,13 +131,14 @@ fn test_metamethods() -> Result<()> {
))]
methods.add_meta_method(MetaMethod::Pairs, |lua, data, ()| {
use std::iter::FromIterator;
let stateless_iter = lua.create_function(|_, (data, i): (MyUserData, i64)| {
let i = i + 1;
if i <= data.0 {
return Ok(mlua::Variadic::from_iter(vec![i, i]));
}
return Ok(mlua::Variadic::new());
})?;
let stateless_iter =
lua.create_function(|_, (data, i): (UserDataRef<Self>, i64)| {
let i = i + 1;
if i <= data.0 {
return Ok(mlua::Variadic::from_iter(vec![i, i]));
}
return Ok(mlua::Variadic::new());
})?;
Ok((stateless_iter, data.clone(), 0))
});
}
@@ -142,7 +150,9 @@ fn test_metamethods() -> Result<()> {
globals.set("userdata2", MyUserData(3))?;
globals.set("userdata3", MyUserData(3))?;
assert_eq!(
lua.load("userdata1 + userdata2").eval::<MyUserData>()?.0,
lua.load("userdata1 + userdata2")
.eval::<UserDataRef<MyUserData>>()?
.0,
10
);
@@ -166,7 +176,12 @@ fn test_metamethods() -> Result<()> {
)
.eval::<Function>()?;
assert_eq!(lua.load("userdata1 - userdata2").eval::<MyUserData>()?.0, 4);
assert_eq!(
lua.load("userdata1 - userdata2")
.eval::<UserDataRef<MyUserData>>()?
.0,
4
);
assert_eq!(lua.load("userdata1:get()").eval::<i64>()?, 7);
assert_eq!(lua.load("userdata2.inner").eval::<i64>()?, 3);
assert!(lua.load("userdata2.nonexist_field").eval::<()>().is_err());
@@ -305,21 +320,19 @@ fn test_userdata_take() -> Result<()> {
fn check_userdata_take(lua: &Lua, userdata: AnyUserData, rc: Arc<i64>) -> Result<()> {
lua.globals().set("userdata", userdata.clone())?;
assert_eq!(Arc::strong_count(&rc), 3);
let userdata_copy = userdata.clone();
{
let _value = userdata.borrow::<MyUserdata>()?;
// We should not be able to take userdata if it's borrowed
match userdata_copy.take::<MyUserdata>() {
match userdata.take::<MyUserdata>() {
Err(Error::UserDataBorrowMutError) => {}
r => panic!("expected `UserDataBorrowMutError` error, got {:?}", r),
}
}
let value = userdata_copy.take::<MyUserdata>()?;
let value = userdata.take::<MyUserdata>()?;
assert_eq!(*value.0, 18);
drop(value);
lua.gc_collect()?;
assert_eq!(Arc::strong_count(&rc), 1);
assert_eq!(Arc::strong_count(&rc), 2);
match userdata.borrow::<MyUserdata>() {
Err(Error::UserDataDestructed) => {}
@@ -332,6 +345,13 @@ fn test_userdata_take() -> Result<()> {
},
r => panic!("improper return for destructed userdata: {:?}", r),
}
drop(userdata);
lua.globals().raw_remove("userdata")?;
lua.gc_collect()?;
lua.gc_collect()?;
assert_eq!(Arc::strong_count(&rc), 1);
Ok(())
}
@@ -403,9 +423,9 @@ fn test_user_values() -> Result<()> {
ud.set_named_user_value("name", "alex")?;
ud.set_named_user_value("age", 10)?;
assert_eq!(ud.get_named_user_value::<_, String>("name")?, "alex");
assert_eq!(ud.get_named_user_value::<_, i32>("age")?, 10);
assert_eq!(ud.get_named_user_value::<_, Value>("nonexist")?, Value::Nil);
assert_eq!(ud.get_named_user_value::<String>("name")?, "alex");
assert_eq!(ud.get_named_user_value::<i32>("age")?, 10);
assert_eq!(ud.get_named_user_value::<Value>("nonexist")?, Value::Nil);
Ok(())
}
@@ -467,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;
@@ -478,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)?;
@@ -497,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)
@@ -519,35 +537,27 @@ 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()?;
match metatable.get::<_, Value>("__gc") {
match metatable.get::<Value>("__gc") {
Ok(_) => panic!("expected MetaMethodRestricted, got no error"),
Err(Error::MetaMethodRestricted(_)) => {}
Err(e) => panic!("expected MetaMethodRestricted, got {:?}", e),
@@ -561,14 +571,13 @@ fn test_metatable() -> Result<()> {
let mut methods = metatable
.pairs()
.into_iter()
.map(|kv: Result<(_, Value)>| Ok(kv?.0))
.collect::<Result<Vec<_>>>()?;
methods.sort_by_cached_key(|k| k.name().to_owned());
assert_eq!(methods, vec![MetaMethod::Index, "__type_name".into()]);
methods.sort();
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) {
@@ -576,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(())
}
@@ -591,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;
@@ -608,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)
"#,
@@ -623,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)
"#,
@@ -637,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)
"#,
@@ -663,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));
}
@@ -697,3 +723,124 @@ fn test_userdata_proxy() -> Result<()> {
)
.exec()
}
#[test]
fn test_any_userdata() -> Result<()> {
let lua = Lua::new();
lua.register_userdata_type::<StdString>(|reg| {
reg.add_method("get", |_, this, ()| Ok(this.clone()));
reg.add_method_mut("concat", |_, this, s: String| {
this.push_str(&s.to_string_lossy());
Ok(())
});
})?;
let ud = lua.create_any_userdata("hello".to_string())?;
assert_eq!(&*ud.borrow::<StdString>()?, "hello");
lua.globals().set("ud", ud)?;
lua.load(
r#"
assert(ud:get() == "hello")
ud:concat(", world")
assert(ud:get() == "hello, world")
"#,
)
.exec()
.unwrap();
Ok(())
}
#[test]
fn test_userdata_ext() -> Result<()> {
let lua = Lua::new();
#[derive(Clone, Copy)]
struct MyUserData(u32);
impl UserData for MyUserData {
fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) {
fields.add_field_method_get("n", |_, this| Ok(this.0));
fields.add_field_method_set("n", |_, this, val| {
this.0 = val;
Ok(())
});
}
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_meta_method(MetaMethod::Call, |_, _this, ()| Ok("called"));
methods.add_method_mut("add", |_, this, x: u32| {
this.0 += x;
Ok(())
});
}
}
let ud = lua.create_userdata(MyUserData(123))?;
assert_eq!(ud.get::<_, u32>("n")?, 123);
ud.set("n", 321)?;
assert_eq!(ud.get::<_, u32>("n")?, 321);
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:?}"),
}
assert_eq!(ud.call::<_, String>(())?, "called");
ud.call_method("add", 2)?;
assert_eq!(ud.get::<_, u32>("n")?, 323);
Ok(())
}
#[test]
fn test_userdata_method_errors() -> Result<()> {
struct MyUserData(i64);
impl UserData for MyUserData {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_method("get_value", |_, data, ()| Ok(data.0));
}
}
let lua = Lua::new();
let ud = lua.create_userdata(MyUserData(123))?;
let res = ud.call_function::<_, ()>("get_value", ());
let Err(Error::CallbackError { cause, .. }) = res else {
panic!("expected CallbackError, got {res:?}");
};
assert!(matches!(
&*cause,
Error::BadArgument {
to,
name,
..
} if to.as_deref() == Some("MyUserData.get_value") && name.as_deref() == Some("self")
));
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(())
}

Some files were not shown because too many files have changed in this diff Show More