Compare commits

...

159 Commits

Author SHA1 Message Date
Alex Orlenko 31efd0cf72 v0.10.5 2025-05-24 12:41:05 +01:00
Alex Orlenko 6b79e4cd76 Update CHANGELOG 2025-05-24 12:41:04 +01:00
Alex Orlenko ee2da72685 Revert default chunk name change 2025-05-24 12:40:58 +01:00
Alex Orlenko 6180a528a7 Revert = prefix from __mlua_async_poll 2025-05-24 12:30:32 +01:00
Alex Orlenko 69ef08d4d2 Switch back to Luau userdata destructors defined in mlua-sys v0.6 2025-05-24 11:35:25 +01:00
Alex Orlenko f7f04d5180 Revert mlua-sys to v0.6.8 2025-05-24 10:43:28 +01:00
Alex Orlenko e10623d658 Update __mlua_index/__mlua_newindex chunk names 2025-05-24 10:22:33 +01:00
Alex Orlenko c3ab89ba66 Fix tests 2025-05-24 10:22:26 +01:00
Alex Orlenko fa500639f0 Generate doc for lua_module macro using doc cfg instead of docsrs 2025-05-24 10:22:20 +01:00
Alex Orlenko 3d2574a855 Revert "Use c string literal where appropriate"
This reverts commit 46e949c184.
2025-05-24 10:19:19 +01:00
Alex Orlenko 003935db47 Revert "Change Lua library name constants from &str to *const c_char."
This reverts commit 272cfdb89b.
2025-05-24 10:18:55 +01:00
Alex Orlenko fbed171f90 Revert "Support 52-bit integers for Luau"
This reverts commit ef8b8e11ec.
2025-05-24 10:18:16 +01:00
Alex Orlenko 298d48f708 Update error message for UserDataRef 2025-05-06 10:24:53 +01:00
Alex Orlenko 4ff677f5fc v0.10.4 2025-05-05 15:43:00 +01:00
Alex Orlenko 5b920321dd mlua-sys: v0.7.0 2025-05-05 13:25:55 +01:00
Alex Orlenko 6bb99783e9 Update CHANGELOG 2025-05-05 13:16:26 +01:00
Alex Orlenko 547f82acc1 Bump TARGET_MLUA_LUAU_ABI_VERSION 2025-05-05 12:13:50 +01:00
Alex Orlenko 9ea98e3f1e Sync mlua-sys with the main branch 2025-05-05 12:02:13 +01:00
Alex Orlenko 0ad03bee98 Update README 2025-05-05 11:59:42 +01:00
Alex Orlenko ba7c331984 Update __mlua_async_poll chunk name 2025-05-05 01:02:15 +01:00
Alex Orlenko 53dea3166a Update Lua* dependencies 2025-05-05 01:01:57 +01:00
Alex Orlenko 129dec43ed Silence clippy false positives 2025-05-05 00:59:06 +01:00
Alex Orlenko a7f58e57ec Update scoped userdata mismatch tests 2025-05-05 00:59:00 +01:00
Alex Orlenko e6871525c4 Check that type passed to scoped userdata self argument is userdata.
If passed type is non-userdata we try to get a pointer (which will be null) that triggers an assertion.
Having a check also allow us to generate right error message.
Fixes #569
2025-05-05 00:58:46 +01:00
Alex Orlenko c4956dbf49 Make StateGuard automatically enabled inside callback_error_ext.
Remove manual usage of `StateGuard` in other places.
Closes #567
2025-05-05 00:57:23 +01:00
Alex Orlenko d0ea428e23 Add encode_empty_tables_as_array serialize option.
This will change the behaviour of encoding empty Lua tables into array instead of map.
2025-05-05 00:52:22 +01:00
Alex Orlenko 3e87d3ea9b Optimize Table::is_empty 2025-05-05 00:52:14 +01:00
krakow10 0f8bde4b4e Fix Reversed Comments & Typo (#560)
* Fix reversed comments

* Fix typos
2025-05-05 00:52:07 +01:00
Alex Orlenko b9c9dfbd03 mlua-sys: Update Luau to 0.671 2025-05-05 00:51:38 +01:00
Alex Orlenko 71824297f6 mlua-sys: Add definitions for Luau require library (since 0.669) 2025-05-05 00:50:39 +01:00
Alex Orlenko c0a8064228 Update references to mlua repo 2025-05-05 00:50:31 +01:00
Alex Orlenko 84efb8bf95 Fix warnings when testing documentation 2025-05-05 00:50:19 +01:00
Alex Orlenko cf687a6ac6 Make Lua::weak() method and WeakLua struct public.
This can be useful to prevent circular dependencies between Rust and Lua
or check that Lua instance is still alive.
2025-05-05 00:50:12 +01:00
Alex Orlenko ea85edb242 Update dependencies 2025-05-05 00:49:41 +01:00
Alex Orlenko 788175e0d6 Restrict access to Luau VM from UserData destructors.
It's unsafe to make almost any Lua calls when userdata destructor is running.
This can cause recursive GC run and crash.
See https://github.com/luau-lang/luau/pull/510 for some details.
2025-05-05 00:49:26 +01:00
Alex Orlenko 90ef25a6ee Prepare for custom (Luau) userdata destructors.
We need to add logic later to prevent calling any Lua functions when UserData destructor is running.
2025-05-05 00:49:17 +01:00
Alex Orlenko ac72f6acae Do not propagate collect_garbage flag when clonning Lua 2025-05-05 00:47:39 +01:00
Alex Orlenko 4444ac4ea3 Add AnyUserData::type_id method 2025-05-05 00:47:09 +01:00
Alex Orlenko 5f950e30ec Update Lua::poll_pending doc (still hidden) 2025-05-05 00:47:02 +01:00
Alex Orlenko 311aa5f6e1 Bump Luau to 0.667 2025-05-05 00:46:45 +01:00
Alex Orlenko 739d5f5262 Reduce stack operations when creating userdata 2025-05-05 00:46:28 +01:00
Alex Orlenko 375028e13f Add Chunk::name(), Chunk::environment() and Chunk::mode() functions.
They can be used to retrieve existing chunk params.
2025-05-05 00:46:20 +01:00
Alex Orlenko 46e949c184 Use c string literal where appropriate 2025-05-05 00:44:05 +01:00
Alex Orlenko 62b53e218c Move some userdata helpers from crate::util to crate::userdata::util 2025-05-05 00:43:12 +01:00
Alex Orlenko 0393406b9f Refactor userdata-wrappers feature.
Support borrowing underlying data in `UserDataRef` and `UserDataRefMut`.
2025-05-04 18:45:42 +01:00
Alex Orlenko 272cfdb89b Change Lua library name constants from &str to *const c_char.
It makes easier to pass them to Lua API functions.
2025-05-04 12:49:08 +01:00
Alex Orlenko 8e244a25ea Update test case 2025-05-04 12:48:47 +01:00
Alex Orlenko ef8b8e11ec Support 52-bit integers for Luau
Simply to float conversion (it actually never fails or goes out of range)
2025-05-04 12:48:34 +01:00
Alex Orlenko 2543414726 Fix clippy warnings 2025-05-04 12:47:30 +01:00
Alex Orlenko ee112568d6 Remove Roblox from references to Luau
Closes #537
2025-05-04 12:46:47 +01:00
Alex Orlenko 69d3ddec29 Remove (internal) borrow counter and use instead "locked" flag and strong reference counter 2025-05-04 12:46:35 +01:00
Alex Orlenko 5b0d811c5a Add Variadic to prelude 2025-05-04 12:31:13 +01:00
Alex Orlenko 19536db976 Add bstr/serde dependency if serialize feature flag is enabled 2025-05-04 12:30:52 +01:00
Alex Orlenko fc69551f87 Fix tests 2025-05-04 12:29:57 +01:00
Alex Orlenko a68b0b6905 Do not allow recursive warnings (Lua 5.4) 2025-05-04 12:29:51 +01:00
Alex Orlenko 1ec1cc9922 Fix tests 2025-05-04 12:28:40 +01:00
Joel Natividad fd68b033f0 Fix typos (#522)
* fix various typos in the codebase

* fix typos in CHANGELOG.md
2025-05-04 12:28:24 +01:00
Andrew Farkas c3817409a8 Fix version number in changelog (#521) 2025-05-04 12:28:04 +01:00
Alex Orlenko 863d8092d6 Imporove BorrowedStr/BorrowedBytes ergonomic.
Implement `FromLua` and `IntoLua` for these types to allow working with them directly.
2025-05-04 12:27:24 +01:00
Alex Orlenko 24b6ff3c20 Improve From/Into Lua char conversion 2025-05-04 12:18:28 +01:00
tk bc36261f5c impl FromLua/IntoLua for char (#516) 2025-05-04 12:18:22 +01:00
Alex Orlenko bcb9a4d220 Enable Thread::reset() for all Lua versions 2025-05-04 12:07:55 +01:00
Alex Orlenko 9caf3542d9 v0.10.3 2025-01-27 21:33:51 +00:00
Alex Orlenko cb45db05fa Update README/CHANGELOG 2025-01-27 21:33:24 +00:00
Alex Orlenko d1cb2a9a96 mlua-sys: v0.6.7 2025-01-27 21:20:47 +00:00
Alex Orlenko aa3f6ba46c Fix prototype of new Luau compiler options and methods 2025-01-27 21:19:31 +00:00
Alex Orlenko cc57bed4c8 Update Luau to 0.657 2025-01-27 14:19:40 +00:00
Radiant b5d38ab2e3 Set Default for LuaValue to be nil. (#512) 2025-01-19 23:52:58 +00:00
Alex Orlenko cd4091f64d Allow exhaustive match on Value.
It was not possible because `ValueRef` variant in `Value::Other` was private.
Closes #502 and #503
2024-12-10 23:37:19 +00:00
Alex Orlenko 91e069a77e Optimize (and simplify) protected mode for Rust function calls 2024-12-07 00:04:12 +00:00
Alex Orlenko cacd3dc70f Add Table::set_safeenv method (Luau) 2024-12-04 10:40:49 +00:00
Alex Orlenko 6f6cda0099 v0.10.2 2024-12-01 13:04:06 +00:00
Alex Orlenko d51ce86142 mlua-sys: v0.6.6 2024-12-01 12:56:39 +00:00
Alex Orlenko aa061bce6f mlua_derive: v0.10.1 2024-12-01 12:55:50 +00:00
Alex Orlenko 1c6b6ad801 Fix tests 2024-12-01 12:54:35 +00:00
Evie 031854fa2a Switch proc-macro-error to proc-macro-error2 (#493) 2024-11-30 22:37:40 +00:00
Alex Orlenko 5fd96c7908 Don't run GC finalizers on ref thread.
If this happen then we don't have full access to the ref thread stack.
Fixes #491
2024-11-30 01:00:31 +00:00
Alex Orlenko 7a3f19b857 Ensure that buffer with Luau compiled code is always freed 2024-11-29 13:36:51 +00:00
Alex Orlenko 55a5d7ef10 Protect Lua(u) during chunk loading if memory limit is enforced
Relates to #488
2024-11-27 00:34:21 +00:00
Alex Orlenko af31dbd180 Use c string literal in few places 2024-11-25 22:53:59 +00:00
Alex Orlenko 9ae3cb0a7c Add doc about possible chunk name prefixes 2024-11-24 12:00:06 +00:00
Alex Orlenko fc1c80c142 Mark Chunk::wrap as non-public 2024-11-22 20:03:17 +00:00
Alex Orlenko ee7ced6334 Add Chunk::wrap method 2024-11-22 14:40:52 +00:00
Alex Orlenko d8307d0e4c Reduce visibility of Wrapped* structs 2024-11-22 13:08:57 +00:00
Alex Orlenko bf9fcc5aca Simplify WrappedString 2024-11-22 13:06:50 +00:00
Alex Orlenko 7ce6b97da9 Add String::wrap method to wrap arbitrary AsRef<[u8]> 2024-11-22 11:48:20 +00:00
Alex Orlenko 4891a6ac10 Fix utf-8 test when bstr v1.11 switchted to LowerHex 2024-11-17 18:01:05 +00:00
Alex Orlenko 4ef0d583fc impl Send for UserDataTypeId (in send mode) 2024-11-16 01:56:18 +00:00
cos 30b0122f5d Make build script work with OpenBSD
On OpenBSD the pkg-config names takes a minimalist format. No dash, and
no separator between the major and minor version number. Build script is
thus adapted to have also these in their set of alt_probes.

Signed-off-by: Alex Orlenko <zxteam@protonmail.com>
2024-11-16 01:52:43 +00:00
cos c31c72076f Make build script work with FreeBSD
On FreeBSD the pkg-config names takes the format with a dash, rather
than without (e.g. lua-5.4, not lua5.4). Thus adapt build script to
iterate over an array of alt_probes until finding a match.

Signed-off-by: Alex Orlenko <zxteam@protonmail.com>
2024-11-16 01:49:02 +00:00
Alex Orlenko 3bfaee4ecc Delay "any" userdata metatable creation until first instance 2024-11-16 01:47:04 +00:00
Alex Orlenko 89b68e2a24 Remove generic from RawLua::push_userdata_metatable.
This should help to reduce amount of generated code.
2024-11-16 01:44:17 +00:00
Alex Orlenko cbf805f492 Avoid ptr->usize->ptr conversion to comply strict provenance 2024-11-15 21:47:19 +00:00
Alex Orlenko c926327a6a v0.10.1 2024-11-09 20:06:04 +00:00
Alex Orlenko 7c099500d0 mlua-sys: v0.6.5 2024-11-09 14:30:33 +00:00
Alex Orlenko 8c889cc353 Add String::display method 2024-11-09 14:24:43 +00:00
Alex Orlenko 958abd050e Update String::to_string_lossy doc 2024-11-09 14:10:45 +00:00
Alex Orlenko 92a8203e1c Fix formatting 2024-11-09 13:58:06 +00:00
Alex Orlenko b34b90eca3 Fix wrong formatting table with string keys that are numbers 2024-11-09 13:51:55 +00:00
Alex Orlenko 7aad0adcb4 Update links to luau.org 2024-11-09 12:48:15 +00:00
Alex Orlenko a3cd25db7a Support Luau 0.650 native vector library 2024-11-09 12:44:00 +00:00
Alex Orlenko a4bfeb7752 clippy 2024-11-09 12:38:05 +00:00
vhyrro 0fda512938 feat(table): improve pretty-printing for simple tables and lists (#478) 2024-11-09 12:08:49 +00:00
Alex Orlenko 58e0661086 Merge Scope::attach_destructor into Scope::seal_userdata 2024-11-08 15:04:46 +00:00
Alex Orlenko c7094d470f Add Scope::create_any_userdata to create Lua objects from any non-static Rust types. 2024-11-07 19:44:18 +00:00
Alex Orlenko a7d0691e10 Add AnyUserData::destroy method 2024-11-07 16:12:20 +00:00
Alex Orlenko 05778fbe6f Don't store and use wrong main Lua state in module mode (Lua 5.1/JIT only).
When mlua module is loaded from a non-main coroutine we store a reference to it to use later.
If the coroutine is destroyed by GC we can pass a wrong pointer to Lua that will trigger a segfault.
Instead, set main_state as Option and use current (active) state if needed.
Relates to #479
2024-11-04 15:48:22 +00:00
Alex Orlenko b34d67ec41 Update Luau to 0.650 (luau0-src 0.11.1) 2024-11-03 14:49:12 +00:00
Alex Orlenko 46ee7ea772 Update tarpaulin.toml 2024-11-03 12:54:21 +00:00
Alex Orlenko 15738dda1f Update tarpaulin.toml to include userdata-wrappers 2024-11-03 12:19:22 +00:00
Alex Orlenko 1f32754f05 Relax UserDataBorrowRef restrictions to allow recursive calls 2024-11-03 12:18:00 +00:00
Alex Orlenko c2eab173c5 Add userdata-wrappers feature
This feature allow to opt into `impl UserData` for `Rc<T>`/`Arc<T>`/`Rc<RefCell<T>>`/`Arc<Mutex<T>>` where `T: UserData`
Close #470
2024-11-03 11:48:47 +00:00
Alex Orlenko bb311349ec Switch between shared and exclusive lock for UserDataRef depending if T: Sync or not. 2024-11-02 10:38:25 +00:00
Alex Orlenko 928e1d9221 Revert &Scope to &mut Scope 2024-10-31 18:41:47 +00:00
Alex Orlenko 5b8681dcf2 Add Scope::add_destructor to attach custom destructors 2024-10-31 14:35:21 +00:00
Alex Orlenko 4e9a17707b Fix tests 2024-10-31 09:23:03 +00:00
Alex Orlenko 6066089cc1 Skip setting Send/Sync in non-send mode for UserDataCell 2024-10-30 23:32:13 +00:00
Alex Orlenko a8d5f23818 Add Lua::try_app_data_ref and Lua::try_app_data_mut 2024-10-30 15:22:55 +00:00
Alex Orlenko 5ec4e0338a Add From<Vec> and Into<Vec> support to MultiValue and Variadic types 2024-10-30 13:03:08 +00:00
Alex Orlenko d27d1365b5 Update v0.10 release notes (add breaking changes) 2024-10-30 00:42:31 +00:00
Alex Orlenko 76b896edcc Fix attaching __gc metamethod
Bug introdused in ddebf56
2024-10-29 22:56:29 +00:00
Alex Orlenko ddebf56b41 Defer metatable return on userdata creation until the end
Relates to #477
2024-10-29 21:22:34 +00:00
Alex Orlenko 4f56575e05 v0.10.0 2024-10-25 13:36:55 +02:00
Alex Orlenko 35fa76263e Update docs 2024-10-25 11:05:21 +02:00
Alex Orlenko 446d63a77e More tests 2024-10-23 15:50:58 +01:00
Alex Orlenko 8d8d521721 Add error-send feature flag 2024-10-23 10:40:25 +01:00
Alex Orlenko 0d31a1caa6 Rename Error::MemoryLimitNotAvailable to Error::MemoryControlNotAvailable 2024-10-22 22:44:52 +01:00
Alex Orlenko 3dc58cdfc9 Move Luau Vector type to top level 2024-10-22 22:41:26 +01:00
Alex Orlenko 5724b5f112 cargo fmt 2024-10-20 13:23:57 +01:00
Alex Orlenko d64d9719c6 Replace Either enum with implementation from either crate 2024-10-20 12:06:08 +01:00
Alex Orlenko 75475fc9a8 Add missing serde::{de, ser} top level comment 2024-10-20 11:52:13 +01:00
Alex Orlenko f8fe9246bb Bump TARGET_MLUA_LUAU_ABI_VERSION 2024-10-20 10:42:59 +01:00
Alex Orlenko 5c54361236 Keep stack in FromLuaMulti::from_stack_multi 2024-10-20 00:47:20 +01:00
Alex Orlenko ec227f9056 Optimize Table readonly check (Luau)
Optimize `Table::has_metatable` check.
2024-10-19 23:58:57 +01:00
Alex Orlenko 93a1a55aaa Update docs 2024-10-19 23:10:43 +01:00
Alex Orlenko a020b2b5b2 Remove functions deprecated in v0.9 2024-10-19 15:14:09 +01:00
Alex Orlenko 2c756e5958 Add back Lua::load_from_std_lib (with deprecated flag) 2024-10-19 15:13:42 +01:00
Alex Orlenko c702077028 More Lua values conversion tests 2024-10-19 15:08:51 +01:00
Alex Orlenko 08545224f4 clippy 2024-10-19 11:49:40 +01:00
Alex Orlenko e122f90837 More Either tests 2024-10-19 11:20:25 +01:00
Alex Orlenko c638d90b02 Fix test_inspect_stack 2024-10-19 00:11:05 +01:00
Alex Orlenko 930fd9c00f Fix Value::String::to_pointer for Lua < 5.4 2024-10-18 23:12:04 +01:00
Alex Orlenko 2a8db87132 Update Value tests 2024-10-18 22:50:48 +01:00
Alex Orlenko cbae4fe59c More async tests 2024-10-18 22:50:15 +01:00
Alex Orlenko c68e3c4f41 Some DebugStack improvements 2024-10-18 22:48:41 +01:00
Alex Orlenko 02d4ceff34 Make Thread::state non-const (private api) 2024-10-18 21:50:57 +01:00
Alex Orlenko 98339c57e6 Update userdata tests 2024-10-18 21:38:02 +01:00
Alex Orlenko 2331995e28 Update coverage ci options 2024-10-18 21:36:49 +01:00
Alex Orlenko c07bdce250 More scope tests 2024-10-17 17:03:31 +01:00
Alex Orlenko 084a85c3d8 Update error tests 2024-10-16 23:57:48 +01:00
Alex Orlenko 735aa22be9 Fix typo in chunk tests 2024-10-16 23:57:07 +01:00
Alex Orlenko 5479546b27 Update chunk tests 2024-10-16 21:54:22 +01:00
Alex Orlenko 179c54f297 Remove generic from Table::equals and Value::equals 2024-10-16 16:11:48 +01:00
Alex Orlenko f9ae4bf05f Update table tests 2024-10-16 16:00:01 +01:00
Alex Orlenko 9e16e18132 Update string tests 2024-10-16 15:59:23 +01:00
Alex Orlenko 7535a23fa2 Update function tests 2024-10-16 15:59:13 +01:00
Alex Orlenko 3787ff9e8c Optimize metatable pointer lookup for userdata (Luau) 2024-10-12 21:38:07 +01:00
Alex Orlenko 0a2a70c15a mlua-sys: v0.6.4 2024-10-12 21:32:08 +01:00
Alex Orlenko 81d7c81532 Update Luau to 0.647 2024-10-12 21:30:56 +01:00
91 changed files with 4960 additions and 2356 deletions
+2 -2
View File
@@ -6,7 +6,7 @@ jobs:
name: coverage
runs-on: ubuntu-latest
container:
image: xd009642/tarpaulin
image: xd009642/tarpaulin:develop-nightly
options: --security-opt seccomp=unconfined
steps:
- name: Checkout repository
@@ -14,7 +14,7 @@ jobs:
- name: Generate coverage report
run: |
cargo tarpaulin --out xml --tests --exclude-files benches/* --exclude-files mlua-sys/src/*/*
cargo +nightly tarpaulin --verbose --out xml --tests --exclude-files benches/* --exclude-files mlua-sys/src/*/*
- name: Upload report to codecov.io
uses: codecov/codecov-action@v4
+14 -14
View File
@@ -27,8 +27,8 @@ jobs:
- name: Build ${{ matrix.lua }} vendored
run: |
cargo build --features "${{ matrix.lua }},vendored"
cargo build --features "${{ matrix.lua }},vendored,async,serialize,macros"
cargo build --features "${{ matrix.lua }},vendored,async,serialize,macros,send"
cargo build --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow,userdata-wrappers"
cargo build --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow,userdata-wrappers,send"
shell: bash
- name: Build ${{ matrix.lua }} pkg-config
if: ${{ matrix.os == 'ubuntu-latest' }}
@@ -51,7 +51,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"
run: cargo build --target aarch64-apple-darwin --features "${{ matrix.lua }},vendored,async,send,serialize,macros,anyhow,userdata-wrappers"
build_aarch64_cross_ubuntu:
name: Cross-compile to aarch64-unknown-linux-gnu
@@ -72,7 +72,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"
run: cargo build --target aarch64-unknown-linux-gnu --features "${{ matrix.lua }},vendored,async,send,serialize,macros,anyhow,userdata-wrappers"
shell: bash
build_armv7_cross_ubuntu:
@@ -94,7 +94,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"
run: cargo build --target armv7-unknown-linux-gnueabihf --features "${{ matrix.lua }},vendored,async,send,serialize,macros,anyhow,userdata-wrappers"
shell: bash
test:
@@ -123,14 +123,14 @@ jobs:
- name: Run ${{ matrix.lua }} tests
run: |
cargo test --features "${{ matrix.lua }},vendored"
cargo test --features "${{ matrix.lua }},vendored,async,serialize,macros"
cargo test --features "${{ matrix.lua }},vendored,async,serialize,macros,send"
cargo test --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow,userdata-wrappers"
cargo test --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow,userdata-wrappers,send"
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" -- --ignored
TRYBUILD=overwrite cargo test --features "${{ matrix.lua }},vendored" --tests -- --ignored
TRYBUILD=overwrite cargo test --features "${{ matrix.lua }},vendored,async,send,serialize,macros" --tests -- --ignored
shell: bash
test_with_sanitizer:
@@ -154,8 +154,8 @@ jobs:
- uses: Swatinem/rust-cache@v2
- name: Run ${{ matrix.lua }} tests with address sanitizer
run: |
cargo test --tests --features "${{ matrix.lua }},vendored,async,serialize,macros" --target x86_64-unknown-linux-gnu -- --skip test_too_many_recursions
cargo test --tests --features "${{ matrix.lua }},vendored,async,serialize,macros,send" --target x86_64-unknown-linux-gnu -- --skip test_too_many_recursions
cargo test --tests --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow" --target x86_64-unknown-linux-gnu -- --skip test_too_many_recursions
cargo test --tests --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow,userdata-wrappers,send" --target x86_64-unknown-linux-gnu -- --skip test_too_many_recursions
shell: bash
env:
RUSTFLAGS: -Z sanitizer=address
@@ -181,7 +181,7 @@ jobs:
- uses: Swatinem/rust-cache@v2
- name: Run ${{ matrix.lua }} tests with forced memory limit
run: |
cargo test --tests --features "${{ matrix.lua }},vendored,async,send,serialize,macros"
cargo test --tests --features "${{ matrix.lua }},vendored,async,send,serialize,macros,anyhow,userdata-wrappers"
shell: bash
env:
RUSTFLAGS: --cfg=force_memory_limit
@@ -254,7 +254,7 @@ jobs:
- name: Run ${{ matrix.lua }} tests
run: |
cargo test --tests --features "${{ matrix.lua }},vendored"
cargo test --tests --features "${{ matrix.lua }},vendored,async,serialize,macros"
cargo test --tests --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow,userdata-wrappers"
rustfmt:
name: Rustfmt
@@ -281,4 +281,4 @@ jobs:
- uses: giraffate/clippy-action@v1
with:
reporter: 'github-pr-review'
clippy_flags: --features "${{ matrix.lua }},vendored,async,send,serialize,macros"
clippy_flags: --features "${{ matrix.lua }},vendored,async,send,serialize,macros,anyhow,userdata-wrappers"
+67 -4
View File
@@ -1,3 +1,66 @@
## v0.10.5 (May 24th, 2025)
- mlua-sys is back to 0.6.x (Luau 0.663)
- Reverted: Trigger abort when Luau userdata destructors are panic (requires new mlua-sys)
- Reverted: Added large (52bit) integers support for Luau (breaking change)
## v0.10.4 (May 5th, 2025)
_yanked_ because of semver-breaking changes
- Luau updated to 0.672
- New serde option `encode_empty_tables_as_array` to serialize empty tables as arrays
- Added `WeakLua` and `Lua::weak()` to create weak references to Lua state
- Trigger abort when Luau userdata destructors are panic (Luau GC does not support it)
- Added `AnyUserData::type_id()` method to get the type id of the userdata
- Added `Chunk::name()`, `Chunk::environment()` and `Chunk::mode()` functions
- Support borrowing underlying wrapped types for `UserDataRef` and `UserDataRefMut` (under `userdata-wrappers` feature)
- Added large (52bit) integers support for Luau
- Enable `serde` for `bstr` if `serialize` feature flag is enabled
- Recursive warnings (Lua 5.4) are no longer allowed
- Implemented `IntoLua`/`FromLua` for `BorrowedString` and `BorrowedBytes`
- Implemented `IntoLua`/`FromLua` for `char`
- Enable `Thread::reset()` for all Lua versions (limited support for 5.1-5.3)
- Bugfixes and improvements
## v0.10.3 (Jan 27th, 2025)
- Set `Default` for `Value` to be `Nil`
- Allow exhaustive match on `Value` (#502)
- Add `Table::set_safeenv` method (Luau)
## v0.10.2 (Dec 1st, 2024)
- Switch proc-macro-error to proc-macro-error2 (#493)
- Do not allow Lua to run GC finalizers on ref thread (#491)
- Fix chunks loading in Luau when memory limit is enforced (#488)
- Added `String::wrap` method to wrap arbitrary `AsRef<[u8]>` into `impl IntoLua`
- Better FreeBSD/OpenBSD support (thanks to cos)
- Delay "any" userdata metatable creation until first instance is created (#482)
- Reduce amount of generated code for `UserData` (less generics)
## v0.10.1 (Nov 9th, 2024)
- Minimal Luau updated to 0.650
- Added Luau native vector library support (this can change behavior if you use `vector` function!)
- Added Lua `String::display` method
- Improved pretty-printing for Lua tables (#478)
- Added `Scope::create_any_userdata` to create Lua objects from any non-`'static` Rust types
- Added `AnyUserData::destroy` method
- New `userdata-wrappers` feature to `impl UserData` for `Rc<T>`/`Arc<T>`/`Rc<RefCell<T>>`/`Arc<Mutex<T>>` (similar to v0.9)
- `UserDataRef` in `send` mode now uses shared lock if `T: Sync` (and exclusive lock otherwise)
- Added `Scope::add_destructor` to attach custom destructors
- Added `Lua::try_app_data_ref` and `Lua::try_app_data_mut` methods
- Added `From<Vec>` and `Into<Vec>` support to `MultiValue` and `Variadic` types
- Bug fixes and improvements (#477 #479)
## v0.10.0 (Oct 25th, 2024)
Changes since v0.10.0-rc.1
- Added `error-send` feature flag (disabled by default) to require `Send + Sync` for `Error`
- Some performance improvements
## v0.10.0-rc.1
- `Lua::scope` is back
@@ -266,7 +329,7 @@ Other:
## v0.8.0
Changes since 0.7.4
- Roblox Luau support
- Luau support
- Removed C glue
- Added async support to `__index` and `__newindex` metamethods
- Added `Function::info()` to get information about functions (#149).
@@ -316,7 +379,7 @@ Breaking changes:
## v0.8.0-beta.1
- Roblox Luau support
- Luau support
- Refactored ffi module. C glue is no longer required
- Added async support to `__index` and `__newindex` metamethods
@@ -429,7 +492,7 @@ Breaking changes:
- [**Breaking**] Removed `AnyUserData::has_metamethod()`
- Added `Thread::reset()` for luajit/lua54 to recycle threads.
It's possible to attach a new function to a thread (coroutine).
- Added `chunk!` macro support to load chunks of Lua code using the Rust tokenizer and optinally capturing Rust variables.
- Added `chunk!` macro support to load chunks of Lua code using the Rust tokenizer and optionally capturing Rust variables.
- Improved error reporting (`Error`'s `__tostring` method formats full stacktraces). This is useful in the module mode.
## v0.6.0-beta.1
@@ -485,7 +548,7 @@ Breaking changes:
- Lua 5.4 support with `MetaMethod::Close`.
- `lua53` feature is disabled by default. Now preferred Lua version have to be chosen explicitly.
- Provide safety guaraness for Lua state, which means that potenially unsafe operations, like loading C modules (using `require` or `package.loadlib`) are disabled. Equalient for the previous `Lua::new()` function is `Lua::unsafe_new()`.
- Provide safety guarantees for Lua state, which means that potentially unsafe operations, like loading C modules (using `require` or `package.loadlib`) are disabled. Equivalent to the previous `Lua::new()` function is `Lua::unsafe_new()`.
- New `send` feature to require `Send`.
- New `module` feature, that disables linking to Lua Core Libraries. Required for modules.
- Don't allow `'callback` outlive `'lua` in `Lua::create_function()` to fix [the unsoundness](tests/compile/static_callback_args.rs).
+14 -10
View File
@@ -1,17 +1,17 @@
[package]
name = "mlua"
version = "0.10.0-rc.1" # remember to update mlua_derive
version = "0.10.5" # remember to update mlua_derive
authors = ["Aleksandr Orlenko <zxteam@pm.me>", "kyren <catherine@kyju.org>"]
rust-version = "1.79.0"
edition = "2021"
repository = "https://github.com/khvzak/mlua"
repository = "https://github.com/mlua-rs/mlua"
documentation = "https://docs.rs/mlua"
readme = "README.md"
keywords = ["lua", "luajit", "luau", "async", "scripting"]
categories = ["api-bindings", "asynchronous"]
license = "MIT"
description = """
High level bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT) and Roblox Luau
High level bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT) and Luau
with async/await features and support of writing native Lua modules in Rust.
"""
@@ -36,16 +36,19 @@ luau = ["ffi/luau", "dep:libloading"]
luau-jit = ["luau", "ffi/luau-codegen"]
luau-vector4 = ["luau", "ffi/luau-vector4"]
vendored = ["ffi/vendored"]
module = ["dep:mlua_derive", "ffi/module"]
module = ["mlua_derive", "ffi/module"]
async = ["dep:futures-util"]
send = ["parking_lot/send_guard"]
serialize = ["dep:serde", "dep:erased-serde", "dep:serde-value"]
send = ["parking_lot/send_guard", "error-send"]
error-send = []
serialize = ["dep:serde", "dep:erased-serde", "dep:serde-value", "bstr/serde"]
macros = ["mlua_derive/macros"]
anyhow = ["dep:anyhow"]
anyhow = ["dep:anyhow", "error-send"]
userdata-wrappers = []
[dependencies]
mlua_derive = { version = "=0.10.0-rc.1", optional = true, path = "mlua_derive" }
mlua_derive = { version = "=0.10.1", optional = true, path = "mlua_derive" }
bstr = { version = "1.0", features = ["std"], default-features = false }
either = "1.0"
num-traits = { version = "0.2.14" }
rustc-hash = "2.0"
futures-util = { version = "0.3", optional = true, default-features = false, features = ["std"] }
@@ -54,8 +57,9 @@ erased-serde = { version = "0.4", optional = true }
serde-value = { version = "0.7", optional = true }
parking_lot = { version = "0.12", features = ["arc_lock"] }
anyhow = { version = "1.0", optional = true }
rustversion = "1.0"
ffi = { package = "mlua-sys", version = "0.6.3", path = "mlua-sys" }
ffi = { package = "mlua-sys", version = "0.6.8", path = "mlua-sys" }
[target.'cfg(unix)'.dependencies]
libloading = { version = "0.8", optional = true }
@@ -75,7 +79,7 @@ static_assertions = "1.0"
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
criterion = { version = "0.5", features = ["async_tokio"] }
rustyline = "14.0"
rustyline = "15.0"
tokio = { version = "1.0", features = ["full"] }
[lints.rust]
+15 -15
View File
@@ -1,8 +1,8 @@
# mlua
[![Build Status]][github-actions] [![Latest Version]][crates.io] [![API Documentation]][docs.rs] [![Coverage Status]][codecov.io] ![MSRV]
[Build Status]: https://github.com/khvzak/mlua/workflows/CI/badge.svg
[github-actions]: https://github.com/khvzak/mlua/actions
[Build Status]: https://github.com/mlua-rs/mlua/workflows/CI/badge.svg
[github-actions]: https://github.com/mlua-rs/mlua/actions
[Latest Version]: https://img.shields.io/crates/v/mlua.svg
[crates.io]: https://crates.io/crates/mlua
[API Documentation]: https://docs.rs/mlua/badge.svg
@@ -17,23 +17,21 @@
[Benchmarks]: https://github.com/khvzak/script-bench-rs
[FAQ]: FAQ.md
# The main branch is the v0.10, development version of `mlua`. Please see the [v0.9](https://github.com/mlua-rs/mlua/tree/v0.9) branch for the stable versions of `mlua`.
> **Note**
>
> See (upcoming) v0.10 [release notes](https://github.com/khvzak/mlua/blob/main/docs/release_notes/v0.10.md).
> See v0.10 [release notes](https://github.com/mlua-rs/mlua/blob/main/docs/release_notes/v0.10.md).
`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.
Started as `rlua` fork, `mlua` supports Lua 5.4, 5.3, 5.2, 5.1 (including LuaJIT) and [Roblox Luau] and allows to write native Lua modules in Rust as well as use Lua in a standalone mode.
Started as `rlua` fork, `mlua` supports Lua 5.4, 5.3, 5.2, 5.1 (including LuaJIT) and [Luau] and allows to write native Lua modules in Rust as well as use Lua in a standalone mode.
`mlua` tested on Windows/macOS/Linux including module mode in [GitHub Actions] on `x86_64` platform and cross-compilation to `aarch64` (other targets are also supported).
WebAssembly (WASM) is supported through `wasm32-unknown-emscripten` target for all Lua versions excluding JIT.
WebAssembly (WASM) is supported through `wasm32-unknown-emscripten` target for all Lua/Luau versions excluding JIT.
[GitHub Actions]: https://github.com/khvzak/mlua/actions
[Roblox Luau]: https://luau-lang.org
[GitHub Actions]: https://github.com/mlua-rs/mlua/actions
[Luau]: https://luau.org
## Usage
@@ -55,18 +53,20 @@ Below is a list of the available feature flags. By default `mlua` does not enabl
* `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])
* `send`: make `mlua::Lua: Send + Sync` (adds [`Send`] requirement to `mlua::Function` and `mlua::UserData`)
* `error-send`: make `mlua:Error: Send + Sync`
* `serialize`: add serialization and deserialization support to `mlua` types using [serde] framework
* `macros`: enable procedural macros (such as `chunk!`)
* `anyhow`: enable `anyhow::Error` conversion into Lua
* `userdata-wrappers`: opt into `impl UserData` for `Rc<T>`/`Arc<T>`/`Rc<RefCell<T>>`/`Arc<Mutex<T>>` where `T: UserData`
[5.4]: https://www.lua.org/manual/5.4/manual.html
[5.3]: https://www.lua.org/manual/5.3/manual.html
[5.2]: https://www.lua.org/manual/5.2/manual.html
[5.1]: https://www.lua.org/manual/5.1/manual.html
[LuaJIT]: https://luajit.org/
[Luau]: https://github.com/Roblox/luau
[lua-src]: https://github.com/khvzak/lua-src-rs
[luajit-src]: https://github.com/khvzak/luajit-src-rs
[Luau]: https://github.com/luau-lang/luau
[lua-src]: https://github.com/mlua-rs/lua-src-rs
[luajit-src]: https://github.com/mlua-rs/luajit-src-rs
[tokio]: https://github.com/tokio-rs/tokio
[async-std]: https://github.com/async-rs/async-std
[`Send`]: https://doc.rust-lang.org/std/marker/trait.Send.html
@@ -133,7 +133,7 @@ Add to `Cargo.toml` :
``` toml
[dependencies]
mlua = { version = "0.10.0-rc.1", features = ["lua54", "vendored"] }
mlua = { version = "0.10", features = ["lua54", "vendored"] }
```
`main.rs`
@@ -168,7 +168,7 @@ Add to `Cargo.toml` :
crate-type = ["cdylib"]
[dependencies]
mlua = { version = "0.10.0-rc.1", features = ["lua54", "module"] }
mlua = { version = "0.10", features = ["lua54", "module"] }
```
`lib.rs` :
@@ -291,7 +291,7 @@ Please check the [Luau Sandboxing] page if you are interested in running untrust
`mlua` provides `Lua::sandbox` method for enabling sandbox mode (Luau only).
[Luau Sandboxing]: https://luau-lang.org/sandbox
[Luau Sandboxing]: https://luau.org/sandbox
## License
+73 -1
View File
@@ -3,7 +3,7 @@
The v0.10 version of mlua has goal to improve the user experience while keeping the same performance and safety guarantees.
This document highlights the most notable features. For a full list of changes, see the [CHANGELOG].
[CHANGELOG]: https://github.com/khvzak/mlua/blob/main/CHANGELOG.md
[CHANGELOG]: https://github.com/mlua-rs/mlua/blob/main/CHANGELOG.md
### New features
@@ -121,3 +121,75 @@ assert_eq!(sum, 15);
```
The `exec_raw` method is longjmp-safe. It's not recommended to move `Drop` types into the closure to avoid possible memory leaks.
#### `anyhow` feature flag
The new `anyhow` feature flag adds `IntoLua` and `Into<mlua::Error>` implementation for the `anyhow::Error` type.
```rust
let f = lua.create_function(|_, ()| {
Err(anyhow!("error message"))?;
Ok(())
})?;
```
### Breaking changes
#### Scope changes
The following `Scope` methods were changed:
- Removed `Scope::create_any_userdata`
- `Scope::create_nonstatic_userdata` is renamed to `Scope::create_userdata`
Instead, scope has comprehensive support for borrowed userdata: `create_any_userdata_ref`, `create_any_userdata_ref_mut`, `create_userdata_ref`, `create_userdata_ref_mut`.
`UserDataRef` and `UserDataRefMut` are no longer acceptable for scoped userdata access as they require owned underlying data.
In mlua v0.9 this can cause read-after-free bug in some edge cases.
To temporarily borrow underlying data, the `AnyUserData::borrow_scoped` and `AnyUserData::borrow_mut_scoped` methods were introduced:
```rust
let data = "hello".to_string();
lua.scope(|scope| {
let ud = scope.create_any_userdata_ref(&data)?;
// We can only borrow scoped userdata using this method
ud.borrow_scoped::<String, ()>(|s| {
assert_eq!(s, "hello");
})?;
Ok(())
})?;
```
Those methods work for scoped and regular userdata objects (but still require `T: 'static`).
#### String changes
Since `mlua::String` holds a weak reference to Lua without any guarantees about the lifetime of the underlying data, getting a `&str` or `&[u8]` from it is no longer safe.
Lua instance can be destroyed while reference to the data is still alive:
```rust
let lua = Lua::new();
let s: mlua::String = lua.create_string("hello, world")?; // only weak reference to Lua!
let s_ref: &str = s.to_str()?; // this is not safe!
drop(lua);
println!("{s_ref}"); // use after free!
```
To solve this issue, return types of `mlua::String::to_str` and `mlua::String::as_bytes` methods changed to `BorrowedStr` and `BorrowedBytes` respectively.
These new types hold a strong reference to the Lua instance and can be safely converted to `&str` or `&[u8]`:
```rust
let lua = Lua::new();
let s: mlua::String = lua.create_string("hello, world")?;
let s_ref: mlua::BorrowedStr = s.to_str()?; // The strong reference to Lua is held here
drop(lua);
println!("{s_ref}"); // ok
```
The good news is that `BorrowedStr` implements `Deref<Target = str>`/`AsRef<str>` as well as `Display`, `Debug`, `Eq`, `PartialEq` and other traits for easy usage.
The same applies to `BorrowedBytes`.
Unfortunately, `mlua::String::to_string_lossy` cannot return `Cow<'a, str>` anymore, because it requires a strong reference to Lua. It now returns Rust `String` instead.
+2 -2
View File
@@ -3,7 +3,7 @@
The v0.9 version of mlua is a major release that includes a number of API changes and improvements. This release is a stepping stone towards the v1.0.
This document highlights the most important changes. For a full list of changes, see the [CHANGELOG].
[CHANGELOG]: https://github.com/khvzak/mlua/blob/main/CHANGELOG.md
[CHANGELOG]: https://github.com/mlua-rs/mlua/blob/main/CHANGELOG.md
### New features
@@ -304,7 +304,7 @@ assert_eq!(f.call::<_, mlua::String>(())?, "hello");
The new mlua version has a number of performance improvements. Please check the [benchmarks results] to see how mlua compares to rlua and rhai.
[benchmarks results]: https://github.com/khvzak/script-bench-rs
[benchmarks results]: https://github.com/mlua-rs/script-bench-rs
### Changes in `module` mode
+4 -4
View File
@@ -1,10 +1,10 @@
[package]
name = "mlua-sys"
version = "0.6.3"
version = "0.6.8"
authors = ["Aleksandr Orlenko <zxteam@pm.me>"]
rust-version = "1.71"
edition = "2021"
repository = "https://github.com/khvzak/mlua"
repository = "https://github.com/mlua-rs/mlua"
documentation = "https://docs.rs/mlua-sys"
readme = "README.md"
categories = ["external-ffi-bindings"]
@@ -12,7 +12,7 @@ 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
Low level (FFI) bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT) and Luau
"""
[package.metadata.docs.rs]
@@ -40,7 +40,7 @@ cfg-if = "1.0"
pkg-config = "0.3.17"
lua-src = { version = ">= 547.0.0, < 547.1.0", optional = true }
luajit-src = { version = ">= 210.5.0, < 210.6.0", optional = true }
luau0-src = { version = "0.10.0", optional = true }
luau0-src = { version = "0.12.0", optional = true }
[lints.rust]
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(raw_dylib)'] }
+2 -2
View File
@@ -1,8 +1,8 @@
# mlua-sys
Low level (FFI) bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT) and Roblox [Luau].
Low level (FFI) bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT) and [Luau].
Intended to be consumed by the [mlua] crate.
[Luau]: https://github.com/Roblox/luau
[Luau]: https://github.com/luau-lang/luau
[mlua]: https://crates.io/crates/mlua
+19 -9
View File
@@ -32,15 +32,19 @@ pub fn probe_lua() {
// Find using `pkg-config`
#[cfg(feature = "lua54")]
let (incl_bound, excl_bound, alt_probe, ver) = ("5.4", "5.5", Some("lua5.4"), "5.4");
let (incl_bound, excl_bound, alt_probe, ver) =
("5.4", "5.5", ["lua5.4", "lua-5.4", "lua54"], "5.4");
#[cfg(feature = "lua53")]
let (incl_bound, excl_bound, alt_probe, ver) = ("5.3", "5.4", Some("lua5.3"), "5.3");
let (incl_bound, excl_bound, alt_probe, ver) =
("5.3", "5.4", ["lua5.3", "lua-5.3", "lua53"], "5.3");
#[cfg(feature = "lua52")]
let (incl_bound, excl_bound, alt_probe, ver) = ("5.2", "5.3", Some("lua5.2"), "5.2");
let (incl_bound, excl_bound, alt_probe, ver) =
("5.2", "5.3", ["lua5.2", "lua-5.2", "lua52"], "5.2");
#[cfg(feature = "lua51")]
let (incl_bound, excl_bound, alt_probe, ver) = ("5.1", "5.2", Some("lua5.1"), "5.1");
let (incl_bound, excl_bound, alt_probe, ver) =
("5.1", "5.2", ["lua5.1", "lua-5.1", "lua51"], "5.1");
#[cfg(feature = "luajit")]
let (incl_bound, excl_bound, alt_probe, ver) = ("2.0.4", "2.2", None, "JIT");
let (incl_bound, excl_bound, alt_probe, ver) = ("2.0.4", "2.2", [], "JIT");
#[rustfmt::skip]
let mut lua = pkg_config::Config::new()
@@ -48,10 +52,16 @@ pub fn probe_lua() {
.cargo_metadata(true)
.probe(if cfg!(feature = "luajit") { "luajit" } else { "lua" });
if lua.is_err() && alt_probe.is_some() {
lua = pkg_config::Config::new()
.cargo_metadata(true)
.probe(alt_probe.unwrap());
if lua.is_err() {
for pkg in alt_probe {
lua = pkg_config::Config::new()
.cargo_metadata(true)
.probe(pkg);
if lua.is_ok() {
break;
}
}
}
lua.unwrap_or_else(|err| panic!("cannot find Lua{ver} using `pkg-config`: {err}"));
+1 -1
View File
@@ -1,4 +1,4 @@
//! Low level bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT) and Roblox Luau.
//! Low level bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT) and Luau.
#![allow(non_camel_case_types, non_snake_case, dead_code)]
#![allow(clippy::missing_safety_doc)]
+1 -1
View File
@@ -548,7 +548,7 @@ pub unsafe fn luaL_getsubtable(L: *mut lua_State, idx: c_int, fname: *const c_ch
pub unsafe fn luaL_requiref(L: *mut lua_State, modname: *const c_char, openf: lua_CFunction, glb: c_int) {
luaL_checkstack(L, 3, cstr!("not enough stack slots available"));
luaL_getsubtable(L, LUA_REGISTRYINDEX, cstr!("_LOADED"));
luaL_getsubtable(L, LUA_REGISTRYINDEX, LUA_LOADED_TABLE);
if lua_getfield(L, -1, modname) == LUA_TNIL {
lua_pop(L, 1);
lua_pushcfunction(L, openf);
+3
View File
@@ -8,6 +8,9 @@ use super::lua::{self, lua_CFunction, lua_Integer, lua_Number, lua_State};
// Extra error code for 'luaL_load'
pub const LUA_ERRFILE: c_int = lua::LUA_ERRERR + 1;
// Key, in the registry, for table of loaded modules
pub const LUA_LOADED_TABLE: *const c_char = cstr!("_LOADED");
#[repr(C)]
pub struct luaL_Reg {
pub name: *const c_char,
+1 -1
View File
@@ -232,7 +232,7 @@ pub unsafe fn luaL_tolstring(L: *mut lua_State, mut idx: c_int, len: *mut usize)
pub unsafe fn luaL_requiref(L: *mut lua_State, modname: *const c_char, openf: lua_CFunction, glb: c_int) {
luaL_checkstack(L, 3, cstr!("not enough stack slots available"));
luaL_getsubtable(L, LUA_REGISTRYINDEX, cstr!("_LOADED"));
luaL_getsubtable(L, LUA_REGISTRYINDEX, LUA_LOADED_TABLE);
if lua_getfield(L, -1, modname) == LUA_TNIL {
lua_pop(L, 1);
lua_pushcfunction(L, openf);
+6
View File
@@ -8,6 +8,12 @@ use super::lua::{self, lua_CFunction, lua_Integer, lua_Number, lua_State, lua_Un
// Extra error code for 'luaL_load'
pub const LUA_ERRFILE: c_int = lua::LUA_ERRERR + 1;
// Key, in the registry, for table of loaded modules
pub const LUA_LOADED_TABLE: *const c_char = cstr!("_LOADED");
// Key, in the registry, for table of preloaded loaders
pub const LUA_PRELOAD_TABLE: *const c_char = cstr!("_PRELOAD");
#[repr(C)]
pub struct luaL_Reg {
pub name: *const c_char,
+14 -3
View File
@@ -1,4 +1,4 @@
//! MLua compatibility layer for Roblox Luau.
//! MLua compatibility layer for Luau.
//!
//! Based on github.com/keplerproject/lua-compat-5.3
@@ -326,12 +326,16 @@ pub unsafe fn luaL_loadbufferenv(
mut size: usize,
name: *const c_char,
mode: *const c_char,
env: c_int,
mut env: c_int,
) -> c_int {
extern "C" {
fn free(p: *mut c_void);
}
unsafe extern "C-unwind" fn data_dtor(data: *mut c_void) {
free(*(data as *mut *mut c_char) as *mut c_void);
}
let chunk_is_text = size == 0 || (*data as u8) >= b'\t';
if !mode.is_null() {
let modeb = CStr::from_ptr(mode).to_bytes();
@@ -345,9 +349,16 @@ pub unsafe fn luaL_loadbufferenv(
}
if chunk_is_text {
if env < 0 {
env -= 1;
}
let data_ud = lua_newuserdatadtor(L, mem::size_of::<*mut c_char>(), data_dtor) as *mut *mut c_char;
let data = luau_compile_(data, size, ptr::null_mut(), &mut size);
ptr::write(data_ud, data);
// By deferring the `free(data)` to the userdata destructor, we ensure that
// even if `luau_load` throws an error, the `data` is still released.
let ok = luau_load(L, name, data, size, env) == 0;
free(data as *mut c_void);
lua_replace(L, -2); // replace data with the result
if !ok {
return LUA_ERRSYNTAX;
}
+5
View File
@@ -185,6 +185,7 @@ extern "C-unwind" {
pub fn lua_pushlightuserdatatagged(L: *mut lua_State, p: *mut c_void, tag: c_int);
pub fn lua_newuserdatatagged(L: *mut lua_State, sz: usize, tag: c_int) -> *mut c_void;
pub fn lua_newuserdatataggedwithmetatable(L: *mut lua_State, sz: usize, tag: c_int) -> *mut c_void;
pub fn lua_newuserdatadtor(L: *mut lua_State, sz: usize, dtor: lua_Udestructor) -> *mut c_void;
pub fn lua_newbuffer(L: *mut lua_State, sz: usize) -> *mut c_void;
@@ -526,6 +527,9 @@ pub struct lua_Callbacks {
pub debuginterrupt: Option<unsafe extern "C-unwind" fn(L: *mut lua_State, ar: *mut lua_Debug)>,
/// gets called when protected call results in an error
pub debugprotectederror: Option<unsafe extern "C-unwind" fn(L: *mut lua_State)>,
/// gets called when memory is allocated
pub onallocate: Option<unsafe extern "C-unwind" fn(L: *mut lua_State, osize: usize, nsize: usize)>,
}
extern "C" {
@@ -535,4 +539,5 @@ extern "C" {
// Functions from customization lib
extern "C" {
pub fn luau_setfflag(name: *const c_char, value: c_int) -> c_int;
pub fn lua_getmetatablepointer(L: *mut lua_State, idx: c_int) -> *const c_void;
}
+51
View File
@@ -1,5 +1,6 @@
//! Contains definitions from `luacode.h`.
use std::marker::{PhantomData, PhantomPinned};
use std::os::raw::{c_char, c_int, c_void};
use std::{ptr, slice};
@@ -15,6 +16,10 @@ pub struct lua_CompileOptions {
pub vectorType: *const c_char,
pub mutableGlobals: *const *const c_char,
pub userdataTypes: *const *const c_char,
pub librariesWithKnownMembers: *const *const c_char,
pub libraryMemberTypeCallback: Option<lua_LibraryMemberTypeCallback>,
pub libraryMemberConstantCallback: Option<lua_LibraryMemberConstantCallback>,
pub disabledBuiltins: *const *const c_char,
}
impl Default for lua_CompileOptions {
@@ -29,10 +34,56 @@ impl Default for lua_CompileOptions {
vectorType: ptr::null(),
mutableGlobals: ptr::null(),
userdataTypes: ptr::null(),
librariesWithKnownMembers: ptr::null(),
libraryMemberTypeCallback: None,
libraryMemberConstantCallback: None,
disabledBuiltins: ptr::null(),
}
}
}
#[repr(C)]
pub struct lua_CompileConstant {
_data: [u8; 0],
_marker: PhantomData<(*mut u8, PhantomPinned)>,
}
/// Type table tags
#[doc(hidden)]
#[repr(i32)]
#[non_exhaustive]
pub enum luau_BytecodeType {
Nil = 0,
Boolean,
Number,
String,
Table,
Function,
Thread,
UserData,
Vector,
Buffer,
Any = 15,
}
pub type lua_LibraryMemberTypeCallback =
unsafe extern "C-unwind" fn(library: *const c_char, member: *const c_char) -> c_int;
pub type lua_LibraryMemberConstantCallback = unsafe extern "C-unwind" fn(
library: *const c_char,
member: *const c_char,
constant: *mut lua_CompileConstant,
);
extern "C" {
pub fn luau_set_compile_constant_nil(cons: *mut lua_CompileConstant);
pub fn luau_set_compile_constant_boolean(cons: *mut lua_CompileConstant, b: c_int);
pub fn luau_set_compile_constant_number(cons: *mut lua_CompileConstant, n: f64);
pub fn luau_set_compile_constant_vector(cons: *mut lua_CompileConstant, x: f32, y: f32, z: f32, w: f32);
pub fn luau_set_compile_constant_string(cons: *mut lua_CompileConstant, s: *const c_char, l: usize);
}
extern "C-unwind" {
#[link_name = "luau_compile"]
pub fn luau_compile_(
+2
View File
@@ -13,6 +13,7 @@ pub const LUA_BUFFERLIBNAME: &str = "buffer";
pub const LUA_UTF8LIBNAME: &str = "utf8";
pub const LUA_MATHLIBNAME: &str = "math";
pub const LUA_DBLIBNAME: &str = "debug";
pub const LUA_VECLIBNAME: &str = "vector";
extern "C-unwind" {
pub fn luaopen_base(L: *mut lua_State) -> c_int;
@@ -25,6 +26,7 @@ extern "C-unwind" {
pub fn luaopen_utf8(L: *mut lua_State) -> c_int;
pub fn luaopen_math(L: *mut lua_State) -> c_int;
pub fn luaopen_debug(L: *mut lua_State) -> c_int;
pub fn luaopen_vector(L: *mut lua_State) -> c_int;
// open all builtin libraries
pub fn luaL_openlibs(L: *mut lua_State);
+5 -5
View File
@@ -1,10 +1,10 @@
[package]
name = "mlua_derive"
version = "0.10.0-rc.1"
version = "0.10.1"
authors = ["Aleksandr Orlenko <zxteam@pm.me>"]
edition = "2021"
description = "Procedural macros for the mlua crate."
repository = "https://github.com/khvzak/mlua"
repository = "https://github.com/mlua-rs/mlua"
keywords = ["lua", "mlua"]
license = "MIT"
@@ -12,13 +12,13 @@ license = "MIT"
proc-macro = true
[features]
macros = ["proc-macro-error", "itertools", "regex", "once_cell"]
macros = ["proc-macro-error2", "itertools", "regex", "once_cell"]
[dependencies]
quote = "1.0"
proc-macro2 = { version = "1.0", features = ["span-locations"] }
proc-macro-error = { version = "1.0", optional = true }
proc-macro-error2 = { version = "2.0.1", optional = true }
syn = { version = "2.0", features = ["full"] }
itertools = { version = "0.13", optional = true }
itertools = { version = "0.14", optional = true }
regex = { version = "1.4", optional = true }
once_cell = { version = "1.0", optional = true }
+1 -1
View File
@@ -7,7 +7,7 @@ use syn::{parse_macro_input, ItemFn, LitStr, Result};
#[cfg(feature = "macros")]
use {
crate::chunk::Chunk, proc_macro::TokenTree, proc_macro2::TokenStream as TokenStream2,
proc_macro_error::proc_macro_error,
proc_macro_error2::proc_macro_error,
};
#[derive(Default)]
+1 -1
View File
@@ -74,7 +74,7 @@ fn parse_pos(span: &Span) -> Option<(usize, usize)> {
fn fallback_span_pos(span: &Span) -> (Pos, Pos) {
let (start, end) = match parse_pos(span) {
Some(v) => v,
None => proc_macro_error::abort_call_site!("Cannot retrieve span information; please use nightly"),
None => proc_macro_error2::abort_call_site!("Cannot retrieve span information; please use nightly"),
};
(Pos::new(1, start), Pos::new(1, end))
}
+62 -10
View File
@@ -2,6 +2,8 @@ use std::borrow::Cow;
use std::collections::HashMap;
use std::ffi::CString;
use std::io::Result as IoResult;
use std::marker::PhantomData;
use std::panic::Location;
use std::path::{Path, PathBuf};
use std::string::String as StdString;
@@ -9,14 +11,16 @@ use crate::error::{Error, Result};
use crate::function::Function;
use crate::state::{Lua, WeakLua};
use crate::table::Table;
use crate::traits::{FromLuaMulti, IntoLuaMulti};
use crate::traits::{FromLuaMulti, IntoLua, IntoLuaMulti};
use crate::value::Value;
/// 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<'a> {
/// Returns optional chunk name
///
/// See [`Chunk::set_name`] for possible name prefixes.
fn name(&self) -> Option<StdString> {
None
}
@@ -95,8 +99,6 @@ impl AsChunk<'static> for PathBuf {
}
/// Returned from [`Lua::load`] and is used to finalize loading and executing Lua main chunks.
///
/// [`Lua::load`]: crate::Lua::load
#[must_use = "`Chunk`s do nothing unless one of `exec`, `eval`, `call`, or `into_function` are called on them"]
pub struct Chunk<'a> {
pub(crate) lua: WeakLua,
@@ -241,7 +243,7 @@ impl Compiler {
/// Compiles the `source` into bytecode.
///
/// Returns `Error::SyntaxError` if the source code is invalid.
/// Returns [`Error::SyntaxError`] if the source code is invalid.
pub fn compile(&self, source: impl AsRef<[u8]>) -> Result<Vec<u8>> {
use std::os::raw::c_int;
use std::ptr;
@@ -304,13 +306,28 @@ impl Compiler {
}
}
impl<'a> Chunk<'a> {
impl Chunk<'_> {
/// Returns the name of this chunk.
pub fn name(&self) -> &str {
&self.name
}
/// Sets the name of this chunk, which results in more informative error traces.
///
/// Possible name prefixes:
/// - `@` - file path (when truncation is needed, the end of the file path is kept, as this is
/// more useful for identifying the file)
/// - `=` - custom chunk name (when truncation is needed, the beginning of the name is kept)
pub fn set_name(mut self, name: impl Into<String>) -> Self {
self.name = name.into();
self
}
/// Returns the environment of this chunk.
pub fn environment(&self) -> Option<&Table> {
self.env.as_ref().ok()?.as_ref()
}
/// Sets the environment of the loaded chunk to the given value.
///
/// In Lua >=5.2 main chunks always have exactly one upvalue, and this upvalue is used as the
@@ -327,6 +344,11 @@ impl<'a> Chunk<'a> {
self
}
/// Returns the mode (auto-detected by default) of this chunk.
pub fn mode(&self) -> ChunkMode {
self.detect_mode()
}
/// Sets whether the chunk is text or binary (autodetected by default).
///
/// Be aware, Lua does not check the consistency of the code inside binary chunks.
@@ -361,7 +383,7 @@ impl<'a> Chunk<'a> {
///
/// Requires `feature = "async"`
///
/// [`exec`]: #method.exec
/// [`exec`]: Chunk::exec
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub async fn exec_async(self) -> Result<()> {
@@ -393,7 +415,7 @@ impl<'a> Chunk<'a> {
///
/// Requires `feature = "async"`
///
/// [`eval`]: #method.eval
/// [`eval`]: Chunk::eval
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub async fn eval_async<R>(self) -> Result<R>
@@ -422,7 +444,7 @@ impl<'a> Chunk<'a> {
///
/// Requires `feature = "async"`
///
/// [`call`]: #method.call
/// [`call`]: Chunk::call
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub async fn call_async<R>(self, args: impl IntoLuaMulti) -> Result<R>
@@ -432,7 +454,7 @@ impl<'a> Chunk<'a> {
self.into_function()?.call_async(args).await
}
/// Load this chunk into a regular `Function`.
/// Load this chunk into a regular [`Function`].
///
/// This simply compiles the chunk without actually executing it.
#[cfg_attr(not(feature = "luau"), allow(unused_mut))]
@@ -564,3 +586,33 @@ impl<'a> Chunk<'a> {
buf
}
}
struct WrappedChunk<'a, T: AsChunk<'a>> {
chunk: T,
caller: &'static Location<'static>,
_marker: PhantomData<&'a T>,
}
impl<'a> Chunk<'a> {
/// Wraps a chunk of Lua code, returning an opaque type that implements [`IntoLua`] trait.
///
/// The resulted `IntoLua` implementation will convert the chunk into a Lua function without
/// executing it.
#[doc(hidden)]
#[track_caller]
pub fn wrap(chunk: impl AsChunk<'a> + 'a) -> impl IntoLua + 'a {
WrappedChunk {
chunk,
caller: Location::caller(),
_marker: PhantomData,
}
}
}
impl<'a, T: AsChunk<'a>> IntoLua for WrappedChunk<'a, T> {
fn into_lua(self, lua: &Lua) -> Result<Value> {
lua.load_with_location(self.chunk, self.caller)
.into_function()
.map(Value::Function)
}
}
+197 -12
View File
@@ -5,7 +5,7 @@ use std::hash::{BuildHasher, Hash};
use std::os::raw::c_int;
use std::path::{Path, PathBuf};
use std::string::String as StdString;
use std::{slice, str};
use std::{mem, slice, str};
use bstr::{BStr, BString, ByteSlice, ByteVec};
use num_traits::cast;
@@ -13,11 +13,11 @@ use num_traits::cast;
use crate::error::{Error, Result};
use crate::function::Function;
use crate::state::{Lua, RawLua};
use crate::string::String;
use crate::string::{BorrowedBytes, BorrowedStr, String};
use crate::table::Table;
use crate::thread::Thread;
use crate::traits::{FromLua, IntoLua, ShortTypeName as _};
use crate::types::{LightUserData, MaybeSend, RegistryKey};
use crate::types::{Either, LightUserData, MaybeSend, RegistryKey};
use crate::userdata::{AnyUserData, UserData};
use crate::value::{Nil, Value};
@@ -91,6 +91,94 @@ impl FromLua for String {
}
}
impl IntoLua for BorrowedStr<'_> {
#[inline]
fn into_lua(self, _: &Lua) -> Result<Value> {
Ok(Value::String(self.borrow.into_owned()))
}
#[inline]
unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> {
lua.push_ref(&self.borrow.0);
Ok(())
}
}
impl IntoLua for &BorrowedStr<'_> {
#[inline]
fn into_lua(self, _: &Lua) -> Result<Value> {
Ok(Value::String(self.borrow.clone().into_owned()))
}
#[inline]
unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> {
lua.push_ref(&self.borrow.0);
Ok(())
}
}
impl FromLua for BorrowedStr<'_> {
fn from_lua(value: Value, lua: &Lua) -> Result<Self> {
let s = String::from_lua(value, lua)?;
let BorrowedStr { buf, _lua, .. } = BorrowedStr::try_from(&s)?;
let buf = unsafe { mem::transmute::<&str, &'static str>(buf) };
let borrow = Cow::Owned(s);
Ok(Self { buf, borrow, _lua })
}
unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result<Self> {
let s = String::from_stack(idx, lua)?;
let BorrowedStr { buf, _lua, .. } = BorrowedStr::try_from(&s)?;
let buf = unsafe { mem::transmute::<&str, &'static str>(buf) };
let borrow = Cow::Owned(s);
Ok(Self { buf, borrow, _lua })
}
}
impl IntoLua for BorrowedBytes<'_> {
#[inline]
fn into_lua(self, _: &Lua) -> Result<Value> {
Ok(Value::String(self.borrow.into_owned()))
}
#[inline]
unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> {
lua.push_ref(&self.borrow.0);
Ok(())
}
}
impl IntoLua for &BorrowedBytes<'_> {
#[inline]
fn into_lua(self, _: &Lua) -> Result<Value> {
Ok(Value::String(self.borrow.clone().into_owned()))
}
#[inline]
unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> {
lua.push_ref(&self.borrow.0);
Ok(())
}
}
impl FromLua for BorrowedBytes<'_> {
fn from_lua(value: Value, lua: &Lua) -> Result<Self> {
let s = String::from_lua(value, lua)?;
let BorrowedBytes { buf, _lua, .. } = BorrowedBytes::from(&s);
let buf = unsafe { mem::transmute::<&[u8], &'static [u8]>(buf) };
let borrow = Cow::Owned(s);
Ok(Self { buf, borrow, _lua })
}
unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result<Self> {
let s = String::from_stack(idx, lua)?;
let BorrowedBytes { buf, _lua, .. } = BorrowedBytes::from(&s);
let buf = unsafe { mem::transmute::<&[u8], &'static [u8]>(buf) };
let borrow = Cow::Owned(s);
Ok(Self { buf, borrow, _lua })
}
}
impl IntoLua for Table {
#[inline]
fn into_lua(self, _: &Lua) -> Result<Value> {
@@ -243,14 +331,10 @@ impl IntoLua for Error {
impl FromLua for Error {
#[inline]
fn from_lua(value: Value, lua: &Lua) -> Result<Error> {
fn from_lua(value: Value, _: &Lua) -> Result<Error> {
match value {
Value::Error(err) => Ok(*err),
val => Ok(Error::runtime(
lua.coerce_string(val)?
.and_then(|s| Some(s.to_str().ok()?.to_owned()))
.unwrap_or_else(|| "<unprintable error>".to_owned()),
)),
val => Ok(Error::runtime(val.to_string()?)),
}
}
}
@@ -354,7 +438,7 @@ impl FromLua for LightUserData {
}
#[cfg(feature = "luau")]
impl IntoLua for crate::types::Vector {
impl IntoLua for crate::Vector {
#[inline]
fn into_lua(self, _: &Lua) -> Result<Value> {
Ok(Value::Vector(self))
@@ -362,7 +446,7 @@ impl IntoLua for crate::types::Vector {
}
#[cfg(feature = "luau")]
impl FromLua for crate::types::Vector {
impl FromLua for crate::Vector {
#[inline]
fn from_lua(value: Value, _: &Lua) -> Result<Self> {
match value {
@@ -659,6 +743,51 @@ impl IntoLua for &Path {
}
}
impl IntoLua for char {
#[inline]
fn into_lua(self, lua: &Lua) -> Result<Value> {
let mut char_bytes = [0; 4];
self.encode_utf8(&mut char_bytes);
Ok(Value::String(lua.create_string(&char_bytes[..self.len_utf8()])?))
}
}
impl FromLua for char {
fn from_lua(value: Value, _lua: &Lua) -> Result<Self> {
let ty = value.type_name();
match value {
Value::Integer(i) => {
cast(i)
.and_then(char::from_u32)
.ok_or_else(|| Error::FromLuaConversionError {
from: ty,
to: "char".to_string(),
message: Some("integer out of range when converting to char".to_string()),
})
}
Value::String(s) => {
let str = s.to_str()?;
let mut str_iter = str.chars();
match (str_iter.next(), str_iter.next()) {
(Some(char), None) => Ok(char),
_ => Err(Error::FromLuaConversionError {
from: ty,
to: "char".to_string(),
message: Some(
"expected string to have exactly one char when converting to char".to_string(),
),
}),
}
}
_ => Err(Error::FromLuaConversionError {
from: ty,
to: Self::type_name(),
message: Some("expected string or integer".to_string()),
}),
}
}
}
#[inline]
unsafe fn push_bytes_into_stack<T>(this: T, lua: &RawLua) -> Result<()>
where
@@ -852,7 +981,7 @@ where
match value {
#[cfg(feature = "luau")]
#[rustfmt::skip]
Value::Vector(v) if N == crate::types::Vector::SIZE => unsafe {
Value::Vector(v) if N == crate::Vector::SIZE => unsafe {
use std::{mem, ptr};
let mut arr: [mem::MaybeUninit<T>; N] = mem::MaybeUninit::uninit().assume_init();
ptr::write(arr[0].as_mut_ptr() , T::from_lua(Value::Number(v.x() as _), _lua)?);
@@ -1043,3 +1172,59 @@ impl<T: FromLua> FromLua for Option<T> {
}
}
}
impl<L: IntoLua, R: IntoLua> IntoLua for Either<L, R> {
#[inline]
fn into_lua(self, lua: &Lua) -> Result<Value> {
match self {
Either::Left(l) => l.into_lua(lua),
Either::Right(r) => r.into_lua(lua),
}
}
#[inline]
unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> {
match self {
Either::Left(l) => l.push_into_stack(lua),
Either::Right(r) => r.push_into_stack(lua),
}
}
}
impl<L: FromLua, R: FromLua> FromLua for Either<L, R> {
#[inline]
fn from_lua(value: Value, lua: &Lua) -> Result<Self> {
let value_type_name = value.type_name();
// Try the left type first
match L::from_lua(value.clone(), lua) {
Ok(l) => Ok(Either::Left(l)),
// Try the right type
Err(_) => match R::from_lua(value, lua).map(Either::Right) {
Ok(r) => Ok(r),
Err(_) => Err(Error::FromLuaConversionError {
from: value_type_name,
to: Self::type_name(),
message: None,
}),
},
}
}
#[inline]
unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result<Self> {
match L::from_stack(idx, lua) {
Ok(l) => Ok(Either::Left(l)),
Err(_) => match R::from_stack(idx, lua).map(Either::Right) {
Ok(r) => Ok(r),
Err(_) => {
let value_type_name = CStr::from_ptr(ffi::luaL_typename(lua.state(), idx));
Err(Error::FromLuaConversionError {
from: value_type_name.to_str().unwrap(),
to: Self::type_name(),
message: None,
})
}
},
}
}
}
+40 -18
View File
@@ -9,6 +9,12 @@ use std::sync::Arc;
use crate::private::Sealed;
#[cfg(feature = "error-send")]
type DynStdError = dyn StdError + Send + Sync;
#[cfg(not(feature = "error-send"))]
type DynStdError = dyn StdError;
/// Error type returned by `mlua` methods.
#[derive(Debug, Clone)]
#[non_exhaustive]
@@ -42,11 +48,11 @@ pub enum Error {
GarbageCollectorError(StdString),
/// Potentially unsafe action in safe mode.
SafetyError(StdString),
/// Setting memory limit is not available.
/// Memory control is not available.
///
/// This error can only happen when Lua state was not created by us and does not have the
/// custom allocator attached.
MemoryLimitNotAvailable,
MemoryControlNotAvailable,
/// A mutable callback has triggered Lua code that has called the same mutable callback again.
///
/// This is an error because a mutable callback can only be borrowed mutably once.
@@ -61,10 +67,12 @@ pub enum Error {
///
/// Due to the way `mlua` works, it should not be directly possible to run out of stack space
/// during normal use. The only way that this error can be triggered is if a `Function` is
/// called with a huge number of arguments, or a rust callback returns a huge number of return
/// called with a huge number of arguments, or a Rust callback returns a huge number of return
/// values.
StackError,
/// Too many arguments to `Function::bind`.
/// Too many arguments to [`Function::bind`].
///
/// [`Function::bind`]: crate::Function::bind
BindError,
/// Bad argument received from Lua (usually when calling a function).
///
@@ -189,7 +197,7 @@ pub enum Error {
/// Returning `Err(ExternalError(...))` from a Rust callback will raise the error as a Lua
/// 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>),
ExternalError(Arc<DynStdError>),
/// An error with additional context.
WithContext {
/// A string containing additional context.
@@ -218,8 +226,8 @@ impl fmt::Display for Error {
Error::SafetyError(msg) => {
write!(fmt, "safety error: {msg}")
},
Error::MemoryLimitNotAvailable => {
write!(fmt, "setting memory limit is not available")
Error::MemoryControlNotAvailable => {
write!(fmt, "memory control is not available")
}
Error::RecursiveMutCallback => write!(fmt, "mutable callback called recursively"),
Error::CallbackDestructed => write!(
@@ -309,7 +317,7 @@ impl fmt::Display for Error {
Error::DeserializeError(err) => {
write!(fmt, "deserialize error: {err}")
},
Error::ExternalError(err) => write!(fmt, "{err}"),
Error::ExternalError(err) => err.fmt(fmt),
Error::WithContext { context, cause } => {
writeln!(fmt, "{context}")?;
write!(fmt, "{cause}")
@@ -328,10 +336,7 @@ impl StdError for Error {
// returns nothing.
Error::CallbackError { .. } => None,
Error::ExternalError(err) => err.source(),
Error::WithContext { cause, .. } => match cause.as_ref() {
Error::ExternalError(err) => err.source(),
_ => None,
},
Error::WithContext { cause, .. } => Self::source(cause),
_ => None,
}
}
@@ -346,7 +351,7 @@ impl Error {
/// Wraps an external error object.
#[inline]
pub fn external<T: Into<Box<dyn StdError + Send + Sync>>>(err: T) -> Self {
pub fn external<T: Into<Box<DynStdError>>>(err: T) -> Self {
Error::ExternalError(err.into().into())
}
@@ -357,10 +362,7 @@ impl Error {
{
match self {
Error::ExternalError(err) => err.downcast_ref(),
Error::WithContext { cause, .. } => match cause.as_ref() {
Error::ExternalError(err) => err.downcast_ref(),
_ => None,
},
Error::WithContext { cause, .. } => Self::downcast_ref(cause),
_ => None,
}
}
@@ -373,6 +375,16 @@ impl Error {
}
}
/// Returns the parent of this error.
#[doc(hidden)]
pub fn parent(&self) -> Option<&Error> {
match self {
Error::CallbackError { cause, .. } => Some(cause.as_ref()),
Error::WithContext { cause, .. } => Some(cause.as_ref()),
_ => None,
}
}
pub(crate) fn bad_self_argument(to: &str, cause: Error) -> Self {
Error::BadArgument {
to: Some(to.to_string()),
@@ -400,7 +412,7 @@ pub trait ExternalError {
fn into_lua_err(self) -> Error;
}
impl<E: Into<Box<dyn StdError + Send + Sync>>> ExternalError for E {
impl<E: Into<Box<DynStdError>>> ExternalError for E {
fn into_lua_err(self) -> Error {
Error::external(self)
}
@@ -546,3 +558,13 @@ impl<'a> Iterator for Chain<'a> {
}
}
}
#[cfg(test)]
mod assertions {
use super::*;
#[cfg(not(feature = "error-send"))]
static_assertions::assert_not_impl_any!(Error: Send, Sync);
#[cfg(feature = "send")]
static_assertions::assert_impl_all!(Error: Send, Sync);
}
+7 -7
View File
@@ -146,7 +146,7 @@ impl Function {
/// Ok(())
/// })?;
///
/// sleep.call_async(10).await?;
/// sleep.call_async::<()>(10).await?;
///
/// # Ok(())
/// # }
@@ -389,9 +389,9 @@ impl Function {
/// If `strip` is true, the binary representation may not include all debug information
/// about the function, to save space.
///
/// For Luau a [Compiler] can be used to compile Lua chunks to bytecode.
/// For Luau a [`Compiler`] can be used to compile Lua chunks to bytecode.
///
/// [Compiler]: crate::chunk::Compiler
/// [`Compiler`]: crate::chunk::Compiler
#[cfg(not(feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
pub fn dump(&self, strip: bool) -> Vec<u8> {
@@ -490,10 +490,10 @@ impl Function {
///
/// Copies the function prototype and all its upvalues to the
/// newly created function.
///
/// This function returns shallow clone (same handle) for Rust/C functions.
///
/// Requires `feature = "luau"`
#[cfg(feature = "luau")]
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn deep_clone(&self) -> Self {
let lua = self.0.lua.lock();
@@ -509,10 +509,10 @@ impl Function {
}
}
pub(crate) struct WrappedFunction(pub(crate) Callback);
struct WrappedFunction(pub(crate) Callback);
#[cfg(feature = "async")]
pub(crate) struct WrappedAsyncFunction(pub(crate) AsyncCallback);
struct WrappedAsyncFunction(pub(crate) AsyncCallback);
impl Function {
/// Wraps a Rust function or closure, returning an opaque type that implements [`IntoLua`]
+14 -9
View File
@@ -16,9 +16,9 @@ use crate::util::{linenumber_to_usize, ptr_to_lossy_str, ptr_to_str};
/// The `Debug` structure is provided as a parameter to the hook function set with
/// [`Lua::set_hook`]. You may call the methods on this structure to retrieve information about the
/// Lua code executing at the time that the hook function was called. Further information can be
/// found in the Lua [documentation][lua_doc].
/// found in the Lua [documentation].
///
/// [lua_doc]: https://www.lua.org/manual/5.4/manual.html#lua_Debug
/// [documentation]: https://www.lua.org/manual/5.4/manual.html#lua_Debug
/// [`Lua::set_hook`]: crate::Lua::set_hook
pub struct Debug<'a> {
lua: EitherLua<'a>,
@@ -66,7 +66,7 @@ impl<'a> Debug<'a> {
/// Returns the specific event that triggered the hook.
///
/// For [Lua 5.1] `DebugEvent::TailCall` is used for return events to indicate a return
/// For [Lua 5.1] [`DebugEvent::TailCall`] is used for return events to indicate a return
/// from a function that did a tail call.
///
/// [Lua 5.1]: https://www.lua.org/manual/5.1/manual.html#pdf-LUA_HOOKTAILRET
@@ -184,8 +184,8 @@ impl<'a> Debug<'a> {
);
#[cfg(feature = "luau")]
mlua_assert!(
ffi::lua_getinfo(self.lua.state(), self.level, cstr!("a"), self.ar.get()) != 0,
"lua_getinfo failed with `a`"
ffi::lua_getinfo(self.lua.state(), self.level, cstr!("au"), self.ar.get()) != 0,
"lua_getinfo failed with `au`"
);
#[cfg(not(feature = "luau"))]
@@ -198,8 +198,8 @@ impl<'a> Debug<'a> {
};
#[cfg(feature = "luau")]
let stack = DebugStack {
num_ups: (*self.ar.get()).nupvals as i32,
num_params: (*self.ar.get()).nparams as i32,
num_ups: (*self.ar.get()).nupvals,
num_params: (*self.ar.get()).nparams,
is_vararg: (*self.ar.get()).isvararg != 0,
};
stack
@@ -262,10 +262,15 @@ pub struct DebugSource<'a> {
#[derive(Copy, Clone, Debug)]
pub struct DebugStack {
pub num_ups: i32,
/// Number of upvalues.
pub num_ups: u8,
/// Number of parameters.
///
/// Requires `feature = "lua54/lua53/lua52/luau"`
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))]
pub num_params: i32,
pub num_params: u8,
/// Whether the function is a vararg function.
///
/// Requires `feature = "lua54/lua53/lua52/luau"`
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))]
pub is_vararg: bool,
+20 -29
View File
@@ -32,41 +32,32 @@
//! [`serde::Serialize`] or [`serde::Deserialize`] can be converted.
//! For convenience, additional functionality to handle `NULL` values and arrays is provided.
//!
//! The [`Value`] enum implements [`serde::Serialize`] trait to support serializing Lua values
//! (including [`UserData`]) into Rust values.
//! The [`Value`] enum and other types implement [`serde::Serialize`] trait to support serializing
//! Lua values into Rust values.
//!
//! Requires `feature = "serialize"`.
//!
//! # Async/await support
//!
//! The [`create_async_function`] allows creating non-blocking functions that returns [`Future`].
//! Lua code with async capabilities can be executed by [`call_async`] family of functions or
//! polling [`AsyncThread`] using any runtime (eg. Tokio).
//! The [`Lua::create_async_function`] allows creating non-blocking functions that returns
//! [`Future`]. Lua code with async capabilities can be executed by [`Function::call_async`] family
//! of functions or polling [`AsyncThread`] using any runtime (eg. Tokio).
//!
//! Requires `feature = "async"`.
//!
//! # `Send` requirement
//! # `Send` and `Sync` support
//!
//! By default `mlua` is `!Send`. This can be changed by enabling `feature = "send"` that adds
//! `Send` requirement to [`Function`]s and [`UserData`].
//! `Send` requirement to Rust functions and [`UserData`] types.
//!
//! In this case [`Lua`] object and their types can be send or used from other threads. Internally
//! access to Lua VM is synchronized using a reentrant mutex that can be locked many times within
//! the same thread.
//!
//! [Lua programming language]: https://www.lua.org/
//! [`Lua`]: crate::Lua
//! [executing]: crate::Chunk::exec
//! [evaluating]: crate::Chunk::eval
//! [globals]: crate::Lua::globals
//! [`IntoLua`]: crate::IntoLua
//! [`FromLua`]: crate::FromLua
//! [`IntoLuaMulti`]: crate::IntoLuaMulti
//! [`FromLuaMulti`]: crate::FromLuaMulti
//! [`Function`]: crate::Function
//! [`UserData`]: crate::UserData
//! [`UserDataFields`]: crate::UserDataFields
//! [`UserDataMethods`]: crate::UserDataMethods
//! [`LuaSerdeExt`]: crate::LuaSerdeExt
//! [`Value`]: crate::Value
//! [`create_async_function`]: crate::Lua::create_async_function
//! [`call_async`]: crate::Function::call_async
//! [`AsyncThread`]: crate::AsyncThread
//! [`Future`]: std::future::Future
//! [`serde::Serialize`]: https://docs.serde.rs/serde/ser/trait.Serialize.html
//! [`serde::Deserialize`]: https://docs.serde.rs/serde/de/trait.Deserialize.html
@@ -74,6 +65,8 @@
// Deny warnings inside doc tests / examples. When this isn't present, rustdoc doesn't show *any*
// warnings at all.
#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(not(send), allow(clippy::arc_with_non_send_sync))]
#![allow(clippy::ptr_eq)]
#[macro_use]
mod macros;
@@ -99,6 +92,7 @@ mod types;
mod userdata;
mod util;
mod value;
mod vector;
pub mod prelude;
@@ -111,7 +105,7 @@ pub use crate::function::{Function, FunctionInfo};
pub use crate::hook::{Debug, DebugEvent, DebugNames, DebugSource, DebugStack};
pub use crate::multi::{MultiValue, Variadic};
pub use crate::scope::Scope;
pub use crate::state::{GCMode, Lua, LuaOptions};
pub use crate::state::{GCMode, Lua, LuaOptions, WeakLua};
pub use crate::stdlib::StdLib;
pub use crate::string::{BorrowedBytes, BorrowedStr, String};
pub use crate::table::{Table, TablePairs, TableSequence};
@@ -133,9 +127,10 @@ pub use crate::hook::HookTriggers;
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub use crate::{buffer::Buffer, chunk::Compiler, function::CoverageInfo, types::Vector};
pub use crate::{buffer::Buffer, chunk::Compiler, function::CoverageInfo, vector::Vector};
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub use crate::{thread::AsyncThread, traits::LuaNativeAsyncFn};
#[cfg(feature = "serialize")]
@@ -199,10 +194,6 @@ extern crate mlua_derive;
/// - The `//` (floor division) operator is unusable, as its start a comment.
///
/// Everything else should work.
///
/// [`AsChunk`]: crate::AsChunk
/// [`UserData`]: crate::UserData
/// [`IntoLua`]: crate::IntoLua
#[cfg(feature = "macros")]
#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
pub use mlua_derive::chunk;
@@ -219,7 +210,7 @@ pub use mlua_derive::FromLua;
///
/// You can register multiple entrypoints as required.
///
/// ```
/// ```ignore
/// use mlua::{Lua, Result, Table};
///
/// #[mlua::lua_module]
@@ -256,7 +247,7 @@ pub use mlua_derive::FromLua;
/// ...
/// }
/// ```
#[cfg(any(feature = "module", docsrs))]
#[cfg(all(feature = "mlua_derive", any(feature = "module", doc)))]
#[cfg_attr(docsrs, doc(cfg(feature = "module")))]
pub use mlua_derive::lua_module;
+1 -17
View File
@@ -1,5 +1,5 @@
use std::ffi::CStr;
use std::os::raw::{c_float, c_int};
use std::os::raw::c_int;
use crate::error::Result;
use crate::state::Lua;
@@ -11,7 +11,6 @@ impl Lua {
let globals = self.globals();
globals.raw_set("collectgarbage", self.create_c_function(lua_collectgarbage)?)?;
globals.raw_set("vector", self.create_c_function(lua_vector)?)?;
// Set `_VERSION` global to include version number
// The environment variable `LUAU_VERSION` set by the build script
@@ -65,21 +64,6 @@ unsafe extern "C-unwind" fn lua_collectgarbage(state: *mut ffi::lua_State) -> c_
}
}
// Luau vector datatype constructor
unsafe extern "C-unwind" fn lua_vector(state: *mut ffi::lua_State) -> c_int {
let x = ffi::luaL_checknumber(state, 1) as c_float;
let y = ffi::luaL_checknumber(state, 2) as c_float;
let z = ffi::luaL_checknumber(state, 3) as c_float;
#[cfg(feature = "luau-vector4")]
let w = ffi::luaL_checknumber(state, 4) as c_float;
#[cfg(not(feature = "luau-vector4"))]
ffi::lua_pushvector(state, x, y, z);
#[cfg(feature = "luau-vector4")]
ffi::lua_pushvector(state, x, y, z, w);
1
}
pub(crate) use package::register_package_module;
mod package;
+4 -4
View File
@@ -20,7 +20,7 @@ use {libloading::Library, rustc_hash::FxHashMap};
//
#[cfg(unix)]
const TARGET_MLUA_LUAU_ABI_VERSION: u32 = 1;
const TARGET_MLUA_LUAU_ABI_VERSION: u32 = 3;
#[cfg(all(unix, feature = "module"))]
#[no_mangle]
@@ -130,10 +130,10 @@ unsafe extern "C-unwind" fn lua_require(state: *mut ffi::lua_State) -> c_int {
for i in 1.. {
if ffi::lua_rawgeti(state, -1, i) == ffi::LUA_TNIL {
// no more loaders?
if (*err_buf).is_empty() {
if (&*err_buf).is_empty() {
ffi::luaL_error(state, cstr!("module '%s' not found"), name);
} else {
let bytes = (*err_buf).as_bytes();
let bytes = (&*err_buf).as_bytes();
let extra = ffi::lua_pushlstring(state, bytes.as_ptr() as *const _, bytes.len());
ffi::luaL_error(state, cstr!("module '%s' not found:%s"), name, extra);
}
@@ -203,7 +203,7 @@ fn lua_loader(lua: &Lua, modname: StdString) -> Result<Value> {
match fs::read(&file_path) {
Ok(buf) => {
return lua
.load(&buf)
.load(buf)
.set_name(format!("={}", file_path.display()))
.set_mode(ChunkMode::Text)
.into_function()
+22 -5
View File
@@ -18,15 +18,32 @@ pub(crate) struct MemoryState {
}
impl MemoryState {
#[cfg(feature = "luau")]
#[inline]
pub(crate) unsafe fn get(state: *mut ffi::lua_State) -> *mut Self {
let mut mem_state = ptr::null_mut();
#[cfg(feature = "luau")]
{
ffi::lua_getallocf(state, &mut mem_state);
mlua_assert!(!mem_state.is_null(), "Luau state has no allocator userdata");
ffi::lua_getallocf(state, &mut mem_state);
mlua_assert!(!mem_state.is_null(), "Luau state has no allocator userdata");
mem_state as *mut MemoryState
}
#[cfg(not(feature = "luau"))]
#[rustversion::since(1.85)]
#[inline]
#[allow(clippy::incompatible_msrv)]
pub(crate) unsafe fn get(state: *mut ffi::lua_State) -> *mut Self {
let mut mem_state = ptr::null_mut();
if !ptr::fn_addr_eq(ffi::lua_getallocf(state, &mut mem_state), ALLOCATOR) {
mem_state = ptr::null_mut();
}
#[cfg(not(feature = "luau"))]
mem_state as *mut MemoryState
}
#[cfg(not(feature = "luau"))]
#[rustversion::before(1.85)]
#[inline]
pub(crate) unsafe fn get(state: *mut ffi::lua_State) -> *mut Self {
let mut mem_state = ptr::null_mut();
if ffi::lua_getallocf(state, &mut mem_state) != ALLOCATOR {
mem_state = ptr::null_mut();
}
+64 -26
View File
@@ -11,7 +11,7 @@ use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti};
use crate::util::check_stack;
use crate::value::{Nil, Value};
/// Result is convertible to `MultiValue` following the common Lua idiom of returning the result
/// 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<T: IntoLua, E: IntoLua> IntoLuaMulti for StdResult<T, E> {
#[inline]
@@ -124,6 +124,23 @@ impl MultiValue {
MultiValue(VecDeque::with_capacity(capacity))
}
/// Creates a `MultiValue` container from vector of values.
///
/// This method works in *O*(1) time and does not allocate any additional memory.
#[inline]
pub fn from_vec(vec: Vec<Value>) -> MultiValue {
vec.into()
}
/// Consumes the `MultiValue` and returns a vector of values.
///
/// This method needs *O*(*n*) data movement if the circular buffer doesn't happen to be at the
/// beginning of the allocation.
#[inline]
pub fn into_vec(self) -> Vec<Value> {
self.into()
}
#[inline]
pub(crate) fn from_lua_iter<T: IntoLua>(lua: &Lua, iter: impl IntoIterator<Item = T>) -> Result<Self> {
let iter = iter.into_iter();
@@ -135,6 +152,20 @@ impl MultiValue {
}
}
impl From<Vec<Value>> for MultiValue {
#[inline]
fn from(value: Vec<Value>) -> Self {
MultiValue(value.into())
}
}
impl From<MultiValue> for Vec<Value> {
#[inline]
fn from(value: MultiValue) -> Self {
value.0.into()
}
}
impl FromIterator<Value> for MultiValue {
#[inline]
fn from_iter<I: IntoIterator<Item = Value>>(iter: I) -> Self {
@@ -203,10 +234,7 @@ impl FromLuaMulti for MultiValue {
/// # Ok(())
/// # }
/// ```
///
/// [`FromLua`]: crate::FromLua
/// [`MultiValue`]: crate::MultiValue
#[derive(Debug, Clone)]
#[derive(Default, Debug, Clone)]
pub struct Variadic<T>(Vec<T>);
impl<T> Variadic<T> {
@@ -214,11 +242,38 @@ impl<T> Variadic<T> {
pub const fn new() -> Variadic<T> {
Variadic(Vec::new())
}
/// Creates an empty `Variadic` container with space for at least `capacity` elements.
pub fn with_capacity(capacity: usize) -> Variadic<T> {
Variadic(Vec::with_capacity(capacity))
}
}
impl<T> Default for Variadic<T> {
fn default() -> Variadic<T> {
const { Variadic::new() }
impl<T> Deref for Variadic<T> {
type Target = Vec<T>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T> DerefMut for Variadic<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl<T> From<Vec<T>> for Variadic<T> {
#[inline]
fn from(vec: Vec<T>) -> Self {
Variadic(vec)
}
}
impl<T> From<Variadic<T>> for Vec<T> {
#[inline]
fn from(value: Variadic<T>) -> Self {
value.0
}
}
@@ -237,20 +292,6 @@ impl<T> IntoIterator for Variadic<T> {
}
}
impl<T> Deref for Variadic<T> {
type Target = Vec<T>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T> DerefMut for Variadic<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl<T: IntoLua> IntoLuaMulti for Variadic<T> {
#[inline]
fn into_lua_multi(self, lua: &Lua) -> Result<MultiValue> {
@@ -290,10 +331,7 @@ macro_rules! impl_tuple {
}
#[inline]
unsafe fn from_stack_multi(nvals: c_int, lua: &RawLua) -> Result<Self> {
if nvals > 0 {
ffi::lua_pop(lua.state(), nvals);
}
unsafe fn from_stack_multi(_nvals: c_int, _lua: &RawLua) -> Result<Self> {
Ok(())
}
}
+1 -1
View File
@@ -13,7 +13,7 @@ pub use crate::{
UserData as LuaUserData, UserDataFields as LuaUserDataFields, UserDataMetatable as LuaUserDataMetatable,
UserDataMethods as LuaUserDataMethods, UserDataRef as LuaUserDataRef,
UserDataRefMut as LuaUserDataRefMut, UserDataRegistry as LuaUserDataRegistry, Value as LuaValue,
VmState as LuaVmState,
Variadic as LuaVariadic, VmState as LuaVmState, WeakLua,
};
#[cfg(not(feature = "luau"))]
+122 -59
View File
@@ -1,7 +1,6 @@
use std::cell::RefCell;
use std::marker::PhantomData;
use std::mem;
use std::os::raw::c_void;
use crate::error::{Error, Result};
use crate::function::Function;
@@ -9,7 +8,9 @@ use crate::state::{Lua, LuaGuard, RawLua};
use crate::traits::{FromLuaMulti, IntoLuaMulti};
use crate::types::{Callback, CallbackUpvalue, ScopedCallback, ValueRef};
use crate::userdata::{AnyUserData, UserData, UserDataRegistry, UserDataStorage};
use crate::util::{self, assert_stack, check_stack, get_userdata, take_userdata, StackGuard};
use crate::util::{
self, assert_stack, check_stack, get_metatable_ptr, get_userdata, take_userdata, StackGuard,
};
/// Constructed by the [`Lua::scope`] method, allows temporarily creating Lua userdata and
/// callbacks that are not required to be `Send` or `'static`.
@@ -17,7 +18,9 @@ use crate::util::{self, assert_stack, check_stack, get_userdata, take_userdata,
/// See [`Lua::scope`] for more details.
pub struct Scope<'scope, 'env: 'scope> {
lua: LuaGuard,
// Internal destructors run first, then user destructors (based on the declaration order)
destructors: Destructors<'env>,
user_destructors: UserDestructors<'env>,
_scope_invariant: PhantomData<&'scope mut &'scope ()>,
_env_invariant: PhantomData<&'env mut &'env ()>,
}
@@ -27,11 +30,14 @@ type DestructorCallback<'a> = Box<dyn FnOnce(&RawLua, ValueRef) -> Vec<Box<dyn F
// Implement Drop on Destructors instead of Scope to avoid compilation error
struct Destructors<'a>(RefCell<Vec<(ValueRef, DestructorCallback<'a>)>>);
struct UserDestructors<'a>(RefCell<Vec<Box<dyn FnOnce() + 'a>>>);
impl<'scope, 'env: 'scope> Scope<'scope, 'env> {
pub(crate) fn new(lua: LuaGuard) -> Self {
Scope {
lua,
destructors: Destructors(RefCell::new(Vec::new())),
user_destructors: UserDestructors(RefCell::new(Vec::new())),
_scope_invariant: PhantomData,
_env_invariant: PhantomData,
}
@@ -82,11 +88,9 @@ impl<'scope, 'env: 'scope> Scope<'scope, 'env> {
where
T: UserData + 'static,
{
unsafe {
let ud = self.lua.make_userdata(UserDataStorage::new_ref(data))?;
self.seal_userdata::<T>(&ud)?;
Ok(ud)
}
let ud = unsafe { self.lua.make_userdata(UserDataStorage::new_ref(data)) }?;
self.seal_userdata::<T>(&ud);
Ok(ud)
}
/// Creates a Lua userdata object from a mutable reference to custom userdata type.
@@ -98,11 +102,9 @@ impl<'scope, 'env: 'scope> Scope<'scope, 'env> {
where
T: UserData + 'static,
{
unsafe {
let ud = self.lua.make_userdata(UserDataStorage::new_ref_mut(data))?;
self.seal_userdata::<T>(&ud)?;
Ok(ud)
}
let ud = unsafe { self.lua.make_userdata(UserDataStorage::new_ref_mut(data)) }?;
self.seal_userdata::<T>(&ud);
Ok(ud)
}
/// Creates a Lua userdata object from a reference to custom Rust type.
@@ -116,11 +118,9 @@ impl<'scope, 'env: 'scope> Scope<'scope, 'env> {
where
T: 'static,
{
unsafe {
let ud = self.lua.make_any_userdata(UserDataStorage::new_ref(data))?;
self.seal_userdata::<T>(&ud)?;
Ok(ud)
}
let ud = unsafe { self.lua.make_any_userdata(UserDataStorage::new_ref(data)) }?;
self.seal_userdata::<T>(&ud);
Ok(ud)
}
/// Creates a Lua userdata object from a mutable reference to custom Rust type.
@@ -132,11 +132,9 @@ impl<'scope, 'env: 'scope> Scope<'scope, 'env> {
where
T: 'static,
{
unsafe {
let ud = self.lua.make_any_userdata(UserDataStorage::new_ref_mut(data))?;
self.seal_userdata::<T>(&ud)?;
Ok(ud)
}
let ud = unsafe { self.lua.make_any_userdata(UserDataStorage::new_ref_mut(data)) }?;
self.seal_userdata::<T>(&ud);
Ok(ud)
}
/// Creates a Lua userdata object from a custom userdata type.
@@ -146,14 +144,15 @@ impl<'scope, 'env: 'scope> Scope<'scope, 'env> {
/// [`Lua::scope`] for more details.
///
/// The main limitation that comes from using non-'static userdata is that the produced userdata
/// will no longer have a `TypeId` associated with it, because `TypeId` can only work for
/// will no longer have a [`TypeId`] associated with it, because [`TypeId`] can only work for
/// `'static` types. This means that it is impossible, once the userdata is created, to get a
/// reference to it back *out* of an `AnyUserData` handle. This also implies that the
/// reference to it back *out* of an [`AnyUserData`] handle. This also implies that the
/// "function" type methods that can be added via [`UserDataMethods`] (the ones that accept
/// `AnyUserData` as a first parameter) are vastly less useful. Also, there is no way to re-use
/// a single metatable for multiple non-'static types, so there is a higher cost associated with
/// creating the userdata metatable each time a new userdata is created.
/// [`AnyUserData`] as a first parameter) are vastly less useful. Also, there is no way to
/// re-use a single metatable for multiple non-'static types, so there is a higher cost
/// associated with creating the userdata metatable each time a new userdata is created.
///
/// [`TypeId`]: std::any::TypeId
/// [`UserDataMethods`]: crate::UserDataMethods
pub fn create_userdata<T>(&'scope self, data: T) -> Result<AnyUserData>
where
@@ -164,20 +163,20 @@ impl<'scope, 'env: 'scope> Scope<'scope, 'env> {
let _sg = StackGuard::new(state);
check_stack(state, 3)?;
// // We don't write the data to the userdata until pushing the metatable
// We don't write the data to the userdata until pushing the metatable
let protect = !self.lua.unlikely_memory_error();
#[cfg(feature = "luau")]
let ud_ptr = {
let data = UserDataStorage::new_scoped(data);
util::push_userdata::<UserDataStorage<T>>(state, data, protect)?
util::push_userdata(state, data, protect)?
};
#[cfg(not(feature = "luau"))]
let ud_ptr = util::push_uninit_userdata::<UserDataStorage<T>>(state, protect)?;
// Push the metatable and register it with no TypeId
let mut registry = UserDataRegistry::new_unique(ud_ptr as *const c_void);
let mut registry = UserDataRegistry::new_unique(self.lua.lua(), ud_ptr as *mut _);
T::register(&mut registry);
self.lua.push_userdata_metatable(registry)?;
self.lua.push_userdata_metatable(registry.into_raw())?;
let mt_ptr = ffi::lua_topointer(state, -1);
self.lua.register_userdata_metatable(mt_ptr, None);
@@ -187,33 +186,84 @@ impl<'scope, 'env: 'scope> Scope<'scope, 'env> {
ffi::lua_setmetatable(state, -2);
let ud = AnyUserData(self.lua.pop_ref());
let destructor: DestructorCallback = Box::new(|rawlua, vref| {
let state = rawlua.state();
let _sg = StackGuard::new(state);
assert_stack(state, 2);
// Check that userdata is valid (very likely)
if rawlua.push_userdata_ref(&vref).is_err() {
return vec![];
}
// Deregister metatable
ffi::lua_getmetatable(state, -1);
let mt_ptr = ffi::lua_topointer(state, -1);
ffi::lua_pop(state, 1);
rawlua.deregister_userdata_metatable(mt_ptr);
let ud = take_userdata::<UserDataStorage<T>>(state);
vec![Box::new(move || drop(ud))]
});
self.destructors.0.borrow_mut().push((ud.0.clone(), destructor));
self.seal_userdata::<T>(&ud);
Ok(ud)
}
}
/// Creates a Lua userdata object from a custom Rust type.
///
/// Since the Rust type is not required to be static and implement [`UserData`] trait,
/// you need to provide a function to register fields or methods for the object.
///
/// See also [`Scope::create_userdata`] for more details about non-static limitations.
pub fn create_any_userdata<T>(
&'scope self,
data: T,
register: impl FnOnce(&mut UserDataRegistry<T>),
) -> Result<AnyUserData>
where
T: 'env,
{
let state = self.lua.state();
let ud = unsafe {
let _sg = StackGuard::new(state);
check_stack(state, 3)?;
// We don't write the data to the userdata until pushing the metatable
let protect = !self.lua.unlikely_memory_error();
#[cfg(feature = "luau")]
let ud_ptr = {
let data = UserDataStorage::new_scoped(data);
util::push_userdata(state, data, protect)?
};
#[cfg(not(feature = "luau"))]
let ud_ptr = util::push_uninit_userdata::<UserDataStorage<T>>(state, protect)?;
// Push the metatable and register it with no TypeId
let mut registry = UserDataRegistry::new_unique(self.lua.lua(), ud_ptr as *mut _);
register(&mut registry);
self.lua.push_userdata_metatable(registry.into_raw())?;
let mt_ptr = ffi::lua_topointer(state, -1);
self.lua.register_userdata_metatable(mt_ptr, None);
// Write data to the pointer and attach metatable
#[cfg(not(feature = "luau"))]
std::ptr::write(ud_ptr, UserDataStorage::new_scoped(data));
ffi::lua_setmetatable(state, -2);
AnyUserData(self.lua.pop_ref())
};
self.seal_userdata::<T>(&ud);
Ok(ud)
}
/// Adds a destructor function to be run when the scope ends.
///
/// This functionality is useful for cleaning up any resources after the scope ends.
///
/// # Example
///
/// ```rust
/// # use mlua::{Error, Lua, Result};
/// # fn main() -> Result<()> {
/// let lua = Lua::new();
/// let ud = lua.create_any_userdata(String::from("hello"))?;
/// lua.scope(|scope| {
/// scope.add_destructor(|| {
/// _ = ud.take::<String>();
/// });
/// // Run the code that uses `ud` here
/// Ok(())
/// })?;
/// assert!(matches!(ud.borrow::<String>(), Err(Error::UserDataDestructed)));
/// # Ok(())
/// # }
pub fn add_destructor(&'scope self, destructor: impl FnOnce() + 'env) {
self.user_destructors.0.borrow_mut().push(Box::new(destructor));
}
unsafe fn create_callback(&'scope self, f: ScopedCallback<'scope>) -> Result<Function> {
let f = mem::transmute::<ScopedCallback, Callback>(f);
let f = self.lua.create_callback(f)?;
@@ -232,23 +282,27 @@ impl<'scope, 'env: 'scope> Scope<'scope, 'env> {
}
/// Shortens the lifetime of the userdata to the lifetime of the scope.
unsafe fn seal_userdata<T: 'static>(&self, ud: &AnyUserData) -> Result<()> {
let destructor: DestructorCallback = Box::new(|rawlua, vref| {
fn seal_userdata<T: 'env>(&self, ud: &AnyUserData) {
let destructor: DestructorCallback = Box::new(|rawlua, vref| unsafe {
let state = rawlua.state();
let _sg = StackGuard::new(state);
assert_stack(state, 2);
// Ensure that userdata is not destructed
if rawlua.push_userdata_ref(&vref).is_err() {
return vec![];
match rawlua.push_userdata_ref(&vref) {
Ok(Some(_)) => {}
Ok(None) => {
// Deregister metatable
let mt_ptr = get_metatable_ptr(state, -1);
rawlua.deregister_userdata_metatable(mt_ptr);
}
Err(_) => return vec![],
}
let data = take_userdata::<UserDataStorage<T>>(state);
vec![Box::new(move || drop(data))]
});
self.destructors.0.borrow_mut().push((ud.0.clone(), destructor));
Ok(())
}
}
@@ -270,3 +324,12 @@ impl Drop for Destructors<'_> {
}
}
}
impl Drop for UserDestructors<'_> {
fn drop(&mut self) {
let destructors = mem::take(&mut *self.0.borrow_mut());
for destructor in destructors {
destructor();
}
}
}
+25 -5
View File
@@ -1,3 +1,5 @@
//! Deserialize Lua values to a Rust data structure.
use std::cell::RefCell;
use std::os::raw::c_void;
use std::rc::Rc;
@@ -47,6 +49,11 @@ pub struct Options {
///
/// Default: **false**
pub sort_keys: bool,
/// If true, empty Lua tables will be encoded as array, instead of map.
///
/// Default: **false**
pub encode_empty_tables_as_array: bool,
}
impl Default for Options {
@@ -62,6 +69,7 @@ impl Options {
deny_unsupported_types: true,
deny_recursive_tables: true,
sort_keys: false,
encode_empty_tables_as_array: false,
}
}
@@ -91,15 +99,24 @@ impl Options {
self.sort_keys = enabled;
self
}
/// Sets [`encode_empty_tables_as_array`] option.
///
/// [`encode_empty_tables_as_array`]: #structfield.encode_empty_tables_as_array
#[must_use]
pub const fn encode_empty_tables_as_array(mut self, enabled: bool) -> Self {
self.encode_empty_tables_as_array = enabled;
self
}
}
impl Deserializer {
/// Creates a new Lua Deserializer for the `Value`.
/// Creates a new Lua Deserializer for the [`Value`].
pub fn new(value: Value) -> Self {
Self::new_with_options(value, Options::default())
}
/// Creates a new Lua Deserializer for the `Value` with custom options.
/// Creates a new Lua Deserializer for the [`Value`] with custom options.
pub fn new_with_options(value: Value, options: Options) -> Self {
Deserializer {
value,
@@ -139,6 +156,9 @@ impl<'de> serde::Deserializer<'de> for Deserializer {
Err(_) => visitor.visit_bytes(&s.as_bytes()),
},
Value::Table(ref t) if t.raw_len() > 0 || t.is_array() => self.deserialize_seq(visitor),
Value::Table(ref t) if self.options.encode_empty_tables_as_array && t.is_empty() => {
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() => {
@@ -418,7 +438,7 @@ impl<'de> de::SeqAccess<'de> for SeqDeserializer<'_> {
#[cfg(feature = "luau")]
struct VecDeserializer {
vec: crate::types::Vector,
vec: crate::Vector,
next: usize,
options: Options,
visited: Rc<RefCell<FxHashSet<*const c_void>>>,
@@ -444,7 +464,7 @@ impl<'de> de::SeqAccess<'de> for VecDeserializer {
}
fn size_hint(&self) -> Option<usize> {
Some(crate::types::Vector::SIZE)
Some(crate::Vector::SIZE)
}
}
@@ -498,7 +518,7 @@ struct MapDeserializer<'a> {
processed: usize,
}
impl<'a> MapDeserializer<'a> {
impl MapDeserializer<'_> {
fn next_key_deserializer(&mut self) -> Result<Option<Deserializer>> {
loop {
match self.pairs.next() {
-6
View File
@@ -106,8 +106,6 @@ pub trait LuaSerdeExt: Sealed {
///
/// Requires `feature = "serialize"`
///
/// [`Value`]: crate::Value
///
/// # Example
///
/// ```
@@ -133,8 +131,6 @@ pub trait LuaSerdeExt: Sealed {
///
/// Requires `feature = "serialize"`
///
/// [`Value`]: crate::Value
///
/// # Example
///
/// ```
@@ -164,8 +160,6 @@ pub trait LuaSerdeExt: Sealed {
///
/// Requires `feature = "serialize"`
///
/// [`Value`]: crate::Value
///
/// # Example
///
/// ```
+5 -3
View File
@@ -1,3 +1,5 @@
//! Serialize a Rust data structure into Lua value.
use serde::{ser, Serialize};
use super::LuaSerdeExt;
@@ -267,7 +269,7 @@ impl<'a> ser::Serializer for Serializer<'a> {
#[inline]
fn serialize_tuple_struct(self, name: &'static str, len: usize) -> Result<Self::SerializeTupleStruct> {
#[cfg(feature = "luau")]
if name == "Vector" && len == crate::types::Vector::SIZE {
if name == "Vector" && len == crate::Vector::SIZE {
return Ok(SerializeSeq::new_vector(self.lua, self.options));
}
_ = name;
@@ -341,7 +343,7 @@ impl<'a> ser::Serializer for Serializer<'a> {
pub struct SerializeSeq<'a> {
lua: &'a Lua,
#[cfg(feature = "luau")]
vector: Option<crate::types::Vector>,
vector: Option<crate::Vector>,
table: Option<Table>,
next: usize,
options: Options,
@@ -363,7 +365,7 @@ impl<'a> SerializeSeq<'a> {
const fn new_vector(lua: &'a Lua, options: Options) -> Self {
Self {
lua,
vector: Some(crate::types::Vector::zero()),
vector: Some(crate::Vector::zero()),
table: None,
next: 0,
options,
+224 -153
View File
@@ -1,5 +1,5 @@
use std::any::TypeId;
use std::cell::RefCell;
use std::cell::{BorrowError, BorrowMutError, RefCell};
use std::marker::PhantomData;
use std::ops::Deref;
use std::os::raw::c_int;
@@ -46,18 +46,20 @@ use serde::Serialize;
pub(crate) use extra::ExtraData;
pub use raw::RawLua;
use util::{callback_error_ext, StateGuard};
use util::callback_error_ext;
/// Top level Lua struct which represents an instance of Lua VM.
#[derive(Clone)]
pub struct Lua {
pub(self) raw: XRc<ReentrantMutex<RawLua>>,
// Controls whether garbage collection should be run on drop
pub(self) collect_garbage: bool,
}
/// Weak reference to Lua instance.
///
/// This can used to prevent circular references between Lua and Rust objects.
#[derive(Clone)]
pub(crate) struct WeakLua(XWeak<ReentrantMutex<RawLua>>);
pub struct WeakLua(XWeak<ReentrantMutex<RawLua>>);
pub(crate) struct LuaGuard(ArcReentrantMutexGuard<RawLua>);
@@ -98,9 +100,6 @@ pub struct LuaOptions {
/// Max size of thread (coroutine) object pool used to execute asynchronous functions.
///
/// It works on Lua 5.4 and Luau, where [`lua_resetthread`] function
/// is available and allows to reuse old coroutines after resetting their state.
///
/// Default: **0** (disabled)
///
/// [`lua_resetthread`]: https://www.lua.org/manual/5.4/manual.html#lua_resetthread
@@ -154,6 +153,16 @@ impl Drop for Lua {
}
}
impl Clone for Lua {
#[inline]
fn clone(&self) -> Self {
Lua {
raw: XRc::clone(&self.raw),
collect_garbage: false,
}
}
}
impl fmt::Debug for Lua {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Lua({:p})", self.lock().state())
@@ -171,12 +180,10 @@ impl Lua {
/// Creates a new Lua state and loads the **safe** subset of the standard libraries.
///
/// # Safety
/// The created Lua state would have _some_ safety guarantees and would not allow to load unsafe
/// The created Lua state will have _some_ safety guarantees and will not allow to load unsafe
/// standard libraries or C modules.
///
/// See [`StdLib`] documentation for a list of unsafe modules that cannot be loaded.
///
/// [`StdLib`]: crate::StdLib
pub fn new() -> Lua {
mlua_expect!(
Self::new_with(StdLib::ALL_SAFE, LuaOptions::default()),
@@ -187,7 +194,7 @@ impl Lua {
/// Creates a new Lua state and loads all the standard libraries.
///
/// # Safety
/// The created Lua state would not have safety guarantees and would allow to load C modules.
/// The created Lua state will not have safety guarantees and will allow to load C modules.
pub unsafe fn unsafe_new() -> Lua {
Self::unsafe_new_with(StdLib::ALL, LuaOptions::default())
}
@@ -197,12 +204,10 @@ impl Lua {
/// Use the [`StdLib`] flags to specify the libraries you want to load.
///
/// # Safety
/// The created Lua state would have _some_ safety guarantees and would not allow to load unsafe
/// The created Lua state will have _some_ safety guarantees and will not allow to load unsafe
/// standard libraries or C modules.
///
/// See [`StdLib`] documentation for a list of unsafe modules that cannot be loaded.
///
/// [`StdLib`]: crate::StdLib
pub fn new_with(libs: StdLib, options: LuaOptions) -> Result<Lua> {
#[cfg(not(feature = "luau"))]
if libs.contains(StdLib::DEBUG) {
@@ -222,7 +227,7 @@ impl Lua {
if libs.contains(StdLib::PACKAGE) {
mlua_expect!(lua.disable_c_modules(), "Error disabling C modules");
}
unsafe { lua.lock().set_safe() };
lua.lock().mark_safe();
Ok(lua)
}
@@ -233,8 +238,6 @@ impl Lua {
///
/// # Safety
/// The created Lua state will not have safety guarantees and allow to load C modules.
///
/// [`StdLib`]: crate::StdLib
pub unsafe fn unsafe_new_with(libs: StdLib, options: LuaOptions) -> Lua {
// Workaround to avoid stripping a few unused Lua symbols that could be imported
// by C modules in unsafe mode
@@ -289,6 +292,28 @@ impl Lua {
///
/// This method ensures that the Lua instance is locked while the function is called
/// and restores Lua stack after the function returns.
///
/// # Example
/// ```
/// # use mlua::{Lua, Result};
/// # fn main() -> Result<()> {
/// let lua = Lua::new();
/// let n: i32 = unsafe {
/// let nums = (3, 4, 5);
/// lua.exec_raw(nums, |state| {
/// let n = ffi::lua_gettop(state);
/// let mut sum = 0;
/// for i in 1..=n {
/// sum += ffi::lua_tointeger(state, i);
/// }
/// ffi::lua_pop(state, n);
/// ffi::lua_pushinteger(state, sum);
/// })
/// }?;
/// assert_eq!(n, 12);
/// # Ok(())
/// # }
/// ```
#[allow(clippy::missing_safety_doc)]
pub unsafe fn exec_raw<R: FromLuaMulti>(
&self,
@@ -306,7 +331,11 @@ impl Lua {
R::from_stack_multi(nresults, &lua)
}
/// FIXME: Deprecated load_from_std_lib
#[doc(hidden)]
#[deprecated(since = "0.10.0", note = "please use `load_std_libs` instead")]
pub fn load_from_std_lib(&self, libs: StdLib) -> Result<()> {
self.load_std_libs(libs)
}
/// Loads the specified subset of the standard libraries into an existing Lua state.
///
@@ -395,13 +424,12 @@ impl Lua {
// Make sure that Lua is initialized
let mut lua = Self::init_from_ptr(state);
lua.collect_garbage = false;
// `Lua` is no longer needed and must be dropped at this point to avoid possible memory leak
// `Lua` is no longer needed and must be dropped at this point to avoid memory leak
// in case of possible longjmp (lua_error) below
drop(lua);
callback_error_ext(state, ptr::null_mut(), move |extra, nargs| {
let rawlua = (*extra).raw_lua();
let _guard = StateGuard::new(rawlua, state);
let args = A::from_stack_args(nargs, 1, None, rawlua)?;
func(rawlua.lua(), args)?.push_into_stack(rawlua)?;
Ok(1)
@@ -440,6 +468,7 @@ impl Lua {
///
/// ```
/// # use mlua::{Lua, Result};
/// # #[cfg(feature = "luau")]
/// # fn main() -> Result<()> {
/// let lua = Lua::new();
///
@@ -452,16 +481,19 @@ impl Lua {
/// assert_eq!(lua.globals().get::<Option<u32>>("var")?, None);
/// # Ok(())
/// # }
///
/// # #[cfg(not(feature = "luau"))]
/// # fn main() {}
/// ```
///
/// Requires `feature = "luau"`
#[cfg(any(feature = "luau", docsrs))]
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn sandbox(&self, enabled: bool) -> Result<()> {
let lua = self.lock();
unsafe {
if (*lua.extra.get()).sandboxed != enabled {
let state = lua.main_state;
let state = lua.main_state();
check_stack(state, 3)?;
protect_lua!(state, 0, 0, |state| {
if enabled {
@@ -480,7 +512,7 @@ impl Lua {
}
}
/// Sets a 'hook' function that will periodically be called as Lua code executes.
/// Sets a hook function that will periodically be called as Lua code executes.
///
/// When exactly the hook function is called depends on the contents of the `triggers`
/// parameter, see [`HookTriggers`] for more details.
@@ -492,7 +524,7 @@ impl Lua {
///
/// This method sets a hook function for the current thread of this Lua instance.
/// If you want to set a hook function for another thread (coroutine), use
/// [`Thread::set_hook()`] instead.
/// [`Thread::set_hook`] instead.
///
/// Please note you cannot have more than one hook function set at a time for this Lua instance.
///
@@ -517,7 +549,6 @@ impl Lua {
/// # }
/// ```
///
/// [`HookTriggers`]: crate::HookTriggers
/// [`HookTriggers.every_nth_instruction`]: crate::HookTriggers::every_nth_instruction
#[cfg(not(feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
@@ -529,7 +560,7 @@ impl Lua {
unsafe { lua.set_thread_hook(lua.state(), triggers, callback) };
}
/// Removes any hook previously set by [`Lua::set_hook()`] or [`Thread::set_hook()`].
/// Removes any hook previously set by [`Lua::set_hook`] or [`Thread::set_hook`].
///
/// This function has no effect if a hook was not previously set.
#[cfg(not(feature = "luau"))]
@@ -539,10 +570,10 @@ impl Lua {
unsafe {
let state = lua.state();
ffi::lua_sethook(state, None, 0, 0);
match crate::util::get_main_state(lua.main_state) {
Some(main_state) if !ptr::eq(state, main_state) => {
match lua.main_state {
Some(main_state) if state != main_state.as_ptr() => {
// If main_state is different from state, remove hook from it too
ffi::lua_sethook(main_state, None, 0, 0);
ffi::lua_sethook(main_state.as_ptr(), None, 0, 0);
}
_ => {}
};
@@ -551,7 +582,7 @@ impl Lua {
}
}
/// Sets an 'interrupt' function that will periodically be called by Luau VM.
/// Sets an interrupt function that will periodically be called by Luau VM.
///
/// Any Luau code is guaranteed to call this handler "eventually"
/// (in practice this can happen at any function call or at any loop iteration).
@@ -570,6 +601,7 @@ impl Lua {
/// ```
/// # use std::sync::{Arc, atomic::{AtomicU64, Ordering}};
/// # use mlua::{Lua, Result, ThreadStatus, VmState};
/// # #[cfg(feature = "luau")]
/// # fn main() -> Result<()> {
/// let lua = Lua::new();
/// let count = Arc::new(AtomicU64::new(0));
@@ -588,12 +620,15 @@ impl Lua {
/// .into_function()?,
/// )?;
/// while co.status() == ThreadStatus::Resumable {
/// co.resume(())?;
/// co.resume::<()>(())?;
/// }
/// # Ok(())
/// # }
///
/// # #[cfg(not(feature = "luau"))]
/// # fn main() {}
/// ```
#[cfg(any(feature = "luau", docsrs))]
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn set_interrupt<F>(&self, callback: F)
where
@@ -612,7 +647,6 @@ impl Lua {
if Rc::strong_count(&interrupt_cb) > 2 {
return Ok(VmState::Continue); // Don't allow recursion
}
let _guard = StateGuard::new((*extra).raw_lua(), state);
interrupt_cb((*extra).lua())
});
match result {
@@ -627,20 +661,20 @@ impl Lua {
let lua = self.lock();
unsafe {
(*lua.extra.get()).interrupt_callback = Some(Rc::new(callback));
(*ffi::lua_callbacks(lua.main_state)).interrupt = Some(interrupt_proc);
(*ffi::lua_callbacks(lua.main_state())).interrupt = Some(interrupt_proc);
}
}
/// Removes any 'interrupt' previously set by `set_interrupt`.
/// Removes any interrupt function previously set by `set_interrupt`.
///
/// This function has no effect if an 'interrupt' was not previously set.
#[cfg(any(feature = "luau", docsrs))]
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn remove_interrupt(&self) {
let lua = self.lock();
unsafe {
(*lua.extra.get()).interrupt_callback = None;
(*ffi::lua_callbacks(lua.main_state)).interrupt = None;
(*ffi::lua_callbacks(lua.main_state())).interrupt = None;
}
}
@@ -660,20 +694,20 @@ impl Lua {
unsafe extern "C-unwind" fn warn_proc(ud: *mut c_void, msg: *const c_char, tocont: c_int) {
let extra = ud as *mut ExtraData;
callback_error_ext((*extra).raw_lua().state(), extra, |extra, _| {
let cb = mlua_expect!(
(*extra).warn_callback.as_ref(),
"no warning callback set in warn_proc"
);
let warn_callback = (*extra).warn_callback.clone();
let warn_callback = mlua_expect!(warn_callback, "no warning callback set in warn_proc");
if XRc::strong_count(&warn_callback) > 2 {
return Ok(());
}
let msg = StdString::from_utf8_lossy(CStr::from_ptr(msg).to_bytes());
cb((*extra).lua(), &msg, tocont != 0)
warn_callback((*extra).lua(), &msg, tocont != 0)
});
}
let lua = self.lock();
let state = lua.main_state;
unsafe {
(*lua.extra.get()).warn_callback = Some(Box::new(callback));
ffi::lua_setwarnf(state, Some(warn_proc), lua.extra.get() as *mut c_void);
(*lua.extra.get()).warn_callback = Some(XRc::new(callback));
ffi::lua_setwarnf(lua.state(), Some(warn_proc), lua.extra.get() as *mut c_void);
}
}
@@ -688,7 +722,7 @@ impl Lua {
let lua = self.lock();
unsafe {
(*lua.extra.get()).warn_callback = None;
ffi::lua_setwarnf(lua.main_state, None, ptr::null_mut());
ffi::lua_setwarnf(lua.state(), None, ptr::null_mut());
}
}
@@ -740,13 +774,14 @@ impl Lua {
/// Returns the amount of memory (in bytes) currently used inside this Lua state.
pub fn used_memory(&self) -> usize {
let lua = self.lock();
let state = lua.main_state();
unsafe {
match MemoryState::get(lua.main_state) {
match MemoryState::get(state) {
mem_state if !mem_state.is_null() => (*mem_state).used_memory(),
_ => {
// Get data from the Lua GC
let used_kbytes = ffi::lua_gc(lua.main_state, ffi::LUA_GCCOUNT, 0);
let used_kbytes_rem = ffi::lua_gc(lua.main_state, ffi::LUA_GCCOUNTB, 0);
let used_kbytes = ffi::lua_gc(state, ffi::LUA_GCCOUNT, 0);
let used_kbytes_rem = ffi::lua_gc(state, ffi::LUA_GCCOUNTB, 0);
(used_kbytes as usize) * 1024 + (used_kbytes_rem as usize)
}
}
@@ -755,40 +790,40 @@ impl Lua {
/// Sets a memory limit (in bytes) on this Lua state.
///
/// Once an allocation occurs that would pass this memory limit,
/// a `Error::MemoryError` is generated instead.
/// Once an allocation occurs that would pass this memory limit, a `Error::MemoryError` is
/// generated instead.
/// Returns previous limit (zero means no limit).
///
/// Does not work in module mode where Lua state is managed externally.
pub fn set_memory_limit(&self, limit: usize) -> Result<usize> {
let lua = self.lock();
unsafe {
match MemoryState::get(lua.main_state) {
match MemoryState::get(lua.state()) {
mem_state if !mem_state.is_null() => Ok((*mem_state).set_memory_limit(limit)),
_ => Err(Error::MemoryLimitNotAvailable),
_ => Err(Error::MemoryControlNotAvailable),
}
}
}
/// Returns true if the garbage collector is currently running automatically.
/// Returns `true` if the garbage collector is currently running automatically.
///
/// Requires `feature = "lua54/lua53/lua52/luau"`
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))]
pub fn gc_is_running(&self) -> bool {
let lua = self.lock();
unsafe { ffi::lua_gc(lua.main_state, ffi::LUA_GCISRUNNING, 0) != 0 }
unsafe { ffi::lua_gc(lua.main_state(), ffi::LUA_GCISRUNNING, 0) != 0 }
}
/// Stop the Lua GC from running
pub fn gc_stop(&self) {
let lua = self.lock();
unsafe { ffi::lua_gc(lua.main_state, ffi::LUA_GCSTOP, 0) };
unsafe { ffi::lua_gc(lua.main_state(), ffi::LUA_GCSTOP, 0) };
}
/// Restarts the Lua GC if it is not running
pub fn gc_restart(&self) {
let lua = self.lock();
unsafe { ffi::lua_gc(lua.main_state, ffi::LUA_GCRESTART, 0) };
unsafe { ffi::lua_gc(lua.main_state(), ffi::LUA_GCRESTART, 0) };
}
/// Perform a full garbage-collection cycle.
@@ -797,15 +832,16 @@ impl Lua {
/// objects. Once to finish the current gc cycle, and once to start and finish the next cycle.
pub fn gc_collect(&self) -> Result<()> {
let lua = self.lock();
let state = lua.main_state();
unsafe {
check_stack(lua.main_state, 2)?;
protect_lua!(lua.main_state, 0, 0, fn(state) ffi::lua_gc(state, ffi::LUA_GCCOLLECT, 0))
check_stack(state, 2)?;
protect_lua!(state, 0, 0, fn(state) ffi::lua_gc(state, ffi::LUA_GCCOLLECT, 0))
}
}
/// Steps the garbage collector one indivisible step.
///
/// Returns true if this has finished a collection cycle.
/// Returns `true` if this has finished a collection cycle.
pub fn gc_step(&self) -> Result<bool> {
self.gc_step_kbytes(0)
}
@@ -816,17 +852,18 @@ impl Lua {
/// finished a collection cycle.
pub fn gc_step_kbytes(&self, kbytes: c_int) -> Result<bool> {
let lua = self.lock();
let state = lua.main_state();
unsafe {
check_stack(lua.main_state, 3)?;
protect_lua!(lua.main_state, 0, 0, |state| {
check_stack(state, 3)?;
protect_lua!(state, 0, 0, |state| {
ffi::lua_gc(state, ffi::LUA_GCSTEP, kbytes) != 0
})
}
}
/// Sets the 'pause' value of the collector.
/// Sets the `pause` value of the collector.
///
/// Returns the previous value of 'pause'. More information can be found in the Lua
/// Returns the previous value of `pause`. More information can be found in the Lua
/// [documentation].
///
/// For Luau this parameter sets GC goal
@@ -834,23 +871,24 @@ impl Lua {
/// [documentation]: https://www.lua.org/manual/5.4/manual.html#2.5
pub fn gc_set_pause(&self, pause: c_int) -> c_int {
let lua = self.lock();
let state = lua.main_state();
unsafe {
#[cfg(not(feature = "luau"))]
return ffi::lua_gc(lua.main_state, ffi::LUA_GCSETPAUSE, pause);
return ffi::lua_gc(state, ffi::LUA_GCSETPAUSE, pause);
#[cfg(feature = "luau")]
return ffi::lua_gc(lua.main_state, ffi::LUA_GCSETGOAL, pause);
return ffi::lua_gc(state, ffi::LUA_GCSETGOAL, pause);
}
}
/// Sets the 'step multiplier' value of the collector.
/// Sets the `step multiplier` value of the collector.
///
/// Returns the previous value of the 'step multiplier'. More information can be found in the
/// Returns the previous value of the `step multiplier`. More information can be found in the
/// Lua [documentation].
///
/// [documentation]: https://www.lua.org/manual/5.4/manual.html#2.5
pub fn gc_set_step_multiplier(&self, step_multiplier: c_int) -> c_int {
let lua = self.lock();
unsafe { ffi::lua_gc(lua.main_state, ffi::LUA_GCSETSTEPMUL, step_multiplier) }
unsafe { ffi::lua_gc(lua.main_state(), ffi::LUA_GCSETSTEPMUL, step_multiplier) }
}
/// Changes the collector to incremental mode with the given parameters.
@@ -861,7 +899,7 @@ impl Lua {
/// [documentation]: https://www.lua.org/manual/5.4/manual.html#2.5.1
pub fn gc_inc(&self, pause: c_int, step_multiplier: c_int, step_size: c_int) -> GCMode {
let lua = self.lock();
let state = lua.main_state;
let state = lua.main_state();
#[cfg(any(
feature = "lua53",
@@ -914,7 +952,7 @@ impl Lua {
#[cfg_attr(docsrs, doc(cfg(feature = "lua54")))]
pub fn gc_gen(&self, minor_multiplier: c_int, major_multiplier: c_int) -> GCMode {
let lua = self.lock();
let state = lua.main_state;
let state = lua.main_state();
let prev_mode = unsafe { ffi::lua_gc(state, ffi::LUA_GCGEN, minor_multiplier, major_multiplier) };
match prev_mode {
ffi::LUA_GCGEN => GCMode::Generational,
@@ -973,10 +1011,17 @@ impl Lua {
/// [`Chunk::exec`]: crate::Chunk::exec
#[track_caller]
pub fn load<'a>(&self, chunk: impl AsChunk<'a>) -> Chunk<'a> {
let caller = Location::caller();
self.load_with_location(chunk, Location::caller())
}
pub(crate) fn load_with_location<'a>(
&self,
chunk: impl AsChunk<'a>,
location: &'static Location<'static>,
) -> Chunk<'a> {
Chunk {
lua: self.weak(),
name: chunk.name().unwrap_or_else(|| caller.to_string()),
name: chunk.name().unwrap_or_else(|| location.to_string()),
env: chunk.environment(self),
mode: chunk.mode(),
source: chunk.source(),
@@ -985,9 +1030,10 @@ impl Lua {
}
}
/// Create and return an interned Lua string. Lua strings can be arbitrary `[u8]` data including
/// embedded nulls, so in addition to `&str` and `&String`, you can also pass plain `&[u8]`
/// here.
/// Create and return an interned Lua string.
///
/// Lua strings can be arbitrary `[u8]` data including embedded nulls, so in addition to `&str`
/// and `&String`, you can also pass plain `&[u8]` here.
#[inline]
pub fn create_string(&self, s: impl AsRef<[u8]>) -> Result<String> {
unsafe { self.lock().create_string(s) }
@@ -997,33 +1043,36 @@ impl Lua {
///
/// Requires `feature = "luau"`
///
/// [buffer]: https://luau-lang.org/library#buffer-library
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
/// [buffer]: https://luau.org/library#buffer-library
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn create_buffer(&self, buf: impl AsRef<[u8]>) -> Result<Buffer> {
let lua = self.lock();
let state = lua.state();
unsafe {
if lua.unlikely_memory_error() {
crate::util::push_buffer(lua.ref_thread(), buf.as_ref(), false)?;
return Ok(Buffer(lua.pop_ref_thread()));
crate::util::push_buffer(state, buf.as_ref(), false)?;
return Ok(Buffer(lua.pop_ref()));
}
let _sg = StackGuard::new(state);
check_stack(state, 4)?;
check_stack(state, 3)?;
crate::util::push_buffer(state, buf.as_ref(), true)?;
Ok(Buffer(lua.pop_ref()))
}
}
/// Creates and returns a new empty table.
#[inline]
pub fn create_table(&self) -> Result<Table> {
self.create_table_with_capacity(0, 0)
}
/// Creates and returns a new empty table, with the specified capacity.
/// `narr` is a hint for how many elements the table will have as a sequence;
/// `nrec` is a hint for how many other elements the table will have.
///
/// - `narr` is a hint for how many elements the table will have as a sequence.
/// - `nrec` is a hint for how many other elements the table will have.
///
/// Lua may use these hints to preallocate memory for the new table.
pub fn create_table_with_capacity(&self, narr: usize, nrec: usize) -> Result<Table> {
unsafe { self.lock().create_table_with_capacity(narr, nrec) }
@@ -1109,9 +1158,6 @@ impl Lua {
/// # Ok(())
/// # }
/// ```
///
/// [`IntoLua`]: crate::IntoLua
/// [`IntoLuaMulti`]: crate::IntoLuaMulti
pub fn create_function<F, A, R>(&self, func: F) -> Result<Function>
where
F: Fn(&Lua, A) -> Result<R> + MaybeSend + 'static,
@@ -1126,10 +1172,7 @@ impl Lua {
/// Wraps a Rust mutable closure, creating a callable Lua function handle to it.
///
/// This is a version of [`create_function`] that accepts a FnMut argument. Refer to
/// [`create_function`] for more information about the implementation.
///
/// [`create_function`]: #method.create_function
/// This is a version of [`Lua::create_function`] that accepts a `FnMut` argument.
pub fn create_function_mut<F, A, R>(&self, func: F) -> Result<Function>
where
F: FnMut(&Lua, A) -> Result<R> + MaybeSend + 'static,
@@ -1158,9 +1201,9 @@ impl Lua {
/// call `yield()` passing internal representation of a `Poll::Pending` value.
///
/// The function must be called inside Lua coroutine ([`Thread`]) to be able to suspend its
/// execution. An executor should be used to poll [`AsyncThread`] and mlua will take a
/// provided Waker in that case. Otherwise noop waker will be used if try to call the
/// function outside of Rust executors.
/// execution. An executor should be used to poll [`AsyncThread`] and mlua will take a provided
/// Waker in that case. Otherwise noop waker will be used if try to call the function outside of
/// Rust executors.
///
/// The family of `call_async()` functions takes care about creating [`Thread`].
///
@@ -1189,7 +1232,6 @@ impl Lua {
/// }
/// ```
///
/// [`Thread`]: crate::Thread
/// [`AsyncThread`]: crate::AsyncThread
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
@@ -1246,7 +1288,7 @@ impl Lua {
/// Creates a Lua userdata object from a custom Rust type.
///
/// You can register the type using [`Lua::register_userdata_type()`] to add fields or methods
/// You can register the type using [`Lua::register_userdata_type`] to add fields or methods
/// _before_ calling this method.
/// Otherwise, the userdata object will have an empty metatable.
///
@@ -1261,7 +1303,7 @@ impl Lua {
/// Creates a Lua userdata object from a custom serializable Rust type.
///
/// See [`Lua::create_any_userdata()`] for more details.
/// See [`Lua::create_any_userdata`] for more details.
///
/// Requires `feature = "serialize"`
#[cfg(feature = "serialize")]
@@ -1279,7 +1321,7 @@ impl Lua {
/// This methods provides a way to add fields or methods to userdata objects of a type `T`.
pub fn register_userdata_type<T: 'static>(&self, f: impl FnOnce(&mut UserDataRegistry<T>)) -> Result<()> {
let type_id = TypeId::of::<T>();
let mut registry = UserDataRegistry::new(type_id);
let mut registry = UserDataRegistry::new(self);
f(&mut registry);
let lua = self.lock();
@@ -1289,8 +1331,8 @@ impl Lua {
ffi::luaL_unref(lua.state(), ffi::LUA_REGISTRYINDEX, table_id);
}
// Register the type
lua.create_userdata_metatable(registry)?;
// Add to "pending" registration map
((*lua.extra.get()).pending_userdata_reg).insert(type_id, registry.into_raw());
}
Ok(())
}
@@ -1419,9 +1461,10 @@ impl Lua {
}
}
/// Returns a handle to the active `Thread`. For calls to `Lua` this will be the main Lua
/// thread, for parameters given to a callback, this will be whatever Lua thread called the
/// callback.
/// Returns a handle to the active `Thread`.
///
/// For calls to `Lua` this will be the main Lua thread, for parameters given to a callback,
/// this will be whatever Lua thread called the callback.
pub fn current_thread(&self) -> Thread {
let lua = self.lock();
let state = lua.state();
@@ -1433,30 +1476,21 @@ impl Lua {
}
}
/// Calls the given function with a `Scope` parameter, giving the function the ability to create
/// userdata and callbacks from rust types that are !Send or non-'static.
/// Calls the given function with a [`Scope`] parameter, giving the function the ability to
/// create userdata and callbacks from Rust types that are `!Send` or non-`'static`.
///
/// The lifetime of any function or userdata created through `Scope` lasts only until the
/// The lifetime of any function or userdata created through [`Scope`] lasts only until the
/// completion of this method call, on completion all such created values are automatically
/// dropped and Lua references to them are invalidated. If a script accesses a value created
/// through `Scope` outside of this method, a Lua error will result. Since we can ensure the
/// lifetime of values created through `Scope`, and we know that `Lua` cannot be sent to another
/// thread while `Scope` is live, it is safe to allow !Send datatypes and whose lifetimes only
/// outlive the scope lifetime.
///
/// Inside the scope callback, all handles created through Scope will share the same unique 'lua
/// lifetime of the parent `Lua`. This allows scoped and non-scoped values to be mixed in
/// API calls, which is very useful (e.g. passing a scoped userdata to a non-scoped function).
/// However, this also enables handles to scoped values to be trivially leaked from the given
/// callback. This is not dangerous, though! After the callback returns, all scoped values are
/// invalidated, which means that though references may exist, the Rust types backing them have
/// dropped. `Function` types will error when called, and `AnyUserData` will be typeless. It
/// would be impossible to prevent handles to scoped values from escaping anyway, since you
/// would always be able to smuggle them through Lua state.
/// through [`Scope`] outside of this method, a Lua error will result. Since we can ensure the
/// lifetime of values created through [`Scope`], and we know that [`Lua`] cannot be sent to
/// another thread while [`Scope`] is live, it is safe to allow `!Send` data types and whose
/// lifetimes only outlive the scope lifetime.
pub fn scope<'env, R>(
&self,
f: impl for<'scope> FnOnce(&'scope mut Scope<'scope, 'env>) -> Result<R>,
) -> Result<R> {
// TODO: Update to `&Scope` in next major release
f(&mut Scope::new(self.lock_arc()))
}
@@ -1544,41 +1578,41 @@ impl Lua {
})
}
/// Converts a value that implements `IntoLua` into a `Value` instance.
/// Converts a value that implements [`IntoLua`] into a [`Value`] instance.
#[inline]
pub fn pack(&self, t: impl IntoLua) -> Result<Value> {
t.into_lua(self)
}
/// Converts a `Value` instance into a value that implements `FromLua`.
/// Converts a [`Value`] instance into a value that implements [`FromLua`].
#[inline]
pub fn unpack<T: FromLua>(&self, value: Value) -> Result<T> {
T::from_lua(value, self)
}
/// Converts a value that implements `IntoLua` into a `FromLua` variant.
/// Converts a value that implements [`IntoLua`] into a [`FromLua`] variant.
#[inline]
pub fn convert<U: FromLua>(&self, value: impl IntoLua) -> Result<U> {
U::from_lua(value.into_lua(self)?, self)
}
/// Converts a value that implements `IntoLuaMulti` into a `MultiValue` instance.
/// Converts a value that implements [`IntoLuaMulti`] into a [`MultiValue`] instance.
#[inline]
pub fn pack_multi(&self, t: impl IntoLuaMulti) -> Result<MultiValue> {
t.into_lua_multi(self)
}
/// Converts a `MultiValue` instance into a value that implements `FromLuaMulti`.
/// Converts a [`MultiValue`] instance into a value that implements [`FromLuaMulti`].
#[inline]
pub fn unpack_multi<T: FromLuaMulti>(&self, value: MultiValue) -> Result<T> {
T::from_lua_multi(value, self)
}
/// Set a value in the Lua registry based on a string name.
/// Set a value in the Lua registry based on a string key.
///
/// This value will be available to rust from all `Lua` instances which share the same main
/// This value will be available to Rust from all Lua instances which share the same main
/// state.
pub fn set_named_registry_value(&self, name: &str, t: impl IntoLua) -> Result<()> {
pub fn set_named_registry_value(&self, key: &str, t: impl IntoLua) -> Result<()> {
let lua = self.lock();
let state = lua.state();
unsafe {
@@ -1586,15 +1620,15 @@ impl Lua {
check_stack(state, 5)?;
lua.push(t)?;
rawset_field(state, ffi::LUA_REGISTRYINDEX, name)
rawset_field(state, ffi::LUA_REGISTRYINDEX, key)
}
}
/// Get a value from the Lua registry based on a string name.
/// Get a value from the Lua registry based on a string key.
///
/// Any Lua instance which shares the underlying main state may call this method to
/// get a value previously set by [`Lua::set_named_registry_value`].
pub fn named_registry_value<T>(&self, name: &str) -> Result<T>
pub fn named_registry_value<T>(&self, key: &str) -> Result<T>
where
T: FromLua,
{
@@ -1605,7 +1639,7 @@ impl Lua {
check_stack(state, 3)?;
let protect = !lua.unlikely_memory_error();
push_string(state, name.as_bytes(), protect)?;
push_string(state, key.as_bytes(), protect)?;
ffi::lua_rawget(state, ffi::LUA_REGISTRYINDEX);
T::from_stack(-1, &lua)
@@ -1614,14 +1648,15 @@ impl Lua {
/// Removes a named value in the Lua registry.
///
/// Equivalent to calling [`Lua::set_named_registry_value`] with a value of Nil.
pub fn unset_named_registry_value(&self, name: &str) -> Result<()> {
self.set_named_registry_value(name, Nil)
/// Equivalent to calling [`Lua::set_named_registry_value`] with a value of [`Nil`].
#[inline]
pub fn unset_named_registry_value(&self, key: &str) -> Result<()> {
self.set_named_registry_value(key, Nil)
}
/// Place a value in the Lua registry with an auto-generated key.
///
/// This value will be available to Rust from all `Lua` instances which share the same main
/// This value will be available to Rust from all Lua instances which share the same main
/// state.
///
/// Be warned, garbage collection of values held inside the registry is not automatic, see
@@ -1663,7 +1698,7 @@ impl Lua {
}
}
/// Get a value from the Lua registry by its `RegistryKey`
/// Get a value from the Lua registry by its [`RegistryKey`]
///
/// Any Lua instance which shares the underlying main state may call this method to get a value
/// previously placed by [`Lua::create_registry_value`].
@@ -1698,9 +1733,7 @@ impl Lua {
return Err(Error::MismatchedRegistryKey);
}
unsafe {
ffi::luaL_unref(lua.state(), ffi::LUA_REGISTRYINDEX, key.take());
}
unsafe { ffi::luaL_unref(lua.state(), ffi::LUA_REGISTRYINDEX, key.take()) };
Ok(())
}
@@ -1746,8 +1779,8 @@ impl Lua {
Ok(())
}
/// Returns true if the given [`RegistryKey`] was created by a [`Lua`] which shares the
/// underlying main state with this [`Lua`] instance.
/// Returns true if the given [`RegistryKey`] was created by a Lua which shares the
/// underlying main state with this Lua instance.
///
/// Other than this, methods that accept a [`RegistryKey`] will return
/// [`Error::MismatchedRegistryKey`] if passed a [`RegistryKey`] that was not created with a
@@ -1767,8 +1800,8 @@ impl Lua {
let state = lua.state();
unsafe {
let mut unref_list = (*lua.extra.get()).registry_unref_list.lock();
let unref_list = mem::replace(&mut *unref_list, Some(Vec::new()));
for id in mlua_expect!(unref_list, "unref list not set") {
let unref_list = unref_list.replace(Vec::new());
for id in mlua_expect!(unref_list, "unref list is not set") {
ffi::luaL_unref(state, ffi::LUA_REGISTRYINDEX, id);
}
}
@@ -1798,7 +1831,7 @@ impl Lua {
/// fn main() -> Result<()> {
/// let lua = Lua::new();
/// lua.set_app_data("hello");
/// lua.create_function(hello)?.call(())?;
/// lua.create_function(hello)?.call::<()>(())?;
/// let s = lua.app_data_ref::<&str>().unwrap();
/// assert_eq!(*s, "world");
/// Ok(())
@@ -1819,14 +1852,14 @@ impl Lua {
/// - `Err(data)` if the data object of type `T` was not inserted because the container is
/// currently borrowed.
///
/// See [`Lua::set_app_data()`] for examples.
/// See [`Lua::set_app_data`] for examples.
pub fn try_set_app_data<T: MaybeSend + 'static>(&self, data: T) -> StdResult<Option<T>, T> {
let lua = self.lock();
let extra = unsafe { &*lua.extra.get() };
extra.app_data.try_insert(data)
}
/// Gets a reference to an application data object stored by [`Lua::set_app_data()`] of type
/// Gets a reference to an application data object stored by [`Lua::set_app_data`] of type
/// `T`.
///
/// # Panics
@@ -1840,7 +1873,15 @@ impl Lua {
extra.app_data.borrow(Some(guard))
}
/// Gets a mutable reference to an application data object stored by [`Lua::set_app_data()`] of
/// Tries to get a reference to an application data object stored by [`Lua::set_app_data`] of
/// type `T`.
pub fn try_app_data_ref<T: 'static>(&self) -> StdResult<Option<AppDataRef<T>>, BorrowError> {
let guard = self.lock_arc();
let extra = unsafe { &*guard.extra.get() };
extra.app_data.try_borrow(Some(guard))
}
/// Gets a mutable reference to an application data object stored by [`Lua::set_app_data`] of
/// type `T`.
///
/// # Panics
@@ -1853,6 +1894,14 @@ impl Lua {
extra.app_data.borrow_mut(Some(guard))
}
/// Tries to get a mutable reference to an application data object stored by
/// [`Lua::set_app_data`] of type `T`.
pub fn try_app_data_mut<T: 'static>(&self) -> StdResult<Option<AppDataRefMut<T>>, BorrowMutError> {
let guard = self.lock_arc();
let extra = unsafe { &*guard.extra.get() };
extra.app_data.try_borrow_mut(Some(guard))
}
/// Removes an application data of type `T`.
///
/// # Panics
@@ -1866,6 +1915,8 @@ impl Lua {
}
/// Returns an internal `Poll::Pending` constant used for executing async callbacks.
///
/// Every time when [`Future`] is Pending, Lua corotine is suspended with this constant.
#[cfg(feature = "async")]
#[doc(hidden)]
#[inline(always)]
@@ -1874,6 +1925,15 @@ impl Lua {
LightUserData(&ASYNC_POLL_PENDING as *const u8 as *mut std::os::raw::c_void)
}
/// Returns a weak reference to the Lua instance.
///
/// This is useful for creating a reference to the Lua instance that does not prevent it from
/// being deallocated.
#[inline(always)]
pub fn weak(&self) -> WeakLua {
WeakLua(XRc::downgrade(&self.raw))
}
// Luau version located in `luau/mod.rs`
#[cfg(not(feature = "luau"))]
fn disable_c_modules(&self) -> Result<()> {
@@ -1912,11 +1972,6 @@ impl Lua {
LuaGuard(self.raw.lock_arc())
}
#[inline(always)]
pub(crate) fn weak(&self) -> WeakLua {
WeakLua(XRc::downgrade(&self.raw))
}
/// Returns a handle to the unprotected Lua state without any synchronization.
///
/// This is useful where we know that the lock is already held by the caller.
@@ -1939,14 +1994,30 @@ impl WeakLua {
Some(LuaGuard::new(self.0.upgrade()?))
}
/// Upgrades the weak Lua reference to a strong reference.
///
/// # Panics
///
/// Panics if the Lua instance is destroyed.
#[track_caller]
#[inline(always)]
pub(crate) fn upgrade(&self) -> Lua {
pub fn upgrade(&self) -> Lua {
Lua {
raw: self.0.upgrade().expect("Lua instance is destroyed"),
collect_garbage: false,
}
}
/// Tries to upgrade the weak Lua reference to a strong reference.
///
/// Returns `None` if the Lua instance is destroyed.
#[inline(always)]
pub fn try_upgrade(&self) -> Option<Lua> {
Some(Lua {
raw: self.0.upgrade()?,
collect_garbage: false,
})
}
}
impl PartialEq for WeakLua {
+8 -3
View File
@@ -13,6 +13,7 @@ use crate::error::Result;
use crate::state::RawLua;
use crate::stdlib::StdLib;
use crate::types::{AppData, ReentrantMutex, XRc};
use crate::userdata::RawUserDataRegistry;
use crate::util::{get_internal_metatable, push_internal_userdata, TypeKey, WrappedFailure};
#[cfg(any(feature = "luau", doc))]
@@ -26,8 +27,8 @@ use super::{Lua, WeakLua};
// Unique key to store `ExtraData` in the registry
static EXTRA_REGISTRY_KEY: u8 = 0;
const WRAPPED_FAILURE_POOL_SIZE: usize = 64;
const REF_STACK_RESERVE: c_int = 1;
const WRAPPED_FAILURE_POOL_DEFAULT_CAPACITY: usize = 64;
const REF_STACK_RESERVE: c_int = 2;
/// Data associated with the Lua state.
pub(crate) struct ExtraData {
@@ -35,6 +36,7 @@ pub(crate) struct ExtraData {
pub(super) weak: MaybeUninit<WeakLua>,
pub(super) owned: bool,
pub(super) pending_userdata_reg: FxHashMap<TypeId, RawUserDataRegistry>,
pub(super) registered_userdata_t: FxHashMap<TypeId, c_int>,
pub(super) registered_userdata_mt: FxHashMap<*const c_void, Option<TypeId>>,
pub(super) last_checked_userdata_mt: (*const c_void, Option<TypeId>),
@@ -58,6 +60,7 @@ pub(crate) struct ExtraData {
// Pool of `WrappedFailure` enums in the ref thread (as userdata)
pub(super) wrapped_failure_pool: Vec<c_int>,
pub(super) wrapped_failure_top: usize,
// Pool of `Thread`s (coroutines) for async execution
#[cfg(feature = "async")]
pub(super) thread_pool: Vec<c_int>,
@@ -144,6 +147,7 @@ impl ExtraData {
lua: MaybeUninit::uninit(),
weak: MaybeUninit::uninit(),
owned,
pending_userdata_reg: FxHashMap::default(),
registered_userdata_t: FxHashMap::default(),
registered_userdata_mt: FxHashMap::default(),
last_checked_userdata_mt: (ptr::null(), None),
@@ -157,7 +161,8 @@ impl ExtraData {
ref_stack_size: ffi::LUA_MINSTACK - REF_STACK_RESERVE,
ref_stack_top: ffi::lua_gettop(ref_thread),
ref_free: Vec::new(),
wrapped_failure_pool: Vec::with_capacity(WRAPPED_FAILURE_POOL_SIZE),
wrapped_failure_pool: Vec::with_capacity(WRAPPED_FAILURE_POOL_DEFAULT_CAPACITY),
wrapped_failure_top: 0,
#[cfg(feature = "async")]
thread_pool: Vec::new(),
wrapped_failure_mt_ptr,
+163 -124
View File
@@ -1,17 +1,18 @@
use std::any::TypeId;
use std::cell::{Cell, UnsafeCell};
use std::ffi::{CStr, CString};
use std::mem;
use std::os::raw::{c_char, c_int, c_void};
use std::panic::resume_unwind;
use std::ptr::{self, NonNull};
use std::result::Result as StdResult;
use std::sync::Arc;
use std::{mem, ptr};
use crate::chunk::ChunkMode;
use crate::error::{Error, Result};
use crate::function::Function;
use crate::memory::{MemoryState, ALLOCATOR};
use crate::state::util::{callback_error_ext, ref_stack_pop, StateGuard};
use crate::state::util::{callback_error_ext, ref_stack_pop};
use crate::stdlib::StdLib;
use crate::string::String;
use crate::table::Table;
@@ -21,10 +22,13 @@ use crate::types::{
AppDataRef, AppDataRefMut, Callback, CallbackUpvalue, DestructedUserdata, Integer, LightUserData,
MaybeSend, ReentrantMutex, RegistryKey, ValueRef, XRc,
};
use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataRegistry, UserDataStorage};
use crate::userdata::{
init_userdata_metatable, AnyUserData, MetaMethod, RawUserDataRegistry, UserData, UserDataRegistry,
UserDataStorage,
};
use crate::util::{
assert_stack, check_stack, get_destructed_userdata_metatable, get_internal_userdata, get_main_state,
get_userdata, init_error_registry, init_internal_metatable, init_userdata_metatable, pop_error,
get_metatable_ptr, get_userdata, init_error_registry, init_internal_metatable, pop_error,
push_internal_userdata, push_string, push_table, rawset_field, safe_pcall, safe_xpcall, short_type_name,
StackGuard, WrappedFailure,
};
@@ -41,7 +45,6 @@ use {
crate::multi::MultiValue,
crate::traits::FromLuaMulti,
crate::types::{AsyncCallback, AsyncCallbackUpvalue, AsyncPollUpvalue},
std::ptr::NonNull,
std::task::{Context, Poll, Waker},
};
@@ -50,7 +53,7 @@ use {
pub struct RawLua {
// The state is dynamic and depends on context
pub(super) state: Cell<*mut ffi::lua_State>,
pub(super) main_state: *mut ffi::lua_State,
pub(super) main_state: Option<NonNull<ffi::lua_State>>,
pub(super) extra: XRc<UnsafeCell<ExtraData>>,
}
@@ -61,9 +64,9 @@ impl Drop for RawLua {
return;
}
let mem_state = MemoryState::get(self.main_state);
let mem_state = MemoryState::get(self.main_state());
ffi::lua_close(self.main_state);
ffi::lua_close(self.main_state());
// Deallocate `MemoryState`
if !mem_state.is_null() {
@@ -95,10 +98,11 @@ impl RawLua {
self.state.get()
}
#[cfg(feature = "luau")]
#[inline(always)]
pub(crate) fn main_state(&self) -> *mut ffi::lua_State {
self.main_state
.map(|state| state.as_ptr())
.unwrap_or_else(|| self.state())
}
#[inline(always)]
@@ -129,7 +133,7 @@ impl RawLua {
let extra = rawlua.lock().extra.get();
mlua_expect!(
load_from_std_lib(state, libs),
load_std_libs(state, libs),
"Error during loading standard libraries"
);
(*extra).libs |= libs;
@@ -221,7 +225,8 @@ impl RawLua {
#[allow(clippy::arc_with_non_send_sync)]
let rawlua = XRc::new(ReentrantMutex::new(RawLua {
state: Cell::new(state),
main_state,
// Make sure that we don't store current state as main state (if it's not available)
main_state: get_main_state(state).and_then(NonNull::new),
extra: XRc::clone(&extra),
}));
(*extra.get()).set_lua(&rawlua);
@@ -238,8 +243,8 @@ impl RawLua {
/// Marks the Lua state as safe.
#[inline(always)]
pub(super) unsafe fn set_safe(&self) {
(*self.extra.get()).safe = true;
pub(super) fn mark_safe(&self) {
unsafe { (*self.extra.get()).safe = true };
}
/// Loads the specified subset of the standard libraries into an existing Lua state.
@@ -263,7 +268,7 @@ impl RawLua {
));
}
let res = load_from_std_lib(self.main_state, libs);
let res = load_std_libs(self.main_state(), libs);
// If `package` library loaded into a safe lua state then disable C modules
let curr_libs = (*self.extra.get()).libs;
@@ -315,41 +320,60 @@ impl RawLua {
let state = self.state();
unsafe {
let _sg = StackGuard::new(state);
check_stack(state, 2)?;
check_stack(state, 3)?;
let mode_str = match mode {
let name = name.map(CStr::as_ptr).unwrap_or(ptr::null());
let mode = match mode {
Some(ChunkMode::Binary) => cstr!("b"),
Some(ChunkMode::Text) => cstr!("t"),
None => cstr!("bt"),
};
match ffi::luaL_loadbufferenv(
state,
source.as_ptr() as *const c_char,
source.len(),
name.map(|n| n.as_ptr()).unwrap_or_else(ptr::null),
mode_str,
match env {
Some(env) => {
self.push_ref(&env.0);
-1
}
_ => 0,
},
) {
ffi::LUA_OK => {
#[cfg(feature = "luau-jit")]
if (*self.extra.get()).enable_jit && ffi::luau_codegen_supported() != 0 {
ffi::luau_codegen_compile(state, -1);
}
Ok(Function(self.pop_ref()))
}
let status = if self.unlikely_memory_error() {
self.load_chunk_inner(state, name, env, mode, source)
} else {
// Luau and Lua 5.2 can trigger an exception during chunk loading
protect_lua!(state, 0, 1, |state| {
self.load_chunk_inner(state, name, env, mode, source)
})?
};
match status {
ffi::LUA_OK => Ok(Function(self.pop_ref())),
err => Err(pop_error(state, err)),
}
}
}
pub(crate) unsafe fn load_chunk_inner(
&self,
state: *mut ffi::lua_State,
name: *const c_char,
env: Option<&Table>,
mode: *const c_char,
source: &[u8],
) -> c_int {
let status = ffi::luaL_loadbufferenv(
state,
source.as_ptr() as *const c_char,
source.len(),
name,
mode,
match env {
Some(env) => {
self.push_ref(&env.0);
-1
}
_ => 0,
},
);
#[cfg(feature = "luau-jit")]
if status == ffi::LUA_OK {
if (*self.extra.get()).enable_jit && ffi::luau_codegen_supported() != 0 {
ffi::luau_codegen_compile(state, -1);
}
}
status
}
/// Sets a 'hook' function for a thread (coroutine).
#[cfg(not(feature = "luau"))]
pub(crate) unsafe fn set_thread_hook<F>(
@@ -377,7 +401,6 @@ impl RawLua {
return Ok(VmState::Continue); // Don't allow recursion
}
let rawlua = (*extra).raw_lua();
let _guard = StateGuard::new(rawlua, state);
let debug = Debug::new(rawlua, ar);
hook_cb((*extra).lua(), debug)
});
@@ -409,8 +432,8 @@ impl RawLua {
pub(crate) unsafe fn create_string(&self, s: impl AsRef<[u8]>) -> Result<String> {
let state = self.state();
if self.unlikely_memory_error() {
push_string(self.ref_thread(), s.as_ref(), false)?;
return Ok(String(self.pop_ref_thread()));
push_string(state, s.as_ref(), false)?;
return Ok(String(self.pop_ref()));
}
let _sg = StackGuard::new(state);
@@ -421,12 +444,12 @@ impl RawLua {
/// See [`Lua::create_table_with_capacity`]
pub(crate) unsafe fn create_table_with_capacity(&self, narr: usize, nrec: usize) -> Result<Table> {
let state = self.state();
if self.unlikely_memory_error() {
push_table(self.ref_thread(), narr, nrec, false)?;
return Ok(Table(self.pop_ref_thread()));
push_table(state, narr, nrec, false)?;
return Ok(Table(self.pop_ref()));
}
let state = self.state();
let _sg = StackGuard::new(state);
check_stack(state, 3)?;
push_table(state, narr, nrec, true)?;
@@ -482,7 +505,6 @@ impl RawLua {
/// Wraps a Lua function into a new or recycled thread (coroutine).
#[cfg(feature = "async")]
pub(crate) unsafe fn create_recycled_thread(&self, func: &Function) -> Result<Thread> {
#[cfg(any(feature = "lua54", feature = "luau"))]
if let Some(index) = (*self.extra.get()).thread_pool.pop() {
let thread_state = ffi::lua_tothread(self.ref_thread(), index);
ffi::lua_xpush(self.ref_thread(), thread_state, func.0.index);
@@ -502,27 +524,47 @@ impl RawLua {
/// Resets thread (coroutine) and returns it to the pool for later use.
#[cfg(feature = "async")]
#[cfg(any(feature = "lua54", feature = "luau"))]
pub(crate) unsafe fn recycle_thread(&self, thread: &mut Thread) -> bool {
pub(crate) unsafe fn recycle_thread(&self, thread: &mut Thread) {
let thread_state = thread.1;
let extra = &mut *self.extra.get();
if extra.thread_pool.len() < extra.thread_pool.capacity() {
let thread_state = ffi::lua_tothread(extra.ref_thread, thread.0.index);
#[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, self.state());
if extra.thread_pool.len() == extra.thread_pool.capacity() {
#[cfg(feature = "lua54")]
if status != ffi::LUA_OK {
// Error object is on top, drop it
if ffi::lua_status(thread_state) != ffi::LUA_OK {
// Close all to-be-closed variables without returning thread to the pool
#[cfg(not(feature = "vendored"))]
ffi::lua_resetthread(thread_state);
#[cfg(feature = "vendored")]
ffi::lua_closethread(thread_state, self.state());
}
return;
}
let mut reset_ok = false;
if ffi::lua_status(thread_state) == ffi::LUA_OK {
if ffi::lua_gettop(thread_state) > 0 {
ffi::lua_settop(thread_state, 0);
}
#[cfg(feature = "luau")]
reset_ok = true;
}
#[cfg(feature = "lua54")]
if !reset_ok {
#[cfg(not(feature = "vendored"))]
let status = ffi::lua_resetthread(thread_state);
#[cfg(feature = "vendored")]
let status = ffi::lua_closethread(thread_state, self.state());
reset_ok = status == ffi::LUA_OK;
}
#[cfg(feature = "luau")]
if !reset_ok {
ffi::lua_resetthread(thread_state);
reset_ok = true;
}
if reset_ok {
extra.thread_pool.push(thread.0.index);
thread.0.drop = false; // Prevent thread from being garbage collected
return true;
}
false
}
/// Pushes a value that implements `IntoLua` onto the Lua stack.
@@ -613,9 +655,9 @@ impl RawLua {
let v = ffi::lua_tovector(state, idx);
mlua_debug_assert!(!v.is_null(), "vector is null");
#[cfg(not(feature = "luau-vector4"))]
return Value::Vector(crate::types::Vector([*v, *v.add(1), *v.add(2)]));
return Value::Vector(crate::Vector([*v, *v.add(1), *v.add(2)]));
#[cfg(feature = "luau-vector4")]
return Value::Vector(crate::types::Vector([*v, *v.add(1), *v.add(2), *v.add(3)]));
return Value::Vector(crate::Vector([*v, *v.add(1), *v.add(2), *v.add(3)]));
}
ffi::LUA_TSTRING => {
@@ -711,6 +753,10 @@ impl RawLua {
pub(crate) unsafe fn drop_ref(&self, vref: &ValueRef) {
let ref_thread = self.ref_thread();
mlua_debug_assert!(
ffi::lua_gettop(ref_thread) >= vref.index,
"GC finalizer is not allowed in ref_thread"
);
ffi::lua_pushnil(ref_thread);
ffi::lua_replace(ref_thread, vref.index);
(*self.extra.get()).ref_free.push(vref.index);
@@ -734,7 +780,7 @@ impl RawLua {
}
// MemoryInfo is empty in module mode so we cannot predict memory limits
match MemoryState::get(self.main_state) {
match MemoryState::get(self.state()) {
mem_state if !mem_state.is_null() => (*mem_state).memory_limit() == 0,
_ => (*self.extra.get()).skip_memory_check, // Check the special flag (only for module mode)
}
@@ -752,10 +798,10 @@ impl RawLua {
}
// Create a new metatable from `UserData` definition
let mut registry = UserDataRegistry::new(type_id);
let mut registry = UserDataRegistry::new(self.lua());
T::register(&mut registry);
self.create_userdata_metatable(registry)
self.create_userdata_metatable(registry.into_raw())
})
}
@@ -770,8 +816,11 @@ impl RawLua {
return Ok(table_id as Integer);
}
// Create an empty metatable
let registry = UserDataRegistry::<T>::new(type_id);
// Check if metatable creation is pending or create an empty metatable otherwise
let registry = match (*self.extra.get()).pending_userdata_reg.remove(&type_id) {
Some(registry) => registry,
None => UserDataRegistry::<T>::new(self.lua()).into_raw(),
};
self.create_userdata_metatable(registry)
})
}
@@ -785,12 +834,11 @@ impl RawLua {
let _sg = StackGuard::new(state);
check_stack(state, 3)?;
// We push metatable first to ensure having correct metatable with `__gc` method
ffi::lua_pushnil(state);
ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, get_metatable_id()?);
// We generate metatable first to make sure it *always* available when userdata pushed
let mt_id = get_metatable_id()?;
let protect = !self.unlikely_memory_error();
crate::util::push_userdata(state, data, protect)?;
ffi::lua_replace(state, -3);
ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, mt_id);
ffi::lua_setmetatable(state, -2);
// Set empty environment for Lua 5.1
@@ -808,12 +856,9 @@ impl RawLua {
Ok(AnyUserData(self.pop_ref()))
}
pub(crate) unsafe fn create_userdata_metatable<T>(
&self,
registry: UserDataRegistry<T>,
) -> Result<Integer> {
pub(crate) unsafe fn create_userdata_metatable(&self, registry: RawUserDataRegistry) -> Result<Integer> {
let state = self.state();
let type_id = registry.type_id();
let type_id = registry.type_id;
self.push_userdata_metatable(registry)?;
@@ -830,9 +875,9 @@ impl RawLua {
Ok(id as Integer)
}
pub(crate) unsafe fn push_userdata_metatable<T>(&self, mut registry: UserDataRegistry<T>) -> Result<()> {
pub(crate) unsafe fn push_userdata_metatable(&self, mut registry: RawUserDataRegistry) -> Result<()> {
let state = self.state();
let _sg = StackGuard::with_top(state, ffi::lua_gettop(state) + 1);
let mut stack_guard = StackGuard::new(state);
check_stack(state, 13)?;
// Prepare metatable, add meta methods first and then meta fields
@@ -850,21 +895,19 @@ impl RawLua {
rawset_field(state, -2, MetaMethod::validate(&k)?)?;
}
let mut has_name = false;
for (k, push_field) in registry.meta_fields {
for (k, v) in registry.meta_fields {
has_name = has_name || k == MetaMethod::Type;
push_field(self)?;
v?.push_into_stack(self)?;
rawset_field(state, -2, MetaMethod::validate(&k)?)?;
}
// Set `__name/__type` if not provided
if !has_name {
let type_name = short_type_name::<T>();
let type_name = registry.type_name;
push_string(state, type_name.as_bytes(), !self.unlikely_memory_error())?;
rawset_field(state, -2, MetaMethod::Type.name())?;
}
let metatable_index = ffi::lua_absindex(state, -1);
let mut extra_tables_count = 0;
let fields_nrec = registry.fields.len();
if fields_nrec > 0 {
// If `__index` is a table then update it in-place
@@ -876,8 +919,8 @@ impl RawLua {
ffi::lua_pop(state, 1);
push_table(state, 0, fields_nrec, true)?;
}
for (k, push_field) in mem::take(&mut registry.fields) {
push_field(self)?;
for (k, v) in mem::take(&mut registry.fields) {
v?.push_into_stack(self)?;
rawset_field(state, -2, &k)?;
}
rawset_field(state, metatable_index, "__index")?;
@@ -897,19 +940,18 @@ impl RawLua {
self.push(self.create_callback(m)?)?;
rawset_field(state, -2, &k)?;
}
for (k, push_field) in registry.fields {
for (k, v) in registry.fields {
unsafe extern "C-unwind" fn return_field(state: *mut ffi::lua_State) -> c_int {
ffi::lua_pushvalue(state, ffi::lua_upvalueindex(1));
1
}
push_field(self)?;
v?.push_into_stack(self)?;
protect_lua!(state, 1, 1, fn(state) {
ffi::lua_pushcclosure(state, return_field, 1);
})?;
rawset_field(state, -2, &k)?;
}
field_getters_index = Some(ffi::lua_absindex(state, -1));
extra_tables_count += 1;
}
let mut field_setters_index = None;
@@ -921,7 +963,6 @@ impl RawLua {
rawset_field(state, -2, &k)?;
}
field_setters_index = Some(ffi::lua_absindex(state, -1));
extra_tables_count += 1;
}
let mut methods_index = None;
@@ -958,18 +999,12 @@ impl RawLua {
}
_ => {
methods_index = Some(ffi::lua_absindex(state, -1));
extra_tables_count += 1;
}
}
}
#[cfg(feature = "luau")]
let extra_init = None;
#[cfg(not(feature = "luau"))]
let extra_init: Option<fn(*mut ffi::lua_State) -> Result<()>> = Some(|state| {
ffi::lua_pushcfunction(state, crate::util::userdata_destructor::<UserDataStorage<T>>);
rawset_field(state, -2, "__gc")
});
ffi::lua_pushcfunction(state, registry.destructor);
rawset_field(state, metatable_index, "__gc")?;
init_userdata_metatable(
state,
@@ -977,11 +1012,10 @@ impl RawLua {
field_getters_index,
field_setters_index,
methods_index,
extra_init,
)?;
// Pop extra tables to get metatable on top of the stack
ffi::lua_pop(state, extra_tables_count);
// Update stack guard to keep metatable after return
stack_guard.keep(1);
Ok(())
}
@@ -1002,17 +1036,22 @@ impl RawLua {
// Returns `TypeId` for the userdata ref, checking that it's registered and not destructed.
//
// Returns `None` if the userdata is registered but non-static.
pub(crate) unsafe fn get_userdata_ref_type_id(&self, vref: &ValueRef) -> Result<Option<TypeId>> {
self.get_userdata_type_id_inner(self.ref_thread(), vref.index)
#[inline(always)]
pub(crate) fn get_userdata_ref_type_id(&self, vref: &ValueRef) -> Result<Option<TypeId>> {
unsafe { self.get_userdata_type_id_inner(self.ref_thread(), vref.index) }
}
// Same as `get_userdata_ref_type_id` but assumes the userdata is already on the stack.
pub(crate) unsafe fn get_userdata_type_id<T>(&self, idx: c_int) -> Result<Option<TypeId>> {
match self.get_userdata_type_id_inner(self.state(), idx) {
pub(crate) unsafe fn get_userdata_type_id<T>(
&self,
state: *mut ffi::lua_State,
idx: c_int,
) -> Result<Option<TypeId>> {
match self.get_userdata_type_id_inner(state, idx) {
Ok(type_id) => Ok(type_id),
Err(Error::UserDataTypeMismatch) if ffi::lua_type(self.state(), idx) != ffi::LUA_TUSERDATA => {
Err(Error::UserDataTypeMismatch) if ffi::lua_type(state, idx) != ffi::LUA_TUSERDATA => {
// Report `FromLuaConversionError` instead
let idx_type_name = CStr::from_ptr(ffi::luaL_typename(self.state(), idx));
let idx_type_name = CStr::from_ptr(ffi::luaL_typename(state, idx));
let idx_type_name = idx_type_name.to_str().unwrap();
let message = format!("expected userdata of type '{}'", short_type_name::<T>());
Err(Error::from_lua_conversion(idx_type_name, "userdata", message))
@@ -1026,11 +1065,10 @@ impl RawLua {
state: *mut ffi::lua_State,
idx: c_int,
) -> Result<Option<TypeId>> {
if ffi::lua_getmetatable(state, idx) == 0 {
let mt_ptr = get_metatable_ptr(state, idx);
if mt_ptr.is_null() {
return Err(Error::UserDataTypeMismatch);
}
let mt_ptr = ffi::lua_topointer(state, -1);
ffi::lua_pop(state, 1);
// Fast path to skip looking up the metatable in the map
let (last_mt, last_type_id) = (*self.extra.get()).last_checked_userdata_mt;
@@ -1066,7 +1104,6 @@ impl RawLua {
// Lua ensures that `LUA_MINSTACK` stack spaces are available (after pushing arguments)
// The lock must be already held as the callback is executed
let rawlua = (*extra).raw_lua();
let _guard = StateGuard::new(rawlua, state);
match (*upvalue).data {
Some(ref func) => func(rawlua, nargs),
None => Err(Error::CallbackDestructed),
@@ -1101,7 +1138,7 @@ impl RawLua {
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))]
unsafe {
if !(*self.extra.get()).libs.contains(StdLib::COROUTINE) {
load_from_std_lib(self.main_state, StdLib::COROUTINE)?;
load_std_libs(self.main_state(), StdLib::COROUTINE)?;
(*self.extra.get()).libs |= StdLib::COROUTINE;
}
}
@@ -1110,12 +1147,10 @@ impl RawLua {
// Async functions cannot be scoped and therefore destroyed,
// so the first upvalue is always valid
let upvalue = get_userdata::<AsyncCallbackUpvalue>(state, ffi::lua_upvalueindex(1));
let extra = (*upvalue).extra.get();
callback_error_ext(state, extra, |extra, nargs| {
callback_error_ext(state, (*upvalue).extra.get(), |extra, nargs| {
// Lua ensures that `LUA_MINSTACK` stack spaces are available (after pushing arguments)
// The lock must be already held as the callback is executed
let rawlua = (*extra).raw_lua();
let _guard = StateGuard::new(rawlua, state);
let func = &*(*upvalue).data;
let fut = func(rawlua, nargs);
@@ -1140,7 +1175,6 @@ impl RawLua {
// Lua ensures that `LUA_MINSTACK` stack spaces are available (after pushing arguments)
// The lock must be already held as the future is polled
let rawlua = (*extra).raw_lua();
let _guard = StateGuard::new(rawlua, state);
let fut = &mut (*upvalue).data;
let mut ctx = Context::from_waker(rawlua.waker());
@@ -1250,7 +1284,7 @@ impl RawLua {
}
// Uses 3 stack spaces
unsafe fn load_from_std_lib(state: *mut ffi::lua_State, libs: StdLib) -> Result<()> {
unsafe fn load_std_libs(state: *mut ffi::lua_State, libs: StdLib) -> Result<()> {
#[inline(always)]
pub unsafe fn requiref(
state: *mut ffi::lua_State,
@@ -1346,6 +1380,12 @@ unsafe fn load_from_std_lib(state: *mut ffi::lua_State, libs: StdLib) -> Result<
ffi::lua_pop(state, 1);
}
#[cfg(feature = "luau")]
if libs.contains(StdLib::VECTOR) {
requiref(state, ffi::LUA_VECLIBNAME, ffi::luaopen_vector, 1)?;
ffi::lua_pop(state, 1);
}
if libs.contains(StdLib::MATH) {
requiref(state, ffi::LUA_MATHLIBNAME, ffi::luaopen_math, 1)?;
ffi::lua_pop(state, 1);
@@ -1368,16 +1408,15 @@ unsafe fn load_from_std_lib(state: *mut ffi::lua_State, libs: StdLib) -> Result<
}
#[cfg(feature = "luajit")]
{
if libs.contains(StdLib::JIT) {
requiref(state, ffi::LUA_JITLIBNAME, ffi::luaopen_jit, 1)?;
ffi::lua_pop(state, 1);
}
if libs.contains(StdLib::JIT) {
requiref(state, ffi::LUA_JITLIBNAME, ffi::luaopen_jit, 1)?;
ffi::lua_pop(state, 1);
}
if libs.contains(StdLib::FFI) {
requiref(state, ffi::LUA_FFILIBNAME, ffi::luaopen_ffi, 1)?;
ffi::lua_pop(state, 1);
}
#[cfg(feature = "luajit")]
if libs.contains(StdLib::FFI) {
requiref(state, ffi::LUA_FFILIBNAME, ffi::luaopen_ffi, 1)?;
ffi::lua_pop(state, 1);
}
Ok(())
+32 -40
View File
@@ -7,25 +7,23 @@ use crate::error::{Error, Result};
use crate::state::{ExtraData, RawLua};
use crate::util::{self, get_internal_metatable, WrappedFailure};
const WRAPPED_FAILURE_POOL_SIZE: usize = 64;
pub(super) struct StateGuard<'a>(&'a RawLua, *mut ffi::lua_State);
struct StateGuard<'a>(&'a RawLua, *mut ffi::lua_State);
impl<'a> StateGuard<'a> {
pub(super) fn new(inner: &'a RawLua, mut state: *mut ffi::lua_State) -> Self {
fn new(inner: &'a RawLua, mut state: *mut ffi::lua_State) -> Self {
state = inner.state.replace(state);
Self(inner, state)
}
}
impl<'a> Drop for StateGuard<'a> {
impl Drop for StateGuard<'_> {
fn drop(&mut self) {
self.0.state.set(self.1);
}
}
// An optimized version of `callback_error` that does not allocate `WrappedFailure` userdata
// and instead reuses unsed values from previous calls (or allocates new).
// and instead reuses unused values from previous calls (or allocates new).
pub(super) unsafe fn callback_error_ext<F, R>(
state: *mut ffi::lua_State,
mut extra: *mut ExtraData,
@@ -42,26 +40,27 @@ where
enum PreallocatedFailure {
New(*mut WrappedFailure),
Existing(i32),
Reserved,
}
impl PreallocatedFailure {
unsafe fn reserve(state: *mut ffi::lua_State, extra: *mut ExtraData) -> Self {
match (*extra).wrapped_failure_pool.pop() {
Some(index) => PreallocatedFailure::Existing(index),
None => {
// We need to check stack for Luau in case when callback is called from interrupt
// See https://github.com/Roblox/luau/issues/446 and mlua #142 and #153
#[cfg(feature = "luau")]
ffi::lua_rawcheckstack(state, 2);
// Place it to the beginning of the stack
let ud = WrappedFailure::new_userdata(state);
ffi::lua_insert(state, 1);
PreallocatedFailure::New(ud)
}
if (*extra).wrapped_failure_top > 0 {
(*extra).wrapped_failure_top -= 1;
return PreallocatedFailure::Reserved;
}
// We need to check stack for Luau in case when callback is called from interrupt
// See https://github.com/luau-lang/luau/issues/446 and mlua #142 and #153
#[cfg(feature = "luau")]
ffi::lua_rawcheckstack(state, 2);
// Place it to the beginning of the stack
let ud = WrappedFailure::new_userdata(state);
ffi::lua_insert(state, 1);
PreallocatedFailure::New(ud)
}
#[cold]
unsafe fn r#use(&self, state: *mut ffi::lua_State, extra: *mut ExtraData) -> *mut WrappedFailure {
let ref_thread = (*extra).ref_thread;
match *self {
@@ -69,12 +68,12 @@ where
ffi::lua_settop(state, 1);
ud
}
PreallocatedFailure::Existing(index) => {
PreallocatedFailure::Reserved => {
let index = (*extra).wrapped_failure_pool.pop().unwrap();
ffi::lua_settop(state, 0);
#[cfg(feature = "luau")]
ffi::lua_rawcheckstack(state, 2);
ffi::lua_pushvalue(ref_thread, index);
ffi::lua_xmove(ref_thread, state, 1);
ffi::lua_xpush(ref_thread, state, index);
ffi::lua_pushnil(ref_thread);
ffi::lua_replace(ref_thread, index);
(*extra).ref_free.push(index);
@@ -87,24 +86,13 @@ where
let ref_thread = (*extra).ref_thread;
match self {
PreallocatedFailure::New(_) => {
if (*extra).wrapped_failure_pool.len() < WRAPPED_FAILURE_POOL_SIZE {
ffi::lua_rotate(state, 1, -1);
ffi::lua_xmove(state, ref_thread, 1);
let index = ref_stack_pop(extra);
(*extra).wrapped_failure_pool.push(index);
} else {
ffi::lua_remove(state, 1);
}
}
PreallocatedFailure::Existing(index) => {
if (*extra).wrapped_failure_pool.len() < WRAPPED_FAILURE_POOL_SIZE {
(*extra).wrapped_failure_pool.push(index);
} else {
ffi::lua_pushnil(ref_thread);
ffi::lua_replace(ref_thread, index);
(*extra).ref_free.push(index);
}
ffi::lua_rotate(state, 1, -1);
ffi::lua_xmove(state, ref_thread, 1);
let index = ref_stack_pop(extra);
(*extra).wrapped_failure_pool.push(index);
(*extra).wrapped_failure_top += 1;
}
PreallocatedFailure::Reserved => (*extra).wrapped_failure_top += 1,
}
}
}
@@ -113,7 +101,11 @@ where
// to store a wrapped failure (error or panic) *before* we proceed.
let prealloc_failure = PreallocatedFailure::reserve(state, extra);
match catch_unwind(AssertUnwindSafe(|| f(extra, nargs))) {
match catch_unwind(AssertUnwindSafe(|| {
let rawlua = (*extra).raw_lua();
let _guard = StateGuard::new(rawlua, state);
f(extra, nargs)
})) {
Ok(Ok(r)) => {
// Return unused `WrappedFailure` to the pool
prealloc_failure.release(state, extra);
+7 -2
View File
@@ -43,17 +43,22 @@ impl StdLib {
/// [`package`](https://www.lua.org/manual/5.4/manual.html#6.3) library
pub const PACKAGE: StdLib = StdLib(1 << 8);
/// [`buffer`](https://luau-lang.org/library#buffer-library) library
/// [`buffer`](https://luau.org/library#buffer-library) library
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub const BUFFER: StdLib = StdLib(1 << 9);
/// [`vector`](https://luau.org/library#vector-library) library
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub const VECTOR: StdLib = StdLib(1 << 10);
/// [`jit`](http://luajit.org/ext_jit.html) library
///
/// Requires `feature = "luajit"`
#[cfg(any(feature = "luajit", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luajit")))]
pub const JIT: StdLib = StdLib(1 << 9);
pub const JIT: StdLib = StdLib(1 << 11);
/// (**unsafe**) [`ffi`](http://luajit.org/ext_ffi.html) library
///
+119 -38
View File
@@ -1,20 +1,22 @@
use std::borrow::Borrow;
use std::borrow::{Borrow, Cow};
use std::hash::{Hash, Hasher};
use std::ops::Deref;
use std::os::raw::{c_int, c_void};
use std::string::String as StdString;
use std::{cmp, fmt, slice, str};
use crate::error::{Error, Result};
use crate::state::Lua;
use crate::traits::IntoLua;
use crate::types::{LuaType, ValueRef};
use crate::value::Value;
#[cfg(feature = "serialize")]
use {
serde::ser::{Serialize, Serializer},
std::result::Result as StdResult,
};
use crate::error::{Error, Result};
use crate::state::Lua;
use crate::types::{LuaType, ValueRef};
/// Handle to an internal Lua string.
///
/// Unlike Rust strings, Lua strings may not be valid UTF-8.
@@ -42,20 +44,18 @@ impl String {
/// ```
#[inline]
pub fn to_str(&self) -> Result<BorrowedStr> {
let BorrowedBytes(bytes, guard) = self.as_bytes();
let s = str::from_utf8(bytes).map_err(|e| Error::FromLuaConversionError {
from: "string",
to: "&str".to_string(),
message: Some(e.to_string()),
})?;
Ok(BorrowedStr(s, guard))
BorrowedStr::try_from(self)
}
/// Converts this string to a [`StdString`].
///
/// Any non-Unicode sequences are replaced with [`U+FFFD REPLACEMENT CHARACTER`][U+FFFD].
///
/// This method returns [`StdString`] instead of [`Cow<'_, str>`] because lifetime cannot be
/// bound to a weak Lua object.
///
/// [U+FFFD]: std::char::REPLACEMENT_CHARACTER
/// [`Cow<'_, str>`]: std::borrow::Cow
///
/// # Examples
///
@@ -74,6 +74,16 @@ impl String {
StdString::from_utf8_lossy(&self.as_bytes()).into_owned()
}
/// Returns an object that implements [`Display`] for safely printing a Lua [`String`] that may
/// contain non-Unicode data.
///
/// This may perform lossy conversion.
///
/// [`Display`]: fmt::Display
pub fn display(&self) -> impl fmt::Display + '_ {
Display(self)
}
/// Get the bytes that make up this string.
///
/// The returned slice will not contain the terminating nul byte, but will contain any nul
@@ -93,19 +103,21 @@ impl String {
/// ```
#[inline]
pub fn as_bytes(&self) -> BorrowedBytes {
let (bytes, guard) = unsafe { self.to_slice() };
BorrowedBytes(&bytes[..bytes.len() - 1], guard)
BorrowedBytes::from(self)
}
/// Get the bytes that make up this string, including the trailing nul byte.
pub fn as_bytes_with_nul(&self) -> BorrowedBytes {
let (bytes, guard) = unsafe { self.to_slice() };
BorrowedBytes(bytes, guard)
let BorrowedBytes { buf, borrow, _lua } = BorrowedBytes::from(self);
// Include the trailing nul byte (it's always present but excluded by default)
let buf = unsafe { slice::from_raw_parts((*buf).as_ptr(), (*buf).len() + 1) };
BorrowedBytes { buf, borrow, _lua }
}
// Does not return the terminating nul byte
unsafe fn to_slice(&self) -> (&[u8], Lua) {
let lua = self.0.lua.upgrade();
let slice = unsafe {
let slice = {
let rawlua = lua.lock();
let ref_thread = rawlua.ref_thread();
@@ -118,7 +130,7 @@ impl String {
// string type
let mut size = 0;
let data = ffi::lua_tolstring(ref_thread, self.0.index, &mut size);
slice::from_raw_parts(data as *const u8, size + 1)
slice::from_raw_parts(data as *const u8, size)
};
(slice, lua)
}
@@ -148,7 +160,7 @@ impl fmt::Debug for String {
}
}
// Lua strings are basically &[u8] slices, so implement PartialEq for anything resembling that.
// Lua strings are basically `&[u8]` slices, so implement `PartialEq` for anything resembling that.
//
// This makes our `String` comparable with `Vec<u8>`, `[u8]`, `&str` and `String`.
//
@@ -172,6 +184,15 @@ impl PartialEq for String {
impl Eq for String {}
impl<T> PartialOrd<T> for String
where
T: AsRef<[u8]> + ?Sized,
{
fn partial_cmp(&self, other: &T) -> Option<cmp::Ordering> {
self.as_bytes().partial_cmp(&other.as_ref())
}
}
impl PartialOrd for String {
fn partial_cmp(&self, other: &String) -> Option<cmp::Ordering> {
Some(self.cmp(other))
@@ -203,41 +224,55 @@ impl Serialize for String {
}
}
struct Display<'a>(&'a String);
impl fmt::Display for Display<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let bytes = self.0.as_bytes();
<bstr::BStr as fmt::Display>::fmt(bstr::BStr::new(&bytes), f)
}
}
/// A borrowed string (`&str`) that holds a strong reference to the Lua state.
pub struct BorrowedStr<'a>(&'a str, #[allow(unused)] Lua);
pub struct BorrowedStr<'a> {
// `buf` points to a readonly memory managed by Lua
pub(crate) buf: &'a str,
pub(crate) borrow: Cow<'a, String>,
pub(crate) _lua: Lua,
}
impl Deref for BorrowedStr<'_> {
type Target = str;
#[inline(always)]
fn deref(&self) -> &str {
self.0
self.buf
}
}
impl Borrow<str> for BorrowedStr<'_> {
#[inline(always)]
fn borrow(&self) -> &str {
self.0
self.buf
}
}
impl AsRef<str> for BorrowedStr<'_> {
#[inline(always)]
fn as_ref(&self) -> &str {
self.0
self.buf
}
}
impl fmt::Display for BorrowedStr<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
self.buf.fmt(f)
}
}
impl fmt::Debug for BorrowedStr<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
self.buf.fmt(f)
}
}
@@ -246,7 +281,7 @@ where
T: AsRef<str>,
{
fn eq(&self, other: &T) -> bool {
self.0 == other.as_ref()
self.buf == other.as_ref()
}
}
@@ -257,45 +292,65 @@ where
T: AsRef<str>,
{
fn partial_cmp(&self, other: &T) -> Option<cmp::Ordering> {
self.0.partial_cmp(other.as_ref())
self.buf.partial_cmp(other.as_ref())
}
}
impl Ord for BorrowedStr<'_> {
fn cmp(&self, other: &Self) -> cmp::Ordering {
self.0.cmp(other.0)
self.buf.cmp(other.buf)
}
}
impl<'a> TryFrom<&'a String> for BorrowedStr<'a> {
type Error = Error;
#[inline]
fn try_from(value: &'a String) -> Result<Self> {
let BorrowedBytes { buf, borrow, _lua } = BorrowedBytes::from(value);
let buf = str::from_utf8(buf).map_err(|e| Error::FromLuaConversionError {
from: "string",
to: "&str".to_string(),
message: Some(e.to_string()),
})?;
Ok(Self { buf, borrow, _lua })
}
}
/// A borrowed byte slice (`&[u8]`) that holds a strong reference to the Lua state.
pub struct BorrowedBytes<'a>(&'a [u8], #[allow(unused)] Lua);
pub struct BorrowedBytes<'a> {
// `buf` points to a readonly memory managed by Lua
pub(crate) buf: &'a [u8],
pub(crate) borrow: Cow<'a, String>,
pub(crate) _lua: Lua,
}
impl Deref for BorrowedBytes<'_> {
type Target = [u8];
#[inline(always)]
fn deref(&self) -> &[u8] {
self.0
self.buf
}
}
impl Borrow<[u8]> for BorrowedBytes<'_> {
#[inline(always)]
fn borrow(&self) -> &[u8] {
self.0
self.buf
}
}
impl AsRef<[u8]> for BorrowedBytes<'_> {
#[inline(always)]
fn as_ref(&self) -> &[u8] {
self.0
self.buf
}
}
impl fmt::Debug for BorrowedBytes<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
self.buf.fmt(f)
}
}
@@ -304,7 +359,7 @@ where
T: AsRef<[u8]>,
{
fn eq(&self, other: &T) -> bool {
self.0 == other.as_ref()
self.buf == other.as_ref()
}
}
@@ -315,22 +370,48 @@ where
T: AsRef<[u8]>,
{
fn partial_cmp(&self, other: &T) -> Option<cmp::Ordering> {
self.0.partial_cmp(other.as_ref())
self.buf.partial_cmp(other.as_ref())
}
}
impl Ord for BorrowedBytes<'_> {
fn cmp(&self, other: &Self) -> cmp::Ordering {
self.0.cmp(other.0)
self.buf.cmp(other.buf)
}
}
impl<'a> IntoIterator for BorrowedBytes<'a> {
impl<'a> IntoIterator for &'a BorrowedBytes<'_> {
type Item = &'a u8;
type IntoIter = slice::Iter<'a, u8>;
fn into_iter(self) -> Self::IntoIter {
self.0.iter()
self.iter()
}
}
impl<'a> From<&'a String> for BorrowedBytes<'a> {
#[inline]
fn from(value: &'a String) -> Self {
let (buf, _lua) = unsafe { value.to_slice() };
let borrow = Cow::Borrowed(value);
Self { buf, borrow, _lua }
}
}
struct WrappedString<T: AsRef<[u8]>>(T);
impl String {
/// Wraps bytes, returning an opaque type that implements [`IntoLua`] trait.
///
/// This function uses [`Lua::create_string`] under the hood.
pub fn wrap(data: impl AsRef<[u8]>) -> impl IntoLua {
WrappedString(data)
}
}
impl<T: AsRef<[u8]>> IntoLua for WrappedString<T> {
fn into_lua(self, lua: &Lua) -> Result<Value> {
lua.create_string(self.0).map(Value::String)
}
}
+113 -93
View File
@@ -4,6 +4,17 @@ use std::marker::PhantomData;
use std::os::raw::{c_int, c_void};
use std::string::String as StdString;
use crate::error::{Error, Result};
use crate::function::Function;
use crate::state::{LuaGuard, RawLua};
use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, ObjectLike};
use crate::types::{Integer, LuaType, ValueRef};
use crate::util::{assert_stack, check_stack, get_metatable_ptr, StackGuard};
use crate::value::{Nil, Value};
#[cfg(feature = "async")]
use futures_util::future::{self, Either, Future};
#[cfg(feature = "serialize")]
use {
rustc_hash::FxHashSet,
@@ -11,17 +22,6 @@ use {
std::{cell::RefCell, rc::Rc, result::Result as StdResult},
};
use crate::error::{Error, Result};
use crate::function::Function;
use crate::state::{LuaGuard, RawLua};
use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, ObjectLike};
use crate::types::{Integer, LuaType, ValueRef};
use crate::util::{assert_stack, check_stack, StackGuard};
use crate::value::{Nil, Value};
#[cfg(feature = "async")]
use futures_util::future::{self, Either, Future};
/// Handle to an internal Lua table.
#[derive(Clone, PartialEq)]
pub struct Table(pub(crate) ValueRef);
@@ -59,7 +59,7 @@ impl Table {
/// # }
/// ```
///
/// [`raw_set`]: #method.raw_set
/// [`raw_set`]: Table::raw_set
pub fn set(&self, key: impl IntoLua, value: impl IntoLua) -> Result<()> {
// Fast track (skip protected call)
if !self.has_metatable() {
@@ -106,7 +106,7 @@ impl Table {
/// # }
/// ```
///
/// [`raw_get`]: #method.raw_get
/// [`raw_get`]: Table::raw_get
pub fn get<V: FromLua>(&self, key: impl IntoLua) -> Result<V> {
// Fast track (skip protected call)
if !self.has_metatable() {
@@ -218,13 +218,12 @@ impl Table {
/// # Ok(())
/// # }
/// ```
pub fn equals<T: AsRef<Self>>(&self, other: T) -> Result<bool> {
let other = other.as_ref();
pub fn equals(&self, other: &Self) -> Result<bool> {
if self == other {
return Ok(true);
}
// Compare using __eq metamethod if exists
// Compare using `__eq` metamethod if exists
// First, check the self for the metamethod.
// If self does not define it, then check the other table.
if let Some(mt) = self.metatable() {
@@ -243,12 +242,12 @@ impl Table {
/// Sets a key-value pair without invoking metamethods.
pub fn raw_set(&self, key: impl IntoLua, value: impl IntoLua) -> Result<()> {
#[cfg(feature = "luau")]
self.check_readonly_write()?;
let lua = self.0.lua.lock();
let state = lua.state();
unsafe {
#[cfg(feature = "luau")]
self.check_readonly_write(&lua)?;
let _sg = StackGuard::new(state);
check_stack(state, 5)?;
@@ -283,7 +282,9 @@ impl Table {
}
/// 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.
/// `table[idx]`.
///
/// The worst case complexity is O(n), where n is the table length.
pub fn raw_insert(&self, idx: Integer, value: impl IntoLua) -> Result<()> {
let size = self.raw_len() as Integer;
if idx < 1 || idx > size + 1 {
@@ -311,12 +312,12 @@ impl Table {
/// Appends a value to the back of the table without invoking metamethods.
pub fn raw_push(&self, value: impl IntoLua) -> Result<()> {
#[cfg(feature = "luau")]
self.check_readonly_write()?;
let lua = self.0.lua.lock();
let state = lua.state();
unsafe {
#[cfg(feature = "luau")]
self.check_readonly_write(&lua)?;
let _sg = StackGuard::new(state);
check_stack(state, 4)?;
@@ -339,12 +340,12 @@ impl Table {
/// Removes the last element from the table and returns it, without invoking metamethods.
pub fn raw_pop<V: FromLua>(&self) -> Result<V> {
#[cfg(feature = "luau")]
self.check_readonly_write()?;
let lua = self.0.lua.lock();
let state = lua.state();
unsafe {
#[cfg(feature = "luau")]
self.check_readonly_write(&lua)?;
let _sg = StackGuard::new(state);
check_stack(state, 3)?;
@@ -362,8 +363,8 @@ impl Table {
/// Removes a key from the table.
///
/// If `key` is an integer, mlua shifts down the elements from `table[key+1]`,
/// and erases element `table[key]`. The complexity is O(n) in the worst case,
/// where n is the table length.
/// and erases element `table[key]`. The complexity is `O(n)` in the worst case,
/// where `n` is the table length.
///
/// For other key types this is equivalent to setting `table[key] = nil`.
pub fn raw_remove(&self, key: impl IntoLua) -> Result<()> {
@@ -400,13 +401,13 @@ impl Table {
///
/// 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.lock();
unsafe {
#[cfg(feature = "luau")]
ffi::lua_cleartable(lua.ref_thread(), self.0.index);
{
self.check_readonly_write(&lua)?;
ffi::lua_cleartable(lua.ref_thread(), self.0.index);
}
#[cfg(not(feature = "luau"))]
{
@@ -438,9 +439,8 @@ impl Table {
/// Returns the result of the Lua `#` operator.
///
/// This might invoke the `__len` metamethod. Use the [`raw_len`] method if that is not desired.
///
/// [`raw_len`]: #method.raw_len
/// This might invoke the `__len` metamethod. Use the [`Table::raw_len`] method if that is not
/// desired.
pub fn len(&self) -> Result<Integer> {
// Fast track (skip protected call)
if !self.has_metatable() {
@@ -468,31 +468,23 @@ impl Table {
///
/// It checks both the array part and the hash part.
pub fn is_empty(&self) -> bool {
// Check array part
if self.raw_len() != 0 {
return false;
}
// Check hash part
let lua = self.0.lua.lock();
let state = lua.state();
let ref_thread = lua.ref_thread();
unsafe {
let _sg = StackGuard::new(state);
assert_stack(state, 4);
lua.push_ref(&self.0);
ffi::lua_pushnil(state);
if ffi::lua_next(state, -2) != 0 {
return false;
ffi::lua_pushnil(ref_thread);
if ffi::lua_next(ref_thread, self.0.index) == 0 {
return true;
}
ffi::lua_pop(ref_thread, 2);
}
true
false
}
/// Returns a reference to the metatable of this table, or `None` if no metatable is set.
///
/// Unlike the `getmetatable` Lua function, this method ignores the `__metatable` field.
/// Unlike the [`getmetatable`] Lua function, this method ignores the `__metatable` field.
///
/// [`getmetatable`]: https://www.lua.org/manual/5.4/manual.html#pdf-getmetatable
pub fn metatable(&self) -> Option<Table> {
let lua = self.0.lua.lock();
let state = lua.state();
@@ -511,6 +503,7 @@ impl Table {
#[doc(hidden)]
#[deprecated(since = "0.10.0", note = "please use `metatable` instead")]
#[cfg(not(tarpaulin_include))]
pub fn get_metatable(&self) -> Option<Table> {
self.metatable()
}
@@ -547,14 +540,7 @@ impl Table {
#[inline]
pub fn has_metatable(&self) -> bool {
let lua = self.0.lua.lock();
let ref_thread = lua.ref_thread();
unsafe {
if ffi::lua_getmetatable(ref_thread, self.0.index) != 0 {
ffi::lua_pop(ref_thread, 1);
return true;
}
}
false
unsafe { !get_metatable_ptr(lua.ref_thread(), self.0.index).is_null() }
}
/// Sets `readonly` attribute on the table.
@@ -585,6 +571,24 @@ impl Table {
unsafe { ffi::lua_getreadonly(ref_thread, self.0.index) != 0 }
}
/// Controls `safeenv` attribute on the table.
///
/// This a special flag that activates some performance optimizations for environment tables.
/// In particular, it controls:
/// - Optimization of import resolution (cache values of constant keys).
/// - Fast-path for built-in iteration with pairs/ipairs.
/// - Fast-path for some built-in functions (fastcall).
///
/// For `safeenv` environments, monkey patching or modifying values may not work as expected.
///
/// Requires `feature = "luau"`
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn set_safeenv(&self, enabled: bool) {
let lua = self.0.lua.lock();
unsafe { ffi::lua_setsafeenv(lua.ref_thread(), self.0.index, enabled as _) };
}
/// Converts this table to a generic C pointer.
///
/// Different tables will give different pointers.
@@ -621,7 +625,6 @@ impl Table {
/// # }
/// ```
///
/// [`Result`]: crate::Result
/// [Lua manual]: http://www.lua.org/manual/5.4/manual.html#pdf-next
pub fn pairs<K: FromLua, V: FromLua>(&self) -> TablePairs<K, V> {
TablePairs {
@@ -688,10 +691,6 @@ impl Table {
/// # Ok(())
/// # }
/// ```
///
/// [`pairs`]: #method.pairs
/// [`Result`]: crate::Result
/// [Lua manual]: http://www.lua.org/manual/5.4/manual.html#pdf-next
pub fn sequence_values<V: FromLua>(&self) -> TableSequence<V> {
TableSequence {
guard: self.0.lua.lock(),
@@ -701,8 +700,9 @@ impl Table {
}
}
#[cfg(feature = "serialize")]
pub(crate) fn for_each_value<V>(&self, mut f: impl FnMut(V) -> Result<()>) -> Result<()>
/// Iterates over the sequence part of the table, invoking the given closure on each value.
#[doc(hidden)]
pub fn for_each_value<V>(&self, mut f: impl FnMut(V) -> Result<()>) -> Result<()>
where
V: FromLua,
{
@@ -726,12 +726,12 @@ impl Table {
/// Sets element value at position `idx` without invoking metamethods.
#[doc(hidden)]
pub fn raw_seti(&self, idx: usize, value: impl IntoLua) -> Result<()> {
#[cfg(feature = "luau")]
self.check_readonly_write()?;
let lua = self.0.lua.lock();
let state = lua.state();
unsafe {
#[cfg(feature = "luau")]
self.check_readonly_write(&lua)?;
let _sg = StackGuard::new(state);
check_stack(state, 5)?;
@@ -767,8 +767,8 @@ impl Table {
#[cfg(feature = "luau")]
#[inline(always)]
pub(crate) fn check_readonly_write(&self) -> Result<()> {
if self.is_readonly() {
fn check_readonly_write(&self, lua: &RawLua) -> Result<()> {
if unsafe { ffi::lua_getreadonly(lua.ref_thread(), self.0.index) != 0 } {
return Err(Error::runtime("attempt to modify a readonly table"));
}
Ok(())
@@ -786,16 +786,40 @@ impl Table {
let mut pairs = self.pairs::<Value, Value>().flatten().collect::<Vec<_>>();
// Sort keys
pairs.sort_by(|(a, _), (b, _)| a.sort_cmp(b));
let is_sequence = (pairs.iter().enumerate())
.all(|(i, (k, _))| matches!(k, Value::Integer(n) if *n == (i + 1) as Integer));
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, ",")?;
if is_sequence {
// Format as list
for (_, value) in pairs {
write!(fmt, "{}", " ".repeat(ident + 2))?;
value.fmt_pretty(fmt, true, ident + 2, visited)?;
writeln!(fmt, ",")?;
}
} else {
fn is_simple_key(key: &[u8]) -> bool {
key.iter().take(1).all(|c| c.is_ascii_alphabetic() || *c == b'_')
&& key.iter().all(|c| c.is_ascii_alphanumeric() || *c == b'_')
}
for (key, value) in pairs {
match key {
Value::String(key) if is_simple_key(&key.as_bytes()) => {
write!(fmt, "{}{}", " ".repeat(ident + 2), key.display())?;
write!(fmt, " = ")?;
}
_ => {
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))
}
@@ -810,13 +834,6 @@ impl fmt::Debug for Table {
}
}
impl AsRef<Table> for Table {
#[inline]
fn as_ref(&self) -> &Self {
self
}
}
impl<T> PartialEq<[T]> for Table
where
T: IntoLua + Clone,
@@ -867,6 +884,10 @@ where
}
}
impl LuaType for Table {
const TYPE_ID: c_int = ffi::LUA_TTABLE;
}
impl ObjectLike for Table {
#[inline]
fn get<V: FromLua>(&self, key: impl IntoLua) -> Result<V> {
@@ -961,10 +982,6 @@ impl Serialize for Table {
}
}
impl LuaType for Table {
const TYPE_ID: c_int = ffi::LUA_TTABLE;
}
#[cfg(feature = "serialize")]
impl<'a> SerializableTable<'a> {
#[inline]
@@ -982,7 +999,7 @@ impl<'a> SerializableTable<'a> {
}
#[cfg(feature = "serialize")]
impl<'a> Serialize for SerializableTable<'a> {
impl Serialize for SerializableTable<'_> {
fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
where
S: Serializer,
@@ -1003,7 +1020,10 @@ impl<'a> Serialize for SerializableTable<'a> {
// Array
let len = self.table.raw_len();
if len > 0 || self.table.is_array() {
if len > 0
|| self.table.is_array()
|| (self.options.encode_empty_tables_as_array && self.table.is_empty())
{
let mut seq = serializer.serialize_seq(Some(len))?;
let mut serialize_err = None;
let res = self.table.for_each_value::<Value>(|value| {
@@ -1073,7 +1093,7 @@ pub struct TablePairs<'a, K, V> {
_phantom: PhantomData<(K, V)>,
}
impl<'a, K, V> Iterator for TablePairs<'a, K, V>
impl<K, V> Iterator for TablePairs<'_, K, V>
where
K: FromLua,
V: FromLua,
@@ -1133,7 +1153,7 @@ pub struct TableSequence<'a, V> {
_phantom: PhantomData<V>,
}
impl<'a, V> Iterator for TableSequence<'a, V>
impl<V> Iterator for TableSequence<'_, V>
where
V: FromLua,
{
+95 -70
View File
@@ -2,8 +2,7 @@ use std::fmt;
use std::os::raw::{c_int, c_void};
use crate::error::{Error, Result};
#[allow(unused)]
use crate::state::Lua;
use crate::function::Function;
use crate::state::RawLua;
use crate::traits::{FromLuaMulti, IntoLuaMulti};
use crate::types::{LuaType, ValueRef};
@@ -42,6 +41,26 @@ pub enum ThreadStatus {
Error,
}
/// Internal representation of a Lua thread status.
///
/// The number in `New` and `Yielded` variants is the number of arguments pushed
/// to the thread stack.
#[derive(Clone, Copy)]
enum ThreadStatusInner {
New,
Running,
Yielded,
Finished,
Error,
}
impl ThreadStatusInner {
#[inline(always)]
fn is_resumable(self) -> bool {
matches!(self, ThreadStatusInner::New | ThreadStatusInner::Yielded)
}
}
/// Handle to an internal Lua thread (coroutine).
#[derive(Clone)]
pub struct Thread(pub(crate) ValueRef, pub(crate) *mut ffi::lua_State);
@@ -69,23 +88,23 @@ pub struct AsyncThread<A, R> {
impl Thread {
#[inline(always)]
const fn state(&self) -> *mut ffi::lua_State {
fn state(&self) -> *mut ffi::lua_State {
self.1
}
/// Resumes execution of this thread.
///
/// Equivalent to `coroutine.resume`.
/// Equivalent to [`coroutine.resume`].
///
/// Passes `args` as arguments to the thread. If the coroutine has called `coroutine.yield`, it
/// will return these arguments. Otherwise, the coroutine wasn't yet started, so the arguments
/// are passed to its main function.
/// Passes `args` as arguments to the thread. If the coroutine has called [`coroutine.yield`],
/// it will return these arguments. Otherwise, the coroutine wasn't yet started, so the
/// arguments are passed to its main function.
///
/// If the thread is no longer in `Active` state (meaning it has finished execution or
/// encountered an error), this will return `Err(CoroutineInactive)`, otherwise will return `Ok`
/// as follows:
/// If the thread is no longer resumable (meaning it has finished execution or encountered an
/// error), this will return [`Error::CoroutineUnresumable`], otherwise will return `Ok` as
/// follows:
///
/// If the thread calls `coroutine.yield`, returns the values passed to `yield`. If the thread
/// If the thread calls [`coroutine.yield`], returns the values passed to `yield`. If the thread
/// `return`s values from its main function, returns those.
///
/// # Examples
@@ -114,12 +133,15 @@ impl Thread {
/// # Ok(())
/// # }
/// ```
///
/// [`coroutine.resume`]: https://www.lua.org/manual/5.4/manual.html#pdf-coroutine.resume
/// [`coroutine.yield`]: https://www.lua.org/manual/5.4/manual.html#pdf-coroutine.yield
pub fn resume<R>(&self, args: impl IntoLuaMulti) -> Result<R>
where
R: FromLuaMulti,
{
let lua = self.0.lua.lock();
if self.status_inner(&lua) != ThreadStatus::Resumable {
if !self.status_inner(&lua).is_resumable() {
return Err(Error::CoroutineUnresumable);
}
@@ -167,35 +189,39 @@ impl Thread {
/// Gets the status of the thread.
pub fn status(&self) -> ThreadStatus {
self.status_inner(&self.0.lua.lock())
match self.status_inner(&self.0.lua.lock()) {
ThreadStatusInner::New | ThreadStatusInner::Yielded => ThreadStatus::Resumable,
ThreadStatusInner::Running => ThreadStatus::Running,
ThreadStatusInner::Finished => ThreadStatus::Finished,
ThreadStatusInner::Error => ThreadStatus::Error,
}
}
/// Gets the status of the thread (internal implementation).
pub(crate) fn status_inner(&self, lua: &RawLua) -> ThreadStatus {
fn status_inner(&self, lua: &RawLua) -> ThreadStatusInner {
let thread_state = self.state();
if thread_state == lua.state() {
// The thread is currently running
return ThreadStatus::Running;
return ThreadStatusInner::Running;
}
let status = unsafe { ffi::lua_status(thread_state) };
if status != ffi::LUA_OK && status != ffi::LUA_YIELD {
ThreadStatus::Error
} else if status == ffi::LUA_YIELD || unsafe { ffi::lua_gettop(thread_state) > 0 } {
ThreadStatus::Resumable
} else {
ThreadStatus::Finished
match status {
ffi::LUA_YIELD => ThreadStatusInner::Yielded,
ffi::LUA_OK if unsafe { ffi::lua_gettop(thread_state) } > 0 => ThreadStatusInner::New,
ffi::LUA_OK => ThreadStatusInner::Finished,
_ => ThreadStatusInner::Error,
}
}
/// Sets a 'hook' function that will periodically be called as Lua code executes.
/// 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()`].
/// 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<crate::VmState> + MaybeSend + 'static,
F: Fn(&crate::Lua, Debug) -> Result<crate::VmState> + MaybeSend + 'static,
{
let lua = self.0.lua.lock();
unsafe {
@@ -212,32 +238,37 @@ impl Thread {
/// In Luau: resets to the initial state of a newly created Lua thread.
/// Lua threads in arbitrary states (like yielded or errored) can be reset properly.
///
/// Other Lua versions can reset only new or finished threads.
///
/// Sets a Lua function for the thread afterwards.
///
/// Requires `feature = "lua54"` OR `feature = "luau"`.
///
/// [Lua 5.4]: https://www.lua.org/manual/5.4/manual.html#lua_closethread
#[cfg(any(feature = "lua54", feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "luau"))))]
pub fn reset(&self, func: crate::function::Function) -> Result<()> {
pub fn reset(&self, func: Function) -> Result<()> {
let lua = self.0.lua.lock();
if self.status_inner(&lua) == ThreadStatus::Running {
return Err(Error::runtime("cannot reset a running thread"));
let thread_state = self.state();
match self.status_inner(&lua) {
ThreadStatusInner::Running => return Err(Error::runtime("cannot reset a running thread")),
// Any Lua can reuse new or finished thread
ThreadStatusInner::New => unsafe { ffi::lua_settop(thread_state, 0) },
ThreadStatusInner::Finished => {}
#[cfg(not(any(feature = "lua54", feature = "luau")))]
_ => return Err(Error::runtime("cannot reset non-finished thread")),
#[cfg(any(feature = "lua54", feature = "luau"))]
_ => unsafe {
#[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());
#[cfg(feature = "lua54")]
if status != ffi::LUA_OK {
return Err(pop_error(thread_state, status));
}
#[cfg(feature = "luau")]
ffi::lua_resetthread(thread_state);
},
}
let thread_state = self.state();
unsafe {
#[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());
#[cfg(feature = "lua54")]
if status != ffi::LUA_OK {
return Err(pop_error(thread_state, status));
}
#[cfg(feature = "luau")]
ffi::lua_resetthread(thread_state);
// Push function to the top of the thread stack
ffi::lua_xpush(lua.ref_thread(), thread_state, func.0.index);
@@ -252,21 +283,22 @@ impl Thread {
}
}
/// Converts Thread to an AsyncThread which implements [`Future`] and [`Stream`] traits.
/// Converts [`Thread`] to an [`AsyncThread`] which implements [`Future`] and [`Stream`] traits.
///
/// `args` are passed as arguments to the thread function for first call.
/// The object calls [`resume()`] while polling and also allows to run rust futures
/// The object calls [`resume`] while polling and also allow to run Rust futures
/// to completion using an executor.
///
/// Using AsyncThread as a Stream allows to iterate through `coroutine.yield()`
/// values whereas Future version discards that values and poll until the final
/// Using [`AsyncThread`] as a [`Stream`] allow to iterate through [`coroutine.yield`]
/// values whereas [`Future`] version discards that values and poll until the final
/// one (returned from the thread function).
///
/// Requires `feature = "async"`
///
/// [`Future`]: std::future::Future
/// [`Stream`]: futures_util::stream::Stream
/// [`resume()`]: https://www.lua.org/manual/5.4/manual.html#lua_resume
/// [`resume`]: https://www.lua.org/manual/5.4/manual.html#lua_resume
/// [`coroutine.yield`]: https://www.lua.org/manual/5.4/manual.html#pdf-coroutine.yield
///
/// # Examples
///
@@ -316,7 +348,7 @@ impl Thread {
/// Under the hood replaces the global environment table with a new table,
/// that performs writes locally and proxies reads to caller's global environment.
///
/// This mode ideally should be used together with the global sandbox mode [`Lua::sandbox()`].
/// This mode ideally should be used together with the global sandbox mode [`Lua::sandbox`].
///
/// Please note that Luau links environment table with chunk when loading it into Lua state.
/// Therefore you need to load chunks into a thread to link with the thread environment.
@@ -325,6 +357,7 @@ impl Thread {
///
/// ```
/// # use mlua::{Lua, Result};
/// # #[cfg(feature = "luau")]
/// # fn main() -> Result<()> {
/// let lua = Lua::new();
/// let thread = lua.create_thread(lua.create_function(|lua2, ()| {
@@ -333,16 +366,19 @@ impl Thread {
/// Ok(())
/// })?)?;
/// thread.sandbox()?;
/// thread.resume(())?;
/// thread.resume::<()>(())?;
///
/// // The global environment should be unchanged
/// assert_eq!(lua.globals().get::<Option<u32>>("var")?, None);
/// # Ok(())
/// # }
///
/// # #[cfg(not(feature = "luau"))]
/// # fn main() { }
/// ```
///
/// Requires `feature = "luau"`
#[cfg(any(feature = "luau", docsrs))]
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
#[doc(hidden)]
pub fn sandbox(&self) -> Result<()> {
@@ -385,30 +421,19 @@ impl LuaType for Thread {
#[cfg(feature = "async")]
impl<A, R> AsyncThread<A, R> {
#[inline]
#[inline(always)]
pub(crate) fn set_recyclable(&mut self, recyclable: bool) {
self.recycle = recyclable;
}
}
#[cfg(feature = "async")]
#[cfg(any(feature = "lua54", feature = "luau"))]
impl<A, R> Drop for AsyncThread<A, R> {
fn drop(&mut self) {
if self.recycle {
if let Some(lua) = self.thread.0.lua.try_lock() {
unsafe {
// For Lua 5.4 this also closes all pending to-be-closed variables
if !lua.recycle_thread(&mut self.thread) {
#[cfg(feature = "lua54")]
if self.thread.status_inner(&lua) == ThreadStatus::Error {
#[cfg(not(feature = "vendored"))]
ffi::lua_resetthread(self.thread.state());
#[cfg(feature = "vendored")]
ffi::lua_closethread(self.thread.state(), lua.state());
}
}
}
// For Lua 5.4 this also closes all pending to-be-closed variables
unsafe { lua.recycle_thread(&mut self.thread) };
}
}
}
@@ -420,7 +445,7 @@ impl<A: IntoLuaMulti, R: FromLuaMulti> Stream for AsyncThread<A, R> {
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let lua = self.thread.0.lua.lock();
if self.thread.status_inner(&lua) != ThreadStatus::Resumable {
if !self.thread.status_inner(&lua).is_resumable() {
return Poll::Ready(None);
}
@@ -458,7 +483,7 @@ impl<A: IntoLuaMulti, R: FromLuaMulti> Future for AsyncThread<A, R> {
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let lua = self.thread.0.lua.lock();
if self.thread.status_inner(&lua) != ThreadStatus::Resumable {
if !self.thread.status_inner(&lua).is_resumable() {
return Poll::Ready(Err(Error::CoroutineUnresumable));
}
@@ -498,7 +523,7 @@ impl<A: IntoLuaMulti, R: FromLuaMulti> Future for AsyncThread<A, R> {
#[cfg(feature = "async")]
#[inline(always)]
unsafe fn is_poll_pending(state: *mut ffi::lua_State) -> bool {
ffi::lua_tolightuserdata(state, -1) == Lua::poll_pending().0
ffi::lua_tolightuserdata(state, -1) == crate::Lua::poll_pending().0
}
#[cfg(feature = "async")]
@@ -522,7 +547,7 @@ impl<'lua, 'a> WakerGuard<'lua, 'a> {
}
#[cfg(feature = "async")]
impl<'lua, 'a> Drop for WakerGuard<'lua, 'a> {
impl Drop for WakerGuard<'_, '_> {
fn drop(&mut self) {
unsafe { self.lua.set_waker(self.prev) };
}
+7 -10
View File
@@ -13,7 +13,7 @@ use crate::value::Value;
#[cfg(feature = "async")]
use std::future::Future;
/// Trait for types convertible to `Value`.
/// Trait for types convertible to [`Value`].
pub trait IntoLua: Sized {
/// Performs the conversion.
fn into_lua(self, lua: &Lua) -> Result<Value>;
@@ -29,7 +29,7 @@ pub trait IntoLua: Sized {
}
}
/// Trait for types convertible from `Value`.
/// Trait for types convertible from [`Value`].
pub trait FromLua: Sized {
/// Performs the conversion.
fn from_lua(value: Value, lua: &Lua) -> Result<Self>;
@@ -71,8 +71,8 @@ pub trait FromLua: Sized {
/// Trait for types convertible to any number of Lua values.
///
/// 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.
/// 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: Sized {
/// Performs the conversion.
fn into_lua_multi(self, lua: &Lua) -> Result<MultiValue>;
@@ -97,8 +97,9 @@ pub trait IntoLuaMulti: Sized {
/// Trait for types that can be created from an arbitrary number of Lua values.
///
/// This is a generalization of `FromLua`, allowing an arbitrary number of Lua values to participate
/// in the conversion. Any type that implements `FromLua` will automatically implement this trait.
/// This is a generalization of [`FromLua`], allowing an arbitrary number of Lua values to
/// participate in the conversion. Any type that implements [`FromLua`] will automatically
/// implement this trait.
pub trait FromLuaMulti: Sized {
/// Performs the conversion.
///
@@ -127,10 +128,6 @@ pub trait FromLuaMulti: Sized {
for idx in 0..nvals {
values.push_back(lua.stack_value(-nvals + idx, None));
}
if nvals > 0 {
// It's safe to clear the stack as all references moved to ref thread
ffi::lua_pop(lua.state(), nvals);
}
Self::from_lua_multi(values, lua.lua())
}
+3 -9
View File
@@ -20,8 +20,6 @@ pub use app_data::{AppData, AppDataRef, AppDataRefMut};
pub use either::Either;
pub use registry_key::RegistryKey;
pub(crate) use value_ref::ValueRef;
#[cfg(any(feature = "luau", doc))]
pub use vector::Vector;
/// Type of Lua integer numbers.
pub type Integer = ffi::lua_Integer;
@@ -66,7 +64,7 @@ pub(crate) type AsyncCallbackUpvalue = Upvalue<AsyncCallback>;
#[cfg(feature = "async")]
pub(crate) type AsyncPollUpvalue = Upvalue<BoxFuture<'static, Result<c_int>>>;
/// Type to set next Luau VM action after executing interrupt function.
/// Type to set next Lua VM action after executing interrupt or hook function.
pub enum VmState {
Continue,
/// Yield the current thread.
@@ -88,10 +86,10 @@ pub(crate) type InterruptCallback = Rc<dyn Fn(&Lua) -> Result<VmState> + Send>;
pub(crate) type InterruptCallback = Rc<dyn Fn(&Lua) -> Result<VmState>>;
#[cfg(all(feature = "send", feature = "lua54"))]
pub(crate) type WarnCallback = Box<dyn Fn(&Lua, &str, bool) -> Result<()> + Send>;
pub(crate) type WarnCallback = XRc<dyn Fn(&Lua, &str, bool) -> Result<()> + Send>;
#[cfg(all(not(feature = "send"), feature = "lua54"))]
pub(crate) type WarnCallback = Box<dyn Fn(&Lua, &str, bool) -> Result<()>>;
pub(crate) type WarnCallback = XRc<dyn Fn(&Lua, &str, bool) -> Result<()>>;
/// A trait that adds `Send` requirement if `send` feature is enabled.
#[cfg(feature = "send")]
@@ -123,14 +121,10 @@ impl LuaType for LightUserData {
}
mod app_data;
mod either;
mod registry_key;
mod sync;
mod value_ref;
#[cfg(any(feature = "luau", doc))]
mod vector;
#[cfg(test)]
mod assertions {
use super::*;
+54 -18
View File
@@ -1,5 +1,5 @@
use std::any::{Any, TypeId};
use std::cell::{Cell, Ref, RefCell, RefMut, UnsafeCell};
use std::cell::{BorrowError, BorrowMutError, Cell, Ref, RefCell, RefMut, UnsafeCell};
use std::fmt;
use std::ops::{Deref, DerefMut};
use std::result::Result as StdResult;
@@ -41,30 +41,66 @@ impl AppData {
.and_then(|data| data.into_inner().downcast::<T>().ok().map(|data| *data)))
}
#[inline]
#[track_caller]
pub(crate) fn borrow<T: 'static>(&self, guard: Option<LuaGuard>) -> 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,
_guard: guard,
})
match self.try_borrow(guard) {
Ok(data) => data,
Err(err) => panic!("already mutably borrowed: {err:?}"),
}
}
pub(crate) fn try_borrow<T: 'static>(
&self,
guard: Option<LuaGuard>,
) -> Result<Option<AppDataRef<T>>, BorrowError> {
let data = unsafe { &*self.container.get() }
.get(&TypeId::of::<T>())
.map(|c| c.try_borrow())
.transpose()?
.and_then(|data| Ref::filter_map(data, |data| data.downcast_ref()).ok());
match data {
Some(data) => {
self.borrow.set(self.borrow.get() + 1);
Ok(Some(AppDataRef {
data,
borrow: &self.borrow,
_guard: guard,
}))
}
None => Ok(None),
}
}
#[inline]
#[track_caller]
pub(crate) fn borrow_mut<T: 'static>(&self, guard: Option<LuaGuard>) -> Option<AppDataRefMut<T>> {
match self.try_borrow_mut(guard) {
Ok(data) => data,
Err(err) => panic!("already borrowed: {err:?}"),
}
}
pub(crate) fn try_borrow_mut<T: 'static>(
&self,
guard: Option<LuaGuard>,
) -> Result<Option<AppDataRefMut<T>>, BorrowMutError> {
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,
_guard: guard,
})
.get(&TypeId::of::<T>())
.map(|c| c.try_borrow_mut())
.transpose()?
.and_then(|data| RefMut::filter_map(data, |data| data.downcast_mut()).ok());
match data {
Some(data) => {
self.borrow.set(self.borrow.get() + 1);
Ok(Some(AppDataRefMut {
data,
borrow: &self.borrow,
_guard: guard,
}))
}
None => Ok(None),
}
}
#[track_caller]
-135
View File
@@ -1,135 +0,0 @@
use std::ffi::CStr;
use std::fmt;
use std::hash::Hash;
use std::os::raw::c_int;
use crate::error::{Error, Result};
use crate::state::{Lua, RawLua};
use crate::traits::{FromLua, IntoLua, ShortTypeName as _};
use crate::value::Value;
/// Combination of two types into a single one.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Either<L, R> {
Left(L),
Right(R),
}
impl<L, R> Either<L, R> {
/// Return true if the value is the Left variant.
#[inline]
pub fn is_left(&self) -> bool {
matches!(self, Either::Left(_))
}
/// Return true if the value is the Right variant.
#[inline]
pub fn is_right(&self) -> bool {
matches!(self, Either::Right(_))
}
/// Convert the left side of `Either<L, R>` to an `Option<L>`.
#[inline]
pub fn left(self) -> Option<L> {
match self {
Either::Left(l) => Some(l),
_ => None,
}
}
/// Convert the right side of `Either<L, R>` to an `Option<R>`.
#[inline]
pub fn right(self) -> Option<R> {
match self {
Either::Right(r) => Some(r),
_ => None,
}
}
/// Convert `&Either<L, R>` to `Either<&L, &R>`.
#[inline]
pub fn as_ref(&self) -> Either<&L, &R> {
match self {
Either::Left(l) => Either::Left(l),
Either::Right(r) => Either::Right(r),
}
}
/// Convert `&mut Either<L, R>` to `Either<&mut L, &mut R>`.
#[inline]
pub fn as_mut(&mut self) -> Either<&mut L, &mut R> {
match self {
Either::Left(l) => Either::Left(l),
Either::Right(r) => Either::Right(r),
}
}
}
impl<L, R> fmt::Display for Either<L, R>
where
L: fmt::Display,
R: fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Either::Left(a) => a.fmt(f),
Either::Right(b) => b.fmt(f),
}
}
}
impl<L: IntoLua, R: IntoLua> IntoLua for Either<L, R> {
#[inline]
fn into_lua(self, lua: &Lua) -> Result<Value> {
match self {
Either::Left(l) => l.into_lua(lua),
Either::Right(r) => r.into_lua(lua),
}
}
#[inline]
unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> {
match self {
Either::Left(l) => l.push_into_stack(lua),
Either::Right(r) => r.push_into_stack(lua),
}
}
}
impl<L: FromLua, R: FromLua> FromLua for Either<L, R> {
#[inline]
fn from_lua(value: Value, lua: &Lua) -> Result<Self> {
let value_type_name = value.type_name();
// Try the left type first
match L::from_lua(value.clone(), lua) {
Ok(l) => Ok(Either::Left(l)),
// Try the right type
Err(_) => match R::from_lua(value, lua).map(Either::Right) {
Ok(r) => Ok(r),
Err(_) => Err(Error::FromLuaConversionError {
from: value_type_name,
to: Self::type_name(),
message: None,
}),
},
}
}
#[inline]
unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result<Self> {
match L::from_stack(idx, lua) {
Ok(l) => Ok(Either::Left(l)),
Err(_) => match R::from_stack(idx, lua).map(Either::Right) {
Ok(r) => Ok(r),
Err(_) => {
let value_type_name = CStr::from_ptr(ffi::luaL_typename(lua.state(), idx));
Err(Error::FromLuaConversionError {
from: value_type_name.to_str().unwrap(),
to: Self::type_name(),
message: None,
})
}
},
}
}
}
+4 -5
View File
@@ -12,17 +12,16 @@ use parking_lot::Mutex;
/// and instances not manually removed can be garbage collected with
/// [`Lua::expire_registry_values`].
///
/// Be warned, If you place this into Lua via a [`UserData`] type or a rust callback, it is *very
/// easy* to accidentally cause reference cycles that the Lua garbage collector cannot resolve.
/// Instead of placing a [`RegistryKey`] into a [`UserData`] type, prefer instead to use
/// [`AnyUserData::set_user_value`] / [`AnyUserData::user_value`].
/// Be warned, If you place this into Lua via a [`UserData`] type or a Rust callback, it is *easy*
/// to accidentally cause reference cycles that the Lua garbage collector cannot resolve. Instead of
/// placing a [`RegistryKey`] into a [`UserData`] type, consider to use
/// [`AnyUserData::set_user_value`].
///
/// [`UserData`]: crate::UserData
/// [`RegistryKey`]: crate::RegistryKey
/// [`Lua::remove_registry_value`]: crate::Lua::remove_registry_value
/// [`Lua::expire_registry_values`]: crate::Lua::expire_registry_values
/// [`AnyUserData::set_user_value`]: crate::AnyUserData::set_user_value
/// [`AnyUserData::user_value`]: crate::AnyUserData::user_value
pub struct RegistryKey {
pub(crate) registry_id: i32,
pub(crate) unref_list: Arc<Mutex<Option<Vec<c_int>>>>,
+1 -1
View File
@@ -53,7 +53,7 @@ mod inner {
pub(crate) struct ReentrantMutexGuard<'a, T>(&'a T);
impl<'a, T> Deref for ReentrantMutexGuard<'a, T> {
impl<T> Deref for ReentrantMutexGuard<'_, T> {
type Target = T;
#[inline(always)]
+1 -1
View File
@@ -4,7 +4,7 @@ use std::os::raw::{c_int, c_void};
use crate::state::{RawLua, WeakLua};
/// A reference to a Lua (complex) value stored in the Lua auxiliary thread.
pub(crate) struct ValueRef {
pub struct ValueRef {
pub(crate) lua: WeakLua,
pub(crate) index: c_int,
pub(crate) drop: bool,
+154 -180
View File
@@ -5,6 +5,16 @@ use std::hash::Hash;
use std::os::raw::{c_char, c_void};
use std::string::String as StdString;
use crate::error::{Error, Result};
use crate::function::Function;
use crate::state::Lua;
use crate::string::String;
use crate::table::{Table, TablePairs};
use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti};
use crate::types::{MaybeSend, ValueRef};
use crate::util::{check_stack, get_userdata, push_string, take_userdata, StackGuard};
use crate::value::Value;
#[cfg(feature = "async")]
use std::future::Future;
@@ -14,28 +24,20 @@ use {
std::result::Result as StdResult,
};
use crate::error::{Error, Result};
use crate::function::Function;
use crate::state::Lua;
use crate::string::String;
use crate::table::{Table, TablePairs};
use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti};
use crate::types::{MaybeSend, ValueRef};
use crate::util::{check_stack, get_userdata, take_userdata, StackGuard};
use crate::value::Value;
// Re-export for convenience
pub(crate) use cell::UserDataStorage;
pub use cell::{UserDataRef, UserDataRefMut};
pub(crate) use registry::UserDataProxy;
pub use r#ref::{UserDataRef, UserDataRefMut};
pub use registry::UserDataRegistry;
pub(crate) use registry::{RawUserDataRegistry, UserDataProxy};
pub(crate) use util::{
borrow_userdata_scoped, borrow_userdata_scoped_mut, collect_userdata, init_userdata_metatable,
TypeIdHints,
};
/// Kinds of metamethods that can be overridden.
///
/// Currently, this mechanism does not allow overriding the `__gc` metamethod, since there is
/// generally no need to do so: [`UserData`] implementors can instead just implement `Drop`.
///
/// [`UserData`]: crate::UserData
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum MetaMethod {
@@ -243,8 +245,6 @@ impl AsRef<str> for MetaMethod {
}
/// Method registry for [`UserData`] implementors.
///
/// [`UserData`]: crate::UserData
pub trait UserDataMethods<T> {
/// Add a regular method which accepts a `&T` as the first parameter.
///
@@ -263,20 +263,20 @@ pub trait UserDataMethods<T> {
///
/// Refer to [`add_method`] for more information about the implementation.
///
/// [`add_method`]: #method.add_method
/// [`add_method`]: UserDataMethods::add_method
fn add_method_mut<M, A, R>(&mut self, name: impl ToString, method: M)
where
M: FnMut(&Lua, &mut T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
R: IntoLuaMulti;
/// Add an async method which accepts a `&T` as the first parameter and returns Future.
/// Add an async method which accepts a `&T` as the first parameter and returns [`Future`].
///
/// Refer to [`add_method`] for more information about the implementation.
///
/// Requires `feature = "async"`
///
/// [`add_method`]: #method.add_method
/// [`add_method`]: UserDataMethods::add_method
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn add_async_method<M, A, MR, R>(&mut self, name: impl ToString, method: M)
@@ -287,13 +287,13 @@ pub trait UserDataMethods<T> {
MR: Future<Output = Result<R>> + MaybeSend + 'static,
R: IntoLuaMulti;
/// Add an async method which accepts a `&mut T` as the first parameter and returns Future.
/// Add an async method which accepts a `&mut T` as the first parameter and returns [`Future`].
///
/// Refer to [`add_method`] for more information about the implementation.
///
/// Requires `feature = "async"`
///
/// [`add_method`]: #method.add_method
/// [`add_method`]: UserDataMethods::add_method
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn add_async_method_mut<M, A, MR, R>(&mut self, name: impl ToString, method: M)
@@ -304,16 +304,11 @@ pub trait UserDataMethods<T> {
MR: Future<Output = Result<R>> + MaybeSend + 'static,
R: IntoLuaMulti;
/// Add a regular method as a function which accepts generic arguments, the first argument will
/// be a [`AnyUserData`] of type `T` if the method is called with Lua method syntax:
/// `my_userdata:my_method(arg1, arg2)`, or it is passed in as the first argument:
/// `my_userdata.my_method(my_userdata, arg1, arg2)`.
/// Add a regular method as a function which accepts generic arguments.
///
/// Prefer to use [`add_method`] or [`add_method_mut`] as they are easier to use.
///
/// [`AnyUserData`]: crate::AnyUserData
/// [`add_method`]: #method.add_method
/// [`add_method_mut`]: #method.add_method_mut
/// The first argument will be a [`AnyUserData`] of type `T` if the method is called with Lua
/// method syntax: `my_userdata:my_method(arg1, arg2)`, or it is passed in as the first
/// argument: `my_userdata.my_method(my_userdata, arg1, arg2)`.
fn add_function<F, A, R>(&mut self, name: impl ToString, function: F)
where
F: Fn(&Lua, A) -> Result<R> + MaybeSend + 'static,
@@ -322,23 +317,23 @@ pub trait UserDataMethods<T> {
/// Add a regular method as a mutable function which accepts generic arguments.
///
/// This is a version of [`add_function`] that accepts a FnMut argument.
/// This is a version of [`add_function`] that accepts a `FnMut` argument.
///
/// [`add_function`]: #method.add_function
/// [`add_function`]: UserDataMethods::add_function
fn add_function_mut<F, A, R>(&mut self, name: impl ToString, function: F)
where
F: FnMut(&Lua, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
R: IntoLuaMulti;
/// Add a regular method as an async function which accepts generic arguments
/// and returns Future.
/// Add a regular method as an async function which accepts generic arguments and returns
/// [`Future`].
///
/// This is an async version of [`add_function`].
///
/// Requires `feature = "async"`
///
/// [`add_function`]: #method.add_function
/// [`add_function`]: UserDataMethods::add_function
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn add_async_function<F, A, FR, R>(&mut self, name: impl ToString, function: F)
@@ -355,7 +350,7 @@ pub trait UserDataMethods<T> {
/// This can cause an error with certain binary metamethods that can trigger if only the right
/// side has a metatable. To prevent this, use [`add_meta_function`].
///
/// [`add_meta_function`]: #method.add_meta_function
/// [`add_meta_function`]: UserDataMethods::add_meta_function
fn add_meta_method<M, A, R>(&mut self, name: impl ToString, method: M)
where
M: Fn(&Lua, &T, A) -> Result<R> + MaybeSend + 'static,
@@ -369,20 +364,20 @@ pub trait UserDataMethods<T> {
/// This can cause an error with certain binary metamethods that can trigger if only the right
/// side has a metatable. To prevent this, use [`add_meta_function`].
///
/// [`add_meta_function`]: #method.add_meta_function
/// [`add_meta_function`]: UserDataMethods::add_meta_function
fn add_meta_method_mut<M, A, R>(&mut self, name: impl ToString, method: M)
where
M: FnMut(&Lua, &mut T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
R: IntoLuaMulti;
/// Add an async metamethod which accepts a `&T` as the first parameter and returns Future.
/// Add an async metamethod which accepts a `&T` as the first parameter and returns [`Future`].
///
/// This is an async version of [`add_meta_method`].
///
/// Requires `feature = "async"`
///
/// [`add_meta_method`]: #method.add_meta_method
/// [`add_meta_method`]: UserDataMethods::add_meta_method
#[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn add_async_meta_method<M, A, MR, R>(&mut self, name: impl ToString, method: M)
@@ -393,13 +388,14 @@ pub trait UserDataMethods<T> {
MR: Future<Output = Result<R>> + MaybeSend + 'static,
R: IntoLuaMulti;
/// Add an async metamethod which accepts a `&mut T` as the first parameter and returns Future.
/// Add an async metamethod which accepts a `&mut T` as the first parameter and returns
/// [`Future`].
///
/// This is an async version of [`add_meta_method_mut`].
///
/// Requires `feature = "async"`
///
/// [`add_meta_method_mut`]: #method.add_meta_method_mut
/// [`add_meta_method_mut`]: UserDataMethods::add_meta_method_mut
#[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn add_async_meta_method_mut<M, A, MR, R>(&mut self, name: impl ToString, method: M)
@@ -423,22 +419,22 @@ pub trait UserDataMethods<T> {
/// Add a metamethod as a mutable function which accepts generic arguments.
///
/// This is a version of [`add_meta_function`] that accepts a FnMut argument.
/// This is a version of [`add_meta_function`] that accepts a `FnMut` argument.
///
/// [`add_meta_function`]: #method.add_meta_function
/// [`add_meta_function`]: UserDataMethods::add_meta_function
fn add_meta_function_mut<F, A, R>(&mut self, name: impl ToString, function: F)
where
F: FnMut(&Lua, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
R: IntoLuaMulti;
/// Add a metamethod which accepts generic arguments and returns Future.
/// Add a metamethod which accepts generic arguments and returns [`Future`].
///
/// This is an async version of [`add_meta_function`].
///
/// Requires `feature = "async"`
///
/// [`add_meta_function`]: #method.add_meta_function
/// [`add_meta_function`]: UserDataMethods::add_meta_function
#[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn add_async_meta_function<F, A, FR, R>(&mut self, name: impl ToString, function: F)
@@ -450,15 +446,13 @@ pub trait UserDataMethods<T> {
}
/// Field registry for [`UserData`] implementors.
///
/// [`UserData`]: crate::UserData
pub trait UserDataFields<T> {
/// Add a static field to the `UserData`.
/// Add a static field to the [`UserData`].
///
/// Static fields are implemented by updating the `__index` metamethod and returning the
/// accessed field. This allows them to be used with the expected `userdata.field` syntax.
///
/// Static fields are usually shared between all instances of the `UserData` of the same type.
/// Static fields are usually shared between all instances of the [`UserData`] of the same type.
///
/// If `add_meta_method` is used to set the `__index` metamethod, it will
/// be used as a fall-back if no regular field or method are found.
@@ -493,11 +487,6 @@ pub trait UserDataFields<T> {
/// Add a regular field getter as a function which accepts a generic [`AnyUserData`] of type `T`
/// argument.
///
/// Prefer to use [`add_field_method_get`] as it is easier to use.
///
/// [`AnyUserData`]: crate::AnyUserData
/// [`add_field_method_get`]: #method.add_field_method_get
fn add_field_function_get<F, R>(&mut self, name: impl ToString, function: F)
where
F: Fn(&Lua, AnyUserData) -> Result<R> + MaybeSend + 'static,
@@ -505,11 +494,6 @@ pub trait UserDataFields<T> {
/// Add a regular field setter as a function which accepts a generic [`AnyUserData`] of type `T`
/// first argument.
///
/// Prefer to use [`add_field_method_set`] as it is easier to use.
///
/// [`AnyUserData`]: crate::AnyUserData
/// [`add_field_method_set`]: #method.add_field_method_set
fn add_field_function_set<F, A>(&mut self, name: impl ToString, function: F)
where
F: FnMut(&Lua, AnyUserData, A) -> Result<()> + MaybeSend + 'static,
@@ -517,7 +501,7 @@ pub trait UserDataFields<T> {
/// Add a metatable field.
///
/// This will initialize the metatable field with `value` on `UserData` creation.
/// This will initialize the metatable field with `value` on [`UserData`] creation.
///
/// # Note
///
@@ -529,7 +513,7 @@ pub trait UserDataFields<T> {
/// Add a metatable field computed from `f`.
///
/// This will initialize the metatable field from `f` on `UserData` creation.
/// This will initialize the metatable field from `f` on [`UserData`] creation.
///
/// # Note
///
@@ -544,6 +528,7 @@ pub trait UserDataFields<T> {
/// Trait for custom userdata types.
///
/// By implementing this trait, a struct becomes eligible for use inside Lua code.
///
/// Implementation of [`IntoLua`] is automatically provided, [`FromLua`] needs to be implemented
/// manually.
///
@@ -603,11 +588,6 @@ pub trait UserDataFields<T> {
/// # Ok(())
/// # }
/// ```
///
/// [`IntoLua`]: crate::IntoLua
/// [`FromLua`]: crate::FromLua
/// [`UserDataFields`]: crate::UserDataFields
/// [`UserDataMethods`]: crate::UserDataMethods
pub trait UserData: Sized {
/// Adds custom fields specific to this userdata.
#[allow(unused_variables)]
@@ -629,18 +609,14 @@ pub trait UserData: Sized {
/// Handle to an internal Lua userdata for any type that implements [`UserData`].
///
/// Similar to `std::any::Any`, this provides an interface for dynamic type checking via the [`is`]
/// and [`borrow`] methods.
///
/// Internally, instances are stored in a `RefCell`, to best match the mutable semantics of the Lua
/// language.
/// Similar to [`std::any::Any`], this provides an interface for dynamic type checking via the
/// [`is`] and [`borrow`] methods.
///
/// # Note
///
/// This API should only be used when necessary. Implementing [`UserData`] already allows defining
/// methods which check the type and acquire a borrow behind the scenes.
///
/// [`UserData`]: crate::UserData
/// [`is`]: crate::AnyUserData::is
/// [`borrow`]: crate::AnyUserData::borrow
#[derive(Clone, Debug, PartialEq)]
@@ -650,18 +626,25 @@ impl AnyUserData {
/// Checks whether the type of this userdata is `T`.
#[inline]
pub fn is<T: 'static>(&self) -> bool {
self.inspect::<T, _, _>(|_| Ok(())).is_ok()
let type_id = self.type_id();
// We do not use wrapped types here, rather prefer to check the "real" type of the userdata
matches!(type_id, Some(type_id) if type_id == TypeId::of::<T>())
}
/// Borrow this userdata immutably if it is of type `T`.
///
/// # Errors
///
/// Returns a `UserDataBorrowError` if the userdata is already mutably borrowed. Returns a
/// `UserDataTypeMismatch` if the userdata is not of type `T` or if it's scoped.
/// Returns a [`UserDataBorrowError`] if the userdata is already mutably borrowed.
/// Returns a [`DataTypeMismatch`] if the userdata is not of type `T` or if it's
/// scoped.
///
/// [`UserDataBorrowError`]: crate::Error::UserDataBorrowError
/// [`DataTypeMismatch`]: crate::Error::UserDataTypeMismatch
#[inline]
pub fn borrow<T: 'static>(&self) -> Result<UserDataRef<T>> {
self.inspect(|ud| ud.try_borrow_owned())
let lua = self.0.lua.lock();
unsafe { UserDataRef::borrow_from_stack(&lua, lua.ref_thread(), self.0.index) }
}
/// Borrow this userdata immutably if it is of type `T`, passing the borrowed value
@@ -669,18 +652,26 @@ impl AnyUserData {
///
/// This method is the only way to borrow scoped userdata (created inside [`Lua::scope`]).
pub fn borrow_scoped<T: 'static, R>(&self, f: impl FnOnce(&T) -> R) -> Result<R> {
self.inspect(|ud| ud.try_borrow_scoped(|ud| f(ud)))
let lua = self.0.lua.lock();
let type_id = lua.get_userdata_ref_type_id(&self.0)?;
let type_hints = TypeIdHints::new::<T>();
unsafe { borrow_userdata_scoped(lua.ref_thread(), self.0.index, type_id, type_hints, f) }
}
/// Borrow this userdata mutably if it is of type `T`.
///
/// # Errors
///
/// Returns a `UserDataBorrowMutError` if the userdata cannot be mutably borrowed.
/// Returns a `UserDataTypeMismatch` if the userdata is not of type `T` or if it's scoped.
/// Returns a [`UserDataBorrowMutError`] if the userdata cannot be mutably borrowed.
/// Returns a [`UserDataTypeMismatch`] if the userdata is not of type `T` or if it's
/// scoped.
///
/// [`UserDataBorrowMutError`]: crate::Error::UserDataBorrowMutError
/// [`UserDataTypeMismatch`]: crate::Error::UserDataTypeMismatch
#[inline]
pub fn borrow_mut<T: 'static>(&self) -> Result<UserDataRefMut<T>> {
self.inspect(|ud| ud.try_borrow_owned_mut())
let lua = self.0.lua.lock();
unsafe { UserDataRefMut::borrow_from_stack(&lua, lua.ref_thread(), self.0.index) }
}
/// Borrow this userdata mutably if it is of type `T`, passing the borrowed value
@@ -688,10 +679,14 @@ impl AnyUserData {
///
/// This method is the only way to borrow scoped userdata (created inside [`Lua::scope`]).
pub fn borrow_mut_scoped<T: 'static, R>(&self, f: impl FnOnce(&mut T) -> R) -> Result<R> {
self.inspect(|ud| ud.try_borrow_scoped_mut(|ud| f(ud)))
let lua = self.0.lua.lock();
let type_id = lua.get_userdata_ref_type_id(&self.0)?;
let type_hints = TypeIdHints::new::<T>();
unsafe { borrow_userdata_scoped_mut(lua.ref_thread(), self.0.index, type_id, type_hints, f) }
}
/// Takes the value out of this userdata.
///
/// Sets the special "destructed" metatable that prevents any further operations with this
/// userdata.
///
@@ -706,23 +701,50 @@ impl AnyUserData {
let type_id = lua.push_userdata_ref(&self.0)?;
match type_id {
Some(type_id) if type_id == TypeId::of::<T>() => {
// Try to borrow userdata exclusively
let _ = (*get_userdata::<UserDataStorage<T>>(state, -1)).try_borrow_mut()?;
take_userdata::<UserDataStorage<T>>(state).into_inner()
if (*get_userdata::<UserDataStorage<T>>(state, -1)).has_exclusive_access() {
take_userdata::<UserDataStorage<T>>(state).into_inner()
} else {
Err(Error::UserDataBorrowMutError)
}
}
_ => Err(Error::UserDataTypeMismatch),
}
}
}
/// Sets an associated value to this `AnyUserData`.
/// Destroys this userdata.
///
/// This is similar to [`AnyUserData::take`], but it doesn't require a type.
///
/// This method works for non-scoped userdata only.
pub fn destroy(&self) -> Result<()> {
let lua = self.0.lua.lock();
let state = lua.state();
unsafe {
let _sg = StackGuard::new(state);
check_stack(state, 3)?;
lua.push_userdata_ref(&self.0)?;
protect_lua!(state, 1, 1, fn(state) {
if ffi::luaL_callmeta(state, -1, cstr!("__gc")) == 0 {
ffi::lua_pushboolean(state, 0);
}
})?;
if ffi::lua_isboolean(state, -1) != 0 && ffi::lua_toboolean(state, -1) != 0 {
return Ok(());
}
Err(Error::UserDataBorrowMutError)
}
}
/// Sets an associated value to this [`AnyUserData`].
///
/// The value may be any Lua value whatsoever, and can be retrieved with [`user_value`].
///
/// This is the same as calling [`set_nth_user_value`] with `n` set to 1.
///
/// [`user_value`]: #method.user_value
/// [`set_nth_user_value`]: #method.set_nth_user_value
/// [`user_value`]: AnyUserData::user_value
/// [`set_nth_user_value`]: AnyUserData::set_nth_user_value
#[inline]
pub fn set_user_value(&self, v: impl IntoLua) -> Result<()> {
self.set_nth_user_value(1, v)
@@ -732,29 +754,21 @@ impl AnyUserData {
///
/// This is the same as calling [`nth_user_value`] with `n` set to 1.
///
/// [`set_user_value`]: #method.set_user_value
/// [`nth_user_value`]: #method.nth_user_value
/// [`set_user_value`]: AnyUserData::set_user_value
/// [`nth_user_value`]: AnyUserData::nth_user_value
#[inline]
pub fn user_value<V: FromLua>(&self) -> Result<V> {
self.nth_user_value(1)
}
#[doc(hidden)]
#[deprecated(since = "0.9.0", note = "please use `user_value` instead")]
pub fn get_user_value<V: FromLua>(&self) -> Result<V> {
self.nth_user_value(1)
}
/// Sets an associated `n`th value to this `AnyUserData`.
/// Sets an associated `n`th value to this [`AnyUserData`].
///
/// The value may be any Lua value whatsoever, and can be retrieved with [`nth_user_value`].
/// `n` starts from 1 and can be up to 65535.
///
/// This is supported for all Lua versions.
/// In Lua 5.4 first 7 elements are stored in a most efficient way.
/// For other Lua versions this functionality is provided using a wrapping table.
/// This is supported for all Lua versions using a wrapping table.
///
/// [`nth_user_value`]: #method.nth_user_value
/// [`nth_user_value`]: AnyUserData::nth_user_value
pub fn set_nth_user_value(&self, n: usize, v: impl IntoLua) -> Result<()> {
if n < 1 || n > u16::MAX as usize {
return Err(Error::runtime("user value index out of bounds"));
@@ -790,11 +804,9 @@ impl AnyUserData {
///
/// `n` starts from 1 and can be up to 65535.
///
/// This is supported for all Lua versions.
/// In Lua 5.4 first 7 elements are stored in a most efficient way.
/// For other Lua versions this functionality is provided using a wrapping table.
/// This is supported for all Lua versions using a wrapping table.
///
/// [`set_nth_user_value`]: #method.set_nth_user_value
/// [`set_nth_user_value`]: AnyUserData::set_nth_user_value
pub fn nth_user_value<V: FromLua>(&self, n: usize) -> Result<V> {
if n < 1 || n > u16::MAX as usize {
return Err(Error::runtime("user value index out of bounds"));
@@ -809,29 +821,20 @@ impl AnyUserData {
lua.push_userdata_ref(&self.0)?;
// Multiple (extra) user values are emulated by storing them in a table
protect_lua!(state, 1, 1, |state| {
if ffi::lua_getuservalue(state, -1) != ffi::LUA_TTABLE {
ffi::lua_pushnil(state);
return;
}
ffi::lua_rawgeti(state, -1, n as ffi::lua_Integer);
})?;
if ffi::lua_getuservalue(state, -1) != ffi::LUA_TTABLE {
return V::from_lua(Value::Nil, lua.lua());
}
ffi::lua_rawgeti(state, -1, n as ffi::lua_Integer);
V::from_lua(lua.pop_value(), lua.lua())
}
}
#[doc(hidden)]
#[deprecated(since = "0.9.0", note = "please use `nth_user_value` instead")]
pub fn get_nth_user_value<V: FromLua>(&self, n: usize) -> Result<V> {
self.nth_user_value(n)
}
/// Sets an associated value to this `AnyUserData` by name.
/// Sets an associated value to this [`AnyUserData`] by name.
///
/// The value can be retrieved with [`named_user_value`].
///
/// [`named_user_value`]: #method.named_user_value
/// [`named_user_value`]: AnyUserData::named_user_value
pub fn set_named_user_value(&self, name: &str, v: impl IntoLua) -> Result<()> {
let lua = self.0.lua.lock();
let state = lua.state();
@@ -862,7 +865,7 @@ impl AnyUserData {
/// Returns an associated value by name set by [`set_named_user_value`].
///
/// [`set_named_user_value`]: #method.set_named_user_value
/// [`set_named_user_value`]: AnyUserData::set_named_user_value
pub fn named_user_value<V: FromLua>(&self, name: &str) -> Result<V> {
let lua = self.0.lua.lock();
let state = lua.state();
@@ -873,36 +876,25 @@ impl AnyUserData {
lua.push_userdata_ref(&self.0)?;
// Multiple (extra) user values are emulated by storing them in a table
protect_lua!(state, 1, 1, |state| {
if ffi::lua_getuservalue(state, -1) != ffi::LUA_TTABLE {
ffi::lua_pushnil(state);
return;
}
ffi::lua_pushlstring(state, name.as_ptr() as *const c_char, name.len());
ffi::lua_rawget(state, -2);
})?;
if ffi::lua_getuservalue(state, -1) != ffi::LUA_TTABLE {
return V::from_lua(Value::Nil, lua.lua());
}
push_string(state, name.as_bytes(), !lua.unlikely_memory_error())?;
ffi::lua_rawget(state, -2);
V::from_lua(lua.pop_value(), lua.lua())
V::from_stack(-1, &lua)
}
}
#[doc(hidden)]
#[deprecated(since = "0.9.0", note = "please use `named_user_value` instead")]
pub fn get_named_user_value<V: FromLua>(&self, name: &str) -> Result<V> {
self.named_user_value(name)
}
/// Returns a metatable of this `UserData`.
/// Returns a metatable of this [`AnyUserData`].
///
/// Returned [`UserDataMetatable`] object wraps the original metatable and
/// provides safe access to its methods.
///
/// For `T: 'static` returned metatable is shared among all instances of type `T`.
///
/// [`UserDataMetatable`]: crate::UserDataMetatable
#[inline]
pub fn metatable(&self) -> Result<UserDataMetatable> {
self.get_raw_metatable().map(UserDataMetatable)
self.raw_metatable().map(UserDataMetatable)
}
#[doc(hidden)]
@@ -911,7 +903,7 @@ impl AnyUserData {
self.metatable()
}
fn get_raw_metatable(&self) -> Result<Table> {
fn raw_metatable(&self) -> Result<Table> {
let lua = self.0.lua.lock();
let state = lua.state();
unsafe {
@@ -934,6 +926,15 @@ impl AnyUserData {
self.0.to_pointer()
}
/// Returns [`TypeId`] of this userdata if it is registered and `'static`.
///
/// This method is not available for scoped userdata.
#[inline]
pub fn type_id(&self) -> Option<TypeId> {
let lua = self.0.lua.lock();
lua.get_userdata_ref_type_id(&self.0).ok().flatten()
}
/// Returns a type name of this `UserData` (from a metatable field).
pub(crate) fn type_name(&self) -> Result<Option<StdString>> {
let lua = self.0.lua.lock();
@@ -958,15 +959,14 @@ impl AnyUserData {
}
}
pub(crate) fn equals<T: AsRef<Self>>(&self, other: T) -> Result<bool> {
let other = other.as_ref();
pub(crate) fn equals(&self, other: &Self) -> Result<bool> {
// Uses lua_rawequal() under the hood
if self == other {
return Ok(true);
}
let mt = self.get_raw_metatable()?;
if mt != other.get_raw_metatable()? {
let mt = self.raw_metatable()?;
if mt != other.raw_metatable()? {
return Ok(false);
}
@@ -977,8 +977,8 @@ impl AnyUserData {
Ok(false)
}
/// Returns `true` if this `AnyUserData` is serializable (eg. was created using
/// `create_ser_userdata`).
/// Returns `true` if this [`AnyUserData`] is serializable (e.g. was created using
/// [`Lua::create_ser_userdata`]).
#[cfg(feature = "serialize")]
pub(crate) fn is_serializable(&self) -> bool {
let lua = self.0.lua.lock();
@@ -990,34 +990,9 @@ impl AnyUserData {
};
is_serializable().unwrap_or(false)
}
pub(crate) fn inspect<T, F, R>(&self, func: F) -> Result<R>
where
T: 'static,
F: FnOnce(&UserDataStorage<T>) -> Result<R>,
{
let lua = self.0.lua.lock();
unsafe {
let type_id = lua.get_userdata_ref_type_id(&self.0)?;
match type_id {
Some(type_id) if type_id == TypeId::of::<T>() => {
let ud = get_userdata::<UserDataStorage<T>>(lua.ref_thread(), self.0.index);
func(&*ud)
}
_ => Err(Error::UserDataTypeMismatch),
}
}
}
}
impl AsRef<AnyUserData> for AnyUserData {
#[inline]
fn as_ref(&self) -> &Self {
self
}
}
/// Handle to a `UserData` metatable.
/// Handle to a [`AnyUserData`] metatable.
#[derive(Clone, Debug)]
pub struct UserDataMetatable(pub(crate) Table);
@@ -1060,17 +1035,14 @@ impl UserDataMetatable {
}
}
/// An iterator over the pairs of a [`UserData`] metatable.
/// An iterator over the pairs of a [`AnyUserData`] metatable.
///
/// It skips restricted metamethods, such as `__gc` or `__metatable`.
///
/// This struct is created by the [`UserDataMetatable::pairs`] method.
///
/// [`UserData`]: crate::UserData
/// [`UserDataMetatable::pairs`]: crate::UserDataMetatable::method.pairs
pub struct UserDataMetatablePairs<'a, V>(TablePairs<'a, StdString, V>);
impl<'a, V> Iterator for UserDataMetatablePairs<'a, V>
impl<V> Iterator for UserDataMetatablePairs<'_, V>
where
V: FromLua,
{
@@ -1108,12 +1080,12 @@ impl Serialize for AnyUserData {
}
}
pub(crate) struct WrappedUserdata<F: FnOnce(&Lua) -> Result<AnyUserData>>(F);
struct WrappedUserdata<F: FnOnce(&Lua) -> Result<AnyUserData>>(F);
impl AnyUserData {
/// Wraps any Rust type, returning an opaque type that implements [`IntoLua`] trait.
///
/// This function uses [`Lua::create_any_userdata()`] under the hood.
/// This function uses [`Lua::create_any_userdata`] under the hood.
pub fn wrap<T: MaybeSend + 'static>(data: T) -> impl IntoLua {
WrappedUserdata(move |lua| lua.create_any_userdata(data))
}
@@ -1121,7 +1093,7 @@ impl AnyUserData {
/// Wraps any Rust type that implements [`Serialize`], returning an opaque type that implements
/// [`IntoLua`] trait.
///
/// This function uses [`Lua::create_ser_any_userdata()`] under the hood.
/// This function uses [`Lua::create_ser_any_userdata`] under the hood.
#[cfg(feature = "serialize")]
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
pub fn wrap_ser<T: Serialize + MaybeSend + 'static>(data: T) -> impl IntoLua {
@@ -1141,7 +1113,9 @@ where
mod cell;
mod lock;
mod object;
mod r#ref;
mod registry;
mod util;
#[cfg(test)]
mod assertions {
+78 -290
View File
@@ -1,21 +1,13 @@
use std::any::{type_name, TypeId};
use std::cell::{RefCell, UnsafeCell};
use std::fmt;
use std::ops::{Deref, DerefMut};
use std::os::raw::c_int;
#[cfg(feature = "serialize")]
use serde::ser::{Serialize, Serializer};
use crate::error::{Error, Result};
use crate::state::{Lua, RawLua};
use crate::traits::FromLua;
use crate::types::XRc;
use crate::userdata::AnyUserData;
use crate::util::get_userdata;
use crate::value::Value;
use super::lock::{RawLock, UserDataLock};
use super::r#ref::{UserDataRef, UserDataRefMut};
#[cfg(all(feature = "serialize", not(feature = "send")))]
type DynSerialize = dyn erased_serde::Serialize;
@@ -33,7 +25,7 @@ pub(crate) enum UserDataStorage<T> {
pub(crate) enum UserDataVariant<T> {
Default(XRc<UserDataCell<T>>),
#[cfg(feature = "serialize")]
Serializable(XRc<UserDataCell<Box<DynSerialize>>>),
Serializable(XRc<UserDataCell<Box<DynSerialize>>>, bool), // bool is `is_sync`
}
impl<T> Clone for UserDataVariant<T> {
@@ -42,16 +34,28 @@ impl<T> Clone for UserDataVariant<T> {
match self {
Self::Default(inner) => Self::Default(XRc::clone(inner)),
#[cfg(feature = "serialize")]
Self::Serializable(inner) => Self::Serializable(XRc::clone(inner)),
Self::Serializable(inner, is_sync) => Self::Serializable(XRc::clone(inner), *is_sync),
}
}
}
impl<T> UserDataVariant<T> {
// Immutably borrows the wrapped value in-place.
#[inline(always)]
fn try_borrow(&self) -> Result<UserDataBorrowRef<T>> {
UserDataBorrowRef::try_from(self)
pub(super) fn try_borrow_scoped<R>(&self, f: impl FnOnce(&T) -> R) -> Result<R> {
// We don't need to check for `T: Sync` because when this method is used (internally),
// Lua mutex is already locked.
// If non-`Sync` userdata is already borrowed by another thread (via `UserDataRef`), it will be
// exclusively locked.
let _guard = (self.raw_lock().try_lock_shared_guarded()).map_err(|_| Error::UserDataBorrowError)?;
Ok(f(unsafe { &*self.as_ptr() }))
}
// Mutably borrows the wrapped value in-place.
#[inline(always)]
fn try_borrow_scoped_mut<R>(&self, f: impl FnOnce(&mut T) -> R) -> Result<R> {
let _guard =
(self.raw_lock().try_lock_exclusive_guarded()).map_err(|_| Error::UserDataBorrowMutError)?;
Ok(f(unsafe { &mut *self.as_ptr() }))
}
// Immutably borrows the wrapped value and returns an owned reference.
@@ -60,12 +64,6 @@ impl<T> UserDataVariant<T> {
UserDataRef::try_from(self.clone())
}
// Mutably borrows the wrapped value in-place.
#[inline(always)]
fn try_borrow_mut(&self) -> Result<UserDataBorrowMut<T>> {
UserDataBorrowMut::try_from(self)
}
// Mutably borrows the wrapped value and returns an owned reference.
#[inline(always)]
fn try_borrow_owned_mut(&self) -> Result<UserDataRefMut<T>> {
@@ -82,7 +80,7 @@ impl<T> UserDataVariant<T> {
Ok(match self {
Self::Default(inner) => XRc::into_inner(inner).unwrap().value.into_inner(),
#[cfg(feature = "serialize")]
Self::Serializable(inner) => unsafe {
Self::Serializable(inner, _) => unsafe {
let raw = Box::into_raw(XRc::into_inner(inner).unwrap().value.into_inner());
*Box::from_raw(raw as *mut T)
},
@@ -90,20 +88,29 @@ impl<T> UserDataVariant<T> {
}
#[inline(always)]
fn raw_lock(&self) -> &RawLock {
fn strong_count(&self) -> usize {
match self {
Self::Default(inner) => &inner.raw_lock,
Self::Default(inner) => XRc::strong_count(inner),
#[cfg(feature = "serialize")]
Self::Serializable(inner) => &inner.raw_lock,
Self::Serializable(inner, _) => XRc::strong_count(inner),
}
}
#[inline(always)]
fn as_ptr(&self) -> *mut T {
pub(super) fn raw_lock(&self) -> &RawLock {
match self {
Self::Default(inner) => &inner.raw_lock,
#[cfg(feature = "serialize")]
Self::Serializable(inner, _) => &inner.raw_lock,
}
}
#[inline(always)]
pub(super) fn as_ptr(&self) -> *mut T {
match self {
Self::Default(inner) => inner.value.get(),
#[cfg(feature = "serialize")]
Self::Serializable(inner) => unsafe { &mut **(inner.value.get() as *mut Box<T>) },
Self::Serializable(inner, _) => unsafe { &mut **(inner.value.get() as *mut Box<T>) },
}
}
}
@@ -112,14 +119,24 @@ impl<T> UserDataVariant<T> {
impl Serialize for UserDataStorage<()> {
fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
match self {
Self::Owned(UserDataVariant::Serializable(inner)) => unsafe {
// We need to borrow the inner value exclusively to serialize it.
Self::Owned(variant @ UserDataVariant::Serializable(inner, is_sync)) => unsafe {
#[cfg(feature = "send")]
let _guard = self.try_borrow_mut().map_err(serde::ser::Error::custom)?;
// No need to do this if the `send` feature is disabled.
if *is_sync {
let _guard = (variant.raw_lock().try_lock_shared_guarded())
.map_err(|_| serde::ser::Error::custom(Error::UserDataBorrowError))?;
(*inner.value.get()).serialize(serializer)
} else {
let _guard = (variant.raw_lock().try_lock_exclusive_guarded())
.map_err(|_| serde::ser::Error::custom(Error::UserDataBorrowError))?;
(*inner.value.get()).serialize(serializer)
}
#[cfg(not(feature = "send"))]
let _guard = self.try_borrow().map_err(serde::ser::Error::custom)?;
(*inner.value.get()).serialize(serializer)
{
let _ = is_sync;
let _guard = (variant.raw_lock().try_lock_shared_guarded())
.map_err(|_| serde::ser::Error::custom(Error::UserDataBorrowError))?;
(*inner.value.get()).serialize(serializer)
}
},
_ => Err(serde::ser::Error::custom("cannot serialize <userdata>")),
}
@@ -132,7 +149,9 @@ pub(crate) struct UserDataCell<T> {
value: UnsafeCell<T>,
}
#[cfg(feature = "send")]
unsafe impl<T: Send> Send for UserDataCell<T> {}
#[cfg(feature = "send")]
unsafe impl<T: Send> Sync for UserDataCell<T> {}
impl<T> UserDataCell<T> {
@@ -145,216 +164,6 @@ impl<T> UserDataCell<T> {
}
}
/// A wrapper type for a [`UserData`] value that provides read access.
///
/// It implements [`FromLua`] and can be used to receive a typed userdata from Lua.
pub struct UserDataRef<T>(UserDataVariant<T>);
impl<T> Deref for UserDataRef<T> {
type Target = T;
#[inline]
fn deref(&self) -> &T {
unsafe { &*self.0.as_ptr() }
}
}
impl<T> Drop for UserDataRef<T> {
#[inline]
fn drop(&mut self) {
unsafe { self.0.raw_lock().unlock_shared() };
}
}
impl<T: fmt::Debug> fmt::Debug for UserDataRef<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
(**self).fmt(f)
}
}
impl<T: fmt::Display> fmt::Display for UserDataRef<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
(**self).fmt(f)
}
}
impl<T> TryFrom<UserDataVariant<T>> for UserDataRef<T> {
type Error = Error;
#[inline]
fn try_from(variant: UserDataVariant<T>) -> Result<Self> {
if !variant.raw_lock().try_lock_shared() {
return Err(Error::UserDataBorrowError);
}
Ok(UserDataRef(variant))
}
}
impl<T: 'static> FromLua for UserDataRef<T> {
fn from_lua(value: Value, _: &Lua) -> Result<Self> {
try_value_to_userdata::<T>(value)?.borrow()
}
unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result<Self> {
let type_id = lua.get_userdata_type_id::<T>(idx)?;
match type_id {
Some(type_id) if type_id == TypeId::of::<T>() => {
(*get_userdata::<UserDataStorage<T>>(lua.state(), idx)).try_borrow_owned()
}
_ => Err(Error::UserDataTypeMismatch),
}
}
}
/// A wrapper type for a mutably borrowed value from a `AnyUserData`.
///
/// It implements [`FromLua`] and can be used to receive a typed userdata from Lua.
pub struct UserDataRefMut<T>(UserDataVariant<T>);
impl<T> Deref for UserDataRefMut<T> {
type Target = T;
#[inline]
fn deref(&self) -> &Self::Target {
unsafe { &*self.0.as_ptr() }
}
}
impl<T> DerefMut for UserDataRefMut<T> {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { &mut *self.0.as_ptr() }
}
}
impl<T> Drop for UserDataRefMut<T> {
#[inline]
fn drop(&mut self) {
unsafe { self.0.raw_lock().unlock_exclusive() };
}
}
impl<T: fmt::Debug> fmt::Debug for UserDataRefMut<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
(**self).fmt(f)
}
}
impl<T: fmt::Display> fmt::Display for UserDataRefMut<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
(**self).fmt(f)
}
}
impl<T> TryFrom<UserDataVariant<T>> for UserDataRefMut<T> {
type Error = Error;
#[inline]
fn try_from(variant: UserDataVariant<T>) -> Result<Self> {
if !variant.raw_lock().try_lock_exclusive() {
return Err(Error::UserDataBorrowMutError);
}
Ok(UserDataRefMut(variant))
}
}
impl<T: 'static> FromLua for UserDataRefMut<T> {
fn from_lua(value: Value, _: &Lua) -> Result<Self> {
try_value_to_userdata::<T>(value)?.borrow_mut()
}
unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result<Self> {
let type_id = lua.get_userdata_type_id::<T>(idx)?;
match type_id {
Some(type_id) if type_id == TypeId::of::<T>() => {
(*get_userdata::<UserDataStorage<T>>(lua.state(), idx)).try_borrow_owned_mut()
}
_ => Err(Error::UserDataTypeMismatch),
}
}
}
/// A type that provides read access to a userdata value (borrowing the value).
pub(crate) struct UserDataBorrowRef<'a, T>(&'a UserDataVariant<T>);
impl<'a, T> Drop for UserDataBorrowRef<'a, T> {
#[inline]
fn drop(&mut self) {
unsafe { self.0.raw_lock().unlock_shared() };
}
}
impl<'a, T> Deref for UserDataBorrowRef<'a, T> {
type Target = T;
#[inline]
fn deref(&self) -> &T {
// SAFETY: `UserDataBorrowRef` is only created with shared access to the value.
unsafe { &*self.0.as_ptr() }
}
}
impl<'a, T> TryFrom<&'a UserDataVariant<T>> for UserDataBorrowRef<'a, T> {
type Error = Error;
#[inline(always)]
fn try_from(variant: &'a UserDataVariant<T>) -> Result<Self> {
if !variant.raw_lock().try_lock_shared() {
return Err(Error::UserDataBorrowError);
}
Ok(UserDataBorrowRef(variant))
}
}
pub(crate) struct UserDataBorrowMut<'a, T>(&'a UserDataVariant<T>);
impl<'a, T> Drop for UserDataBorrowMut<'a, T> {
#[inline]
fn drop(&mut self) {
unsafe { self.0.raw_lock().unlock_exclusive() };
}
}
impl<'a, T> Deref for UserDataBorrowMut<'a, T> {
type Target = T;
#[inline]
fn deref(&self) -> &T {
unsafe { &*self.0.as_ptr() }
}
}
impl<'a, T> DerefMut for UserDataBorrowMut<'a, T> {
#[inline]
fn deref_mut(&mut self) -> &mut T {
unsafe { &mut *self.0.as_ptr() }
}
}
impl<'a, T> TryFrom<&'a UserDataVariant<T>> for UserDataBorrowMut<'a, T> {
type Error = Error;
#[inline(always)]
fn try_from(variant: &'a UserDataVariant<T>) -> Result<Self> {
if !variant.raw_lock().try_lock_exclusive() {
return Err(Error::UserDataBorrowMutError);
}
Ok(UserDataBorrowMut(variant))
}
}
#[inline]
fn try_value_to_userdata<T>(value: Value) -> Result<AnyUserData> {
match value {
Value::UserData(ud) => Ok(ud),
_ => Err(Error::FromLuaConversionError {
from: value.type_name(),
to: "userdata".to_string(),
message: Some(format!("expected userdata of type {}", type_name::<T>())),
}),
}
}
pub(crate) enum ScopedUserDataVariant<T> {
Ref(*const T),
RefMut(RefCell<*mut T>),
@@ -395,13 +204,15 @@ impl<T: 'static> UserDataStorage<T> {
T: Serialize + crate::types::MaybeSend,
{
let data = Box::new(data) as Box<DynSerialize>;
Self::Owned(UserDataVariant::Serializable(XRc::new(UserDataCell::new(data))))
let is_sync = super::util::is_sync::<T>();
let variant = UserDataVariant::Serializable(XRc::new(UserDataCell::new(data)), is_sync);
Self::Owned(variant)
}
#[cfg(feature = "serialize")]
#[inline(always)]
pub(crate) fn is_serializable(&self) -> bool {
matches!(self, Self::Owned(UserDataVariant::Serializable(_)))
matches!(self, Self::Owned(UserDataVariant::Serializable(..)))
}
// Immutably borrows the wrapped value and returns an owned reference.
@@ -413,23 +224,6 @@ impl<T: 'static> UserDataStorage<T> {
}
}
#[allow(unused)]
#[inline(always)]
pub(crate) fn try_borrow(&self) -> Result<UserDataBorrowRef<T>> {
match self {
Self::Owned(data) => data.try_borrow(),
Self::Scoped(_) => Err(Error::UserDataTypeMismatch),
}
}
#[inline(always)]
pub(crate) fn try_borrow_mut(&self) -> Result<UserDataBorrowMut<T>> {
match self {
Self::Owned(data) => data.try_borrow_mut(),
Self::Scoped(_) => Err(Error::UserDataTypeMismatch),
}
}
// Mutably borrows the wrapped value and returns an owned reference.
#[inline(always)]
pub(crate) fn try_borrow_owned_mut(&self) -> Result<UserDataRefMut<T>> {
@@ -455,10 +249,31 @@ impl<T> UserDataStorage<T> {
Self::Scoped(ScopedUserDataVariant::Boxed(RefCell::new(data)))
}
/// Returns `true` if it's safe to destroy the container.
///
/// It's safe to destroy the container if the reference count is greater than 1 or the lock is
/// not acquired.
#[inline(always)]
pub(crate) fn is_safe_to_destroy(&self) -> bool {
match self {
Self::Owned(variant) => variant.strong_count() > 1 || !variant.raw_lock().is_locked(),
Self::Scoped(_) => false,
}
}
/// Returns `true` if the container has exclusive access to the value.
#[inline(always)]
pub(crate) fn has_exclusive_access(&self) -> bool {
match self {
Self::Owned(variant) => !variant.raw_lock().is_locked(),
Self::Scoped(_) => false,
}
}
#[inline]
pub(crate) fn try_borrow_scoped<R>(&self, f: impl FnOnce(&T) -> R) -> Result<R> {
match self {
Self::Owned(data) => Ok(f(&*data.try_borrow()?)),
Self::Owned(data) => data.try_borrow_scoped(f),
Self::Scoped(ScopedUserDataVariant::Ref(value)) => Ok(f(unsafe { &**value })),
Self::Scoped(ScopedUserDataVariant::RefMut(value) | ScopedUserDataVariant::Boxed(value)) => {
let t = value.try_borrow().map_err(|_| Error::UserDataBorrowError)?;
@@ -470,7 +285,7 @@ impl<T> UserDataStorage<T> {
#[inline]
pub(crate) fn try_borrow_scoped_mut<R>(&self, f: impl FnOnce(&mut T) -> R) -> Result<R> {
match self {
Self::Owned(data) => Ok(f(&mut *data.try_borrow_mut()?)),
Self::Owned(data) => data.try_borrow_scoped_mut(f),
Self::Scoped(ScopedUserDataVariant::Ref(_)) => Err(Error::UserDataBorrowMutError),
Self::Scoped(ScopedUserDataVariant::RefMut(value) | ScopedUserDataVariant::Boxed(value)) => {
let mut t = value
@@ -481,30 +296,3 @@ impl<T> UserDataStorage<T> {
}
}
}
#[cfg(test)]
mod assertions {
use super::*;
#[cfg(feature = "send")]
static_assertions::assert_impl_all!(UserDataRef<()>: Send, Sync);
#[cfg(feature = "send")]
static_assertions::assert_not_impl_all!(UserDataRef<std::rc::Rc<()>>: Send, Sync);
#[cfg(feature = "send")]
static_assertions::assert_impl_all!(UserDataRefMut<()>: Sync, Send);
#[cfg(feature = "send")]
static_assertions::assert_not_impl_all!(UserDataRefMut<std::rc::Rc<()>>: Send, Sync);
#[cfg(feature = "send")]
static_assertions::assert_impl_all!(UserDataBorrowRef<'_, ()>: Send, Sync);
#[cfg(feature = "send")]
static_assertions::assert_impl_all!(UserDataBorrowMut<'_, ()>: Send, Sync);
#[cfg(not(feature = "send"))]
static_assertions::assert_not_impl_all!(UserDataRef<()>: Send, Sync);
#[cfg(not(feature = "send"))]
static_assertions::assert_not_impl_all!(UserDataRefMut<()>: Send, Sync);
#[cfg(not(feature = "send"))]
static_assertions::assert_not_impl_all!(UserDataBorrowRef<'_, ()>: Send, Sync);
#[cfg(not(feature = "send"))]
static_assertions::assert_not_impl_all!(UserDataBorrowMut<'_, ()>: Send, Sync);
}
+58 -7
View File
@@ -1,16 +1,57 @@
pub(crate) trait UserDataLock {
const INIT: Self;
fn is_locked(&self) -> bool;
fn try_lock_shared(&self) -> bool;
fn try_lock_exclusive(&self) -> bool;
unsafe fn unlock_shared(&self);
unsafe fn unlock_exclusive(&self);
fn try_lock_shared_guarded(&self) -> Result<LockGuard<'_, Self>, ()> {
if self.try_lock_shared() {
Ok(LockGuard {
lock: self,
exclusive: false,
})
} else {
Err(())
}
}
fn try_lock_exclusive_guarded(&self) -> Result<LockGuard<'_, Self>, ()> {
if self.try_lock_exclusive() {
Ok(LockGuard {
lock: self,
exclusive: true,
})
} else {
Err(())
}
}
}
pub(crate) struct LockGuard<'a, L: UserDataLock + ?Sized> {
lock: &'a L,
exclusive: bool,
}
impl<L: UserDataLock + ?Sized> Drop for LockGuard<'_, L> {
fn drop(&mut self) {
unsafe {
if self.exclusive {
self.lock.unlock_exclusive();
} else {
self.lock.unlock_shared();
}
}
}
}
pub(crate) use lock_impl::RawLock;
#[cfg(not(feature = "send"))]
#[cfg(not(tarpaulin_include))]
mod lock_impl {
use std::cell::Cell;
@@ -24,6 +65,11 @@ mod lock_impl {
#[allow(clippy::declare_interior_mutable_const)]
const INIT: Self = Cell::new(UNUSED);
#[inline(always)]
fn is_locked(&self) -> bool {
self.get() != UNUSED
}
#[inline(always)]
fn try_lock_shared(&self) -> bool {
let flag = self.get().wrapping_add(1);
@@ -62,32 +108,37 @@ mod lock_impl {
#[cfg(feature = "send")]
mod lock_impl {
use parking_lot::lock_api::RawMutex;
use parking_lot::lock_api::RawRwLock;
pub(crate) type RawLock = parking_lot::RawMutex;
pub(crate) type RawLock = parking_lot::RawRwLock;
impl super::UserDataLock for RawLock {
#[allow(clippy::declare_interior_mutable_const)]
const INIT: Self = <Self as parking_lot::lock_api::RawMutex>::INIT;
const INIT: Self = <Self as parking_lot::lock_api::RawRwLock>::INIT;
#[inline(always)]
fn is_locked(&self) -> bool {
RawRwLock::is_locked(self)
}
#[inline(always)]
fn try_lock_shared(&self) -> bool {
RawLock::try_lock(self)
RawRwLock::try_lock_shared(self)
}
#[inline(always)]
fn try_lock_exclusive(&self) -> bool {
RawLock::try_lock(self)
RawRwLock::try_lock_exclusive(self)
}
#[inline(always)]
unsafe fn unlock_shared(&self) {
RawLock::unlock(self)
RawRwLock::unlock_shared(self)
}
#[inline(always)]
unsafe fn unlock_exclusive(&self) {
RawLock::unlock(self)
RawRwLock::unlock_exclusive(self)
}
}
}
+474
View File
@@ -0,0 +1,474 @@
use std::any::TypeId;
use std::ops::{Deref, DerefMut};
use std::os::raw::c_int;
use std::{fmt, mem};
use crate::error::{Error, Result};
use crate::state::{Lua, RawLua};
use crate::traits::FromLua;
use crate::userdata::AnyUserData;
use crate::util::{get_userdata, short_type_name};
use crate::value::Value;
use super::cell::{UserDataStorage, UserDataVariant};
use super::lock::{LockGuard, RawLock, UserDataLock};
use super::util::is_sync;
#[cfg(feature = "userdata-wrappers")]
use {
parking_lot::{
Mutex as MutexPL, MutexGuard as MutexGuardPL, RwLock as RwLockPL,
RwLockReadGuard as RwLockReadGuardPL, RwLockWriteGuard as RwLockWriteGuardPL,
},
std::sync::Arc,
};
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
use {
std::cell::{Ref, RefCell, RefMut},
std::rc::Rc,
};
/// A wrapper type for a userdata value that provides read access.
///
/// It implements [`FromLua`] and can be used to receive a typed userdata from Lua.
pub struct UserDataRef<T: 'static> {
// It's important to drop the guard first, as it refers to the `inner` data.
_guard: LockGuard<'static, RawLock>,
inner: UserDataRefInner<T>,
}
impl<T> Deref for UserDataRef<T> {
type Target = T;
#[inline]
fn deref(&self) -> &T {
&self.inner
}
}
impl<T: fmt::Debug> fmt::Debug for UserDataRef<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
(**self).fmt(f)
}
}
impl<T: fmt::Display> fmt::Display for UserDataRef<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
(**self).fmt(f)
}
}
impl<T> TryFrom<UserDataVariant<T>> for UserDataRef<T> {
type Error = Error;
#[inline]
fn try_from(variant: UserDataVariant<T>) -> Result<Self> {
let guard = if !cfg!(feature = "send") || is_sync::<T>() {
variant.raw_lock().try_lock_shared_guarded()
} else {
variant.raw_lock().try_lock_exclusive_guarded()
};
let guard = guard.map_err(|_| Error::UserDataBorrowError)?;
let guard = unsafe { mem::transmute::<LockGuard<_>, LockGuard<'static, _>>(guard) };
Ok(UserDataRef::from_parts(UserDataRefInner::Default(variant), guard))
}
}
impl<T: 'static> FromLua for UserDataRef<T> {
fn from_lua(value: Value, _: &Lua) -> Result<Self> {
try_value_to_userdata::<T>(value)?.borrow()
}
#[inline]
unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result<Self> {
Self::borrow_from_stack(lua, lua.state(), idx)
}
}
impl<T: 'static> UserDataRef<T> {
#[inline(always)]
fn from_parts(inner: UserDataRefInner<T>, guard: LockGuard<'static, RawLock>) -> Self {
Self { _guard: guard, inner }
}
#[cfg(feature = "userdata-wrappers")]
fn remap<U>(
self,
f: impl FnOnce(UserDataVariant<T>) -> Result<UserDataRefInner<U>>,
) -> Result<UserDataRef<U>> {
match &self.inner {
UserDataRefInner::Default(variant) => {
let inner = f(variant.clone())?;
Ok(UserDataRef::from_parts(inner, self._guard))
}
_ => Err(Error::UserDataTypeMismatch),
}
}
pub(crate) unsafe fn borrow_from_stack(
lua: &RawLua,
state: *mut ffi::lua_State,
idx: c_int,
) -> Result<Self> {
let type_id = lua.get_userdata_type_id::<T>(state, idx)?;
match type_id {
Some(type_id) if type_id == TypeId::of::<T>() => {
let ud = get_userdata::<UserDataStorage<T>>(state, idx);
(*ud).try_borrow_owned()
}
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
Some(type_id) if type_id == TypeId::of::<Rc<T>>() => {
let ud = get_userdata::<UserDataStorage<Rc<T>>>(state, idx);
((*ud).try_borrow_owned()).and_then(|ud| ud.transform_rc())
}
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
Some(type_id) if type_id == TypeId::of::<Rc<RefCell<T>>>() => {
let ud = get_userdata::<UserDataStorage<Rc<RefCell<T>>>>(state, idx);
((*ud).try_borrow_owned()).and_then(|ud| ud.transform_rc_refcell())
}
#[cfg(feature = "userdata-wrappers")]
Some(type_id) if type_id == TypeId::of::<Arc<T>>() => {
let ud = get_userdata::<UserDataStorage<Arc<T>>>(state, idx);
((*ud).try_borrow_owned()).and_then(|ud| ud.transform_arc())
}
#[cfg(feature = "userdata-wrappers")]
Some(type_id) if type_id == TypeId::of::<Arc<MutexPL<T>>>() => {
let ud = get_userdata::<UserDataStorage<Arc<MutexPL<T>>>>(state, idx);
((*ud).try_borrow_owned()).and_then(|ud| ud.transform_arc_mutex_pl())
}
#[cfg(feature = "userdata-wrappers")]
Some(type_id) if type_id == TypeId::of::<Arc<RwLockPL<T>>>() => {
let ud = get_userdata::<UserDataStorage<Arc<RwLockPL<T>>>>(state, idx);
((*ud).try_borrow_owned()).and_then(|ud| ud.transform_arc_rwlock_pl())
}
_ => Err(Error::UserDataTypeMismatch),
}
}
}
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
impl<T> UserDataRef<Rc<T>> {
fn transform_rc(self) -> Result<UserDataRef<T>> {
self.remap(|variant| Ok(UserDataRefInner::Rc(variant)))
}
}
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
impl<T> UserDataRef<Rc<RefCell<T>>> {
fn transform_rc_refcell(self) -> Result<UserDataRef<T>> {
self.remap(|variant| unsafe {
let obj = &*variant.as_ptr();
let r#ref = obj.try_borrow().map_err(|_| Error::UserDataBorrowError)?;
let borrow = std::mem::transmute::<Ref<T>, Ref<'static, T>>(r#ref);
Ok(UserDataRefInner::RcRefCell(borrow, variant))
})
}
}
#[cfg(feature = "userdata-wrappers")]
impl<T> UserDataRef<Arc<T>> {
fn transform_arc(self) -> Result<UserDataRef<T>> {
self.remap(|variant| Ok(UserDataRefInner::Arc(variant)))
}
}
#[cfg(feature = "userdata-wrappers")]
impl<T> UserDataRef<Arc<MutexPL<T>>> {
fn transform_arc_mutex_pl(self) -> Result<UserDataRef<T>> {
self.remap(|variant| unsafe {
let obj = &*variant.as_ptr();
let guard = obj.try_lock().ok_or(Error::UserDataBorrowError)?;
let borrow = std::mem::transmute::<MutexGuardPL<T>, MutexGuardPL<'static, T>>(guard);
Ok(UserDataRefInner::ArcMutexPL(borrow, variant))
})
}
}
#[cfg(feature = "userdata-wrappers")]
impl<T> UserDataRef<Arc<RwLockPL<T>>> {
fn transform_arc_rwlock_pl(self) -> Result<UserDataRef<T>> {
self.remap(|variant| unsafe {
let obj = &*variant.as_ptr();
let guard = obj.try_read().ok_or(Error::UserDataBorrowError)?;
let borrow = std::mem::transmute::<RwLockReadGuardPL<T>, RwLockReadGuardPL<'static, T>>(guard);
Ok(UserDataRefInner::ArcRwLockPL(borrow, variant))
})
}
}
#[allow(unused)]
enum UserDataRefInner<T: 'static> {
Default(UserDataVariant<T>),
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
Rc(UserDataVariant<Rc<T>>),
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
RcRefCell(Ref<'static, T>, UserDataVariant<Rc<RefCell<T>>>),
#[cfg(feature = "userdata-wrappers")]
Arc(UserDataVariant<Arc<T>>),
#[cfg(feature = "userdata-wrappers")]
ArcMutexPL(MutexGuardPL<'static, T>, UserDataVariant<Arc<MutexPL<T>>>),
#[cfg(feature = "userdata-wrappers")]
ArcRwLockPL(RwLockReadGuardPL<'static, T>, UserDataVariant<Arc<RwLockPL<T>>>),
}
impl<T> Deref for UserDataRefInner<T> {
type Target = T;
#[inline]
fn deref(&self) -> &T {
match self {
Self::Default(inner) => unsafe { &*inner.as_ptr() },
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
Self::Rc(inner) => unsafe { &*Rc::as_ptr(&*inner.as_ptr()) },
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
Self::RcRefCell(x, ..) => x,
#[cfg(feature = "userdata-wrappers")]
Self::Arc(inner) => unsafe { &*Arc::as_ptr(&*inner.as_ptr()) },
#[cfg(feature = "userdata-wrappers")]
Self::ArcMutexPL(x, ..) => x,
#[cfg(feature = "userdata-wrappers")]
Self::ArcRwLockPL(x, ..) => x,
}
}
}
/// A wrapper type for a userdata value that provides read and write access.
///
/// It implements [`FromLua`] and can be used to receive a typed userdata from Lua.
pub struct UserDataRefMut<T: 'static> {
// It's important to drop the guard first, as it refers to the `inner` data.
_guard: LockGuard<'static, RawLock>,
inner: UserDataRefMutInner<T>,
}
impl<T> Deref for UserDataRefMut<T> {
type Target = T;
#[inline]
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl<T> DerefMut for UserDataRefMut<T> {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}
impl<T: fmt::Debug> fmt::Debug for UserDataRefMut<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
(**self).fmt(f)
}
}
impl<T: fmt::Display> fmt::Display for UserDataRefMut<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
(**self).fmt(f)
}
}
impl<T> TryFrom<UserDataVariant<T>> for UserDataRefMut<T> {
type Error = Error;
#[inline]
fn try_from(variant: UserDataVariant<T>) -> Result<Self> {
let guard = variant.raw_lock().try_lock_exclusive_guarded();
let guard = guard.map_err(|_| Error::UserDataBorrowMutError)?;
let guard = unsafe { mem::transmute::<LockGuard<_>, LockGuard<'static, _>>(guard) };
Ok(UserDataRefMut::from_parts(
UserDataRefMutInner::Default(variant),
guard,
))
}
}
impl<T: 'static> FromLua for UserDataRefMut<T> {
fn from_lua(value: Value, _: &Lua) -> Result<Self> {
try_value_to_userdata::<T>(value)?.borrow_mut()
}
unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result<Self> {
Self::borrow_from_stack(lua, lua.state(), idx)
}
}
impl<T: 'static> UserDataRefMut<T> {
#[inline(always)]
fn from_parts(inner: UserDataRefMutInner<T>, guard: LockGuard<'static, RawLock>) -> Self {
Self { _guard: guard, inner }
}
#[cfg(feature = "userdata-wrappers")]
fn remap<U>(
self,
f: impl FnOnce(UserDataVariant<T>) -> Result<UserDataRefMutInner<U>>,
) -> Result<UserDataRefMut<U>> {
match &self.inner {
UserDataRefMutInner::Default(variant) => {
let inner = f(variant.clone())?;
Ok(UserDataRefMut::from_parts(inner, self._guard))
}
_ => Err(Error::UserDataTypeMismatch),
}
}
pub(crate) unsafe fn borrow_from_stack(
lua: &RawLua,
state: *mut ffi::lua_State,
idx: c_int,
) -> Result<Self> {
let type_id = lua.get_userdata_type_id::<T>(state, idx)?;
match type_id {
Some(type_id) if type_id == TypeId::of::<T>() => {
let ud = get_userdata::<UserDataStorage<T>>(state, idx);
(*ud).try_borrow_owned_mut()
}
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
Some(type_id) if type_id == TypeId::of::<Rc<T>>() => Err(Error::UserDataBorrowMutError),
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
Some(type_id) if type_id == TypeId::of::<Rc<RefCell<T>>>() => {
let ud = get_userdata::<UserDataStorage<Rc<RefCell<T>>>>(state, idx);
((*ud).try_borrow_owned_mut()).and_then(|ud| ud.transform_rc_refcell())
}
#[cfg(feature = "userdata-wrappers")]
Some(type_id) if type_id == TypeId::of::<Arc<T>>() => Err(Error::UserDataBorrowMutError),
#[cfg(feature = "userdata-wrappers")]
Some(type_id) if type_id == TypeId::of::<Arc<MutexPL<T>>>() => {
let ud = get_userdata::<UserDataStorage<Arc<MutexPL<T>>>>(state, idx);
((*ud).try_borrow_owned_mut()).and_then(|ud| ud.transform_arc_mutex_pl())
}
#[cfg(feature = "userdata-wrappers")]
Some(type_id) if type_id == TypeId::of::<Arc<RwLockPL<T>>>() => {
let ud = get_userdata::<UserDataStorage<Arc<RwLockPL<T>>>>(state, idx);
((*ud).try_borrow_owned_mut()).and_then(|ud| ud.transform_arc_rwlock_pl())
}
_ => Err(Error::UserDataTypeMismatch),
}
}
}
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
impl<T> UserDataRefMut<Rc<RefCell<T>>> {
fn transform_rc_refcell(self) -> Result<UserDataRefMut<T>> {
self.remap(|variant| unsafe {
let obj = &*variant.as_ptr();
let refmut = obj.try_borrow_mut().map_err(|_| Error::UserDataBorrowMutError)?;
let borrow = std::mem::transmute::<RefMut<T>, RefMut<'static, T>>(refmut);
Ok(UserDataRefMutInner::RcRefCell(borrow, variant))
})
}
}
#[cfg(feature = "userdata-wrappers")]
impl<T> UserDataRefMut<Arc<MutexPL<T>>> {
fn transform_arc_mutex_pl(self) -> Result<UserDataRefMut<T>> {
self.remap(|variant| unsafe {
let obj = &*variant.as_ptr();
let guard = obj.try_lock().ok_or(Error::UserDataBorrowMutError)?;
let borrow = std::mem::transmute::<MutexGuardPL<T>, MutexGuardPL<'static, T>>(guard);
Ok(UserDataRefMutInner::ArcMutexPL(borrow, variant))
})
}
}
#[cfg(feature = "userdata-wrappers")]
impl<T> UserDataRefMut<Arc<RwLockPL<T>>> {
fn transform_arc_rwlock_pl(self) -> Result<UserDataRefMut<T>> {
self.remap(|variant| unsafe {
let obj = &*variant.as_ptr();
let guard = obj.try_write().ok_or(Error::UserDataBorrowMutError)?;
let borrow = std::mem::transmute::<RwLockWriteGuardPL<T>, RwLockWriteGuardPL<'static, T>>(guard);
Ok(UserDataRefMutInner::ArcRwLockPL(borrow, variant))
})
}
}
#[allow(unused)]
enum UserDataRefMutInner<T: 'static> {
Default(UserDataVariant<T>),
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
RcRefCell(RefMut<'static, T>, UserDataVariant<Rc<RefCell<T>>>),
#[cfg(feature = "userdata-wrappers")]
ArcMutexPL(MutexGuardPL<'static, T>, UserDataVariant<Arc<MutexPL<T>>>),
#[cfg(feature = "userdata-wrappers")]
ArcRwLockPL(RwLockWriteGuardPL<'static, T>, UserDataVariant<Arc<RwLockPL<T>>>),
}
impl<T> Deref for UserDataRefMutInner<T> {
type Target = T;
#[inline]
fn deref(&self) -> &T {
match self {
Self::Default(inner) => unsafe { &*inner.as_ptr() },
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
Self::RcRefCell(x, ..) => x,
#[cfg(feature = "userdata-wrappers")]
Self::ArcMutexPL(x, ..) => x,
#[cfg(feature = "userdata-wrappers")]
Self::ArcRwLockPL(x, ..) => x,
}
}
}
impl<T> DerefMut for UserDataRefMutInner<T> {
#[inline]
fn deref_mut(&mut self) -> &mut T {
match self {
Self::Default(inner) => unsafe { &mut *inner.as_ptr() },
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
Self::RcRefCell(x, ..) => x,
#[cfg(feature = "userdata-wrappers")]
Self::ArcMutexPL(x, ..) => x,
#[cfg(feature = "userdata-wrappers")]
Self::ArcRwLockPL(x, ..) => x,
}
}
}
#[inline]
fn try_value_to_userdata<T>(value: Value) -> Result<AnyUserData> {
match value {
Value::UserData(ud) => Ok(ud),
_ => Err(Error::FromLuaConversionError {
from: value.type_name(),
to: "userdata".to_string(),
message: Some(format!("expected userdata of type {}", short_type_name::<T>())),
}),
}
}
#[cfg(test)]
mod assertions {
use super::*;
#[cfg(feature = "send")]
static_assertions::assert_impl_all!(UserDataRef<()>: Send, Sync);
#[cfg(feature = "send")]
static_assertions::assert_not_impl_all!(UserDataRef<std::rc::Rc<()>>: Send, Sync);
#[cfg(feature = "send")]
static_assertions::assert_impl_all!(UserDataRefMut<()>: Sync, Send);
#[cfg(feature = "send")]
static_assertions::assert_not_impl_all!(UserDataRefMut<std::rc::Rc<()>>: Send, Sync);
#[cfg(not(feature = "send"))]
static_assertions::assert_not_impl_all!(UserDataRef<()>: Send, Sync);
#[cfg(not(feature = "send"))]
static_assertions::assert_not_impl_all!(UserDataRefMut<()>: Send, Sync);
}
+151 -132
View File
@@ -7,11 +7,14 @@ use std::os::raw::c_void;
use std::string::String as StdString;
use crate::error::{Error, Result};
use crate::state::{Lua, RawLua};
use crate::state::{Lua, LuaGuard};
use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti};
use crate::types::{Callback, MaybeSend};
use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataFields, UserDataMethods, UserDataStorage};
use crate::util::{get_userdata, short_type_name};
use crate::userdata::{
borrow_userdata_scoped, borrow_userdata_scoped_mut, AnyUserData, MetaMethod, TypeIdHints, UserData,
UserDataFields, UserDataMethods, UserDataStorage,
};
use crate::util::short_type_name;
use crate::value::Value;
#[cfg(feature = "async")]
@@ -21,21 +24,26 @@ use {
std::future::{self, Future},
};
type StaticFieldCallback = Box<dyn FnOnce(&RawLua) -> Result<()> + 'static>;
#[derive(Clone, Copy)]
pub(crate) enum UserDataTypeId {
Shared(TypeId),
Unique(usize),
enum UserDataType {
Shared(TypeIdHints),
Unique(*mut c_void),
}
/// Handle to registry for userdata methods and metamethods.
pub struct UserDataRegistry<T> {
lua: LuaGuard,
raw: RawUserDataRegistry,
r#type: UserDataType,
_phantom: PhantomData<T>,
}
pub(crate) struct RawUserDataRegistry {
// Fields
pub(crate) fields: Vec<(String, StaticFieldCallback)>,
pub(crate) fields: Vec<(String, Result<Value>)>,
pub(crate) field_getters: Vec<(String, Callback)>,
pub(crate) field_setters: Vec<(String, Callback)>,
pub(crate) meta_fields: Vec<(String, StaticFieldCallback)>,
pub(crate) meta_fields: Vec<(String, Result<Value>)>,
// Methods
pub(crate) methods: Vec<(String, Callback)>,
@@ -45,14 +53,40 @@ pub struct UserDataRegistry<T> {
#[cfg(feature = "async")]
pub(crate) async_meta_methods: Vec<(String, AsyncCallback)>,
pub(crate) type_id: UserDataTypeId,
_type: PhantomData<T>,
pub(crate) destructor: ffi::lua_CFunction,
pub(crate) type_id: Option<TypeId>,
pub(crate) type_name: StdString,
}
impl UserDataType {
#[inline]
pub(crate) fn type_id(&self) -> Option<TypeId> {
match self {
UserDataType::Shared(hints) => Some(hints.type_id()),
UserDataType::Unique(_) => None,
}
}
}
#[cfg(feature = "send")]
unsafe impl Send for UserDataType {}
impl<T: 'static> UserDataRegistry<T> {
#[inline(always)]
pub(crate) fn new(lua: &Lua) -> Self {
Self::with_type(lua, UserDataType::Shared(TypeIdHints::new::<T>()))
}
}
impl<T> UserDataRegistry<T> {
#[inline]
pub(crate) fn new(type_id: TypeId) -> Self {
UserDataRegistry {
#[inline(always)]
pub(crate) fn new_unique(lua: &Lua, ud_ptr: *mut c_void) -> Self {
Self::with_type(lua, UserDataType::Unique(ud_ptr))
}
#[inline(always)]
fn with_type(lua: &Lua, r#type: UserDataType) -> Self {
let raw = RawUserDataRegistry {
fields: Vec::new(),
field_getters: Vec::new(),
field_setters: Vec::new(),
@@ -63,34 +97,16 @@ impl<T> UserDataRegistry<T> {
meta_methods: Vec::new(),
#[cfg(feature = "async")]
async_meta_methods: Vec::new(),
type_id: UserDataTypeId::Shared(type_id),
_type: PhantomData,
}
}
destructor: super::util::destroy_userdata_storage::<T>,
type_id: r#type.type_id(),
type_name: short_type_name::<T>(),
};
#[inline]
pub(crate) fn new_unique(ud_ptr: *const c_void) -> Self {
UserDataRegistry {
fields: Vec::new(),
field_getters: Vec::new(),
field_setters: Vec::new(),
meta_fields: Vec::new(),
methods: Vec::new(),
#[cfg(feature = "async")]
async_methods: Vec::new(),
meta_methods: Vec::new(),
#[cfg(feature = "async")]
async_meta_methods: Vec::new(),
type_id: UserDataTypeId::Unique(ud_ptr as usize),
_type: PhantomData,
}
}
#[inline]
pub(crate) fn type_id(&self) -> Option<TypeId> {
match self.type_id {
UserDataTypeId::Shared(type_id) => Some(type_id),
UserDataTypeId::Unique(_) => None,
lua: lua.lock_arc(),
raw,
r#type,
_phantom: PhantomData,
}
}
@@ -107,7 +123,7 @@ impl<T> UserDataRegistry<T> {
};
}
let target_type_id = self.type_id;
let target_type = self.r#type;
Box::new(move |rawlua, nargs| unsafe {
if nargs == 0 {
let err = Error::from_lua_conversion("missing argument", "userdata", None);
@@ -119,28 +135,23 @@ impl<T> UserDataRegistry<T> {
// Self was at position 1, so we pass 2 here
let args = A::from_stack_args(nargs - 1, 2, Some(&name), rawlua);
match target_type_id {
// This branch is for `'static` userdata that share type metatable
UserDataTypeId::Shared(target_type_id) => {
match try_self_arg!(rawlua.get_userdata_type_id::<T>(self_index)) {
Some(self_type_id) if self_type_id == target_type_id => {
let ud = get_userdata::<UserDataStorage<T>>(state, self_index);
try_self_arg!((*ud).try_borrow_scoped(|ud| {
method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua)
}))
}
_ => Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)),
}
match target_type {
#[rustfmt::skip]
UserDataType::Shared(type_hints) => {
let type_id = try_self_arg!(rawlua.get_userdata_type_id::<T>(state, self_index));
try_self_arg!(borrow_userdata_scoped(state, self_index, type_id, type_hints, |ud| {
method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua)
}))
}
UserDataTypeId::Unique(target_ptr) => {
match get_userdata::<UserDataStorage<T>>(state, self_index) {
ud if ud as usize == target_ptr => {
try_self_arg!((*ud).try_borrow_scoped(|ud| {
method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua)
}))
}
_ => Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)),
}
UserDataType::Unique(target_ptr) if ffi::lua_touserdata(state, self_index) == target_ptr => {
let ud = target_ptr as *mut UserDataStorage<T>;
try_self_arg!((*ud).try_borrow_scoped(|ud| {
method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua)
}))
}
UserDataType::Unique(_) => {
try_self_arg!(rawlua.get_userdata_type_id::<T>(state, self_index));
Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch))
}
}
})
@@ -160,7 +171,7 @@ impl<T> UserDataRegistry<T> {
}
let method = RefCell::new(method);
let target_type_id = self.type_id;
let target_type = self.r#type;
Box::new(move |rawlua, nargs| unsafe {
let mut method = method.try_borrow_mut().map_err(|_| Error::RecursiveMutCallback)?;
if nargs == 0 {
@@ -173,28 +184,23 @@ impl<T> UserDataRegistry<T> {
// Self was at position 1, so we pass 2 here
let args = A::from_stack_args(nargs - 1, 2, Some(&name), rawlua);
match target_type_id {
// This branch is for `'static` userdata that share type metatable
UserDataTypeId::Shared(target_type_id) => {
match try_self_arg!(rawlua.get_userdata_type_id::<T>(self_index)) {
Some(self_type_id) if self_type_id == target_type_id => {
let ud = get_userdata::<UserDataStorage<T>>(state, self_index);
try_self_arg!((*ud).try_borrow_scoped_mut(|ud| {
method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua)
}))
}
_ => Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)),
}
match target_type {
#[rustfmt::skip]
UserDataType::Shared(type_hints) => {
let type_id = try_self_arg!(rawlua.get_userdata_type_id::<T>(state, self_index));
try_self_arg!(borrow_userdata_scoped_mut(state, self_index, type_id, type_hints, |ud| {
method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua)
}))
}
UserDataTypeId::Unique(target_ptr) => {
match get_userdata::<UserDataStorage<T>>(state, self_index) {
ud if ud as usize == target_ptr => {
try_self_arg!((*ud).try_borrow_scoped_mut(|ud| {
method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua)
}))
}
_ => Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)),
}
UserDataType::Unique(target_ptr) if ffi::lua_touserdata(state, self_index) == target_ptr => {
let ud = target_ptr as *mut UserDataStorage<T>;
try_self_arg!((*ud).try_borrow_scoped_mut(|ud| {
method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua)
}))
}
UserDataType::Unique(_) => {
try_self_arg!(rawlua.get_userdata_type_id::<T>(state, self_index));
Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch))
}
}
})
@@ -346,6 +352,11 @@ impl<T> UserDataRegistry<T> {
}
value.into_lua(lua)
}
#[inline(always)]
pub(crate) fn into_raw(self) -> RawUserDataRegistry {
self.raw
}
}
// Returns function name for the type `T`, without the module path
@@ -359,10 +370,7 @@ impl<T> UserDataFields<T> for UserDataRegistry<T> {
V: IntoLua + 'static,
{
let name = name.to_string();
self.fields.push((
name,
Box::new(move |rawlua| unsafe { value.push_into_stack(rawlua) }),
));
self.raw.fields.push((name, value.into_lua(self.lua.lua())));
}
fn add_field_method_get<M, R>(&mut self, name: impl ToString, method: M)
@@ -372,7 +380,7 @@ impl<T> UserDataFields<T> for UserDataRegistry<T> {
{
let name = name.to_string();
let callback = self.box_method(&name, move |lua, data, ()| method(lua, data));
self.field_getters.push((name, callback));
self.raw.field_getters.push((name, callback));
}
fn add_field_method_set<M, A>(&mut self, name: impl ToString, method: M)
@@ -382,7 +390,7 @@ impl<T> UserDataFields<T> for UserDataRegistry<T> {
{
let name = name.to_string();
let callback = self.box_method_mut(&name, method);
self.field_setters.push((name, callback));
self.raw.field_setters.push((name, callback));
}
fn add_field_function_get<F, R>(&mut self, name: impl ToString, function: F)
@@ -392,7 +400,7 @@ impl<T> UserDataFields<T> for UserDataRegistry<T> {
{
let name = name.to_string();
let callback = self.box_function(&name, function);
self.field_getters.push((name, callback));
self.raw.field_getters.push((name, callback));
}
fn add_field_function_set<F, A>(&mut self, name: impl ToString, mut function: F)
@@ -402,20 +410,17 @@ impl<T> UserDataFields<T> for UserDataRegistry<T> {
{
let name = name.to_string();
let callback = self.box_function_mut(&name, move |lua, (data, val)| function(lua, data, val));
self.field_setters.push((name, callback));
self.raw.field_setters.push((name, callback));
}
fn add_meta_field<V>(&mut self, name: impl ToString, value: V)
where
V: IntoLua + 'static,
{
let lua = self.lua.lua();
let name = name.to_string();
self.meta_fields.push((
name.clone(),
Box::new(move |rawlua| unsafe {
Self::check_meta_field(rawlua.lua(), &name, value)?.push_into_stack(rawlua)
}),
));
let field = Self::check_meta_field(lua, &name, value).and_then(|v| v.into_lua(lua));
self.raw.meta_fields.push((name, field));
}
fn add_meta_field_with<F, R>(&mut self, name: impl ToString, f: F)
@@ -423,14 +428,10 @@ impl<T> UserDataFields<T> for UserDataRegistry<T> {
F: FnOnce(&Lua) -> Result<R> + 'static,
R: IntoLua,
{
let lua = self.lua.lua();
let name = name.to_string();
self.meta_fields.push((
name.clone(),
Box::new(move |rawlua| unsafe {
let lua = rawlua.lua();
Self::check_meta_field(lua, &name, f(lua)?)?.push_into_stack(rawlua)
}),
));
let field = f(lua).and_then(|v| Self::check_meta_field(lua, &name, v).and_then(|v| v.into_lua(lua)));
self.raw.meta_fields.push((name, field));
}
}
@@ -443,7 +444,7 @@ impl<T> UserDataMethods<T> for UserDataRegistry<T> {
{
let name = name.to_string();
let callback = self.box_method(&name, method);
self.methods.push((name, callback));
self.raw.methods.push((name, callback));
}
fn add_method_mut<M, A, R>(&mut self, name: impl ToString, method: M)
@@ -454,7 +455,7 @@ impl<T> UserDataMethods<T> for UserDataRegistry<T> {
{
let name = name.to_string();
let callback = self.box_method_mut(&name, method);
self.methods.push((name, callback));
self.raw.methods.push((name, callback));
}
#[cfg(feature = "async")]
@@ -468,7 +469,7 @@ impl<T> UserDataMethods<T> for UserDataRegistry<T> {
{
let name = name.to_string();
let callback = self.box_async_method(&name, method);
self.async_methods.push((name, callback));
self.raw.async_methods.push((name, callback));
}
#[cfg(feature = "async")]
@@ -482,7 +483,7 @@ impl<T> UserDataMethods<T> for UserDataRegistry<T> {
{
let name = name.to_string();
let callback = self.box_async_method_mut(&name, method);
self.async_methods.push((name, callback));
self.raw.async_methods.push((name, callback));
}
fn add_function<F, A, R>(&mut self, name: impl ToString, function: F)
@@ -493,7 +494,7 @@ impl<T> UserDataMethods<T> for UserDataRegistry<T> {
{
let name = name.to_string();
let callback = self.box_function(&name, function);
self.methods.push((name, callback));
self.raw.methods.push((name, callback));
}
fn add_function_mut<F, A, R>(&mut self, name: impl ToString, function: F)
@@ -504,7 +505,7 @@ impl<T> UserDataMethods<T> for UserDataRegistry<T> {
{
let name = name.to_string();
let callback = self.box_function_mut(&name, function);
self.methods.push((name, callback));
self.raw.methods.push((name, callback));
}
#[cfg(feature = "async")]
@@ -517,7 +518,7 @@ impl<T> UserDataMethods<T> for UserDataRegistry<T> {
{
let name = name.to_string();
let callback = self.box_async_function(&name, function);
self.async_methods.push((name, callback));
self.raw.async_methods.push((name, callback));
}
fn add_meta_method<M, A, R>(&mut self, name: impl ToString, method: M)
@@ -528,7 +529,7 @@ impl<T> UserDataMethods<T> for UserDataRegistry<T> {
{
let name = name.to_string();
let callback = self.box_method(&name, method);
self.meta_methods.push((name, callback));
self.raw.meta_methods.push((name, callback));
}
fn add_meta_method_mut<M, A, R>(&mut self, name: impl ToString, method: M)
@@ -539,7 +540,7 @@ impl<T> UserDataMethods<T> for UserDataRegistry<T> {
{
let name = name.to_string();
let callback = self.box_method_mut(&name, method);
self.meta_methods.push((name, callback));
self.raw.meta_methods.push((name, callback));
}
#[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))]
@@ -553,7 +554,7 @@ impl<T> UserDataMethods<T> for UserDataRegistry<T> {
{
let name = name.to_string();
let callback = self.box_async_method(&name, method);
self.async_meta_methods.push((name, callback));
self.raw.async_meta_methods.push((name, callback));
}
#[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))]
@@ -567,7 +568,7 @@ impl<T> UserDataMethods<T> for UserDataRegistry<T> {
{
let name = name.to_string();
let callback = self.box_async_method_mut(&name, method);
self.async_meta_methods.push((name, callback));
self.raw.async_meta_methods.push((name, callback));
}
fn add_meta_function<F, A, R>(&mut self, name: impl ToString, function: F)
@@ -578,7 +579,7 @@ impl<T> UserDataMethods<T> for UserDataRegistry<T> {
{
let name = name.to_string();
let callback = self.box_function(&name, function);
self.meta_methods.push((name, callback));
self.raw.meta_methods.push((name, callback));
}
fn add_meta_function_mut<F, A, R>(&mut self, name: impl ToString, function: F)
@@ -589,7 +590,7 @@ impl<T> UserDataMethods<T> for UserDataRegistry<T> {
{
let name = name.to_string();
let callback = self.box_function_mut(&name, function);
self.meta_methods.push((name, callback));
self.raw.meta_methods.push((name, callback));
}
#[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))]
@@ -602,7 +603,7 @@ impl<T> UserDataMethods<T> for UserDataRegistry<T> {
{
let name = name.to_string();
let callback = self.box_async_function(&name, function);
self.async_meta_methods.push((name, callback));
self.raw.async_meta_methods.push((name, callback));
}
}
@@ -610,23 +611,20 @@ macro_rules! lua_userdata_impl {
($type:ty) => {
impl<T: UserData + 'static> UserData for $type {
fn register(registry: &mut UserDataRegistry<Self>) {
let type_id = TypeId::of::<T>();
let mut orig_registry = UserDataRegistry::new(type_id);
let mut orig_registry = UserDataRegistry::new(registry.lua.lua());
T::register(&mut orig_registry);
// Copy all fields, methods, etc. from the original registry
registry.fields.extend(orig_registry.fields);
registry.field_getters.extend(orig_registry.field_getters);
registry.field_setters.extend(orig_registry.field_setters);
registry.meta_fields.extend(orig_registry.meta_fields);
registry.methods.extend(orig_registry.methods);
(registry.raw.fields).extend(orig_registry.raw.fields);
(registry.raw.field_getters).extend(orig_registry.raw.field_getters);
(registry.raw.field_setters).extend(orig_registry.raw.field_setters);
(registry.raw.meta_fields).extend(orig_registry.raw.meta_fields);
(registry.raw.methods).extend(orig_registry.raw.methods);
#[cfg(feature = "async")]
registry.async_methods.extend(orig_registry.async_methods);
registry.meta_methods.extend(orig_registry.meta_methods);
(registry.raw.async_methods).extend(orig_registry.raw.async_methods);
(registry.raw.meta_methods).extend(orig_registry.raw.meta_methods);
#[cfg(feature = "async")]
registry
.async_meta_methods
.extend(orig_registry.async_meta_methods);
(registry.raw.async_meta_methods).extend(orig_registry.raw.async_meta_methods);
}
}
};
@@ -636,3 +634,24 @@ macro_rules! lua_userdata_impl {
pub(crate) struct UserDataProxy<T>(pub(crate) PhantomData<T>);
lua_userdata_impl!(UserDataProxy<T>);
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
lua_userdata_impl!(std::rc::Rc<T>);
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
lua_userdata_impl!(std::rc::Rc<std::cell::RefCell<T>>);
#[cfg(feature = "userdata-wrappers")]
lua_userdata_impl!(std::sync::Arc<T>);
#[cfg(feature = "userdata-wrappers")]
lua_userdata_impl!(std::sync::Arc<std::sync::Mutex<T>>);
#[cfg(feature = "userdata-wrappers")]
lua_userdata_impl!(std::sync::Arc<std::sync::RwLock<T>>);
#[cfg(feature = "userdata-wrappers")]
lua_userdata_impl!(std::sync::Arc<parking_lot::Mutex<T>>);
#[cfg(feature = "userdata-wrappers")]
lua_userdata_impl!(std::sync::Arc<parking_lot::RwLock<T>>);
#[cfg(test)]
mod assertions {
#[cfg(feature = "send")]
static_assertions::assert_impl_all!(super::RawUserDataRegistry: Send);
}
+459
View File
@@ -0,0 +1,459 @@
use std::any::TypeId;
use std::cell::Cell;
use std::marker::PhantomData;
use std::os::raw::c_int;
use std::ptr;
use super::UserDataStorage;
use crate::error::{Error, Result};
use crate::util::{get_userdata, rawget_field, rawset_field, take_userdata};
// This is a trick to check if a type is `Sync` or not.
// It uses leaked specialization feature from stdlib.
struct IsSync<'a, T> {
is_sync: &'a Cell<bool>,
_marker: PhantomData<T>,
}
impl<T> Clone for IsSync<'_, T> {
fn clone(&self) -> Self {
self.is_sync.set(false);
IsSync {
is_sync: self.is_sync,
_marker: PhantomData,
}
}
}
impl<T: Sync> Copy for IsSync<'_, T> {}
pub(crate) fn is_sync<T>() -> bool {
let is_sync = Cell::new(true);
let _ = [IsSync::<T> {
is_sync: &is_sync,
_marker: PhantomData,
}]
.clone();
is_sync.get()
}
// Userdata type hints, used to match types of wrapped userdata
#[derive(Clone, Copy)]
pub(crate) struct TypeIdHints {
t: TypeId,
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
rc: TypeId,
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
rc_refcell: TypeId,
#[cfg(feature = "userdata-wrappers")]
arc: TypeId,
#[cfg(feature = "userdata-wrappers")]
arc_mutex: TypeId,
#[cfg(feature = "userdata-wrappers")]
arc_rwlock: TypeId,
#[cfg(feature = "userdata-wrappers")]
arc_pl_mutex: TypeId,
#[cfg(feature = "userdata-wrappers")]
arc_pl_rwlock: TypeId,
}
impl TypeIdHints {
pub(crate) fn new<T: 'static>() -> Self {
Self {
t: TypeId::of::<T>(),
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
rc: TypeId::of::<std::rc::Rc<T>>(),
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
rc_refcell: TypeId::of::<std::rc::Rc<std::cell::RefCell<T>>>(),
#[cfg(feature = "userdata-wrappers")]
arc: TypeId::of::<std::sync::Arc<T>>(),
#[cfg(feature = "userdata-wrappers")]
arc_mutex: TypeId::of::<std::sync::Arc<std::sync::Mutex<T>>>(),
#[cfg(feature = "userdata-wrappers")]
arc_rwlock: TypeId::of::<std::sync::Arc<std::sync::RwLock<T>>>(),
#[cfg(feature = "userdata-wrappers")]
arc_pl_mutex: TypeId::of::<std::sync::Arc<parking_lot::Mutex<T>>>(),
#[cfg(feature = "userdata-wrappers")]
arc_pl_rwlock: TypeId::of::<std::sync::Arc<parking_lot::RwLock<T>>>(),
}
}
#[inline(always)]
pub(crate) fn type_id(&self) -> TypeId {
self.t
}
}
pub(crate) unsafe fn borrow_userdata_scoped<T, R>(
state: *mut ffi::lua_State,
idx: c_int,
type_id: Option<TypeId>,
type_hints: TypeIdHints,
f: impl FnOnce(&T) -> R,
) -> Result<R> {
match type_id {
Some(type_id) if type_id == type_hints.t => {
let ud = get_userdata::<UserDataStorage<T>>(state, idx);
(*ud).try_borrow_scoped(|ud| f(ud))
}
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
Some(type_id) if type_id == type_hints.rc => {
let ud = get_userdata::<UserDataStorage<std::rc::Rc<T>>>(state, idx);
(*ud).try_borrow_scoped(|ud| f(ud))
}
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
Some(type_id) if type_id == type_hints.rc_refcell => {
let ud = get_userdata::<UserDataStorage<std::rc::Rc<std::cell::RefCell<T>>>>(state, idx);
(*ud).try_borrow_scoped(|ud| {
let ud = ud.try_borrow().map_err(|_| Error::UserDataBorrowError)?;
Ok(f(&ud))
})?
}
#[cfg(feature = "userdata-wrappers")]
Some(type_id) if type_id == type_hints.arc => {
let ud = get_userdata::<UserDataStorage<std::sync::Arc<T>>>(state, idx);
(*ud).try_borrow_scoped(|ud| f(ud))
}
#[cfg(feature = "userdata-wrappers")]
Some(type_id) if type_id == type_hints.arc_mutex => {
let ud = get_userdata::<UserDataStorage<std::sync::Arc<std::sync::Mutex<T>>>>(state, idx);
(*ud).try_borrow_scoped(|ud| {
let ud = ud.try_lock().map_err(|_| Error::UserDataBorrowError)?;
Ok(f(&ud))
})?
}
#[cfg(feature = "userdata-wrappers")]
Some(type_id) if type_id == type_hints.arc_rwlock => {
let ud = get_userdata::<UserDataStorage<std::sync::Arc<std::sync::RwLock<T>>>>(state, idx);
(*ud).try_borrow_scoped(|ud| {
let ud = ud.try_read().map_err(|_| Error::UserDataBorrowError)?;
Ok(f(&ud))
})?
}
#[cfg(feature = "userdata-wrappers")]
Some(type_id) if type_id == type_hints.arc_pl_mutex => {
let ud = get_userdata::<UserDataStorage<std::sync::Arc<parking_lot::Mutex<T>>>>(state, idx);
(*ud).try_borrow_scoped(|ud| {
let ud = ud.try_lock().ok_or(Error::UserDataBorrowError)?;
Ok(f(&ud))
})?
}
#[cfg(feature = "userdata-wrappers")]
Some(type_id) if type_id == type_hints.arc_pl_rwlock => {
let ud = get_userdata::<UserDataStorage<std::sync::Arc<parking_lot::RwLock<T>>>>(state, idx);
(*ud).try_borrow_scoped(|ud| {
let ud = ud.try_read().ok_or(Error::UserDataBorrowError)?;
Ok(f(&ud))
})?
}
_ => Err(Error::UserDataTypeMismatch),
}
}
pub(crate) unsafe fn borrow_userdata_scoped_mut<T, R>(
state: *mut ffi::lua_State,
idx: c_int,
type_id: Option<TypeId>,
type_hints: TypeIdHints,
f: impl FnOnce(&mut T) -> R,
) -> Result<R> {
match type_id {
Some(type_id) if type_id == type_hints.t => {
let ud = get_userdata::<UserDataStorage<T>>(state, idx);
(*ud).try_borrow_scoped_mut(|ud| f(ud))
}
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
Some(type_id) if type_id == type_hints.rc => {
let ud = get_userdata::<UserDataStorage<std::rc::Rc<T>>>(state, idx);
(*ud).try_borrow_scoped_mut(|ud| match std::rc::Rc::get_mut(ud) {
Some(ud) => Ok(f(ud)),
None => Err(Error::UserDataBorrowMutError),
})?
}
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
Some(type_id) if type_id == type_hints.rc_refcell => {
let ud = get_userdata::<UserDataStorage<std::rc::Rc<std::cell::RefCell<T>>>>(state, idx);
(*ud).try_borrow_scoped(|ud| {
let mut ud = ud.try_borrow_mut().map_err(|_| Error::UserDataBorrowMutError)?;
Ok(f(&mut ud))
})?
}
#[cfg(feature = "userdata-wrappers")]
Some(type_id) if type_id == type_hints.arc => {
let ud = get_userdata::<UserDataStorage<std::sync::Arc<T>>>(state, idx);
(*ud).try_borrow_scoped_mut(|ud| match std::sync::Arc::get_mut(ud) {
Some(ud) => Ok(f(ud)),
None => Err(Error::UserDataBorrowMutError),
})?
}
#[cfg(feature = "userdata-wrappers")]
Some(type_id) if type_id == type_hints.arc_mutex => {
let ud = get_userdata::<UserDataStorage<std::sync::Arc<std::sync::Mutex<T>>>>(state, idx);
(*ud).try_borrow_scoped_mut(|ud| {
let mut ud = ud.try_lock().map_err(|_| Error::UserDataBorrowMutError)?;
Ok(f(&mut ud))
})?
}
#[cfg(feature = "userdata-wrappers")]
Some(type_id) if type_id == type_hints.arc_rwlock => {
let ud = get_userdata::<UserDataStorage<std::sync::Arc<std::sync::RwLock<T>>>>(state, idx);
(*ud).try_borrow_scoped_mut(|ud| {
let mut ud = ud.try_write().map_err(|_| Error::UserDataBorrowMutError)?;
Ok(f(&mut ud))
})?
}
#[cfg(feature = "userdata-wrappers")]
Some(type_id) if type_id == type_hints.arc_pl_mutex => {
let ud = get_userdata::<UserDataStorage<std::sync::Arc<parking_lot::Mutex<T>>>>(state, idx);
(*ud).try_borrow_scoped_mut(|ud| {
let mut ud = ud.try_lock().ok_or(Error::UserDataBorrowMutError)?;
Ok(f(&mut ud))
})?
}
#[cfg(feature = "userdata-wrappers")]
Some(type_id) if type_id == type_hints.arc_pl_rwlock => {
let ud = get_userdata::<UserDataStorage<std::sync::Arc<parking_lot::RwLock<T>>>>(state, idx);
(*ud).try_borrow_scoped_mut(|ud| {
let mut ud = ud.try_write().ok_or(Error::UserDataBorrowMutError)?;
Ok(f(&mut ud))
})?
}
_ => Err(Error::UserDataTypeMismatch),
}
}
// Populates the given table with the appropriate members to be a userdata metatable for the given
// type. This function takes the given table at the `metatable` index, and adds an appropriate
// `__gc` member to it for the given type and a `__metatable` entry to protect the table from script
// access. The function also, if given a `field_getters` or `methods` tables, will create an
// `__index` metamethod (capturing previous one) to lookup in `field_getters` first, then `methods`
// and falling back to the captured `__index` if no matches found.
// The same is also applicable for `__newindex` metamethod and `field_setters` table.
// Internally uses 9 stack spaces and does not call checkstack.
pub(crate) unsafe fn init_userdata_metatable(
state: *mut ffi::lua_State,
metatable: c_int,
field_getters: Option<c_int>,
field_setters: Option<c_int>,
methods: Option<c_int>,
) -> Result<()> {
if field_getters.is_some() || methods.is_some() {
// Push `__index` generator function
init_userdata_metatable_index(state)?;
let index_type = rawget_field(state, metatable, "__index")?;
match index_type {
ffi::LUA_TNIL | ffi::LUA_TTABLE | ffi::LUA_TFUNCTION => {
for &idx in &[field_getters, methods] {
if let Some(idx) = idx {
ffi::lua_pushvalue(state, idx);
} else {
ffi::lua_pushnil(state);
}
}
// Generate `__index`
protect_lua!(state, 4, 1, fn(state) ffi::lua_call(state, 3, 1))?;
}
_ => mlua_panic!("improper `__index` type: {}", index_type),
}
rawset_field(state, metatable, "__index")?;
}
if let Some(field_setters) = field_setters {
// Push `__newindex` generator function
init_userdata_metatable_newindex(state)?;
let newindex_type = rawget_field(state, metatable, "__newindex")?;
match newindex_type {
ffi::LUA_TNIL | ffi::LUA_TTABLE | ffi::LUA_TFUNCTION => {
ffi::lua_pushvalue(state, field_setters);
// Generate `__newindex`
protect_lua!(state, 3, 1, fn(state) ffi::lua_call(state, 2, 1))?;
}
_ => mlua_panic!("improper `__newindex` type: {}", newindex_type),
}
rawset_field(state, metatable, "__newindex")?;
}
ffi::lua_pushboolean(state, 0);
rawset_field(state, metatable, "__metatable")?;
Ok(())
}
unsafe extern "C-unwind" fn lua_error_impl(state: *mut ffi::lua_State) -> c_int {
ffi::lua_error(state);
}
unsafe extern "C-unwind" fn lua_isfunction_impl(state: *mut ffi::lua_State) -> c_int {
ffi::lua_pushboolean(state, ffi::lua_isfunction(state, -1));
1
}
unsafe extern "C-unwind" fn lua_istable_impl(state: *mut ffi::lua_State) -> c_int {
ffi::lua_pushboolean(state, ffi::lua_istable(state, -1));
1
}
unsafe fn init_userdata_metatable_index(state: *mut ffi::lua_State) -> Result<()> {
let index_key = &USERDATA_METATABLE_INDEX as *const u8 as *const _;
if ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, index_key) == ffi::LUA_TFUNCTION {
return Ok(());
}
ffi::lua_pop(state, 1);
// Create and cache `__index` generator
let code = cr#"
local error, isfunction, istable = ...
return function (__index, field_getters, 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
return function (self, key)
if field_getters ~= nil then
local field_getter = field_getters[key]
if field_getter ~= nil then
return field_getter(self)
end
end
if methods ~= nil then
local method = methods[key]
if method ~= nil then
return method
end
end
if isfunction(__index) then
return __index(self, key)
elseif __index == nil then
error("attempt to get an unknown field '"..key.."'")
else
return __index[key]
end
end
end
"#;
protect_lua!(state, 0, 1, |state| {
let ret = ffi::luaL_loadbuffer(state, code.as_ptr(), code.count_bytes(), cstr!("=__mlua_index"));
if ret != ffi::LUA_OK {
ffi::lua_error(state);
}
ffi::lua_pushcfunction(state, lua_error_impl);
ffi::lua_pushcfunction(state, lua_isfunction_impl);
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);
ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, index_key);
})
}
unsafe fn init_userdata_metatable_newindex(state: *mut ffi::lua_State) -> Result<()> {
let newindex_key = &USERDATA_METATABLE_NEWINDEX as *const u8 as *const _;
if ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, newindex_key) == ffi::LUA_TFUNCTION {
return Ok(());
}
ffi::lua_pop(state, 1);
// Create and cache `__newindex` generator
let code = cr#"
local error, isfunction = ...
return function (__newindex, field_setters)
return function (self, key, value)
if field_setters ~= nil then
local field_setter = field_setters[key]
if field_setter ~= nil then
field_setter(self, value)
return
end
end
if isfunction(__newindex) then
__newindex(self, key, value)
elseif __newindex == nil then
error("attempt to set an unknown field '"..key.."'")
else
__newindex[key] = value
end
end
end
"#;
protect_lua!(state, 0, 1, |state| {
let code_len = code.count_bytes();
let ret = ffi::luaL_loadbuffer(state, code.as_ptr(), code_len, cstr!("=__mlua_newindex"));
if ret != ffi::LUA_OK {
ffi::lua_error(state);
}
ffi::lua_pushcfunction(state, lua_error_impl);
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);
})
}
// This method is called by Lua GC when it's time to collect the userdata.
//
// This method is usually used to collect internal userdata.
#[cfg(not(feature = "luau"))]
pub(crate) unsafe extern "C-unwind" fn collect_userdata<T>(state: *mut ffi::lua_State) -> c_int {
let ud = get_userdata::<T>(state, -1);
ptr::drop_in_place(ud);
0
}
// This method is called by Luau GC when it's time to collect the userdata.
#[cfg(feature = "luau")]
pub(crate) unsafe extern "C-unwind" fn collect_userdata<T>(ud: *mut std::os::raw::c_void) {
ptr::drop_in_place(ud as *mut T);
}
// This method can be called by user or Lua GC to destroy the userdata.
// It checks if the userdata is safe to destroy and sets the "destroyed" metatable
// to prevent further GC collection.
pub(super) unsafe extern "C-unwind" fn destroy_userdata_storage<T>(state: *mut ffi::lua_State) -> c_int {
let ud = get_userdata::<UserDataStorage<T>>(state, -1);
if (*ud).is_safe_to_destroy() {
take_userdata::<UserDataStorage<T>>(state);
ffi::lua_pushboolean(state, 1);
} else {
ffi::lua_pushboolean(state, 0);
}
1
}
static USERDATA_METATABLE_INDEX: u8 = 0;
static USERDATA_METATABLE_NEWINDEX: u8 = 0;
+5 -15
View File
@@ -9,9 +9,8 @@ use std::sync::Arc;
use crate::error::{Error, Result};
use crate::memory::MemoryState;
use crate::util::{
check_stack, get_internal_metatable, get_internal_userdata, init_internal_metatable,
push_internal_userdata, push_string, push_table, rawset_field, to_string, TypeKey,
DESTRUCTED_USERDATA_METATABLE,
check_stack, get_internal_userdata, init_internal_metatable, push_internal_userdata, push_string,
push_table, rawset_field, to_string, TypeKey, DESTRUCTED_USERDATA_METATABLE,
};
static WRAPPED_FAILURE_TYPE_KEY: u8 = 0;
@@ -31,12 +30,8 @@ impl TypeKey for WrappedFailure {
impl WrappedFailure {
pub(crate) unsafe fn new_userdata(state: *mut ffi::lua_State) -> *mut Self {
#[cfg(feature = "luau")]
let ud = ffi::lua_newuserdata_t::<Self>(state);
#[cfg(not(feature = "luau"))]
let ud = ffi::lua_newuserdata(state, std::mem::size_of::<Self>()) as *mut Self;
ptr::write(ud, WrappedFailure::None);
ud
// Unprotected calls always return `Ok`
push_internal_userdata(state, WrappedFailure::None, false).unwrap()
}
}
@@ -90,16 +85,11 @@ where
let cause = Arc::new(err);
let wrapped_error = WrappedFailure::Error(Error::CallbackError { traceback, cause });
ptr::write(ud, wrapped_error);
get_internal_metatable::<WrappedFailure>(state);
ffi::lua_setmetatable(state, -2);
ffi::lua_error(state)
}
Err(p) => {
ffi::lua_settop(state, 1);
ptr::write(ud, WrappedFailure::Panic(Some(p)));
get_internal_metatable::<WrappedFailure>(state);
ffi::lua_setmetatable(state, -2);
ffi::lua_error(state)
}
}
@@ -262,7 +252,7 @@ where
pub(crate) unsafe extern "C-unwind" 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
// See https://github.com/luau-lang/luau/issues/880
#[cfg(feature = "luau")]
if MemoryState::limit_reached(state) {
return 0;
+31 -6
View File
@@ -1,6 +1,6 @@
use std::borrow::Cow;
use std::ffi::CStr;
use std::os::raw::{c_char, c_int};
use std::os::raw::{c_char, c_int, c_void};
use std::{ptr, slice, str};
use crate::error::{Error, Result};
@@ -13,16 +13,12 @@ pub(crate) use short_names::short_type_name;
pub(crate) use types::TypeKey;
pub(crate) use userdata::{
get_destructed_userdata_metatable, get_internal_metatable, get_internal_userdata, get_userdata,
init_internal_metatable, init_userdata_metatable, push_internal_userdata, take_userdata,
init_internal_metatable, push_internal_userdata, push_userdata, take_userdata,
DESTRUCTED_USERDATA_METATABLE,
};
#[cfg(not(feature = "luau"))]
pub(crate) use userdata::push_uninit_userdata;
pub(crate) use userdata::push_userdata;
#[cfg(not(feature = "luau"))]
pub(crate) use userdata::userdata_destructor;
// Checks that Lua has enough free stack space for future stack operations. On failure, this will
// panic with an internal error message.
@@ -66,6 +62,11 @@ impl StackGuard {
pub(crate) fn with_top(state: *mut ffi::lua_State, top: c_int) -> StackGuard {
StackGuard { state, top }
}
#[inline]
pub(crate) fn keep(&mut self, n: c_int) {
self.top += n;
}
}
impl Drop for StackGuard {
@@ -129,6 +130,15 @@ pub(crate) unsafe fn push_table(
}
}
// Uses 4 stack spaces, does not call checkstack.
pub(crate) unsafe fn rawget_field(state: *mut ffi::lua_State, table: c_int, field: &str) -> Result<c_int> {
ffi::lua_pushvalue(state, table);
protect_lua!(state, 1, 1, |state| {
ffi::lua_pushlstring(state, field.as_ptr() as *const c_char, field.len());
ffi::lua_rawget(state, -2)
})
}
// Uses 4 stack spaces, does not call checkstack.
pub(crate) unsafe fn rawset_field(state: *mut ffi::lua_State, table: c_int, field: &str) -> Result<()> {
ffi::lua_pushvalue(state, table);
@@ -282,6 +292,21 @@ pub(crate) unsafe fn to_string(state: *mut ffi::lua_State, index: c_int) -> Stri
}
}
#[inline(always)]
pub(crate) unsafe fn get_metatable_ptr(state: *mut ffi::lua_State, index: c_int) -> *const c_void {
#[cfg(feature = "luau")]
return ffi::lua_getmetatablepointer(state, index);
#[cfg(not(feature = "luau"))]
if ffi::lua_getmetatable(state, index) == 0 {
ptr::null()
} else {
let p = ffi::lua_topointer(state, -1);
ffi::lua_pop(state, 1);
p
}
}
pub(crate) unsafe fn ptr_to_str<'a>(input: *const c_char) -> Option<&'a str> {
if input.is_null() {
return None;
+48 -249
View File
@@ -1,9 +1,9 @@
use std::ffi::CStr;
use std::os::raw::{c_int, c_void};
use std::{ptr, str};
use std::{mem, ptr};
use crate::error::Result;
use crate::util::{check_stack, push_string, push_table, rawset_field, TypeKey};
use crate::userdata::collect_userdata;
use crate::util::{check_stack, get_metatable_ptr, push_table, rawset_field, TypeKey};
// Pushes the userdata and attaches a metatable with __gc method.
// Internally uses 3 stack spaces, does not call checkstack.
@@ -11,11 +11,27 @@ pub(crate) unsafe fn push_internal_userdata<T: TypeKey>(
state: *mut ffi::lua_State,
t: T,
protect: bool,
) -> Result<()> {
push_userdata(state, t, protect)?;
) -> Result<*mut T> {
#[cfg(not(feature = "luau"))]
let ud_ptr = if protect {
protect_lua!(state, 0, 1, move |state| {
ffi::lua_newuserdata(state, const { mem::size_of::<T>() }) as *mut T
})?
} else {
ffi::lua_newuserdata(state, const { mem::size_of::<T>() }) as *mut T
};
#[cfg(feature = "luau")]
let ud_ptr = if protect {
protect_lua!(state, 0, 1, move |state| ffi::lua_newuserdata_t::<T>(state))?
} else {
ffi::lua_newuserdata_t::<T>(state)
};
ptr::write(ud_ptr, t);
get_internal_metatable::<T>(state);
ffi::lua_setmetatable(state, -2);
Ok(())
Ok(ud_ptr)
}
#[track_caller]
@@ -36,7 +52,7 @@ pub(crate) unsafe fn init_internal_metatable<T: TypeKey>(
#[cfg(not(feature = "luau"))]
{
ffi::lua_pushcfunction(state, userdata_destructor::<T>);
ffi::lua_pushcfunction(state, collect_userdata::<T>);
rawset_field(state, -2, "__gc")?;
}
@@ -58,25 +74,20 @@ pub(crate) unsafe fn init_internal_metatable<T: TypeKey>(
pub(crate) unsafe fn get_internal_userdata<T: TypeKey>(
state: *mut ffi::lua_State,
index: c_int,
type_mt_ptr: *const c_void,
mut type_mt_ptr: *const c_void,
) -> *mut T {
let ud = ffi::lua_touserdata(state, index) as *mut T;
if ud.is_null() || ffi::lua_getmetatable(state, index) == 0 {
if ud.is_null() {
return ptr::null_mut();
}
if !type_mt_ptr.is_null() {
let ud_mt_ptr = ffi::lua_topointer(state, -1);
ffi::lua_pop(state, 1);
if ud_mt_ptr != type_mt_ptr {
return ptr::null_mut();
}
} else {
let mt_ptr = get_metatable_ptr(state, index);
if type_mt_ptr.is_null() {
get_internal_metatable::<T>(state);
let res = ffi::lua_rawequal(state, -1, -2);
ffi::lua_pop(state, 2);
if res == 0 {
return ptr::null_mut();
}
type_mt_ptr = ffi::lua_topointer(state, -1);
ffi::lua_pop(state, 1);
}
if mt_ptr != type_mt_ptr {
return ptr::null_mut();
}
ud
}
@@ -87,24 +98,34 @@ pub(crate) unsafe fn get_internal_userdata<T: TypeKey>(
pub(crate) unsafe fn push_uninit_userdata<T>(state: *mut ffi::lua_State, protect: bool) -> Result<*mut T> {
if protect {
protect_lua!(state, 0, 1, |state| {
ffi::lua_newuserdata(state, std::mem::size_of::<T>()) as *mut T
ffi::lua_newuserdata(state, const { mem::size_of::<T>() }) as *mut T
})
} else {
Ok(ffi::lua_newuserdata(state, std::mem::size_of::<T>()) as *mut T)
Ok(ffi::lua_newuserdata(state, const { mem::size_of::<T>() }) as *mut T)
}
}
// Internally uses 3 stack spaces, does not call checkstack.
#[inline]
pub(crate) unsafe fn push_userdata<T>(state: *mut ffi::lua_State, t: T, protect: bool) -> Result<*mut T> {
let size = const { mem::size_of::<T>() };
#[cfg(not(feature = "luau"))]
let ud_ptr = push_uninit_userdata(state, protect)?;
let ud_ptr = if protect {
protect_lua!(state, 0, 1, move |state| ffi::lua_newuserdata(state, size))?
} else {
ffi::lua_newuserdata(state, size)
} as *mut T;
#[cfg(feature = "luau")]
let ud_ptr = if protect {
protect_lua!(state, 0, 1, |state| { ffi::lua_newuserdata_t::<T>(state) })?
protect_lua!(state, 0, 1, |state| {
ffi::lua_newuserdatadtor(state, size, collect_userdata::<T>)
})?
} else {
ffi::lua_newuserdata_t::<T>(state)
};
ffi::lua_newuserdatadtor(state, size, collect_userdata::<T>)
} as *mut T;
ptr::write(ud_ptr, t);
Ok(ud_ptr)
}
@@ -143,226 +164,4 @@ pub(crate) unsafe fn get_destructed_userdata_metatable(state: *mut ffi::lua_Stat
ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, key);
}
// Populates the given table with the appropriate members to be a userdata metatable for the given
// type. This function takes the given table at the `metatable` index, and adds an appropriate
// `__gc` member to it for the given type and a `__metatable` entry to protect the table from script
// access. The function also, if given a `field_getters` or `methods` tables, will create an
// `__index` metamethod (capturing previous one) to lookup in `field_getters` first, then `methods`
// and falling back to the captured `__index` if no matches found.
// The same is also applicable for `__newindex` metamethod and `field_setters` table.
// Internally uses 9 stack spaces and does not call checkstack.
pub(crate) unsafe fn init_userdata_metatable(
state: *mut ffi::lua_State,
metatable: c_int,
field_getters: Option<c_int>,
field_setters: Option<c_int>,
methods: Option<c_int>,
extra_init: Option<fn(*mut ffi::lua_State) -> Result<()>>,
) -> Result<()> {
ffi::lua_pushvalue(state, metatable);
if field_getters.is_some() || methods.is_some() {
// Push `__index` generator function
init_userdata_metatable_index(state)?;
push_string(state, b"__index", true)?;
let index_type = ffi::lua_rawget(state, -3);
match index_type {
ffi::LUA_TNIL | ffi::LUA_TTABLE | ffi::LUA_TFUNCTION => {
for &idx in &[field_getters, methods] {
if let Some(idx) = idx {
ffi::lua_pushvalue(state, idx);
} else {
ffi::lua_pushnil(state);
}
}
// Generate `__index`
protect_lua!(state, 4, 1, fn(state) ffi::lua_call(state, 3, 1))?;
}
_ => mlua_panic!("improper __index type {}", index_type),
}
rawset_field(state, -2, "__index")?;
}
if let Some(field_setters) = field_setters {
// Push `__newindex` generator function
init_userdata_metatable_newindex(state)?;
push_string(state, b"__newindex", true)?;
let newindex_type = ffi::lua_rawget(state, -3);
match newindex_type {
ffi::LUA_TNIL | ffi::LUA_TTABLE | ffi::LUA_TFUNCTION => {
ffi::lua_pushvalue(state, field_setters);
// Generate `__newindex`
protect_lua!(state, 3, 1, fn(state) ffi::lua_call(state, 2, 1))?;
}
_ => mlua_panic!("improper __newindex type {}", newindex_type),
}
rawset_field(state, -2, "__newindex")?;
}
// Additional initialization
if let Some(extra_init) = extra_init {
extra_init(state)?;
}
ffi::lua_pushboolean(state, 0);
rawset_field(state, -2, "__metatable")?;
ffi::lua_pop(state, 1);
Ok(())
}
unsafe extern "C-unwind" fn lua_error_impl(state: *mut ffi::lua_State) -> c_int {
ffi::lua_error(state);
}
unsafe extern "C-unwind" fn lua_isfunction_impl(state: *mut ffi::lua_State) -> c_int {
ffi::lua_pushboolean(state, ffi::lua_isfunction(state, -1));
1
}
unsafe extern "C-unwind" fn lua_istable_impl(state: *mut ffi::lua_State) -> c_int {
ffi::lua_pushboolean(state, ffi::lua_istable(state, -1));
1
}
unsafe fn init_userdata_metatable_index(state: *mut ffi::lua_State) -> Result<()> {
let index_key = &USERDATA_METATABLE_INDEX as *const u8 as *const _;
if ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, index_key) == ffi::LUA_TFUNCTION {
return Ok(());
}
ffi::lua_pop(state, 1);
// Create and cache `__index` generator
let code = cstr!(
r#"
local error, isfunction, istable = ...
return function (__index, field_getters, 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
return function (self, key)
if field_getters ~= nil then
local field_getter = field_getters[key]
if field_getter ~= nil then
return field_getter(self)
end
end
if methods ~= nil then
local method = methods[key]
if method ~= nil then
return method
end
end
if isfunction(__index) then
return __index(self, key)
elseif __index == nil then
error("attempt to get an unknown field '"..key.."'")
else
return __index[key]
end
end
end
"#
);
let code_len = CStr::from_ptr(code).to_bytes().len();
protect_lua!(state, 0, 1, |state| {
let ret = ffi::luaL_loadbuffer(state, code, code_len, cstr!("__mlua_index"));
if ret != ffi::LUA_OK {
ffi::lua_error(state);
}
ffi::lua_pushcfunction(state, lua_error_impl);
ffi::lua_pushcfunction(state, lua_isfunction_impl);
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);
ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, index_key);
})
}
unsafe fn init_userdata_metatable_newindex(state: *mut ffi::lua_State) -> Result<()> {
let newindex_key = &USERDATA_METATABLE_NEWINDEX as *const u8 as *const _;
if ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, newindex_key) == ffi::LUA_TFUNCTION {
return Ok(());
}
ffi::lua_pop(state, 1);
// Create and cache `__newindex` generator
let code = cstr!(
r#"
local error, isfunction = ...
return function (__newindex, field_setters)
return function (self, key, value)
if field_setters ~= nil then
local field_setter = field_setters[key]
if field_setter ~= nil then
field_setter(self, value)
return
end
end
if isfunction(__newindex) then
__newindex(self, key, value)
elseif __newindex == nil then
error("attempt to set an unknown field '"..key.."'")
else
__newindex[key] = value
end
end
end
"#
);
let code_len = CStr::from_ptr(code).to_bytes().len();
protect_lua!(state, 0, 1, |state| {
let ret = ffi::luaL_loadbuffer(state, code, code_len, cstr!("__mlua_newindex"));
if ret != ffi::LUA_OK {
ffi::lua_error(state);
}
ffi::lua_pushcfunction(state, lua_error_impl);
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);
})
}
#[cfg(not(feature = "luau"))]
pub(crate) unsafe extern "C-unwind" fn userdata_destructor<T>(state: *mut ffi::lua_State) -> c_int {
// It's probably NOT a good idea to catch Rust panics in finalizer
// Lua 5.4 ignores it, other versions generates `LUA_ERRGCMM` without calling message handler
take_userdata::<T>(state);
0
}
pub(crate) static DESTRUCTED_USERDATA_METATABLE: u8 = 0;
static USERDATA_METATABLE_INDEX: u8 = 0;
static USERDATA_METATABLE_NEWINDEX: u8 = 0;
+40 -22
View File
@@ -67,8 +67,7 @@ pub enum Value {
/// `Error` is a special builtin userdata type. When received from Lua it is implicitly cloned.
Error(Box<Error>),
/// Any other value not known to mlua (eg. LuaJIT CData).
#[allow(private_interfaces)]
Other(ValueRef),
Other(#[doc(hidden)] ValueRef),
}
pub use self::Value::Nil;
@@ -104,15 +103,15 @@ impl Value {
/// Compares two values for equality.
///
/// Equality comparisons do not convert strings to numbers or vice versa.
/// Tables, Functions, Threads, and Userdata are compared by reference:
/// Tables, functions, threads, and userdata are compared by reference:
/// two objects are considered equal only if they are the same object.
///
/// If Tables or Userdata have `__eq` metamethod then mlua will try to invoke it.
/// If table or userdata have `__eq` metamethod then mlua will try to invoke it.
/// The first value is checked first. If that value does not define a metamethod
/// for `__eq`, then mlua will check the second value.
/// Then mlua calls the metamethod with the two values as arguments, if found.
pub fn equals<T: AsRef<Self>>(&self, other: T) -> Result<bool> {
match (self, other.as_ref()) {
pub fn equals(&self, other: &Self) -> Result<bool> {
match (self, other) {
(Value::Table(a), Value::Table(b)) => a.equals(b),
(Value::UserData(a), Value::UserData(b)) => a.equals(b),
(a, b) => Ok(a == b),
@@ -129,9 +128,14 @@ impl Value {
#[inline]
pub fn to_pointer(&self) -> *const c_void {
match self {
Value::String(String(vref)) => {
// In Lua < 5.4 (excluding Luau), string pointers are NULL
// Use alternative approach
let lua = vref.lua.lock();
unsafe { ffi::lua_tostring(lua.ref_thread(), vref.index) as *const c_void }
}
Value::LightUserData(ud) => ud.0,
Value::String(String(vref))
| Value::Table(Table(vref))
Value::Table(Table(vref))
| Value::Function(Function(vref))
| Value::Thread(Thread(vref, ..))
| Value::UserData(AnyUserData(vref))
@@ -188,6 +192,8 @@ impl Value {
}
/// Returns `true` if the value is a [`NULL`].
///
/// [`NULL`]: Value::NULL
#[inline]
pub fn is_null(&self) -> bool {
self == &Self::NULL
@@ -428,9 +434,11 @@ impl Value {
}
}
/// Cast the value to a `Buffer`.
/// Cast the value to a [`Buffer`].
///
/// If the value is `Buffer`, returns it or `None` otherwise.
/// If the value is [`Buffer`], returns it or `None` otherwise.
///
/// [`Buffer`]: crate::Buffer
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
#[inline]
@@ -441,7 +449,9 @@ impl Value {
}
}
/// Returns `true` if the value is a `Buffer`.
/// Returns `true` if the value is a [`Buffer`].
///
/// [`Buffer`]: crate::Buffer
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
#[inline]
@@ -501,9 +511,9 @@ impl Value {
(_, 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(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,
// Vector (Luau)
@@ -559,6 +569,12 @@ impl Value {
}
}
impl Default for Value {
fn default() -> Self {
Self::Nil
}
}
impl fmt::Debug for Value {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
if fmt.alternate() {
@@ -610,13 +626,6 @@ impl PartialEq for Value {
}
}
impl AsRef<Value> for Value {
#[inline]
fn as_ref(&self) -> &Self {
self
}
}
/// A wrapped [`Value`] with customized serialization behavior.
#[cfg(feature = "serialize")]
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
@@ -688,10 +697,19 @@ impl<'a> SerializableValue<'a> {
self.options.sort_keys = enabled;
self
}
/// If true, empty Lua tables will be encoded as array, instead of map.
///
/// Default: **false**
#[must_use]
pub const fn encode_empty_tables_as_array(mut self, enabled: bool) -> Self {
self.options.encode_empty_tables_as_array = enabled;
self
}
}
#[cfg(feature = "serialize")]
impl<'a> Serialize for SerializableValue<'a> {
impl Serialize for SerializableValue<'_> {
fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
where
S: Serializer,
+6 -11
View File
@@ -1,10 +1,8 @@
use std::fmt;
#[cfg(all(any(feature = "luau", doc), feature = "serialize"))]
#[cfg(feature = "serialize")]
use serde::ser::{Serialize, SerializeTupleStruct, Serializer};
use super::LuaType;
/// A Luau vector type.
///
/// By default vectors are 3-dimensional, but can be 4-dimensional
@@ -23,6 +21,7 @@ impl fmt::Display for Vector {
}
}
#[cfg_attr(not(feature = "luau"), allow(unused))]
impl Vector {
pub(crate) const SIZE: usize = if cfg!(feature = "luau-vector4") { 4 } else { 3 };
@@ -67,7 +66,7 @@ impl Vector {
}
}
#[cfg(all(any(feature = "luau", doc), feature = "serialize"))]
#[cfg(feature = "serialize")]
impl Serialize for Vector {
fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
let mut ts = serializer.serialize_tuple_struct("Vector", Self::SIZE)?;
@@ -87,11 +86,7 @@ impl PartialEq<[f32; Self::SIZE]> for Vector {
}
}
impl LuaType for Vector {
#[cfg(feature = "luau")]
const TYPE_ID: i32 = ffi::LUA_TVECTOR;
// This is a dummy value, as `Vector` is supported only by Luau
#[cfg(not(feature = "luau"))]
const TYPE_ID: i32 = ffi::LUA_TNONE;
#[cfg(feature = "luau")]
impl crate::types::LuaType for Vector {
const TYPE_ID: std::os::raw::c_int = ffi::LUA_TVECTOR;
}
+11 -8
View File
@@ -1,20 +1,23 @@
[lua54_coverage]
features = "lua54,vendored,async,send,serialize,macros"
[lua54]
features = "lua54,vendored,async,send,serialize,macros,anyhow,userdata-wrappers"
[lua54_with_memory_limit_coverage]
features = "lua54,vendored,async,send,serialize,macros"
[lua54_non_send]
features = "lua54,vendored,async,serialize,macros,anyhow,userdata-wrappers"
[lua54_with_memory_limit]
features = "lua54,vendored,async,send,serialize,macros,anyhow,userdata-wrappers"
rustflags = "--cfg force_memory_limit"
[lua51_coverage]
[lua51]
features = "lua51,vendored,async,send,serialize,macros"
[lua51_with_memory_limit_coverage]
[lua51_with_memory_limit]
features = "lua51,vendored,async,send,serialize,macros"
rustflags = "--cfg force_memory_limit"
[luau_coverage]
[luau]
features = "luau,async,send,serialize,macros"
[luau_with_memory_limit_coverage]
[luau_with_memory_limit]
features = "luau,async,send,serialize,macros"
rustflags = "--cfg force_memory_limit"
+20
View File
@@ -48,6 +48,19 @@ async fn test_async_function_wrap() -> Result<()> {
let res: String = lua.load(r#"f("hello")"#).eval_async().await?;
assert_eq!(res, "hello");
// Return error
let ferr = Function::wrap_async(|| async move { Err::<(), _>(Error::runtime("some async error")) });
lua.globals().set("ferr", ferr)?;
lua.load(
r#"
local ok, err = pcall(ferr)
assert(not ok and tostring(err):find("some async error"))
"#,
)
.exec_async()
.await
.unwrap();
Ok(())
}
@@ -376,6 +389,13 @@ async fn test_async_table_object_like() -> Result<()> {
table.set_metatable(Some(metatable));
assert_eq!(table.call_async::<i64>(()).await.unwrap(), 15);
match table.call_async_method::<()>("non_existent", ()).await {
Err(Error::RuntimeError(err)) => {
assert!(err.contains("attempt to call a nil value (function 'non_existent')"))
}
r => panic!("expected RuntimeError, got {r:?}"),
}
Ok(())
}
+1
View File
@@ -35,6 +35,7 @@ fn test_buffer() -> Result<()> {
let buf3 = lua.create_buffer(b"")?;
assert!(buf3.is_empty());
assert!(!Value::Buffer(buf3).to_pointer().is_null());
Ok(())
}
+90 -2
View File
@@ -1,6 +1,24 @@
use std::{fs, io};
use mlua::{Lua, Result};
use mlua::{Chunk, ChunkMode, Lua, Result};
#[test]
fn test_chunk_methods() -> Result<()> {
let lua = Lua::new();
#[cfg(unix)]
assert!(lua.load("return 123").name().contains("tests/chunk.rs"));
let chunk2 = lua.load("return 123").set_name("@new_name");
assert_eq!(chunk2.name(), "@new_name");
let env = lua.create_table_from([("a", 987)])?;
let chunk3 = lua.load("return a").set_environment(env.clone());
assert_eq!(chunk3.environment().unwrap(), &env);
assert_eq!(chunk3.mode(), ChunkMode::Text);
assert_eq!(chunk3.call::<i32>(())?, 987);
Ok(())
}
#[test]
fn test_chunk_path() -> Result<()> {
@@ -19,7 +37,7 @@ 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() {
@@ -27,6 +45,30 @@ fn test_chunk_path() -> Result<()> {
res => panic!("expected io::Error, got {:?}", res),
};
// &Path
assert_eq!(
(lua.load(&*temp_dir.path().join("module.lua").as_path())).eval::<i32>()?,
321
);
Ok(())
}
#[test]
fn test_chunk_impls() -> Result<()> {
let lua = Lua::new();
// StdString
assert_eq!(lua.load(String::from("1")).eval::<i32>()?, 1);
assert_eq!(lua.load(&String::from("2")).eval::<i32>()?, 2);
// &[u8]
assert_eq!(lua.load(&b"3"[..]).eval::<i32>()?, 3);
// Vec<u8>
assert_eq!(lua.load(b"4".to_vec()).eval::<i32>()?, 4);
assert_eq!(lua.load(&b"5".to_vec()).eval::<i32>()?, 5);
Ok(())
}
@@ -68,3 +110,49 @@ fn test_chunk_macro() -> Result<()> {
Ok(())
}
#[cfg(feature = "luau")]
#[test]
fn test_compiler() -> Result<()> {
use std::vec;
let compiler = mlua::Compiler::new()
.set_optimization_level(2)
.set_debug_level(2)
.set_type_info_level(1)
.set_coverage_level(2)
.set_vector_lib("vector")
.set_vector_ctor("new")
.set_vector_type("vector")
.set_mutable_globals(vec!["mutable_global".into()])
.set_userdata_types(vec!["MyUserdata".into()]);
assert!(compiler.compile("return vector.new(1, 2, 3)").is_ok());
// Error
match compiler.compile("%") {
Err(mlua::Error::SyntaxError { ref message, .. }) => {
assert!(message.contains("Expected identifier when parsing expression, got '%'"),);
}
res => panic!("expected result: {res:?}"),
}
Ok(())
}
#[test]
fn test_chunk_wrap() -> Result<()> {
let lua = Lua::new();
let f = Chunk::wrap("return 123");
lua.globals().set("f", f)?;
lua.load("assert(f() == 123)").exec().unwrap();
lua.globals().set("f2", Chunk::wrap("c()"))?;
assert!(
(lua.load("f2()").exec().err().unwrap().to_string()).contains(file!()),
"wrong chunk location"
);
Ok(())
}
+29 -51
View File
@@ -1,32 +1,28 @@
error[E0277]: the type `UnsafeCell<mlua::state::raw::RawLua>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
error[E0277]: the type `UnsafeCell<*mut lua_State>` 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::state::raw::RawLua>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
| ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<*mut lua_State>` 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::state::raw::RawLua>`, which is required by `{closure@$DIR/tests/compile/lua_norefunwindsafe.rs:7:18: 7:20}: UnwindSafe`
note: required because it appears within the type `lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>`
--> $CARGO/lock_api-0.4.12/src/remutex.rs
= help: within `mlua::types::sync::inner::ReentrantMutex<mlua::state::raw::RawLua>`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<*mut lua_State>`
note: required because it appears within the type `Cell<*mut lua_State>`
--> $RUST/core/src/cell.rs
|
| pub struct ReentrantMutex<R, G, T: ?Sized> {
| ^^^^^^^^^^^^^^
note: required because it appears within the type `alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>`
--> $RUST/alloc/src/sync.rs
| pub struct Cell<T: ?Sized> {
| ^^^^
note: required because it appears within the type `mlua::state::raw::RawLua`
--> src/state/raw.rs
|
| struct ArcInner<T: ?Sized> {
| ^^^^^^^^
note: required because it appears within the type `PhantomData<alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>>`
--> $RUST/core/src/marker.rs
| pub struct RawLua {
| ^^^^^^
note: required because it appears within the type `mlua::types::sync::inner::ReentrantMutex<mlua::state::raw::RawLua>`
--> src/types/sync.rs
|
| pub struct PhantomData<T: ?Sized>;
| ^^^^^^^^^^^
note: required because it appears within the type `Arc<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>`
--> $RUST/alloc/src/sync.rs
|
| pub struct Arc<
| ^^^
| pub(crate) struct ReentrantMutex<T>(T);
| ^^^^^^^^^^^^^^
= note: required for `Rc<mlua::types::sync::inner::ReentrantMutex<mlua::state::raw::RawLua>>` to implement `RefUnwindSafe`
note: required because it appears within the type `Lua`
--> src/state.rs
|
@@ -44,45 +40,27 @@ note: required by a bound in `std::panic::catch_unwind`
| pub fn catch_unwind<F: FnOnce() -> R + UnwindSafe, R>(f: F) -> Result<R> {
| ^^^^^^^^^^ required by this bound in `catch_unwind`
error[E0277]: the type `UnsafeCell<usize>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
error[E0277]: the type `UnsafeCell<mlua::state::extra::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<usize>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
| ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<mlua::state::extra::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<usize>`, which is required by `{closure@$DIR/tests/compile/lua_norefunwindsafe.rs:7:18: 7:20}: UnwindSafe`
note: required because it appears within the type `Cell<usize>`
--> $RUST/core/src/cell.rs
= help: the trait `RefUnwindSafe` is not implemented for `UnsafeCell<mlua::state::extra::ExtraData>`
= note: required for `Rc<UnsafeCell<mlua::state::extra::ExtraData>>` to implement `RefUnwindSafe`
note: required because it appears within the type `mlua::state::raw::RawLua`
--> src/state/raw.rs
|
| pub struct Cell<T: ?Sized> {
| ^^^^
note: required because it appears within the type `lock_api::remutex::RawReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId>`
--> $CARGO/lock_api-0.4.12/src/remutex.rs
| pub struct RawLua {
| ^^^^^^
note: required because it appears within the type `mlua::types::sync::inner::ReentrantMutex<mlua::state::raw::RawLua>`
--> src/types/sync.rs
|
| pub struct RawReentrantMutex<R, G> {
| ^^^^^^^^^^^^^^^^^
note: required because it appears within the type `lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>`
--> $CARGO/lock_api-0.4.12/src/remutex.rs
|
| pub struct ReentrantMutex<R, G, T: ?Sized> {
| ^^^^^^^^^^^^^^
note: required because it appears within the type `alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>`
--> $RUST/alloc/src/sync.rs
|
| struct ArcInner<T: ?Sized> {
| ^^^^^^^^
note: required because it appears within the type `PhantomData<alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>>`
--> $RUST/core/src/marker.rs
|
| pub struct PhantomData<T: ?Sized>;
| ^^^^^^^^^^^
note: required because it appears within the type `Arc<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>`
--> $RUST/alloc/src/sync.rs
|
| pub struct Arc<
| ^^^
| pub(crate) struct ReentrantMutex<T>(T);
| ^^^^^^^^^^^^^^
= note: required for `Rc<mlua::types::sync::inner::ReentrantMutex<mlua::state::raw::RawLua>>` to implement `RefUnwindSafe`
note: required because it appears within the type `Lua`
--> src/state.rs
|
+1 -1
View File
@@ -8,7 +8,7 @@ error[E0277]: `Rc<Cell<i32>>` cannot be sent between threads safely
| | within this `{closure@$DIR/tests/compile/non_send.rs:11:25: 11:37}`
| required by a bound introduced by this call
|
= help: within `{closure@$DIR/tests/compile/non_send.rs:11:25: 11:37}`, the trait `Send` is not implemented for `Rc<Cell<i32>>`, which is required by `{closure@$DIR/tests/compile/non_send.rs:11:25: 11:37}: MaybeSend`
= 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
|
+104 -47
View File
@@ -1,38 +1,38 @@
error[E0277]: the type `UnsafeCell<mlua::state::raw::RawLua>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
error[E0277]: the type `UnsafeCell<usize>` 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::state::raw::RawLua>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
| ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<usize>` 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 `alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<mlua::state::raw::RawLua>`, which is required by `{closure@$DIR/tests/compile/ref_nounwindsafe.rs:8:18: 8:25}: UnwindSafe`
note: required because it appears within the type `lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>`
--> $CARGO/lock_api-0.4.12/src/remutex.rs
= help: within `rc::RcInner<mlua::types::sync::inner::ReentrantMutex<mlua::state::raw::RawLua>>`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<usize>`
note: required because it appears within the type `Cell<usize>`
--> $RUST/core/src/cell.rs
|
| pub struct ReentrantMutex<R, G, T: ?Sized> {
| ^^^^^^^^^^^^^^
note: required because it appears within the type `alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>`
--> $RUST/alloc/src/sync.rs
| pub struct Cell<T: ?Sized> {
| ^^^^
note: required because it appears within the type `rc::RcInner<mlua::types::sync::inner::ReentrantMutex<mlua::state::raw::RawLua>>`
--> $RUST/alloc/src/rc.rs
|
| struct ArcInner<T: ?Sized> {
| ^^^^^^^^
= note: required for `NonNull<alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>>` to implement `UnwindSafe`
note: required because it appears within the type `std::sync::Weak<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>`
--> $RUST/alloc/src/sync.rs
| struct RcInner<T: ?Sized> {
| ^^^^^^^
= note: required for `NonNull<rc::RcInner<mlua::types::sync::inner::ReentrantMutex<mlua::state::raw::RawLua>>>` to implement `UnwindSafe`
note: required because it appears within the type `std::rc::Weak<mlua::types::sync::inner::ReentrantMutex<mlua::state::raw::RawLua>>`
--> $RUST/alloc/src/rc.rs
|
| pub struct Weak<
| ^^^^
note: required because it appears within the type `mlua::state::WeakLua`
note: required because it appears within the type `WeakLua`
--> src/state.rs
|
| pub(crate) struct WeakLua(XWeak<ReentrantMutex<RawLua>>);
| ^^^^^^^
note: required because it appears within the type `mlua::types::ValueRef`
--> src/types.rs
| pub struct WeakLua(XWeak<ReentrantMutex<RawLua>>);
| ^^^^^^^
note: required because it appears within the type `mlua::types::value_ref::ValueRef`
--> src/types/value_ref.rs
|
| pub(crate) struct ValueRef {
| ^^^^^^^^
| pub struct ValueRef {
| ^^^^^^^^
note: required because it appears within the type `LuaTable`
--> src/table.rs
|
@@ -49,51 +49,108 @@ note: required by a bound in `std::panic::catch_unwind`
| pub fn catch_unwind<F: FnOnce() -> R + UnwindSafe, R>(f: F) -> Result<R> {
| ^^^^^^^^^^ required by this bound in `catch_unwind`
error[E0277]: the type `UnsafeCell<usize>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
error[E0277]: the type `UnsafeCell<*mut lua_State>` 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<usize>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
| ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<*mut lua_State>` 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 `alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<usize>`, which is required by `{closure@$DIR/tests/compile/ref_nounwindsafe.rs:8:18: 8:25}: UnwindSafe`
note: required because it appears within the type `Cell<usize>`
= help: within `rc::RcInner<mlua::types::sync::inner::ReentrantMutex<mlua::state::raw::RawLua>>`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<*mut lua_State>`
note: required because it appears within the type `Cell<*mut lua_State>`
--> $RUST/core/src/cell.rs
|
| pub struct Cell<T: ?Sized> {
| ^^^^
note: required because it appears within the type `lock_api::remutex::RawReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId>`
--> $CARGO/lock_api-0.4.12/src/remutex.rs
note: required because it appears within the type `mlua::state::raw::RawLua`
--> src/state/raw.rs
|
| pub struct RawReentrantMutex<R, G> {
| ^^^^^^^^^^^^^^^^^
note: required because it appears within the type `lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>`
--> $CARGO/lock_api-0.4.12/src/remutex.rs
| pub struct RawLua {
| ^^^^^^
note: required because it appears within the type `mlua::types::sync::inner::ReentrantMutex<mlua::state::raw::RawLua>`
--> src/types/sync.rs
|
| pub struct ReentrantMutex<R, G, T: ?Sized> {
| ^^^^^^^^^^^^^^
note: required because it appears within the type `alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>`
--> $RUST/alloc/src/sync.rs
| pub(crate) struct ReentrantMutex<T>(T);
| ^^^^^^^^^^^^^^
note: required because it appears within the type `rc::RcInner<mlua::types::sync::inner::ReentrantMutex<mlua::state::raw::RawLua>>`
--> $RUST/alloc/src/rc.rs
|
| struct ArcInner<T: ?Sized> {
| ^^^^^^^^
= note: required for `NonNull<alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>>` to implement `UnwindSafe`
note: required because it appears within the type `std::sync::Weak<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>`
--> $RUST/alloc/src/sync.rs
| struct RcInner<T: ?Sized> {
| ^^^^^^^
= note: required for `NonNull<rc::RcInner<mlua::types::sync::inner::ReentrantMutex<mlua::state::raw::RawLua>>>` to implement `UnwindSafe`
note: required because it appears within the type `std::rc::Weak<mlua::types::sync::inner::ReentrantMutex<mlua::state::raw::RawLua>>`
--> $RUST/alloc/src/rc.rs
|
| pub struct Weak<
| ^^^^
note: required because it appears within the type `mlua::state::WeakLua`
note: required because it appears within the type `WeakLua`
--> src/state.rs
|
| pub(crate) struct WeakLua(XWeak<ReentrantMutex<RawLua>>);
| ^^^^^^^
note: required because it appears within the type `mlua::types::ValueRef`
--> src/types.rs
| pub struct WeakLua(XWeak<ReentrantMutex<RawLua>>);
| ^^^^^^^
note: required because it appears within the type `mlua::types::value_ref::ValueRef`
--> src/types/value_ref.rs
|
| pub(crate) struct ValueRef {
| ^^^^^^^^
| pub struct ValueRef {
| ^^^^^^^^
note: required because it appears within the type `LuaTable`
--> src/table.rs
|
| pub struct Table(pub(crate) ValueRef);
| ^^^^^
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());
| ^^^^^^^
note: required by a bound in `std::panic::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`
error[E0277]: the type `UnsafeCell<mlua::state::extra::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::state::extra::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: the trait `RefUnwindSafe` is not implemented for `UnsafeCell<mlua::state::extra::ExtraData>`
= note: required for `Rc<UnsafeCell<mlua::state::extra::ExtraData>>` to implement `RefUnwindSafe`
note: required because it appears within the type `mlua::state::raw::RawLua`
--> src/state/raw.rs
|
| pub struct RawLua {
| ^^^^^^
note: required because it appears within the type `mlua::types::sync::inner::ReentrantMutex<mlua::state::raw::RawLua>`
--> src/types/sync.rs
|
| pub(crate) struct ReentrantMutex<T>(T);
| ^^^^^^^^^^^^^^
note: required because it appears within the type `rc::RcInner<mlua::types::sync::inner::ReentrantMutex<mlua::state::raw::RawLua>>`
--> $RUST/alloc/src/rc.rs
|
| struct RcInner<T: ?Sized> {
| ^^^^^^^
= note: required for `NonNull<rc::RcInner<mlua::types::sync::inner::ReentrantMutex<mlua::state::raw::RawLua>>>` to implement `UnwindSafe`
note: required because it appears within the type `std::rc::Weak<mlua::types::sync::inner::ReentrantMutex<mlua::state::raw::RawLua>>`
--> $RUST/alloc/src/rc.rs
|
| pub struct Weak<
| ^^^^
note: required because it appears within the type `WeakLua`
--> src/state.rs
|
| pub struct WeakLua(XWeak<ReentrantMutex<RawLua>>);
| ^^^^^^^
note: required because it appears within the type `mlua::types::value_ref::ValueRef`
--> src/types/value_ref.rs
|
| pub struct ValueRef {
| ^^^^^^^^
note: required because it appears within the type `LuaTable`
--> src/table.rs
|
+1 -1
View File
@@ -2,7 +2,7 @@ error[E0373]: closure may outlive the current function, but it borrows `inner`,
--> tests/compile/scope_callback_capture.rs:7:43
|
5 | lua.scope(|scope| {
| ----- has type `&'1 mut mlua::scope::Scope<'1, '_>`
| ----- has type `&'1 mlua::Scope<'1, '_>`
6 | let mut inner: Option<Table> = None;
7 | let f = scope.create_function_mut(|_, t: Table| {
| ^^^^^^^^^^^^^ may outlive borrowed value `inner`
+1 -1
View File
@@ -2,7 +2,7 @@ error[E0373]: closure may outlive the current function, but it borrows `test.fie
--> tests/compile/scope_invariance.rs:13:39
|
9 | lua.scope(|scope| {
| ----- has type `&'1 mut mlua::scope::Scope<'1, '_>`
| ----- has type `&'1 mlua::Scope<'1, '_>`
...
13 | scope.create_function_mut(|_, ()| {
| ^^^^^^^ may outlive borrowed value `test.field`
+1 -1
View File
@@ -2,7 +2,7 @@ error[E0499]: cannot borrow `i` as mutable more than once at a time
--> tests/compile/scope_mutable_aliasing.rs:12:51
|
10 | lua.scope(|scope| {
| ----- has type `&mut mlua::scope::Scope<'_, '1>`
| ----- has type `&mlua::Scope<'_, '1>`
11 | let _a = scope.create_userdata(MyUserData(&mut i)).unwrap();
| -----------------------------------------
| | |
+1 -1
View File
@@ -2,7 +2,7 @@ error[E0597]: `ibad` does not live long enough
--> tests/compile/scope_userdata_borrow.rs:15:46
|
11 | lua.scope(|scope| {
| ----- has type `&mut mlua::scope::Scope<'_, '1>`
| ----- has type `&mlua::Scope<'_, '1>`
...
14 | let ibad = 42;
| ---- binding `ibad` declared here
+207 -5
View File
@@ -6,8 +6,8 @@ use std::path::PathBuf;
use bstr::BString;
use maplit::{btreemap, btreeset, hashmap, hashset};
use mlua::{
AnyUserData, Either, Error, Function, IntoLua, Lua, RegistryKey, Result, Table, Thread, UserDataRef,
Value,
AnyUserData, BorrowedBytes, BorrowedStr, Either, Error, Function, IntoLua, Lua, RegistryKey, Result,
Table, Thread, UserDataRef, Value,
};
#[test]
@@ -60,6 +60,66 @@ fn test_string_from_lua() -> Result<()> {
Ok(())
}
#[test]
fn test_borrowedstr_into_lua() -> Result<()> {
let lua = Lua::new();
// Direct conversion
let s = lua.create_string("hello, world!")?;
let bs = s.to_str()?;
let bs2 = (&bs).into_lua(&lua)?;
assert_eq!(bs2.as_string().unwrap(), "hello, world!");
// Push into stack
let table = lua.create_table()?;
table.set("bs", &bs)?;
assert_eq!(bs, table.get::<String>("bs")?);
Ok(())
}
#[test]
fn test_borrowedstr_from_lua() -> Result<()> {
let lua = Lua::new();
// From stack
let f = lua.create_function(|_, s: BorrowedStr| Ok(s))?;
let s = f.call::<String>("hello, world!")?;
assert_eq!(s, "hello, world!");
Ok(())
}
#[test]
fn test_borrowedbytes_into_lua() -> Result<()> {
let lua = Lua::new();
// Direct conversion
let s = lua.create_string("hello, world!")?;
let bb = s.as_bytes();
let bb2 = (&bb).into_lua(&lua)?;
assert_eq!(bb2.as_string().unwrap(), "hello, world!");
// Push into stack
let table = lua.create_table()?;
table.set("bb", &bb)?;
assert_eq!(bb, table.get::<String>("bb")?.as_bytes());
Ok(())
}
#[test]
fn test_borrowedbytes_from_lua() -> Result<()> {
let lua = Lua::new();
// From stack
let f = lua.create_function(|_, s: BorrowedBytes| Ok(s))?;
let s = f.call::<String>("hello, world!")?;
assert_eq!(s, "hello, world!");
Ok(())
}
#[test]
fn test_table_into_lua() -> Result<()> {
let lua = Lua::new();
@@ -94,6 +154,21 @@ fn test_function_into_lua() -> Result<()> {
Ok(())
}
#[test]
fn test_function_from_lua() -> Result<()> {
let lua = Lua::new();
assert!(lua.globals().get::<Function>("print").is_ok());
match lua.globals().get::<Function>("math") {
Err(err @ Error::FromLuaConversionError { .. }) => {
assert_eq!(err.to_string(), "error converting Lua table to function");
}
_ => panic!("expected `Error::FromLuaConversionError`"),
}
Ok(())
}
#[test]
fn test_thread_into_lua() -> Result<()> {
let lua = Lua::new();
@@ -112,6 +187,20 @@ fn test_thread_into_lua() -> Result<()> {
Ok(())
}
#[test]
fn test_thread_from_lua() -> Result<()> {
let lua = Lua::new();
match lua.globals().get::<Thread>("print") {
Err(err @ Error::FromLuaConversionError { .. }) => {
assert_eq!(err.to_string(), "error converting Lua function to thread");
}
_ => panic!("expected `Error::FromLuaConversionError`"),
}
Ok(())
}
#[test]
fn test_anyuserdata_into_lua() -> Result<()> {
let lua = Lua::new();
@@ -130,6 +219,45 @@ fn test_anyuserdata_into_lua() -> Result<()> {
Ok(())
}
#[test]
fn test_anyuserdata_from_lua() -> Result<()> {
let lua = Lua::new();
match lua.globals().get::<AnyUserData>("print") {
Err(err @ Error::FromLuaConversionError { .. }) => {
assert_eq!(err.to_string(), "error converting Lua function to userdata");
}
_ => panic!("expected `Error::FromLuaConversionError`"),
}
Ok(())
}
#[test]
fn test_error_conversion() -> Result<()> {
let lua = Lua::new();
// Any Lua value can be converted to `Error`
match lua.convert::<Error>(Error::external("external error")) {
Ok(Error::ExternalError(msg)) => assert_eq!(msg.to_string(), "external error"),
res => panic!("expected `Error::ExternalError`, got {res:?}"),
}
match lua.convert::<Error>("abc") {
Ok(Error::RuntimeError(msg)) => assert_eq!(msg, "abc"),
res => panic!("expected `Error::RuntimeError`, got {res:?}"),
}
match lua.convert::<Error>(true) {
Ok(Error::RuntimeError(msg)) => assert_eq!(msg, "true"),
res => panic!("expected `Error::RuntimeError`, got {res:?}"),
}
match lua.convert::<Error>(lua.globals()) {
Ok(Error::RuntimeError(msg)) => assert!(msg.starts_with("table:")),
res => panic!("expected `Error::RuntimeError`, got {res:?}"),
}
Ok(())
}
#[test]
fn test_registry_value_into_lua() -> Result<()> {
let lua = Lua::new();
@@ -140,7 +268,7 @@ fn test_registry_value_into_lua() -> Result<()> {
let value1 = lua.pack(&r)?;
let value2 = lua.pack(r)?;
assert_eq!(value1.as_str().as_deref(), Some("hello, world"));
assert_eq!(value2.to_pointer(), value2.to_pointer());
assert_eq!(value1.to_pointer(), value2.to_pointer());
// Push into stack
let t = lua.create_table()?;
@@ -175,6 +303,32 @@ fn test_registry_key_from_lua() -> Result<()> {
Ok(())
}
#[test]
fn test_bool_into_lua() -> Result<()> {
let lua = Lua::new();
// Direct conversion
assert!(true.into_lua(&lua)?.is_boolean());
// Push into stack
let table = lua.create_table()?;
table.set("b", true)?;
assert_eq!(true, table.get::<bool>("b")?);
Ok(())
}
#[test]
fn test_bool_from_lua() -> Result<()> {
let lua = Lua::new();
assert!(lua.globals().get::<bool>("print")?);
assert!(lua.convert::<bool>(123)?);
assert!(!lua.convert::<bool>(Value::Nil)?);
Ok(())
}
#[test]
fn test_integer_from_lua() -> Result<()> {
let lua = Lua::new();
@@ -474,6 +628,7 @@ fn test_either_enum() -> Result<()> {
*either.as_mut().left().unwrap() = 44;
assert_eq!(*either.as_ref().left().unwrap(), 44);
assert_eq!(format!("{either}"), "44");
assert_eq!(either.right(), None);
// Right
either = Either::Right("hello".to_string());
@@ -482,6 +637,7 @@ fn test_either_enum() -> Result<()> {
*either.as_mut().right().unwrap() = "world".to_string();
assert_eq!(*either.as_ref().right().unwrap(), "world");
assert_eq!(format!("{either}"), "world");
assert_eq!(either.left(), None);
Ok(())
}
@@ -492,8 +648,10 @@ fn test_either_into_lua() -> Result<()> {
// Direct conversion
let mut either = Either::<i32, &Table>::Left(42);
let value = either.into_lua(&lua)?;
assert_eq!(value, Value::Integer(42));
assert_eq!(either.into_lua(&lua)?, Value::Integer(42));
let t = lua.create_table()?;
either = Either::Right(&t);
assert!(matches!(either.into_lua(&lua)?, Value::Table(_)));
// Push into stack
let f =
@@ -514,6 +672,19 @@ fn test_either_into_lua() -> Result<()> {
fn test_either_from_lua() -> Result<()> {
let lua = Lua::new();
// From value
let mut either = lua.unpack::<Either<i32, Table>>(Value::Integer(42))?;
assert!(either.is_left());
assert_eq!(*either.as_ref().left().unwrap(), 42);
let t = lua.create_table()?;
either = lua.unpack::<Either<i32, Table>>(Value::Table(t.clone()))?;
assert!(either.is_right());
assert_eq!(either.as_ref().right().unwrap(), &t);
match lua.unpack::<Either<i32, Table>>(Value::String(lua.create_string("abc")?)) {
Err(Error::FromLuaConversionError { to, .. }) => assert_eq!(to, "Either<i32, Table>"),
_ => panic!("expected `Error::FromLuaConversionError`"),
}
// From stack
let f = lua.create_function(|_, either: Either<i32, Table>| Ok(either))?;
let either = f.call::<Either<i32, Table>>(42)?;
@@ -546,3 +717,34 @@ fn test_either_from_lua() -> Result<()> {
Ok(())
}
#[test]
fn test_char_into_lua() -> Result<()> {
let lua = Lua::new();
let v = '🦀';
let v2 = v.into_lua(&lua)?;
assert_eq!(Some(v.to_string()), v2.as_string_lossy());
Ok(())
}
#[test]
fn test_char_from_lua() -> Result<()> {
let lua = Lua::new();
assert_eq!(lua.convert::<char>("A")?, 'A');
assert_eq!(lua.convert::<char>(65)?, 'A');
assert_eq!(lua.convert::<char>(128175)?, '💯');
assert!(lua
.convert::<char>(5456324)
.is_err_and(|e| e.to_string().contains("integer out of range")));
assert!(lua
.convert::<char>("hello")
.is_err_and(|e| e.to_string().contains("expected string to have exactly one char")));
assert!(lua
.convert::<char>(HashMap::<String, String>::new())
.is_err_and(|e| e.to_string().contains("expected string or integer")));
Ok(())
}
+1 -1
View File
@@ -7,7 +7,7 @@ fn test_debug_format() -> Result<()> {
// Globals
let globals = lua.globals();
let dump = format!("{globals:#?}");
assert!(dump.starts_with("{\n [\"_G\"] = table:"));
assert!(dump.starts_with("{\n _G = table:"));
// TODO: Other cases
+14 -10
View File
@@ -1,4 +1,5 @@
use std::io;
use std::error::Error as _;
use std::{fmt, io};
use mlua::{Error, ErrorContext, Lua, Result};
@@ -27,7 +28,6 @@ fn test_error_context() -> Result<()> {
.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`
@@ -36,13 +36,12 @@ fn test_error_context() -> Result<()> {
.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());
let err = func3.call::<()>(()).unwrap_err();
let err = err.parent().unwrap();
assert!(!err.to_string().contains("some context"));
assert!(err.to_string().contains("some new context"));
assert!(err.downcast_ref::<io::Error>().is_some());
assert!(err.downcast_ref::<fmt::Error>().is_none());
Ok(())
}
@@ -59,7 +58,7 @@ fn test_error_chain() -> Result<()> {
let err = Error::external(io::Error::new(io::ErrorKind::Other, "other")).context("io error");
Err::<(), _>(err)
})?;
let err = func.call::<()>(()).err().unwrap();
let err = func.call::<()>(()).unwrap_err();
assert_eq!(err.chain().count(), 3);
for (i, err) in err.chain().enumerate() {
match i {
@@ -70,6 +69,11 @@ fn test_error_chain() -> Result<()> {
}
}
let err = err.parent().unwrap();
assert!(err.source().is_none()); // The source is included to the `Display` output
assert!(err.to_string().contains("io error"));
assert!(err.to_string().contains("other"));
Ok(())
}
+121 -70
View File
@@ -1,27 +1,34 @@
use mlua::{Error, Function, Lua, Result, String, Table};
use mlua::{Error, Function, Lua, Result, String, Table, Variadic};
#[test]
fn test_function() -> Result<()> {
fn test_function_call() -> Result<()> {
let lua = Lua::new();
let globals = lua.globals();
lua.load(
r#"
function concat(arg1, arg2)
return arg1 .. arg2
end
"#,
)
.exec()?;
let concat = globals.get::<Function>("concat")?;
let concat = lua
.load(r#"function(arg1, arg2) return arg1 .. arg2 end"#)
.eval::<Function>()?;
assert_eq!(concat.call::<String>(("foo", "bar"))?, "foobar");
Ok(())
}
#[test]
fn test_bind() -> Result<()> {
fn test_function_call_error() -> Result<()> {
let lua = Lua::new();
let concat_err = lua
.load(r#"function(arg1, arg2) error("concat error") end"#)
.eval::<Function>()?;
match concat_err.call::<String>(("foo", "bar")) {
Err(Error::RuntimeError(msg)) if msg.contains("concat error") => {}
other => panic!("unexpected result: {other:?}"),
}
Ok(())
}
#[test]
fn test_function_bind() -> Result<()> {
let lua = Lua::new();
let globals = lua.globals();
@@ -54,59 +61,13 @@ fn test_bind() -> Result<()> {
}
#[test]
fn test_rust_function() -> Result<()> {
#[cfg(not(target_arch = "wasm32"))]
fn test_function_bind_error() -> Result<()> {
let lua = Lua::new();
let globals = lua.globals();
lua.load(
r#"
function lua_function()
return rust_function()
end
-- Test to make sure chunk return is ignored
return 1
"#,
)
.exec()?;
let lua_function = globals.get::<Function>("lua_function")?;
let rust_function = lua.create_function(|_, ()| Ok("hello"))?;
globals.set("rust_function", rust_function)?;
assert_eq!(lua_function.call::<String>(())?, "hello");
Ok(())
}
#[test]
fn test_c_function() -> Result<()> {
let lua = Lua::new();
unsafe extern "C-unwind" fn c_function(state: *mut mlua::lua_State) -> std::os::raw::c_int {
ffi::lua_pushboolean(state, 1);
ffi::lua_setglobal(state, b"c_function\0" as *const _ as *const _);
0
}
let func = unsafe { lua.create_c_function(c_function)? };
func.call::<()>(())?;
assert_eq!(lua.globals().get::<bool>("c_function")?, true);
Ok(())
}
#[cfg(not(feature = "luau"))]
#[test]
fn test_dump() -> Result<()> {
let lua = unsafe { Lua::unsafe_new() };
let concat_lua = lua
.load(r#"function(arg1, arg2) return arg1 .. arg2 end"#)
.eval::<Function>()?;
let concat = lua.load(&concat_lua.dump(false)).into_function()?;
assert_eq!(concat.call::<String>(("foo", "bar"))?, "foobar");
let func = lua.load(r#"function(...) end"#).eval::<Function>()?;
assert!(func.bind(Variadic::from_iter(1..1000000)).is_err());
assert!(func.call::<()>(Variadic::from_iter(1..1000000)).is_err());
Ok(())
}
@@ -114,14 +75,15 @@ fn test_dump() -> Result<()> {
#[test]
fn test_function_environment() -> Result<()> {
let lua = Lua::new();
let globals = lua.globals();
// 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));
assert_eq!(rust_func.set_environment(globals.clone()).ok(), Some(false));
// Test getting Lua function environment
lua.globals().set("hello", "global")?;
globals.set("hello", "global")?;
let lua_func = lua
.load(
r#"
@@ -135,7 +97,7 @@ fn test_function_environment() -> Result<()> {
.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()));
assert_eq!(lua_func.environment().as_ref(), Some(&globals));
// Test changing the environment
let env = lua.create_table_from([("hello", "local")])?;
@@ -154,9 +116,9 @@ fn test_function_environment() -> Result<()> {
"#,
)
.exec()?;
let lucky = lua.globals().get::<Function>("lucky")?;
let lucky = globals.get::<Function>("lucky")?;
assert_eq!(lucky.call::<String>(())?, "number is 15");
let new_env = lua.globals().get::<Table>("new_env")?;
let new_env = globals.get::<Table>("new_env")?;
lucky.set_environment(new_env)?;
assert_eq!(lucky.call::<String>(())?, "15");
@@ -235,6 +197,95 @@ fn test_function_info() -> Result<()> {
Ok(())
}
#[cfg(not(feature = "luau"))]
#[test]
fn test_function_dump() -> Result<()> {
let lua = unsafe { Lua::unsafe_new() };
let concat_lua = lua
.load(r#"function(arg1, arg2) return arg1 .. arg2 end"#)
.eval::<Function>()?;
let concat = lua.load(&concat_lua.dump(false)).into_function()?;
assert_eq!(concat.call::<String>(("foo", "bar"))?, "foobar");
Ok(())
}
#[cfg(feature = "luau")]
#[test]
fn test_function_coverage() -> Result<()> {
let lua = Lua::new();
lua.set_compiler(mlua::Compiler::default().set_coverage_level(1));
let f = lua
.load(
r#"local s = "abc"
assert(#s == 3)
function abc(i)
if i < 5 then
return 0
else
return 1
end
end
(function()
(function() abc(10) end)()
end)()
"#,
)
.into_function()?;
f.call::<()>(())?;
let mut report = Vec::new();
f.coverage(|cov| {
report.push(cov);
});
assert_eq!(
report[0],
mlua::CoverageInfo {
function: None,
line_defined: 1,
depth: 0,
hits: vec![-1, 1, 1, -1, 1, -1, -1, -1, -1, -1, -1, -1, 1, -1, -1, -1],
}
);
assert_eq!(
report[1],
mlua::CoverageInfo {
function: Some("abc".into()),
line_defined: 4,
depth: 1,
hits: vec![-1, -1, -1, -1, -1, 1, 0, -1, 1, -1, -1, -1, -1, -1, -1, -1],
}
);
assert_eq!(
report[2],
mlua::CoverageInfo {
function: None,
line_defined: 12,
depth: 1,
hits: vec![-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, -1, -1],
}
);
assert_eq!(
report[3],
mlua::CoverageInfo {
function: None,
line_defined: 13,
depth: 2,
hits: vec![-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, -1, -1],
}
);
Ok(())
}
#[test]
fn test_function_pointer() -> Result<()> {
let lua = Lua::new();
+12
View File
@@ -75,9 +75,21 @@ fn test_function_calls() -> Result<()> {
let output = output.lock().unwrap();
if cfg!(feature = "luajit") && lua.load("jit.version_num").eval::<i64>()? >= 20100 {
#[cfg(not(force_memory_limit))]
assert_eq!(*output, vec![(None, "main"), (Some("len".to_string()), "Lua")]);
#[cfg(force_memory_limit)]
assert_eq!(
*output,
vec![(None, "C"), (None, "main"), (Some("len".to_string()), "Lua")]
);
} else {
#[cfg(not(force_memory_limit))]
assert_eq!(*output, vec![(None, "main"), (Some("len".to_string()), "C")]);
#[cfg(force_memory_limit)]
assert_eq!(
*output,
vec![(None, "C"), (None, "main"), (Some("len".to_string()), "C")]
);
}
Ok(())
+29 -87
View File
@@ -6,10 +6,7 @@ use std::panic::{catch_unwind, AssertUnwindSafe};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use mlua::{
Compiler, CoverageInfo, Error, Lua, LuaOptions, Result, StdLib, Table, ThreadStatus, Value, Vector,
VmState,
};
use mlua::{Compiler, Error, Lua, LuaOptions, Result, StdLib, Table, ThreadStatus, Value, Vector, VmState};
#[test]
fn test_version() -> Result<()> {
@@ -100,17 +97,19 @@ fn test_require() -> Result<()> {
fn test_vectors() -> Result<()> {
let lua = Lua::new();
let v: Vector = lua.load("vector(1, 2, 3) + vector(3, 2, 1)").eval()?;
let v: Vector = lua
.load("vector.create(1, 2, 3) + vector.create(3, 2, 1)")
.eval()?;
assert_eq!(v, [4.0, 4.0, 4.0]);
// Test conversion into Rust array
let v: [f64; 3] = lua.load("vector(1, 2, 3)").eval()?;
let v: [f64; 3] = lua.load("vector.create(1, 2, 3)").eval()?;
assert!(v == [1.0, 2.0, 3.0]);
// Test vector methods
lua.load(
r#"
local v = vector(1, 2, 3)
local v = vector.create(1, 2, 3)
assert(v.x == 1)
assert(v.y == 2)
assert(v.z == 3)
@@ -121,7 +120,7 @@ fn test_vectors() -> Result<()> {
// Test vector methods (fastcall)
lua.load(
r#"
local v = vector(1, 2, 3)
local v = vector.create(1, 2, 3)
assert(v.x == 1)
assert(v.y == 2)
assert(v.z == 3)
@@ -138,17 +137,19 @@ fn test_vectors() -> Result<()> {
fn test_vectors() -> Result<()> {
let lua = Lua::new();
let v: Vector = lua.load("vector(1, 2, 3, 4) + vector(4, 3, 2, 1)").eval()?;
let v: Vector = lua
.load("vector.create(1, 2, 3, 4) + vector.create(4, 3, 2, 1)")
.eval()?;
assert_eq!(v, [5.0, 5.0, 5.0, 5.0]);
// Test conversion into Rust array
let v: [f64; 4] = lua.load("vector(1, 2, 3, 4)").eval()?;
let v: [f64; 4] = lua.load("vector.create(1, 2, 3, 4)").eval()?;
assert!(v == [1.0, 2.0, 3.0, 4.0]);
// Test vector methods
lua.load(
r#"
local v = vector(1, 2, 3, 4)
local v = vector.create(1, 2, 3, 4)
assert(v.x == 1)
assert(v.y == 2)
assert(v.z == 3)
@@ -160,7 +161,7 @@ fn test_vectors() -> Result<()> {
// Test vector methods (fastcall)
lua.load(
r#"
local v = vector(1, 2, 3, 4)
local v = vector.create(1, 2, 3, 4)
assert(v.x == 1)
assert(v.y == 2)
assert(v.z == 3)
@@ -183,10 +184,10 @@ fn test_vector_metatable() -> Result<()> {
r#"
{
__index = {
new = vector,
new = vector.create,
product = function(a, b)
return vector(a.x * b.x, a.y * b.y, a.z * b.z)
return vector.create(a.x * b.x, a.y * b.y, a.z * b.z)
end
}
}
@@ -281,6 +282,20 @@ fn test_sandbox() -> Result<()> {
Ok(())
}
#[test]
fn test_sandbox_safeenv() -> Result<()> {
let lua = Lua::new();
lua.sandbox(true)?;
lua.globals().set("state", lua.create_table()?)?;
lua.globals().set_safeenv(false);
lua.load("state.a = 123").exec()?;
let a: i32 = lua.load("state.a = 321; return state.a").eval()?;
assert_eq!(a, 321);
Ok(())
}
#[test]
fn test_sandbox_nolibs() -> Result<()> {
let lua = Lua::new_with(StdLib::NONE, LuaOptions::default()).unwrap();
@@ -392,79 +407,6 @@ fn test_interrupts() -> Result<()> {
Ok(())
}
#[test]
fn test_coverage() -> Result<()> {
let lua = Lua::new();
lua.set_compiler(Compiler::default().set_coverage_level(1));
let f = lua
.load(
r#"local s = "abc"
assert(#s == 3)
function abc(i)
if i < 5 then
return 0
else
return 1
end
end
(function()
(function() abc(10) end)()
end)()
"#,
)
.into_function()?;
f.call::<()>(())?;
let mut report = Vec::new();
f.coverage(|cov| {
report.push(cov);
});
assert_eq!(
report[0],
CoverageInfo {
function: None,
line_defined: 1,
depth: 0,
hits: vec![-1, 1, 1, -1, 1, -1, -1, -1, -1, -1, -1, -1, 1, -1, -1, -1],
}
);
assert_eq!(
report[1],
CoverageInfo {
function: Some("abc".into()),
line_defined: 4,
depth: 1,
hits: vec![-1, -1, -1, -1, -1, 1, 0, -1, 1, -1, -1, -1, -1, -1, -1, -1],
}
);
assert_eq!(
report[2],
CoverageInfo {
function: None,
line_defined: 12,
depth: 1,
hits: vec![-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, -1, -1],
}
);
assert_eq!(
report[3],
CoverageInfo {
function: None,
line_defined: 13,
depth: 2,
hits: vec![-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, -1, -1],
}
);
Ok(())
}
#[test]
fn test_fflags() {
// We cannot really on any particular feature flag to be present
+11 -1
View File
@@ -31,6 +31,16 @@ fn test_memory_limit() -> Result<()> {
lua.set_memory_limit(0)?;
f.call::<()>(()).expect("should trigger no memory limit");
// Test memory limit during chunk loading
lua.set_memory_limit(1024)?;
match lua
.load("local t = {}; for i = 1,10000 do t[i] = i end")
.into_function()
{
Err(Error::MemoryError(_)) => {}
_ => panic!("did not trigger memory error"),
};
Ok(())
}
@@ -47,8 +57,8 @@ fn test_memory_limit_thread() -> Result<()> {
return Ok(());
}
lua.set_memory_limit(lua.used_memory() + 10000)?;
let thread = lua.create_thread(f)?;
lua.set_memory_limit(lua.used_memory() + 10000)?;
match thread.resume::<()>(()) {
Err(Error::MemoryError(_)) => {}
something_else => panic!("did not trigger memory error: {:?}", something_else),
+26 -1
View File
@@ -1,4 +1,4 @@
use mlua::{Error, ExternalError, IntoLuaMulti, Lua, Result, String, Value};
use mlua::{Error, ExternalError, Integer, IntoLuaMulti, Lua, MultiValue, Result, String, Value, Variadic};
#[test]
fn test_result_conversions() -> Result<()> {
@@ -58,3 +58,28 @@ fn test_result_conversions() -> Result<()> {
Ok(())
}
#[test]
fn test_multivalue() {
let mut multi = MultiValue::with_capacity(3);
multi.push_back(Value::Integer(1));
multi.push_back(Value::Integer(2));
multi.push_front(Value::Integer(3));
assert_eq!(multi.iter().filter_map(|v| v.as_integer()).sum::<Integer>(), 6);
let vec = multi.into_vec();
assert_eq!(&vec, &[Value::Integer(3), Value::Integer(1), Value::Integer(2)]);
let _multi2 = MultiValue::from_vec(vec);
}
#[test]
fn test_variadic() {
let mut var = Variadic::with_capacity(3);
var.extend_from_slice(&[1, 2, 3]);
assert_eq!(var.iter().sum::<u32>(), 6);
let vec = Vec::<u32>::from(var);
assert_eq!(&vec, &[1, 2, 3]);
let var2 = Variadic::from(vec);
assert_eq!(var2.as_slice(), &[1, 2, 3]);
}
+173 -44
View File
@@ -1,6 +1,7 @@
use std::cell::Cell;
use std::rc::Rc;
use std::string::String as StdString;
use std::sync::Arc;
use mlua::{
AnyUserData, Error, Function, Lua, MetaMethod, ObjectLike, Result, String, UserData, UserDataFields,
@@ -66,6 +67,27 @@ fn test_scope_outer_lua_access() -> Result<()> {
Ok(())
}
#[test]
fn test_scope_capture_scope() -> Result<()> {
let lua = Lua::new();
let i = Cell::new(0);
lua.scope(|scope| {
let f = scope.create_function(|_, ()| {
scope.create_function(|_, n: u32| {
i.set(i.get() + n);
Ok(())
})
})?;
f.call::<Function>(())?.call::<()>(10)?;
Ok(())
})?;
assert_eq!(i.get(), 10);
Ok(())
}
#[test]
fn test_scope_userdata_fields() -> Result<()> {
struct MyUserData<'a>(&'a Cell<i64>);
@@ -213,12 +235,14 @@ fn test_scope_userdata_values() -> Result<()> {
#[test]
fn test_scope_userdata_mismatch() -> Result<()> {
struct MyUserData<'a>(&'a Cell<i64>);
struct MyUserData<'a>(&'a mut i64);
impl<'a> UserData for MyUserData<'a> {
fn register(reg: &mut UserDataRegistry<Self>) {
reg.add_method("inc", |_, data, ()| {
data.0.set(data.0.get() + 1);
reg.add_method("get", |_, data, ()| Ok(*data.0));
reg.add_method_mut("inc", |_, data, ()| {
*data.0 = data.0.wrapping_add(1);
Ok(())
});
}
@@ -229,30 +253,53 @@ fn test_scope_userdata_mismatch() -> Result<()> {
lua.load(
r#"
function inc(a, b) a.inc(b) end
function get(a, b) a.get(b) end
"#,
)
.exec()?;
let a = Cell::new(1);
let b = Cell::new(1);
let mut a = 1;
let mut b = 1;
let inc: Function = lua.globals().get("inc")?;
lua.scope(|scope| {
let au = scope.create_userdata(MyUserData(&a))?;
let bu = scope.create_userdata(MyUserData(&b))?;
assert!(inc.call::<()>((&au, &au)).is_ok());
match inc.call::<()>((&au, &bu)) {
Err(Error::CallbackError { ref cause, .. }) => match cause.as_ref() {
Error::BadArgument { to, pos, name, cause } => {
assert_eq!(to.as_deref(), Some("MyUserData.inc"));
assert_eq!(*pos, 1);
assert_eq!(name.as_deref(), Some("self"));
assert!(matches!(*cause.as_ref(), Error::UserDataTypeMismatch));
}
let au = scope.create_userdata(MyUserData(&mut a))?;
let bu = scope.create_userdata(MyUserData(&mut b))?;
for method_name in ["get", "inc"] {
let f: Function = lua.globals().get(method_name)?;
let full_name = format!("MyUserData.{method_name}");
let full_name = full_name.as_str();
assert!(f.call::<()>((&au, &au)).is_ok());
match f.call::<()>((&au, &bu)) {
Err(Error::CallbackError { ref cause, .. }) => match cause.as_ref() {
Error::BadArgument { to, pos, name, cause } => {
assert_eq!(to.as_deref(), Some(full_name));
assert_eq!(*pos, 1);
assert_eq!(name.as_deref(), Some("self"));
assert!(matches!(*cause.as_ref(), Error::UserDataTypeMismatch));
}
other => panic!("wrong error type {other:?}"),
},
Err(other) => panic!("wrong error type {other:?}"),
Ok(_) => panic!("incorrectly returned Ok"),
}
// Pass non-userdata type
let err = f.call::<()>((&au, 321)).err().unwrap();
match err {
Error::CallbackError { ref cause, .. } => match cause.as_ref() {
Error::BadArgument { to, pos, name, cause } => {
assert_eq!(to.as_deref(), Some(full_name));
assert_eq!(*pos, 1);
assert_eq!(name.as_deref(), Some("self"));
assert!(matches!(*cause.as_ref(), Error::FromLuaConversionError { .. }));
}
other => panic!("wrong error type {other:?}"),
},
other => panic!("wrong error type {other:?}"),
},
Err(other) => panic!("wrong error type {other:?}"),
Ok(_) => panic!("incorrectly returned Ok"),
}
let err_msg = format!("bad argument `self` to `{full_name}`: error converting Lua number to userdata (expected userdata of type 'MyUserData')");
assert!(err.to_string().contains(&err_msg));
}
Ok(())
})?;
@@ -296,7 +343,7 @@ fn test_scope_userdata_drop() -> Result<()> {
let ud = lua.globals().get::<AnyUserData>("ud")?;
match ud.borrow_scoped::<MyUserData, _>(|_| Ok::<_, Error>(())) {
Ok(_) => panic!("succesfull borrow for destructed userdata"),
Ok(_) => panic!("successful borrow for destructed userdata"),
Err(Error::UserDataDestructed) => {}
Err(err) => panic!("improper borrow error for destructed userdata: {err:?}"),
}
@@ -332,7 +379,15 @@ fn test_scope_userdata_ref() -> Result<()> {
let data = MyUserData(Cell::new(1));
lua.scope(|scope| {
let ud = scope.create_userdata_ref(&data)?;
modify_userdata(&lua, ud)
modify_userdata(&lua, &ud)?;
// We can only borrow userdata scoped
assert!((matches!(ud.borrow::<MyUserData>(), Err(Error::UserDataTypeMismatch))));
ud.borrow_scoped::<MyUserData, ()>(|ud_inst| {
assert_eq!(ud_inst.0.get(), 2);
})?;
Ok(())
})?;
assert_eq!(data.0.get(), 2);
@@ -362,9 +417,16 @@ fn test_scope_userdata_ref_mut() -> Result<()> {
let mut data = MyUserData(1);
lua.scope(|scope| {
let ud = scope.create_userdata_ref_mut(&mut data)?;
modify_userdata(&lua, ud)
modify_userdata(&lua, &ud)?;
assert!((matches!(ud.borrow_mut::<MyUserData>(), Err(Error::UserDataTypeMismatch))));
ud.borrow_mut_scoped::<MyUserData, ()>(|ud_inst| {
ud_inst.0 += 10;
})?;
Ok(())
})?;
assert_eq!(data.0, 2);
assert_eq!(data.0, 12);
Ok(())
}
@@ -373,15 +435,26 @@ fn test_scope_userdata_ref_mut() -> Result<()> {
fn test_scope_any_userdata() -> Result<()> {
let lua = Lua::new();
lua.register_userdata_type::<StdString>(|reg| {
reg.add_meta_method("__tostring", |_, data, ()| Ok(data.clone()));
})?;
fn register(reg: &mut UserDataRegistry<&mut StdString>) {
reg.add_method_mut("push", |_, this, s: String| {
this.push_str(&s.to_str()?);
Ok(())
});
reg.add_meta_method("__tostring", |_, data, ()| Ok((*data).clone()));
}
let data = StdString::from("foo");
let mut data = StdString::from("foo");
lua.scope(|scope| {
let ud = scope.create_any_userdata_ref(&data)?;
let ud = scope.create_any_userdata(&mut data, register)?;
lua.globals().set("ud", ud)?;
lua.load("assert(tostring(ud) == 'foo')").exec()
lua.load(
r#"
assert(tostring(ud) == "foo")
ud:push("bar")
assert(tostring(ud) == "foobar")
"#,
)
.exec()
})?;
// Check that userdata is destructed
@@ -415,27 +488,83 @@ fn test_scope_any_userdata_ref() -> Result<()> {
let data = Cell::new(1i64);
lua.scope(|scope| {
let ud = scope.create_any_userdata_ref(&data)?;
modify_userdata(&lua, ud)
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()?;
#[test]
fn test_scope_any_userdata_ref_mut() -> Result<()> {
let lua = Lua::new();
f.call::<()>(ud)?;
lua.register_userdata_type::<i64>(|reg| {
reg.add_method_mut("inc", |_, data, ()| {
*data += 1;
Ok(())
});
reg.add_method_mut("dec", |_, data, ()| {
*data -= 1;
Ok(())
});
})?;
let mut data = 1i64;
lua.scope(|scope| {
let ud = scope.create_any_userdata_ref_mut(&mut data)?;
modify_userdata(&lua, &ud)
})?;
assert_eq!(data, 2);
Ok(())
}
#[test]
fn test_scope_destructors() -> Result<()> {
let lua = Lua::new();
lua.register_userdata_type::<Arc<StdString>>(|reg| {
reg.add_meta_method("__tostring", |_, data, ()| Ok(data.to_string()));
})?;
let arc_str = Arc::new(StdString::from("foo"));
let ud = lua.create_any_userdata(arc_str.clone())?;
lua.scope(|scope| {
scope.add_destructor(|| {
assert!(ud.destroy().is_ok());
});
Ok(())
})?;
assert_eq!(Arc::strong_count(&arc_str), 1);
// Try destructing the userdata while it's borrowed
let ud = lua.create_any_userdata(arc_str.clone())?;
ud.borrow_scoped::<Arc<StdString>, _>(|arc_str| {
assert_eq!(arc_str.as_str(), "foo");
lua.scope(|scope| {
scope.add_destructor(|| {
assert!(ud.destroy().is_err());
});
Ok(())
})
.unwrap();
assert_eq!(arc_str.as_str(), "foo");
})?;
Ok(())
}
fn modify_userdata(lua: &Lua, ud: &AnyUserData) -> Result<()> {
lua.load(
r#"
local u = ...
u:inc()
u:dec()
u:inc()
"#,
)
.call(ud)
}
+59 -9
View File
@@ -4,25 +4,35 @@ use std::cell::UnsafeCell;
use std::marker::PhantomData;
use std::string::String as StdString;
use mlua::{AnyUserData, Error, Lua, Result, UserDataRef};
use mlua::{AnyUserData, Error, Lua, ObjectLike, Result, UserData, UserDataMethods, UserDataRef};
use static_assertions::{assert_impl_all, assert_not_impl_all};
#[test]
fn test_userdata_multithread_access() -> Result<()> {
fn test_userdata_multithread_access_send_only() -> Result<()> {
let lua = Lua::new();
// This type is `Send` but not `Sync`.
struct MyUserData(#[allow(unused)] StdString, PhantomData<UnsafeCell<()>>);
struct MyUserData(StdString, PhantomData<UnsafeCell<()>>);
assert_impl_all!(MyUserData: Send);
assert_not_impl_all!(MyUserData: Sync);
lua.globals().set(
"ud",
AnyUserData::wrap(MyUserData("hello".to_string(), PhantomData)),
)?;
impl UserData for MyUserData {
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_method("method", |lua, this, ()| {
let ud = lua.globals().get::<AnyUserData>("ud")?;
assert_eq!(ud.call_method::<String>("method2", ())?, "method2");
Ok(this.0.clone())
});
methods.add_method("method2", |_, _, ()| Ok("method2"));
}
}
lua.globals()
.set("ud", MyUserData("hello".to_string(), PhantomData))?;
// We acquired the exclusive reference.
let _ud1 = lua.globals().get::<UserDataRef<MyUserData>>("ud")?;
let ud = lua.globals().get::<UserDataRef<MyUserData>>("ud")?;
std::thread::scope(|s| {
s.spawn(|| {
@@ -31,5 +41,45 @@ fn test_userdata_multithread_access() -> Result<()> {
});
});
drop(ud);
lua.load("ud:method()").exec().unwrap();
Ok(())
}
#[test]
fn test_userdata_multithread_access_sync() -> Result<()> {
let lua = Lua::new();
// This type is `Send` and `Sync`.
struct MyUserData(StdString);
assert_impl_all!(MyUserData: Send, Sync);
impl UserData for MyUserData {
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_method("method", |lua, this, ()| {
let ud = lua.globals().get::<AnyUserData>("ud")?;
assert!(ud.call_method::<()>("method2", ()).is_ok());
Ok(this.0.clone())
});
methods.add_method("method2", |_, _, ()| Ok(()));
}
}
lua.globals().set("ud", MyUserData("hello".to_string()))?;
// We acquired the shared reference.
let _ud = lua.globals().get::<UserDataRef<MyUserData>>("ud")?;
std::thread::scope(|s| {
s.spawn(|| {
// Getting another shared reference for `Sync` type is allowed.
let _ = lua.globals().get::<UserDataRef<MyUserData>>("ud").unwrap();
});
});
lua.load("ud:method()").exec().unwrap();
Ok(())
}
+59 -14
View File
@@ -3,6 +3,7 @@
use std::collections::HashMap;
use std::error::Error as StdError;
use bstr::BString;
use mlua::{
AnyUserData, DeserializeOptions, Error, ExternalResult, IntoLua, Lua, LuaSerdeExt, Result as LuaResult,
SerializeOptions, UserData, Value,
@@ -133,13 +134,7 @@ fn test_serialize_failure() -> Result<(), Box<dyn StdError>> {
fn test_serialize_vector() -> Result<(), Box<dyn StdError>> {
let lua = Lua::new();
let globals = lua.globals();
globals.set(
"vector",
lua.create_function(|_, (x, y, z)| Ok(mlua::Vector::new(x, y, z)))?,
)?;
let val = lua.load("{_vector = vector(1, 2, 3)}").eval::<Value>()?;
let val = lua.load("{_vector = vector.create(1, 2, 3)}").eval::<Value>()?;
let json = serde_json::json!({
"_vector": [1.0, 2.0, 3.0],
});
@@ -156,13 +151,9 @@ fn test_serialize_vector() -> Result<(), Box<dyn StdError>> {
fn test_serialize_vector() -> Result<(), Box<dyn StdError>> {
let lua = Lua::new();
let globals = lua.globals();
globals.set(
"vector",
lua.create_function(|_, (x, y, z, w)| Ok(mlua::Vector::new(x, y, z, w)))?,
)?;
let val = lua.load("{_vector = vector(1, 2, 3, 4)}").eval::<Value>()?;
let val = lua
.load("{_vector = vector.create(1, 2, 3, 4)}")
.eval::<Value>()?;
let json = serde_json::json!({
"_vector": [1.0, 2.0, 3.0, 4.0],
});
@@ -258,6 +249,26 @@ fn test_serialize_same_table_twice() -> LuaResult<()> {
Ok(())
}
#[test]
fn test_serialize_empty_table() -> LuaResult<()> {
let lua = Lua::new();
let table = Value::Table(lua.create_table()?);
let json = serde_json::to_string(&table.to_serializable()).unwrap();
assert_eq!(json, "{}");
// Set the option to encode empty tables as array
let json = serde_json::to_string(&table.to_serializable().encode_empty_tables_as_array(true)).unwrap();
assert_eq!(json, "[]");
// Check hashmap table with this option
table.as_table().unwrap().set("hello", "world")?;
let json = serde_json::to_string(&table.to_serializable().encode_empty_tables_as_array(true)).unwrap();
assert_eq!(json, r#"{"hello":"world"}"#);
Ok(())
}
#[test]
fn test_to_value_struct() -> LuaResult<()> {
let lua = Lua::new();
@@ -430,6 +441,7 @@ fn test_from_value_struct() -> Result<(), Box<dyn StdError>> {
map: HashMap<i32, i32>,
empty: Vec<()>,
tuple: (u8, u8, u8),
bytes: BString,
}
let value = lua
@@ -441,6 +453,7 @@ fn test_from_value_struct() -> Result<(), Box<dyn StdError>> {
map = {2, [4] = 1},
empty = {},
tuple = {10, 20, 30},
bytes = "\240\040\140\040",
}
"#,
)
@@ -453,6 +466,7 @@ fn test_from_value_struct() -> Result<(), Box<dyn StdError>> {
map: vec![(1, 2), (4, 1)].into_iter().collect(),
empty: vec![],
tuple: (10, 20, 30),
bytes: BString::from([240, 40, 140, 40]),
},
got
);
@@ -673,6 +687,37 @@ fn test_from_value_userdata() -> Result<(), Box<dyn StdError>> {
Ok(())
}
#[test]
fn test_from_value_empty_table() -> Result<(), Box<dyn StdError>> {
let lua = Lua::new();
// By default we encode empty tables as objects
let t = lua.create_table()?;
let got = lua.from_value::<serde_json::Value>(Value::Table(t.clone()))?;
assert_eq!(got, serde_json::json!({}));
// Set the option to encode empty tables as array
let got = lua
.from_value_with::<serde_json::Value>(
Value::Table(t.clone()),
DeserializeOptions::new().encode_empty_tables_as_array(true),
)
.unwrap();
assert_eq!(got, serde_json::json!([]));
// Check hashmap table with this option
t.raw_set("hello", "world")?;
let got = lua
.from_value_with::<serde_json::Value>(
Value::Table(t),
DeserializeOptions::new().encode_empty_tables_as_array(true),
)
.unwrap();
assert_eq!(got, serde_json::json!({"hello": "world"}));
Ok(())
}
#[test]
fn test_from_value_sorted() -> Result<(), Box<dyn StdError>> {
let lua = Lua::new();
+58 -4
View File
@@ -17,6 +17,15 @@ fn test_string_compare() {
with_str("teststring", |t| assert_eq!(t, t)); // mlua::String
with_str("teststring", |t| assert_eq!(t, Cow::from(b"teststring".as_ref()))); // Cow (borrowed)
with_str("bla", |t| assert_eq!(t, Cow::from(b"bla".to_vec()))); // Cow (owned)
// Test ordering
with_str("a", |a| {
assert!(!(a < a));
assert!(!(a > a));
});
with_str("a", |a| assert!(a < "b"));
with_str("a", |a| assert!(a < b"b"));
with_str("a", |a| with_str("b", |b| assert!(a < b)));
}
#[test]
@@ -52,7 +61,7 @@ fn test_string_views() -> Result<()> {
}
#[test]
fn test_raw_string() -> Result<()> {
fn test_string_from_bytes() -> Result<()> {
let lua = Lua::new();
let rs = lua.create_string(&[0, 1, 2, 3, 0, 1, 2, 3])?;
@@ -77,16 +86,18 @@ fn test_string_hash() -> Result<()> {
}
#[test]
fn test_string_debug() -> Result<()> {
fn test_string_fmt_debug() -> Result<()> {
let lua = Lua::new();
// Valid utf8
let s = lua.create_string("hello")?;
assert_eq!(format!("{s:?}"), r#""hello""#);
assert_eq!(format!("{:?}", s.to_str()?), r#""hello""#);
assert_eq!(format!("{:?}", s.as_bytes()), "[104, 101, 108, 108, 111]");
// 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""#);
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(())
}
@@ -103,3 +114,46 @@ fn test_string_pointer() -> Result<()> {
Ok(())
}
#[test]
fn test_string_display() -> Result<()> {
let lua = Lua::new();
let s = lua.create_string("hello")?;
assert_eq!(format!("{}", s.display()), "hello");
// With invalid utf8
let s = lua.create_string(b"hello\0world\xFF")?;
assert_eq!(format!("{}", s.display()), "hello\0world");
Ok(())
}
#[test]
fn test_string_wrap() -> Result<()> {
let lua = Lua::new();
let s = String::wrap("hello, world");
lua.globals().set("s", s)?;
assert_eq!(lua.globals().get::<String>("s")?, "hello, world");
let s2 = String::wrap("hello, world (owned)".to_string());
lua.globals().set("s2", s2)?;
assert_eq!(lua.globals().get::<String>("s2")?, "hello, world (owned)");
Ok(())
}
#[test]
fn test_bytes_into_iter() -> Result<()> {
let lua = Lua::new();
let s = lua.create_string("hello")?;
let bytes = s.as_bytes();
for (i, &b) in bytes.into_iter().enumerate() {
assert_eq!(b, s.as_bytes()[i]);
}
Ok(())
}
+84 -59
View File
@@ -1,4 +1,4 @@
use mlua::{Error, Lua, Nil, ObjectLike, Result, Table, Value};
use mlua::{Error, Lua, ObjectLike, Result, Table, Value};
#[test]
fn test_globals_set_get() -> Result<()> {
@@ -10,6 +10,8 @@ fn test_globals_set_get() -> Result<()> {
assert_eq!(globals.get::<String>("foo")?, "bar");
assert_eq!(globals.get::<String>("baz")?, "baf");
lua.load(r#"assert(foo == "bar")"#).exec().unwrap();
Ok(())
}
@@ -19,16 +21,6 @@ fn test_table() -> Result<()> {
let globals = lua.globals();
globals.set("table", lua.create_table()?)?;
let table1: Table = globals.get("table")?;
let table2: Table = globals.get("table")?;
table1.set("foo", "bar")?;
table2.set("baz", "baf")?;
assert_eq!(table2.get::<String>("foo")?, "bar");
assert_eq!(table1.get::<String>("baz")?, "baf");
lua.load(
r#"
table1 = {1, 2, 3, 4, 5}
@@ -39,29 +31,26 @@ fn test_table() -> Result<()> {
.exec()?;
let table1 = globals.get::<Table>("table1")?;
let table2 = globals.get::<Table>("table2")?;
let table3 = globals.get::<Table>("table3")?;
assert_eq!(table1.len()?, 5);
assert!(!table1.is_empty());
assert_eq!(
table1.clone().pairs().collect::<Result<Vec<(i64, i64)>>>()?,
table1.pairs().collect::<Result<Vec<(i64, i64)>>>()?,
vec![(1, 1), (2, 2), (3, 3), (4, 4), (5, 5)]
);
assert_eq!(
table1.clone().sequence_values().collect::<Result<Vec<i64>>>()?,
table1.sequence_values().collect::<Result<Vec<i64>>>()?,
vec![1, 2, 3, 4, 5]
);
assert_eq!(table1, [1, 2, 3, 4, 5]);
assert_eq!(table1, [1, 2, 3, 4, 5].as_slice());
let table2 = globals.get::<Table>("table2")?;
assert_eq!(table2.len()?, 0);
assert!(table2.is_empty());
assert_eq!(
table2.clone().pairs().collect::<Result<Vec<(i64, i64)>>>()?,
vec![]
);
assert_eq!(table2.pairs().collect::<Result<Vec<(i64, i64)>>>()?, vec![]);
assert_eq!(table2, [0; 0]);
let table3 = globals.get::<Table>("table3")?;
// sequence_values should only iterate until the first border
assert_eq!(table3, [1, 2]);
assert_eq!(
@@ -69,26 +58,6 @@ fn test_table() -> Result<()> {
vec![1, 2]
);
globals.set("table4", lua.create_sequence_from(vec![1, 2, 3, 4, 5])?)?;
let table4 = globals.get::<Table>("table4")?;
assert_eq!(
table4.clone().pairs().collect::<Result<Vec<(i64, i64)>>>()?,
vec![(1, 1), (2, 2), (3, 3), (4, 4), (5, 5)]
);
table4.raw_insert(4, 35)?;
table4.raw_insert(7, 7)?;
assert_eq!(
table4.clone().pairs().collect::<Result<Vec<(i64, i64)>>>()?,
vec![(1, 1), (2, 2), (3, 3), (4, 35), (5, 4), (6, 5), (7, 7)]
);
table4.raw_remove(1)?;
assert_eq!(
table4.clone().pairs().collect::<Result<Vec<(i64, i64)>>>()?,
vec![(1, 2), (2, 3), (3, 35), (4, 4), (5, 5), (6, 7)]
);
Ok(())
}
@@ -97,7 +66,7 @@ fn test_table_push_pop() -> Result<()> {
let lua = Lua::new();
// Test raw access
let table1 = lua.create_sequence_from(vec![123])?;
let table1 = lua.create_sequence_from([123])?;
table1.raw_push(321)?;
assert_eq!(table1, [123, 321]);
assert_eq!(table1.raw_pop::<i64>()?, 321);
@@ -123,10 +92,7 @@ fn test_table_push_pop() -> Result<()> {
table2.push(345)?;
assert_eq!(table2.len()?, 2);
assert_eq!(
table2
.clone()
.sequence_values::<i64>()
.collect::<Result<Vec<_>>>()?,
table2.sequence_values::<i64>().collect::<Result<Vec<_>>>()?,
vec![]
);
assert_eq!(table2.pop::<i64>()?, 345);
@@ -137,22 +103,53 @@ fn test_table_push_pop() -> Result<()> {
Ok(())
}
#[test]
fn test_table_insert_remove() -> Result<()> {
let lua = Lua::new();
let globals = lua.globals();
globals.set("table4", [1, 2, 3, 4, 5])?;
let table4 = globals.get::<Table>("table4")?;
assert_eq!(
table4.pairs().collect::<Result<Vec<(i64, i64)>>>()?,
vec![(1, 1), (2, 2), (3, 3), (4, 4), (5, 5)]
);
table4.raw_insert(4, 35)?;
table4.raw_insert(7, 7)?;
assert_eq!(
table4.pairs().collect::<Result<Vec<(i64, i64)>>>()?,
vec![(1, 1), (2, 2), (3, 3), (4, 35), (5, 4), (6, 5), (7, 7)]
);
table4.raw_remove(1)?;
assert_eq!(
table4.pairs().collect::<Result<Vec<(i64, i64)>>>()?,
vec![(1, 2), (2, 3), (3, 35), (4, 4), (5, 5), (6, 7)]
);
// Wrong index, tables are 1-indexed
assert!(table4.raw_insert(0, "123").is_err());
Ok(())
}
#[test]
fn test_table_clear() -> Result<()> {
let lua = Lua::new();
let t = lua.create_table()?;
// 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")
));
t.set_readonly(false);
}
let t = lua.create_table()?;
// Set array and hash parts
t.push("abc")?;
t.push("bcd")?;
@@ -217,15 +214,14 @@ fn test_table_pairs() -> Result<()> {
)
.eval::<Table>()?;
let table2 = table.clone();
for (i, kv) in table.pairs::<String, Value>().enumerate() {
let (k, _v) = kv.unwrap();
match i {
// Try to add a new key
0 => table2.set("new_key", "new_value")?,
0 => table.set("new_key", "new_value")?,
// Try to delete the 2nd key
1 => {
table2.set(k, Value::Nil)?;
table.set(k, Value::Nil)?;
lua.gc_collect()?;
}
_ => {}
@@ -304,21 +300,15 @@ fn test_metatable() -> Result<()> {
metatable.set("__index", lua.create_function(|_, ()| Ok("index_value"))?)?;
table.set_metatable(Some(metatable));
assert_eq!(table.get::<String>("any_key")?, "index_value");
match table.raw_get::<Value>("any_key")? {
Nil => {}
_ => panic!(),
}
assert_eq!(table.raw_get::<Value>("any_key")?, Value::Nil);
table.set_metatable(None);
match table.get::<Value>("any_key")? {
Nil => {}
_ => panic!(),
};
assert_eq!(table.get::<Value>("any_key")?, Value::Nil);
Ok(())
}
#[test]
fn test_table_eq() -> Result<()> {
fn test_table_equals() -> Result<()> {
let lua = Lua::new();
let globals = lua.globals();
@@ -358,6 +348,7 @@ fn test_table_pointer() -> Result<()> {
let table1 = lua.create_table()?;
let table2 = lua.create_table()?;
// Clone should not create a new table
assert_eq!(table1.to_pointer(), table1.clone().to_pointer());
assert_ne!(table1.to_pointer(), table2.to_pointer());
@@ -398,6 +389,40 @@ fn test_table_error() -> Result<()> {
Ok(())
}
#[test]
fn test_table_fmt() -> Result<()> {
let lua = Lua::new();
let table = lua
.load(
r#"
local t = {1, 2, 3, a = 5, b = { 6 }}
t["special-<chars>"] = 10
t[9.2] = 9.2
t[1.99] = 1.99
t[true] = true
t[false] = false
return t
"#,
)
.eval::<Table>()?;
assert!(format!("{table:?}").starts_with("Table(Ref("));
// Pretty print
assert_eq!(
format!("{table:#?}"),
"{\n [false] = false,\n [true] = true,\n [1] = 1,\n [1.99] = 1.99,\n [2] = 2,\n [3] = 3,\n [9.2] = 9.2,\n a = 5,\n b = {\n 6,\n },\n [\"special-<chars>\"] = 10,\n}"
);
let table2 = lua.create_table_from([("1", "first"), ("2", "second")])?;
assert_eq!(
format!("{table2:#?}"),
"{\n [\"1\"] = \"first\",\n [\"2\"] = \"second\",\n}"
);
Ok(())
}
#[test]
fn test_table_object_like() -> Result<()> {
let lua = Lua::new();
+122 -24
View File
@@ -12,6 +12,24 @@ use mlua::{
Value, Variadic,
};
#[test]
fn test_weak_lua() {
let lua = Lua::new();
let weak_lua = lua.weak();
assert!(weak_lua.try_upgrade().is_some());
drop(lua);
assert!(weak_lua.try_upgrade().is_none());
}
#[test]
#[should_panic(expected = "Lua instance is destroyed")]
fn test_weak_lua_panic() {
let lua = Lua::new();
let weak_lua = lua.weak();
drop(lua);
let _ = weak_lua.upgrade();
}
#[cfg(not(feature = "luau"))]
#[test]
fn test_safety() -> Result<()> {
@@ -106,7 +124,6 @@ fn test_exec() -> Result<()> {
"#,
)
.eval()?;
println!("checkpoint");
assert!(module.contains_key("func")?);
assert_eq!(module.get::<Function>("func")?.call::<String>(())?, "hello");
@@ -323,7 +340,7 @@ fn test_error() -> Result<()> {
let return_string_error = globals.get::<Function>("return_string_error")?;
assert!(return_string_error.call::<Error>(()).is_ok());
match lua.load("if youre happy and you know it syntax error").exec() {
match lua.load("if you are happy and you know it syntax error").exec() {
Err(Error::SyntaxError {
incomplete_input: false,
..
@@ -631,8 +648,7 @@ fn test_recursive_mut_callback_error() -> Result<()> {
// Whoops, this will recurse into the function and produce another mutable reference!
lua.globals().get::<Function>("f")?.call::<()>(true)?;
println!("Should not get here, mutable aliasing has occurred!");
println!("value at {:p}", r as *mut _);
println!("value is {}", r);
println!("value at {:p} is {r}", r as *mut _);
}
Ok(())
@@ -846,10 +862,12 @@ fn test_application_data() -> Result<()> {
assert_eq!(format!("{s:?}"), "\"test1\"");
// Borrowing immutably and mutably of the same type is not allowed
assert!(lua.try_app_data_mut::<&str>().is_err());
match catch_unwind(AssertUnwindSafe(|| lua.app_data_mut::<&str>().unwrap())) {
Ok(_) => panic!("expected panic"),
Err(_) => {}
}
assert!(lua.try_app_data_ref::<Vec<&str>>().is_err());
drop((s, v));
// Test that application data is accessible from anywhere
@@ -874,6 +892,49 @@ fn test_application_data() -> Result<()> {
Ok(())
}
#[test]
fn test_rust_function() -> Result<()> {
let lua = Lua::new();
let globals = lua.globals();
lua.load(
r#"
function lua_function()
return rust_function()
end
-- Test to make sure chunk return is ignored
return 1
"#,
)
.exec()?;
let lua_function = globals.get::<Function>("lua_function")?;
let rust_function = lua.create_function(|_, ()| Ok("hello"))?;
globals.set("rust_function", rust_function)?;
assert_eq!(lua_function.call::<String>(())?, "hello");
Ok(())
}
#[test]
fn test_c_function() -> Result<()> {
let lua = Lua::new();
unsafe extern "C-unwind" fn c_function(state: *mut mlua::lua_State) -> std::os::raw::c_int {
ffi::lua_pushboolean(state, 1);
ffi::lua_setglobal(state, b"c_function\0" as *const _ as *const _);
0
}
let func = unsafe { lua.create_c_function(c_function)? };
func.call::<()>(())?;
assert_eq!(lua.globals().get::<bool>("c_function")?, true);
Ok(())
}
#[test]
#[cfg(not(target_arch = "wasm32"))]
fn test_recursion() -> Result<()> {
@@ -925,26 +986,6 @@ fn test_too_many_recursions() -> Result<()> {
Ok(())
}
#[test]
#[cfg(not(target_arch = "wasm32"))]
fn test_too_many_binds() -> Result<()> {
let lua = Lua::new();
let globals = lua.globals();
lua.load(
r#"
function f(...)
end
"#,
)
.exec()?;
let concat = globals.get::<Function>("f")?;
assert!(concat.bind(Variadic::from_iter(1..1000000)).is_err());
assert!(concat.call::<()>(Variadic::from_iter(1..1000000)).is_err());
Ok(())
}
#[test]
#[cfg(not(target_arch = "wasm32"))]
fn test_ref_stack_exhaustion() {
@@ -1172,6 +1213,38 @@ fn test_inspect_stack() -> Result<()> {
.set_name("chunk")
.exec()?;
let stack_info = lua.create_function(|lua, ()| {
let debug = lua.inspect_stack(1).unwrap(); // caller
let stack_info = debug.stack();
Ok(format!("{stack_info:?}"))
})?;
lua.globals().set("stack_info", stack_info)?;
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))]
lua.load(
r#"
local stack_info = stack_info
local function baz(a, b, c, ...)
return stack_info()
end
assert(baz() == 'DebugStack { num_ups: 1, num_params: 3, is_vararg: true }')
"#,
)
.exec()?;
// LuaJIT does not pass this test for some reason
#[cfg(feature = "lua51")]
lua.load(
r#"
local stack_info = stack_info
local function baz(a, b, c, ...)
return stack_info()
end
assert(baz() == 'DebugStack { num_ups: 1 }')
"#,
)
.exec()?;
Ok(())
}
@@ -1234,6 +1307,13 @@ fn test_warnings() -> Result<()> {
if matches!(*cause, Error::RuntimeError(ref err) if err == "warning error")
));
// Recursive warning
lua.set_warning_function(|lua, _, _| {
lua.warning("inner", false);
Ok(())
});
lua.warning("hello", false);
Ok(())
}
@@ -1322,3 +1402,21 @@ fn test_exec_raw() -> Result<()> {
Ok(())
}
#[test]
fn test_gc_drop_ref_thread() -> Result<()> {
let lua = Lua::new();
let t = lua.create_table()?;
lua.create_function(move |_, ()| {
_ = &t;
Ok(())
})?;
for _ in 0..10000 {
// GC will run eventually to collect the function and the table above
lua.create_table()?;
}
Ok(())
}
+3 -7
View File
@@ -107,7 +107,6 @@ fn test_thread() -> Result<()> {
}
#[test]
#[cfg(any(feature = "lua54", feature = "luau"))]
fn test_thread_reset() -> Result<()> {
use mlua::{AnyUserData, UserData};
use std::sync::Arc;
@@ -120,7 +119,8 @@ fn test_thread_reset() -> Result<()> {
let arc = Arc::new(());
let func: Function = lua.load(r#"function(ud) coroutine.yield(ud) end"#).eval()?;
let thread = lua.create_thread(func.clone())?;
let thread = lua.create_thread(lua.load("return 0").into_function()?)?; // Dummy function first
assert!(thread.reset(func.clone()).is_ok());
for _ in 0..2 {
assert_eq!(thread.status(), ThreadStatus::Resumable);
@@ -145,11 +145,7 @@ fn test_thread_reset() -> Result<()> {
assert!(thread.reset(func.clone()).is_err());
// Reset behavior has changed in Lua v5.4.4
// It's became possible to force reset thread by popping error object
assert!(matches!(
thread.status(),
ThreadStatus::Finished | ThreadStatus::Error
));
// Would pass in 5.4.4
assert!(matches!(thread.status(), ThreadStatus::Finished));
assert!(thread.reset(func.clone()).is_ok());
assert_eq!(thread.status(), ThreadStatus::Resumable);
}
+420 -4
View File
@@ -1,3 +1,4 @@
use std::any::TypeId;
use std::collections::HashMap;
use std::string::String as StdString;
use std::sync::Arc;
@@ -23,9 +24,11 @@ fn test_userdata() -> Result<()> {
let userdata2 = lua.create_userdata(UserData2(Box::new(2)))?;
assert!(userdata1.is::<UserData1>());
assert!(userdata1.type_id() == Some(TypeId::of::<UserData1>()));
assert!(!userdata1.is::<UserData2>());
assert!(userdata2.is::<UserData2>());
assert!(!userdata2.is::<UserData1>());
assert!(userdata2.type_id() == Some(TypeId::of::<UserData2>()));
assert_eq!(userdata1.borrow::<UserData1>()?.0, 1);
assert_eq!(*userdata2.borrow::<UserData2>()?.0, 2);
@@ -195,7 +198,7 @@ fn test_metamethods() -> Result<()> {
assert!(lua.load("userdata2 == userdata3").eval::<bool>()?);
assert!(userdata2 != userdata3); // because references are differ
assert!(userdata2.equals(userdata3)?);
assert!(userdata2.equals(&userdata3)?);
let userdata1: AnyUserData = globals.get("userdata1")?;
assert!(userdata1.metatable()?.contains(MetaMethod::Add)?);
@@ -261,7 +264,7 @@ fn test_gc_userdata() -> Result<()> {
impl UserData for MyUserdata {
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_method("access", |_, this, ()| {
assert!(this.id == 123);
assert_eq!(this.id, 123);
Ok(())
});
}
@@ -376,7 +379,18 @@ fn test_userdata_take() -> Result<()> {
fn test_userdata_destroy() -> Result<()> {
struct MyUserdata(#[allow(unused)] Arc<()>);
impl UserData for MyUserdata {}
impl UserData for MyUserdata {
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_method("try_destroy", |lua, _this, ()| {
let ud = lua.globals().get::<AnyUserData>("ud")?;
match ud.destroy() {
Err(Error::UserDataBorrowMutError) => {}
r => panic!("expected `UserDataBorrowMutError` error, got {:?}", r),
}
Ok(())
});
}
}
let rc = Arc::new(());
@@ -394,6 +408,23 @@ fn test_userdata_destroy() -> Result<()> {
assert_eq!(Arc::strong_count(&rc), 1);
let ud = lua.create_userdata(MyUserdata(rc.clone()))?;
assert_eq!(Arc::strong_count(&rc), 2);
let ud_ref = ud.borrow::<MyUserdata>()?;
// With active `UserDataRef` this methods only marks userdata as destructed
// without running destructor
ud.destroy().unwrap();
assert_eq!(Arc::strong_count(&rc), 2);
drop(ud_ref);
assert_eq!(Arc::strong_count(&rc), 1);
// We cannot destroy (internally) borrowed userdata
let ud = lua.create_userdata(MyUserdata(rc.clone()))?;
lua.globals().set("ud", &ud)?;
lua.load("ud:try_destroy()").exec().unwrap();
ud.destroy().unwrap();
assert_eq!(Arc::strong_count(&rc), 1);
Ok(())
}
@@ -418,6 +449,7 @@ fn test_user_values() -> Result<()> {
assert!(ud.nth_user_value::<Value>(65536).is_err());
// Named user values
let ud = lua.create_userdata(MyUserData)?;
ud.set_named_user_value("name", "alex")?;
ud.set_named_user_value("age", 10)?;
@@ -802,7 +834,6 @@ fn test_userdata_method_errors() -> Result<()> {
} => {
assert_eq!(to.as_deref(), Some("MyUserData.get_value"));
assert_eq!(name.as_deref(), Some("self"));
println!("{}", cause2.to_string());
assert_eq!(
cause2.to_string(),
"error converting Lua string to userdata (expected userdata of type 'MyUserData')"
@@ -881,3 +912,388 @@ fn test_nested_userdata_gc() -> Result<()> {
Ok(())
}
#[cfg(feature = "userdata-wrappers")]
#[test]
fn test_userdata_wrappers() -> Result<()> {
#[derive(Debug)]
struct MyUserData(i64);
impl UserData for MyUserData {
fn add_fields<F: UserDataFields<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;
Ok(())
})
}
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_method("dbg", |_, this, ()| Ok(format!("{this:?}")));
}
}
let lua = Lua::new();
let globals = lua.globals();
// Rc<T>
#[cfg(not(feature = "send"))]
{
use std::rc::Rc;
let ud = Rc::new(MyUserData(1));
globals.set("ud", ud.clone())?;
lua.load(
r#"
assert(ud.static == "constant")
local ok, err = pcall(function() ud.data = 2 end)
assert(
tostring(err):find("error mutably borrowing userdata") ~= nil,
"expected 'error mutably borrowing userdata', got '" .. tostring(err) .. "'"
)
assert(ud.data == 1)
assert(ud:dbg(), "MyUserData(1)")
"#,
)
.exec()
.unwrap();
// Test borrowing original userdata
{
let ud = globals.get::<AnyUserData>("ud")?;
assert!(ud.is::<Rc<MyUserData>>());
assert!(!ud.is::<MyUserData>());
assert_eq!(ud.borrow::<MyUserData>()?.0, 1);
assert!(matches!(
ud.borrow_mut::<MyUserData>(),
Err(Error::UserDataBorrowMutError)
));
assert!(ud.borrow_mut::<Rc<MyUserData>>().is_ok());
assert_eq!(ud.borrow_scoped::<MyUserData, _>(|x| x.0)?, 1);
assert!(matches!(
ud.borrow_mut_scoped::<MyUserData, _>(|_| ()),
Err(Error::UserDataBorrowMutError)
));
}
// Collect userdata
globals.set("ud", Nil)?;
lua.gc_collect()?;
assert_eq!(Rc::strong_count(&ud), 1);
// We must be able to mutate userdata when having one reference only
globals.set("ud", ud)?;
lua.load(
r#"
ud.data = 2
assert(ud.data == 2)
"#,
)
.exec()
.unwrap();
}
// Rc<RefCell<T>>
#[cfg(not(feature = "send"))]
{
use std::cell::RefCell;
use std::rc::Rc;
let ud = Rc::new(RefCell::new(MyUserData(2)));
globals.set("ud", ud.clone())?;
lua.load(
r#"
assert(ud.static == "constant")
assert(ud.data == 2)
ud.data = 10
assert(ud.data == 10)
assert(ud:dbg() == "MyUserData(10)")
"#,
)
.exec()
.unwrap();
// Test borrowing original userdata
{
let ud = globals.get::<AnyUserData>("ud")?;
assert!(ud.is::<Rc<RefCell<MyUserData>>>());
assert!(!ud.is::<MyUserData>());
assert_eq!(ud.borrow::<MyUserData>()?.0, 10);
assert_eq!(ud.borrow_mut::<MyUserData>()?.0, 10);
ud.borrow_mut::<MyUserData>()?.0 = 20;
assert_eq!(ud.borrow::<MyUserData>()?.0, 20);
assert_eq!(ud.borrow_scoped::<MyUserData, _>(|x| x.0)?, 20);
ud.borrow_mut_scoped::<MyUserData, _>(|x| x.0 = 30)?;
assert_eq!(ud.borrow::<MyUserData>()?.0, 30);
// Double (read) borrow is okay
let _borrow = ud.borrow::<MyUserData>()?;
assert_eq!(ud.borrow::<MyUserData>()?.0, 30);
assert!(matches!(
ud.borrow_mut::<MyUserData>(),
Err(Error::UserDataBorrowMutError)
));
}
// Collect userdata
globals.set("ud", Nil)?;
lua.gc_collect()?;
assert_eq!(Rc::strong_count(&ud), 1);
// Check destroying wrapped UserDataRef without references in Lua
let ud = lua.convert::<UserDataRef<MyUserData>>(ud)?;
lua.gc_collect()?;
assert_eq!(ud.0, 30);
drop(ud);
}
// Arc<T>
{
let ud = Arc::new(MyUserData(3));
globals.set("ud", ud.clone())?;
lua.load(
r#"
assert(ud.static == "constant")
local ok, err = pcall(function() ud.data = 4 end)
assert(
tostring(err):find("error mutably borrowing userdata") ~= nil,
"expected 'error mutably borrowing userdata', got '" .. tostring(err) .. "'"
)
assert(ud.data == 3)
assert(ud:dbg() == "MyUserData(3)")
"#,
)
.exec()
.unwrap();
// Test borrowing original userdata
{
let ud = globals.get::<AnyUserData>("ud")?;
assert!(ud.is::<Arc<MyUserData>>());
assert!(!ud.is::<MyUserData>());
assert_eq!(ud.borrow::<MyUserData>()?.0, 3);
assert!(matches!(
ud.borrow_mut::<MyUserData>(),
Err(Error::UserDataBorrowMutError)
));
assert!(ud.borrow_mut::<Arc<MyUserData>>().is_ok());
assert_eq!(ud.borrow_scoped::<MyUserData, _>(|x| x.0)?, 3);
assert!(matches!(
ud.borrow_mut_scoped::<MyUserData, _>(|_| ()),
Err(Error::UserDataBorrowMutError)
));
}
// Collect userdata
globals.set("ud", Nil)?;
lua.gc_collect()?;
assert_eq!(Arc::strong_count(&ud), 1);
// We must be able to mutate userdata when having one reference only
globals.set("ud", ud)?;
lua.load(
r#"
ud.data = 4
assert(ud.data == 4)
"#,
)
.exec()
.unwrap();
}
// Arc<Mutex<T>>
{
use std::sync::Mutex;
let ud = Arc::new(Mutex::new(MyUserData(5)));
globals.set("ud", ud.clone())?;
lua.load(
r#"
assert(ud.static == "constant")
assert(ud.data == 5)
ud.data = 6
assert(ud.data == 6)
assert(ud:dbg() == "MyUserData(6)")
"#,
)
.exec()
.unwrap();
// Test borrowing original userdata
{
let ud = globals.get::<AnyUserData>("ud")?;
assert!(ud.is::<Arc<Mutex<MyUserData>>>());
assert!(!ud.is::<MyUserData>());
#[rustfmt::skip]
assert!(matches!(ud.borrow::<MyUserData>(), Err(Error::UserDataTypeMismatch)));
#[rustfmt::skip]
assert!(matches!(ud.borrow_mut::<MyUserData>(), Err(Error::UserDataTypeMismatch)));
assert_eq!(ud.borrow_scoped::<MyUserData, _>(|x| x.0)?, 6);
ud.borrow_mut_scoped::<MyUserData, _>(|x| x.0 = 8)?;
assert_eq!(ud.borrow_scoped::<MyUserData, _>(|x| x.0)?, 8);
}
// Collect userdata
globals.set("ud", Nil)?;
lua.gc_collect()?;
assert_eq!(Arc::strong_count(&ud), 1);
}
// Arc<RwLock<T>>
{
use std::sync::RwLock;
let ud = Arc::new(RwLock::new(MyUserData(9)));
globals.set("ud", ud.clone())?;
lua.load(
r#"
assert(ud.static == "constant")
assert(ud.data == 9)
ud.data = 10
assert(ud.data == 10)
assert(ud:dbg() == "MyUserData(10)")
"#,
)
.exec()
.unwrap();
// Test borrowing original userdata
{
let ud = globals.get::<AnyUserData>("ud")?;
assert!(ud.is::<Arc<RwLock<MyUserData>>>());
assert!(!ud.is::<MyUserData>());
#[rustfmt::skip]
assert!(matches!(ud.borrow::<MyUserData>(), Err(Error::UserDataTypeMismatch)));
#[rustfmt::skip]
assert!(matches!(ud.borrow_mut::<MyUserData>(), Err(Error::UserDataTypeMismatch)));
assert_eq!(ud.borrow_scoped::<MyUserData, _>(|x| x.0)?, 10);
ud.borrow_mut_scoped::<MyUserData, _>(|x| x.0 = 12)?;
assert_eq!(ud.borrow_scoped::<MyUserData, _>(|x| x.0)?, 12);
}
// Collect userdata
globals.set("ud", Nil)?;
lua.gc_collect()?;
assert_eq!(Arc::strong_count(&ud), 1);
}
// Arc<parking_lot::Mutex<T>>
{
use parking_lot::Mutex;
let ud = Arc::new(Mutex::new(MyUserData(13)));
globals.set("ud", ud.clone())?;
lua.load(
r#"
assert(ud.static == "constant")
assert(ud.data == 13)
ud.data = 14
assert(ud.data == 14)
assert(ud:dbg() == "MyUserData(14)")
"#,
)
.exec()
.unwrap();
// Test borrowing original userdata
{
let ud = globals.get::<AnyUserData>("ud")?;
assert!(ud.is::<Arc<Mutex<MyUserData>>>());
assert!(!ud.is::<MyUserData>());
assert_eq!(ud.borrow::<MyUserData>()?.0, 14);
assert_eq!(ud.borrow_mut::<MyUserData>()?.0, 14);
ud.borrow_mut::<MyUserData>()?.0 = 15;
assert_eq!(ud.borrow::<MyUserData>()?.0, 15);
assert_eq!(ud.borrow_scoped::<MyUserData, _>(|x| x.0)?, 15);
ud.borrow_mut_scoped::<MyUserData, _>(|x| x.0 = 16)?;
assert_eq!(ud.borrow::<MyUserData>()?.0, 16);
// Double borrow is not allowed
let _borrow = ud.borrow::<MyUserData>()?;
assert!(matches!(
ud.borrow::<MyUserData>(),
Err(Error::UserDataBorrowError)
));
}
// Collect userdata
globals.set("ud", Nil)?;
lua.gc_collect()?;
assert_eq!(Arc::strong_count(&ud), 1);
// Check destroying wrapped UserDataRef without references in Lua
let ud = lua.convert::<UserDataRef<MyUserData>>(ud)?;
lua.gc_collect()?;
assert_eq!(ud.0, 16);
drop(ud);
}
// Arc<parking_lot::RwLock<T>>
{
use parking_lot::RwLock;
let ud = Arc::new(RwLock::new(MyUserData(17)));
globals.set("ud", ud.clone())?;
lua.load(
r#"
assert(ud.static == "constant")
assert(ud.data == 17)
ud.data = 18
assert(ud.data == 18)
assert(ud:dbg() == "MyUserData(18)")
"#,
)
.exec()
.unwrap();
// Test borrowing original userdata
{
let ud = globals.get::<AnyUserData>("ud")?;
assert!(ud.is::<Arc<RwLock<MyUserData>>>());
assert!(!ud.is::<MyUserData>());
assert_eq!(ud.borrow::<MyUserData>()?.0, 18);
assert_eq!(ud.borrow_mut::<MyUserData>()?.0, 18);
ud.borrow_mut::<MyUserData>()?.0 = 19;
assert_eq!(ud.borrow::<MyUserData>()?.0, 19);
assert_eq!(ud.borrow_scoped::<MyUserData, _>(|x| x.0)?, 19);
ud.borrow_mut_scoped::<MyUserData, _>(|x| x.0 = 20)?;
assert_eq!(ud.borrow::<MyUserData>()?.0, 20);
// Multiple read borrows are allowed with parking_lot::RwLock
let _borrow1 = ud.borrow::<MyUserData>()?;
let _borrow2 = ud.borrow::<MyUserData>()?;
assert!(matches!(
ud.borrow_mut::<MyUserData>(),
Err(Error::UserDataBorrowMutError)
));
}
// Collect userdata
globals.set("ud", Nil)?;
lua.gc_collect()?;
assert_eq!(Arc::strong_count(&ud), 1);
// Check destroying wrapped UserDataRef without references in Lua
let ud = lua.convert::<UserDataRef<MyUserData>>(ud)?;
lua.gc_collect()?;
assert_eq!(ud.0, 20);
drop(ud);
}
Ok(())
}
+79 -2
View File
@@ -52,7 +52,7 @@ fn test_value_eq() -> Result<()> {
assert!(string1 == string2);
assert!(string1.equals(&string2)?);
assert!(num1 == num2);
assert!(num1.equals(num2)?);
assert!(num1.equals(&num2)?);
assert!(num1 != num3);
assert!(func1 == func2);
assert!(func1 != func3);
@@ -88,30 +88,79 @@ fn test_multi_value() {
assert!(multi_value.is_empty());
}
#[test]
fn test_value_to_pointer() -> Result<()> {
let lua = Lua::new();
let globals = lua.globals();
lua.load(
r#"
table = {}
string = "hello"
num = 1
func = function() end
thread = coroutine.create(function() end)
"#,
)
.exec()?;
globals.set("null", Value::NULL)?;
let table: Value = globals.get("table")?;
let string: Value = globals.get("string")?;
let num: Value = globals.get("num")?;
let func: Value = globals.get("func")?;
let thread: Value = globals.get("thread")?;
let null: Value = globals.get("null")?;
let ud: Value = Value::UserData(lua.create_any_userdata(())?);
assert!(!table.to_pointer().is_null());
assert!(!string.to_pointer().is_null());
assert!(num.to_pointer().is_null());
assert!(!func.to_pointer().is_null());
assert!(!thread.to_pointer().is_null());
assert!(null.to_pointer().is_null());
assert!(!ud.to_pointer().is_null());
Ok(())
}
#[test]
fn test_value_to_string() -> Result<()> {
let lua = Lua::new();
assert_eq!(Value::Nil.to_string()?, "nil");
assert_eq!(Value::Nil.type_name(), "nil");
assert_eq!(Value::Boolean(true).to_string()?, "true");
assert_eq!(Value::Boolean(true).type_name(), "boolean");
assert_eq!(Value::NULL.to_string()?, "null");
assert_eq!(Value::NULL.type_name(), "lightuserdata");
assert_eq!(
Value::LightUserData(LightUserData(0x1 as *const c_void as *mut _)).to_string()?,
"lightuserdata: 0x1"
);
assert_eq!(Value::Integer(1).to_string()?, "1");
assert_eq!(Value::Integer(1).type_name(), "integer");
assert_eq!(Value::Number(34.59).to_string()?, "34.59");
assert_eq!(Value::Number(34.59).type_name(), "number");
#[cfg(all(feature = "luau", not(feature = "luau-vector4")))]
assert_eq!(
Value::Vector(mlua::Vector::new(10.0, 11.1, 12.2)).to_string()?,
"vector(10, 11.1, 12.2)"
);
#[cfg(all(feature = "luau", not(feature = "luau-vector4")))]
assert_eq!(
Value::Vector(mlua::Vector::new(10.0, 11.1, 12.2)).type_name(),
"vector"
);
#[cfg(feature = "luau-vector4")]
assert_eq!(
Value::Vector(mlua::Vector::new(10.0, 11.1, 12.2, 13.3)).to_string()?,
"vector(10, 11.1, 12.2, 13.3)"
);
assert_eq!(Value::String(lua.create_string("hello")?).to_string()?, "hello");
let s = Value::String(lua.create_string("hello")?);
assert_eq!(s.to_string()?, "hello");
assert_eq!(s.type_name(), "string");
let table: Value = lua.load("{}").eval()?;
assert!(table.to_string()?.starts_with("table:"));
@@ -119,18 +168,22 @@ fn test_value_to_string() -> Result<()> {
.load("setmetatable({}, {__tostring = function() return 'test table' end})")
.eval()?;
assert_eq!(table.to_string()?, "test table");
assert_eq!(table.type_name(), "table");
let func: Value = lua.load("function() end").eval()?;
assert!(func.to_string()?.starts_with("function:"));
assert_eq!(func.type_name(), "function");
let thread: Value = lua.load("coroutine.create(function() end)").eval()?;
assert!(thread.to_string()?.starts_with("thread:"));
assert_eq!(thread.type_name(), "thread");
lua.register_userdata_type::<StdString>(|reg| {
reg.add_meta_method("__tostring", |_, this, ()| Ok(this.clone()));
})?;
let ud: Value = Value::UserData(lua.create_any_userdata(String::from("string userdata"))?);
assert_eq!(ud.to_string()?, "string userdata");
assert_eq!(ud.type_name(), "userdata");
struct MyUserData;
impl UserData for MyUserData {}
@@ -139,11 +192,13 @@ fn test_value_to_string() -> Result<()> {
let err = Value::Error(Box::new(Error::runtime("test error")));
assert_eq!(err.to_string()?, "runtime error: test error");
assert_eq!(err.type_name(), "error");
#[cfg(feature = "luau")]
{
let buf = Value::Buffer(lua.create_buffer(b"hello")?);
assert!(buf.to_string()?.starts_with("buffer:"));
assert_eq!(buf.type_name(), "buffer");
// Set `__tostring` metamethod for buffer
let mt = lua.load("{__tostring = buffer.tostring}").eval()?;
@@ -241,3 +296,25 @@ fn test_value_conversions() -> Result<()> {
Ok(())
}
#[test]
fn test_value_exhaustive_match() {
match Value::Nil {
Value::Nil => {}
Value::Boolean(_) => {}
Value::LightUserData(_) => {}
Value::Integer(_) => {}
Value::Number(_) => {}
#[cfg(feature = "luau")]
Value::Vector(_) => {}
Value::String(_) => {}
Value::Table(_) => {}
Value::Function(_) => {}
Value::Thread(_) => {}
Value::UserData(_) => {}
#[cfg(feature = "luau")]
Value::Buffer(_) => {}
Value::Error(_) => {}
Value::Other(_) => {}
}
}