Compare commits

..

68 Commits

Author SHA1 Message Date
Alex Orlenko 5f0e06fb66 Update CHANGELOG 2026-04-22 00:06:11 +01:00
Alex Orlenko 181c9d07b7 v0.12.0-rc.1 2026-04-20 00:24:58 +01:00
Alex Orlenko 4e827179d1 Update compile tests messages 2026-04-20 00:19:56 +01:00
Alex Orlenko cc26dcd4ff Bump rustyline 2026-04-20 00:16:34 +01:00
Alex Orlenko 201e30bc07 Add UserDataOwned<T> wrapper to take ownership of userdata T
It implements `FromLua` and takes ownership of a Lua userdata value.
The semantics is similar to `AnyUserData::take`, preventing any
further use from Lua.

Closes #686
2026-04-20 00:05:57 +01:00
Alex Orlenko 4e028d8409 Change AnyUserData::type_name to return LuaString instead.
This avoids unnecessary allocation and returns type name as it stored in metatable.
2026-04-19 23:26:24 +01:00
Alex Orlenko 3d1ae981d3 Update docs 2026-04-19 22:12:17 +01:00
Alex Orlenko 8c93948f2f Fix tests 2026-04-18 16:13:23 +01:00
Alex Orlenko f2b5cc44de traits module no longer need to be public
The LuaNativeFn traits were moved to the `function` module and all other traits
as re-exported.
2026-04-18 16:09:17 +01:00
Alex Orlenko 27f91dfd1b Accept any error in Function::wrap/wrap_mut/wrap_async
Previously wrapped functions were required to return `mlua::Result`.
Now it's possible to wrap functions returning any errors as long as
they implement `std::error::Error`.

Existing code remains compatible with `mlua::Result` as this type
is not converted to an external error.
2026-04-18 16:01:35 +01:00
Alex Orlenko 75ff11f795 Move LuaNativeFn/Mut into function module 2026-04-18 14:56:24 +01:00
Alex Orlenko df6097ab38 Mark Luau CompileConstant as non_exhaustive 2026-04-18 14:51:54 +01:00
Alex Orlenko 65bb6279ee Update serde types visibility 2026-04-18 14:47:26 +01:00
Alex Orlenko 31b88e85bb cargo fmt 2026-04-18 13:26:42 +01:00
Alex Orlenko 3be4745190 Add initial Luau integer64 type support
RFC: https://rfcs.luau.org/type-long-integer.html
Unfortunately this type is not backward compatible with regular numbers
and require a special "integer" library.
It's not integrated with `Value` enum to keep it simple.
2026-04-18 13:14:05 +01:00
Alex Orlenko c52deec988 Use c_int for userdata metatable id 2026-04-18 13:14:05 +01:00
三咲雅 misaki masa 3ab3c997b3 feat: support external strings for Cow<str> and Cow<CStr> (#692) 2026-04-09 10:45:15 +01:00
Alex Orlenko 5872ed70f5 Make traits module public 2026-04-04 15:04:00 +01:00
Alex Orlenko e7e92b4f6f Make chunk module public 2026-04-03 13:58:05 +01:00
Alex Orlenko d27693b61a Make error module public 2026-03-29 10:26:45 +01:00
Alex Orlenko c9848d6faf Update doc/example for (hidden) Lua::exec_raw_lua 2026-03-28 23:57:35 +00:00
Alex Orlenko 9126bb8ce0 Add RawLua::pop method 2026-03-28 22:37:25 +00:00
Alex Orlenko 7f1d716a44 Remove deprecated Lua::load_from_function 2026-03-28 17:40:22 +00:00
Alex Orlenko be56e2205c Update GC step_size doc 2026-03-28 17:38:49 +00:00
Alex Orlenko c5aadc68cd Open UserDataMethods::add_method_once and UserDataMethods::add_async_method_once 2026-03-28 17:17:57 +00:00
Alex Orlenko a5ae2a1fc3 Update useratom doc 2026-03-28 17:07:03 +00:00
Alex Orlenko 59872da63d mlua-sys: Bump lua-src and luajit-src dependencies 2026-03-28 16:31:19 +00:00
Alex Orlenko a24d2151af Minor fixes in docs 2026-03-27 22:54:56 +00:00
Alex Orlenko 56c227fd7e Make state module public 2026-03-08 00:06:18 +00:00
Alex Orlenko d5d66abe42 Refactor GC control API
- Replace `gc_inc/gc_gen` with `gc_set_mode`
- Add `GcIncParams` and `GcGenParams` for GC tuning
- Remove `gc_step_kbytes` (it's very rare needed and Lua 5.5 has changed the input param from kbytes to bytes)
2026-03-07 23:52:10 +00:00
Alex Orlenko a2d8b21964 Make luau module public 2026-03-01 16:40:19 +00:00
Alex Orlenko a9604c4946 Rename Luau's TextRequirer to FsRequirer 2026-03-01 16:27:41 +00:00
Alex Orlenko 81ae8e1393 Make Chunk::wrap public 2026-03-01 16:17:11 +00:00
Alex Orlenko a959b98d30 Add chunk module doc and update prelude re-exports 2026-03-01 16:11:38 +00:00
Alex Orlenko efd0856033 Derive PartialEq for Thread 2026-02-28 14:52:59 +00:00
Alex Orlenko c91066006f Make thread module public 2026-02-28 14:44:50 +00:00
Alex Orlenko a45fe9bb93 Bump luau-src to 0.19 (Luau 0.710) 2026-02-28 12:03:37 +00:00
Alex Orlenko f1a97e4193 Open Thread::state() that returns *mut lua_State pointer. 2026-02-28 11:48:59 +00:00
Alex Orlenko 47e6a37323 Add shortcuts to check thread status (Thread::is_resumable(), Thread::is_finished() etc) 2026-02-28 11:46:05 +00:00
Alex Orlenko bf0c96908f Remove lifetime from BorrowedStr and BorrowedBytes
The underlying `ValueRef` is cheap to clone as only increases reference count,
instead of allocating a new Lua stack slot.
2026-02-23 10:24:31 +00:00
Alex Orlenko 8817720362 Re-export (hidden) TablePairs and TableSequence 2026-02-23 09:51:24 +00:00
Alex Orlenko 35294359ad Inline doc for some types 2026-02-22 19:58:28 +00:00
Alex Orlenko eb76db59da Make userdata module public 2026-02-22 19:52:57 +00:00
Alex Orlenko 33bf3ffde7 Fix doc warnings 2026-02-22 14:37:23 +00:00
Alex Orlenko 0f3fdb0539 Make string module public 2026-02-22 14:33:38 +00:00
Alex Orlenko 79d438aaad Build CI docs on main branch 2026-02-22 13:28:48 +00:00
Alex Orlenko 30cf4bef58 Use RwLock directly instead of UserDataCell 2026-02-22 00:13:19 +00:00
Alex Orlenko 5776c72208 Use parking_lot::RwLock in UserDataCell container in "send" mode.
In non-send mode, mimic the `RwLock` API (using `Cell<isize>` counter).
We're continue manually operating the underlying `RawRwLock` for flexibility.
2026-02-21 23:18:39 +00:00
Alex Orlenko 943c3aed58 Some minor fixes in userdata cell 2026-02-21 21:19:42 +00:00
Alex Orlenko 8fcb6a8416 Update dependencies 2026-02-21 15:38:00 +00:00
Alex Orlenko 452dc8be88 clippy 2026-02-21 15:31:48 +00:00
Alex Orlenko 63a255bbc9 Replace is_sync specialization trick with MaybeSync trait bound.
The `is_sync::<T>()` runtime check relied on implicit specialization via
`Copy`/`Clone` array behavior, which has changed in Rust 1.86+.
`UserDataRef` always taking an exclusive lock even for `Sync` userdata,
preventing concurrent shared borrows.

With the `send` feature flag enabled, userdata types must now be `Send + Sync`.
This is a breaking change, `T: Send + !Sync` userdata types can be wrapped in a `Mutex`
or used inside a `Scope` where this restriction is lifted.
2026-02-21 15:05:55 +00:00
Alex Orlenko 151adc0e87 Implement pretty debug format for AnyUserData similar to Value::UserData. 2026-02-20 20:31:29 +00:00
Alex Orlenko 7f3ec63ab5 Support __todebugstring for pretty userdata debug output
Close #681
2026-02-20 18:59:53 +00:00
Alex Orlenko f19c6aac3b Fix tests 2026-02-12 16:20:51 +00:00
Alex Orlenko 29af448ad9 Update README to indicate dev status 2026-02-12 15:28:32 +00:00
Alex Orlenko 497d84828a Add CI to build dev docs 2026-02-03 22:40:29 +00:00
Alex Orlenko 0e489901a5 Update debug module
Move debug types from root to new new module.
2026-02-03 22:39:33 +00:00
Alex Orlenko 88063e756f Make table module public 2026-01-30 13:18:46 +00:00
Alex Orlenko c8436e2b80 Make function module public
Reduce number of function-specific types exported to the mlua root and keep
them inside the module.
2026-01-30 13:18:05 +00:00
Alex Orlenko 613748ec16 Use Error::from_lua_conversion helper 2026-01-29 23:09:52 +00:00
Alex Orlenko 2fbd266da6 Remove Error::ToLuaConversionError
This variant used only once and not practically useful.
2026-01-29 23:04:30 +00:00
Alex Orlenko c79b5e9cdb cargo fmt 2026-01-29 23:03:56 +00:00
Alex Orlenko 2ace892613 Rename string::String to LuaString 2026-01-29 18:45:41 +00:00
Alex Orlenko c1ffd4e790 Replace get_or_insert_with with get_or_insert_default 2026-01-29 10:46:58 +00:00
Alex Orlenko d9c139b55f Rust 2024 2026-01-29 10:33:28 +00:00
Alex Orlenko 0c4206c97d Bump min Rust version to 1.88 2026-01-29 10:13:04 +00:00
Alex Orlenko a985dc7a37 Start 0.12.0-dev.1 2026-01-29 10:07:05 +00:00
81 changed files with 2167 additions and 1510 deletions
+68
View File
@@ -0,0 +1,68 @@
name: Documentation (main)
on:
push:
branches: [main]
workflow_dispatch:
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
contents: read
pages: write
id-token: write
# Allow only one concurrent deployment
concurrency:
group: pages
cancel-in-progress: true
jobs:
build:
name: Build Documentation
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@main
- uses: dtolnay/rust-toolchain@nightly
- uses: Swatinem/rust-cache@v2
- name: Build documentation
env:
RUSTDOCFLAGS: "--cfg docsrs"
run: |
cargo +nightly doc --no-deps \
--features "lua55,vendored,async,send,serde,macros,anyhow,userdata-wrappers"
- name: Create index redirect
run: |
echo '<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Redirecting to mlua documentation</title>
<meta http-equiv="refresh" content="0; URL=mlua/index.html">
<link rel="canonical" href="mlua/index.html">
</head>
<body>
<p>Redirecting to <a href="mlua/index.html">mlua documentation</a>...</p>
</body>
</html>' > target/doc/index.html
- name: Setup Pages
uses: actions/configure-pages@v5
- name: Upload artifact
uses: actions/upload-pages-artifact@v4
with:
path: target/doc
deploy:
name: Deploy to GitHub Pages
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
+19
View File
@@ -1,3 +1,22 @@
## v0.12.0-rc.1 (Apr 21, 2026)
- Rust 2024 edition
- Removed `Error::ToLuaConversionError` variant as it was unused (and not practically useful)
- New modules to group data types: `chunk`, `debug`, `error`, `function`, `table`, `string`, `state`, `thread`, `userdata`, `luau`
- Support `__todebugstring` metamethod for pretty formatting userdata value (for debugging)
- New `MaybeSync` trait that is required for userdata types
- Removed lifetime from `BorrowedStr` and `BorrowedBytes`
- New `Thread` methods: `is_resumable`, `is_running`, `is_finished`, `is_error`
- Added `Thread::state` to get raw Lua state pointer
- Luau `TextRequirer` is renamed to `FsRequirer`
- GC interface refactor: `Lua::gc_inc/Lua::gc_gen` is replaced with `gc_set_mode`
- Added `GcIncParams` and `GcGenParams` for GC tuning
- New `UserDataMethods::add_method_once` and `UserDataMethods::add_async_method_once`
- Initial Luau integer64 type support
- Changed interface of `Function::wrap/wrap_mut/wrap_async` to support any Error type
- Changed `AnyUserData::type_name` to return `LuaString` instead
- Added `UserDataOwned<T>` wrapper to take ownership of userdata `T` and implements `FromLua`
## v0.11.6 (Jan 27, 2026)
- Added Lua 5.5 support (`lua55` feature flag)
+7 -8
View File
@@ -1,9 +1,9 @@
[package]
name = "mlua"
version = "0.11.6" # remember to update mlua_derive
version = "0.12.0-rc.1" # remember to update mlua_derive
authors = ["Aleksandr Orlenko <zxteam@pm.me>", "kyren <catherine@kyju.org>"]
rust-version = "1.85.0"
edition = "2021"
rust-version = "1.88"
edition = "2024"
repository = "https://github.com/mlua-rs/mlua"
documentation = "https://docs.rs/mlua"
readme = "README.md"
@@ -61,10 +61,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"
libc = "0.2"
ffi = { package = "mlua-sys", version = "0.10.0", path = "mlua-sys" }
ffi = { package = "mlua-sys", version = "0.11.0-rc.1", path = "mlua-sys" }
[dev-dependencies]
trybuild = "1.0"
@@ -78,10 +77,10 @@ static_assertions = "1.0"
hyper = { version = "1.2", features = ["full"] }
hyper-util = { version = "0.1.3", features = ["full"] }
http-body-util = "0.1.1"
reqwest = { version = "0.12", features = ["json"] }
reqwest = { version = "0.13", features = ["json"] }
tempfile = "3"
criterion = { version = "0.7", features = ["async_tokio"] }
rustyline = "17.0"
criterion = { version = "0.8", features = ["async_tokio"] }
rustyline = "18.0"
tokio = { version = "1.0", features = ["full"] }
[lints.rust]
+3 -1
View File
@@ -17,6 +17,8 @@
[Benchmarks]: https://github.com/khvzak/script-bench-rs
[FAQ]: FAQ.md
## The main branch is the development version of `mlua`. Please see the [v0.11](https://github.com/mlua-rs/mlua/tree/v0.11) branch for the stable versions of `mlua`.
`mlua` is a set of bindings to the [Lua](https://www.lua.org) programming language for Rust with a goal of providing a
_safe_ (as much as possible), high level, easy to use, practical and flexible API.
@@ -125,7 +127,7 @@ my_project $ LUA_LIB=$HOME/tmp/lua-5.2.4/src LUA_LIB_NAME=lua LUA_LINK=static ca
Just enable the `vendored` feature and cargo will automatically build and link the specified Lua/LuaJIT version. This is the easiest way to get started with `mlua`.
### Standalone mode
In standalone mode, `mlua` allows adding scripting support to your application with a gently configured Lua runtime to ensure safety and soundness.
In standalone mode, `mlua` allows adding scripting support to your application with a properly configured Lua runtime to ensure safety and soundness.
Add to `Cargo.toml`:
+1 -1
View File
@@ -1,7 +1,7 @@
use std::sync::atomic::{AtomicUsize, Ordering};
use std::time::Duration;
use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
use criterion::{BatchSize, Criterion, criterion_group, criterion_main};
use tokio::runtime::Runtime;
use tokio::task;
+1 -1
View File
@@ -1,6 +1,6 @@
use std::time::Duration;
use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
use criterion::{BatchSize, Criterion, criterion_group, criterion_main};
use mlua::prelude::*;
+3 -3
View File
@@ -1,6 +1,6 @@
## mlua v0.10 release notes
The v0.10 version of mlua has goal to improve the user experience while keeping the same performance and safety guarantees.
The v0.10 version of mlua has a 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/mlua-rs/mlua/blob/main/CHANGELOG.md
@@ -40,7 +40,7 @@ assert_eq!(lua.globals().get::<i32>("i")?, 20);
Under the hood, to synchronize access to the Lua state, mlua uses [`ReentrantMutex`] which can be recursively locked by a single thread. Only one thread can execute Lua code at a time, but it's possible to share Lua values between threads.
This has some performance penalties (about 10-20%) compared to the lock free mode. This flag is disabled by default and does not supported in module mode.
This has some performance penalties (about 10-20%) compared to the lock free mode. This flag is disabled by default and is not supported in module mode.
[`ReentrantMutex`]: https://docs.rs/parking_lot/latest/parking_lot/type.ReentrantMutex.html
@@ -144,7 +144,7 @@ The following `Scope` methods were changed:
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.
In mlua v0.9 this could cause a 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:
+3 -3
View File
@@ -152,9 +152,9 @@ It will automatically trigger JIT compilation for new Lua chunks. To disable it,
#### 1. Better error reporting
When calling a Rust function from Lua and passing wrong arguments, previous mlua versions reported a error message without any context or reference to the particular argument.
When calling a Rust function from Lua and passing wrong arguments, previous mlua versions reported an error message without any context or reference to the particular argument.
In v0.9 it reports a error message with the argument index and expected type:
In v0.9 it reports an error message with the argument index and expected type:
```rust
let func = lua.create_function(|_, _a: i32| Ok(()))?;
@@ -327,7 +327,7 @@ Under the hood a new function `luaopen_alt_module` will be created for the Lua m
- `skip_memory_check` - skip memory allocation checks for some operations.
In module mode, mlua runs in unknown environment and cannot say are there any memory limits or not. As result, some operations that require memory allocation runs in
In module mode, mlua runs in an unknown environment and cannot tell whether there are any memory limits or not. As a result, some operations that require memory allocation run in
protected mode. Setting this attribute will improve performance of such operations with risk of having uncaught exceptions and memory leaks.
#### Improved Windows target
+1 -1
View File
@@ -5,7 +5,7 @@ use hyper::body::Incoming;
use hyper_util::client::legacy::Client as HyperClient;
use hyper_util::rt::TokioExecutor;
use mlua::{chunk, ExternalResult, Lua, Result, UserData, UserDataMethods};
use mlua::{ExternalResult, Lua, Result, UserData, UserDataMethods, chunk};
struct BodyReader(Incoming);
+1 -1
View File
@@ -1,4 +1,4 @@
use mlua::{chunk, ExternalResult, Lua, LuaSerdeExt, Result, Value};
use mlua::{ExternalResult, Lua, LuaSerdeExt, Result, Value, chunk};
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<()> {
+1 -1
View File
@@ -11,7 +11,7 @@ use hyper::{Request, Response};
use hyper_util::rt::TokioIo;
use tokio::net::TcpListener;
use mlua::{chunk, Error as LuaError, Function, Lua, String as LuaString, Table, UserData, UserDataMethods};
use mlua::{Error as LuaError, Function, Lua, String as LuaString, Table, UserData, UserDataMethods, chunk};
/// Wrapper around incoming request that implements UserData
struct LuaRequest(SocketAddr, Request<Incoming>);
+1 -1
View File
@@ -4,7 +4,7 @@ use std::net::SocketAddr;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::{TcpListener, TcpStream};
use mlua::{chunk, BString, Function, Lua, UserData, UserDataMethods};
use mlua::{BString, Function, Lua, UserData, UserDataMethods, chunk};
struct LuaTcpStream(TcpStream);
+1 -1
View File
@@ -1,7 +1,7 @@
use std::f32;
use std::iter::FromIterator;
use mlua::{chunk, FromLua, Function, Lua, MetaMethod, Result, UserData, UserDataMethods, Value, Variadic};
use mlua::{FromLua, Function, Lua, MetaMethod, Result, UserData, UserDataMethods, Value, Variadic, chunk};
fn main() -> Result<()> {
// You can create a new Lua state with `Lua::new()`. This loads the default Lua std library
+1 -1
View File
@@ -1,4 +1,4 @@
use mlua::{chunk, Lua, MetaMethod, Result, UserData};
use mlua::{Lua, MetaMethod, Result, UserData, chunk};
#[derive(Default)]
struct Rectangle {
+5 -5
View File
@@ -1,8 +1,8 @@
[package]
name = "mlua-sys"
version = "0.10.0"
version = "0.11.0-rc.1"
authors = ["Aleksandr Orlenko <zxteam@pm.me>"]
rust-version = "1.85"
rust-version = "1.88"
edition = "2024"
repository = "https://github.com/mlua-rs/mlua"
documentation = "https://docs.rs/mlua-sys"
@@ -41,9 +41,9 @@ libc = "0.2"
cc = "1.0"
cfg-if = "1.0"
pkg-config = "0.3.17"
lua-src = { version = ">= 550.0.0, < 550.1.0", optional = true }
luajit-src = { version = ">= 210.6.0, < 210.7.0", optional = true }
luau0-src = { version = "0.18.0", optional = true }
lua-src = { version = ">= 550.1.0, < 550.2.0", optional = true }
luajit-src = { version = ">= 210.7.0, < 210.8.0", optional = true }
luau0-src = { version = "0.20.0", optional = true }
[lints.rust]
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(raw_dylib)'] }
+2
View File
@@ -38,8 +38,10 @@ unsafe extern "C-unwind" {
#[link_name = "luaL_checkinteger"]
pub fn luaL_checkinteger_(L: *mut lua_State, narg: c_int) -> c_int;
pub fn luaL_checkinteger64(L: *mut lua_State, narg: c_int) -> i64;
#[link_name = "luaL_optinteger"]
pub fn luaL_optinteger_(L: *mut lua_State, narg: c_int, def: c_int) -> c_int;
pub fn luaL_optinteger64(L: *mut lua_State, narg: c_int, def: i64) -> i64;
pub fn luaL_checkunsigned(L: *mut lua_State, narg: c_int) -> lua_Unsigned;
pub fn luaL_optunsigned(L: *mut lua_State, narg: c_int, def: lua_Unsigned) -> lua_Unsigned;
+31 -9
View File
@@ -65,14 +65,15 @@ pub const LUA_TBOOLEAN: c_int = 1;
pub const LUA_TLIGHTUSERDATA: c_int = 2;
pub const LUA_TNUMBER: c_int = 3;
pub const LUA_TVECTOR: c_int = 4;
pub const LUA_TINTEGER: c_int = 4;
pub const LUA_TVECTOR: c_int = 5;
pub const LUA_TSTRING: c_int = 5;
pub const LUA_TTABLE: c_int = 6;
pub const LUA_TFUNCTION: c_int = 7;
pub const LUA_TUSERDATA: c_int = 8;
pub const LUA_TTHREAD: c_int = 9;
pub const LUA_TBUFFER: c_int = 10;
pub const LUA_TSTRING: c_int = 6;
pub const LUA_TTABLE: c_int = 7;
pub const LUA_TFUNCTION: c_int = 8;
pub const LUA_TUSERDATA: c_int = 9;
pub const LUA_TTHREAD: c_int = 10;
pub const LUA_TBUFFER: c_int = 11;
/// Guaranteed number of Lua stack slots available to a C function.
pub const LUA_MINSTACK: c_int = 20;
@@ -153,6 +154,7 @@ unsafe extern "C-unwind" {
pub fn lua_tounsignedx(L: *mut lua_State, idx: c_int, isnum: *mut c_int) -> lua_Unsigned;
pub fn lua_tovector(L: *mut lua_State, idx: c_int) -> *const c_float;
pub fn lua_toboolean(L: *mut lua_State, idx: c_int) -> c_int;
pub fn lua_tointeger64(L: *mut lua_State, idx: c_int, isinteger: *mut c_int) -> i64;
pub fn lua_tolstring(L: *mut lua_State, idx: c_int, len: *mut usize) -> *const c_char;
pub fn lua_tostringatom(L: *mut lua_State, idx: c_int, atom: *mut c_int) -> *const c_char;
pub fn lua_tolstringatom(
@@ -182,6 +184,7 @@ unsafe extern "C-unwind" {
pub fn lua_pushnumber(L: *mut lua_State, n: lua_Number);
#[link_name = "lua_pushinteger"]
pub fn lua_pushinteger_(L: *mut lua_State, n: c_int);
pub fn lua_pushinteger64(L: *mut lua_State, n: i64);
pub fn lua_pushunsigned(L: *mut lua_State, n: lua_Unsigned);
#[cfg(not(feature = "luau-vector4"))]
pub fn lua_pushvector(L: *mut lua_State, x: c_float, y: c_float, z: c_float);
@@ -412,6 +415,11 @@ pub unsafe fn lua_isboolean(L: *mut lua_State, n: c_int) -> c_int {
(lua_type(L, n) == LUA_TBOOLEAN) as c_int
}
#[inline(always)]
pub unsafe fn lua_isinteger64(L: *mut lua_State, n: c_int) -> c_int {
(lua_type(L, n) == LUA_TINTEGER) as c_int
}
#[inline(always)]
pub unsafe fn lua_isvector(L: *mut lua_State, n: c_int) -> c_int {
(lua_type(L, n) == LUA_TVECTOR) as c_int
@@ -501,6 +509,12 @@ pub type lua_Coverage = unsafe extern "C-unwind" fn(
size: usize,
);
pub type lua_CounterFunction =
unsafe extern "C-unwind" fn(context: *mut c_void, function: *const c_char, linedefined: c_int);
pub type lua_CounterValue =
unsafe extern "C-unwind" fn(context: *mut c_void, kind: c_int, line: c_int, hits: u64);
unsafe extern "C-unwind" {
pub fn lua_stackdepth(L: *mut lua_State) -> c_int;
pub fn lua_getinfo(L: *mut lua_State, level: c_int, what: *const c_char, ar: *mut lua_Debug) -> c_int;
@@ -515,6 +529,14 @@ unsafe extern "C-unwind" {
pub fn lua_getcoverage(L: *mut lua_State, funcindex: c_int, context: *mut c_void, callback: lua_Coverage);
pub fn lua_getcounters(
L: *mut lua_State,
funcindex: c_int,
context: *mut c_void,
functionvisit: lua_CounterFunction,
countervisit: lua_CounterValue,
);
pub fn lua_debugtrace(L: *mut lua_State) -> *const c_char;
}
@@ -551,8 +573,8 @@ pub struct lua_Callbacks {
/// gets called when L is created (LP == parent) or destroyed (LP == NULL)
pub userthread: Option<unsafe extern "C-unwind" fn(LP: *mut lua_State, L: *mut lua_State)>,
/// gets called when a string is created; returned atom can be retrieved via tostringatom
pub useratom: Option<unsafe extern "C-unwind" fn(s: *const c_char, l: usize) -> i16>,
/// gets called when a string is created to assign an atom id
pub useratom: Option<unsafe extern "C-unwind" fn(L: *mut lua_State, s: *const c_char, l: usize) -> i16>,
/// gets called when BREAK instruction is encountered
pub debugbreak: Option<unsafe extern "C-unwind" fn(L: *mut lua_State, ar: *mut lua_Debug)>,
+1
View File
@@ -80,6 +80,7 @@ unsafe 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_integer64(cons: *mut lua_CompileConstant, l: i64);
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);
}
+2
View File
@@ -14,6 +14,7 @@ pub const LUA_UTF8LIBNAME: *const c_char = cstr!("utf8");
pub const LUA_MATHLIBNAME: *const c_char = cstr!("math");
pub const LUA_DBLIBNAME: *const c_char = cstr!("debug");
pub const LUA_VECLIBNAME: *const c_char = cstr!("vector");
pub const LUA_INTLIBNAME: *const c_char = cstr!("integer");
unsafe extern "C-unwind" {
pub fn luaopen_base(L: *mut lua_State) -> c_int;
@@ -27,6 +28,7 @@ unsafe extern "C-unwind" {
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;
pub fn luaopen_integer(L: *mut lua_State) -> c_int;
// open all builtin libraries
pub fn luaL_openlibs(L: *mut lua_State);
+4 -4
View File
@@ -59,7 +59,7 @@ impl Buffer {
/// Returns an adaptor implementing [`io::Read`], [`io::Write`] and [`io::Seek`] over the
/// buffer.
///
/// Buffer operations are infallible, none of the read/write functions will return a Err.
/// Buffer operations are infallible, none of the read/write functions will return an Err.
pub fn cursor(self) -> impl io::Read + io::Write + io::Seek {
BufferCursor(self, 0)
}
@@ -97,7 +97,7 @@ struct BufferCursor(Buffer, usize);
impl io::Read for BufferCursor {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let lua = self.0 .0.lua.lock();
let lua = self.0.0.lua.lock();
let data = self.0.as_slice(&lua);
if self.1 == data.len() {
return Ok(0);
@@ -111,7 +111,7 @@ impl io::Read for BufferCursor {
impl io::Write for BufferCursor {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let lua = self.0 .0.lua.lock();
let lua = self.0.0.lua.lock();
let data = self.0.as_slice_mut(&lua);
if self.1 == data.len() {
return Ok(0);
@@ -129,7 +129,7 @@ impl io::Write for BufferCursor {
impl io::Seek for BufferCursor {
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
let lua = self.0 .0.lua.lock();
let lua = self.0.0.lua.lock();
let data = self.0.as_slice(&lua);
let new_offset = match pos {
io::SeekFrom::Start(offset) => offset as i64,
+75 -72
View File
@@ -1,10 +1,16 @@
//! Lua chunk loading and execution.
//!
//! This module provides types for loading Lua source code or bytecode into a [`Chunk`],
//! configuring how it is compiled and executed, and converting it into a callable [`Function`].
//!
//! Chunks can be loaded from strings, byte slices, or files via the [`AsChunk`] trait.
use std::borrow::Cow;
use std::collections::HashMap;
use std::ffi::CString;
use std::io::Result as IoResult;
use std::panic::Location;
use std::path::{Path, PathBuf};
use std::string::String as StdString;
use crate::error::{Error, Result};
use crate::function::Function;
@@ -20,7 +26,7 @@ pub trait AsChunk {
/// Returns optional chunk name
///
/// See [`Chunk::set_name`] for possible name prefixes.
fn name(&self) -> Option<StdString> {
fn name(&self) -> Option<String> {
None
}
@@ -52,13 +58,13 @@ impl AsChunk for &str {
}
}
impl AsChunk for StdString {
impl AsChunk for String {
fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>> {
Ok(Cow::Owned(self.clone().into_bytes()))
}
}
impl AsChunk for &StdString {
impl AsChunk for &String {
fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>>
where
Self: 'a,
@@ -92,7 +98,7 @@ impl AsChunk for &Vec<u8> {
}
impl AsChunk for &Path {
fn name(&self) -> Option<StdString> {
fn name(&self) -> Option<String> {
Some(format!("@{}", self.display()))
}
@@ -102,7 +108,7 @@ impl AsChunk for &Path {
}
impl AsChunk for PathBuf {
fn name(&self) -> Option<StdString> {
fn name(&self) -> Option<String> {
Some(format!("@{}", self.display()))
}
@@ -112,7 +118,7 @@ impl AsChunk for PathBuf {
}
impl<C: AsChunk + ?Sized> AsChunk for Box<C> {
fn name(&self) -> Option<StdString> {
fn name(&self) -> Option<String> {
(**self).name()
}
@@ -136,7 +142,7 @@ impl<C: AsChunk + ?Sized> AsChunk for Box<C> {
#[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,
pub(crate) name: StdString,
pub(crate) name: String,
pub(crate) env: Result<Option<Table>>,
pub(crate) mode: Option<ChunkMode>,
pub(crate) source: IoResult<Cow<'a, [u8]>>,
@@ -154,13 +160,14 @@ pub enum ChunkMode {
/// Represents a constant value that can be used by Luau compiler.
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
#[non_exhaustive]
#[derive(Clone, Debug)]
pub enum CompileConstant {
Nil,
Boolean(bool),
Number(crate::Number),
Vector(crate::Vector),
String(StdString),
String(String),
}
#[cfg(any(feature = "luau", doc))]
@@ -192,7 +199,7 @@ impl From<&str> for CompileConstant {
}
#[cfg(any(feature = "luau", doc))]
type LibraryMemberConstantMap = HashMap<(StdString, StdString), CompileConstant>;
type LibraryMemberConstantMap = HashMap<(String, String), CompileConstant>;
/// Luau compiler
#[cfg(any(feature = "luau", doc))]
@@ -203,14 +210,14 @@ pub struct Compiler {
debug_level: u8,
type_info_level: u8,
coverage_level: u8,
vector_lib: Option<StdString>,
vector_ctor: Option<StdString>,
vector_type: Option<StdString>,
mutable_globals: Vec<StdString>,
userdata_types: Vec<StdString>,
libraries_with_known_members: Vec<StdString>,
vector_lib: Option<String>,
vector_ctor: Option<String>,
vector_type: Option<String>,
mutable_globals: Vec<String>,
userdata_types: Vec<String>,
libraries_with_known_members: Vec<String>,
library_constants: Option<LibraryMemberConstantMap>,
disabled_builtins: Vec<StdString>,
disabled_builtins: Vec<String>,
}
#[cfg(any(feature = "luau", doc))]
@@ -294,7 +301,7 @@ impl Compiler {
/// To set the library and method name, use the `lib.ctor` format.
#[doc(hidden)]
#[must_use]
pub fn set_vector_ctor(mut self, ctor: impl Into<StdString>) -> Self {
pub fn set_vector_ctor(mut self, ctor: impl Into<String>) -> Self {
let ctor = ctor.into();
let lib_ctor = ctor.split_once('.');
self.vector_lib = lib_ctor.as_ref().map(|&(lib, _)| lib.to_owned());
@@ -307,7 +314,7 @@ impl Compiler {
/// Sets alternative vector type name for type tables, in addition to default type `vector`.
#[doc(hidden)]
#[must_use]
pub fn set_vector_type(mut self, r#type: impl Into<StdString>) -> Self {
pub fn set_vector_type(mut self, r#type: impl Into<String>) -> Self {
self.vector_type = Some(r#type.into());
self
}
@@ -316,7 +323,7 @@ impl Compiler {
///
/// It disables the import optimization for fields accessed through it.
#[must_use]
pub fn add_mutable_global(mut self, global: impl Into<StdString>) -> Self {
pub fn add_mutable_global(mut self, global: impl Into<String>) -> Self {
self.mutable_globals.push(global.into());
self
}
@@ -325,21 +332,21 @@ impl Compiler {
///
/// It disables the import optimization for fields accessed through these.
#[must_use]
pub fn set_mutable_globals<S: Into<StdString>>(mut self, globals: impl IntoIterator<Item = S>) -> Self {
pub fn set_mutable_globals<S: Into<String>>(mut self, globals: impl IntoIterator<Item = S>) -> Self {
self.mutable_globals = globals.into_iter().map(|s| s.into()).collect();
self
}
/// Adds a userdata type to the list that will be included in the type information.
#[must_use]
pub fn add_userdata_type(mut self, r#type: impl Into<StdString>) -> Self {
pub fn add_userdata_type(mut self, r#type: impl Into<String>) -> Self {
self.userdata_types.push(r#type.into());
self
}
/// Sets a list of userdata types that will be included in the type information.
#[must_use]
pub fn set_userdata_types<S: Into<StdString>>(mut self, types: impl IntoIterator<Item = S>) -> Self {
pub fn set_userdata_types<S: Into<String>>(mut self, types: impl IntoIterator<Item = S>) -> Self {
self.userdata_types = types.into_iter().map(|s| s.into()).collect();
self
}
@@ -366,24 +373,21 @@ impl Compiler {
self.libraries_with_known_members.push(lib.clone());
}
self.library_constants
.get_or_insert_with(HashMap::new)
.get_or_insert_default()
.insert((lib, member), r#const.into());
self
}
/// Adds a builtin that should be disabled.
#[must_use]
pub fn add_disabled_builtin(mut self, builtin: impl Into<StdString>) -> Self {
pub fn add_disabled_builtin(mut self, builtin: impl Into<String>) -> Self {
self.disabled_builtins.push(builtin.into());
self
}
/// Sets a list of builtins that should be disabled.
#[must_use]
pub fn set_disabled_builtins<S: Into<StdString>>(
mut self,
builtins: impl IntoIterator<Item = S>,
) -> Self {
pub fn set_disabled_builtins<S: Into<String>>(mut self, builtins: impl IntoIterator<Item = S>) -> Self {
self.disabled_builtins = builtins.into_iter().map(|s| s.into()).collect();
self
}
@@ -477,11 +481,11 @@ impl Compiler {
options.mutableGlobals = mutable_globals_ptr;
options.userdataTypes = userdata_types_ptr;
options.librariesWithKnownMembers = libraries_with_known_members_ptr;
if let Some(map) = self.library_constants.as_ref() {
if !self.libraries_with_known_members.is_empty() {
LIBRARY_MEMBER_CONSTANT_MAP.with_borrow_mut(|gmap| *gmap = map.clone());
options.libraryMemberConstantCallback = Some(library_member_constant_callback);
}
if let Some(map) = self.library_constants.as_ref()
&& !self.libraries_with_known_members.is_empty()
{
LIBRARY_MEMBER_CONSTANT_MAP.with_borrow_mut(|gmap| *gmap = map.clone());
options.libraryMemberConstantCallback = Some(library_member_constant_callback);
}
options.disabledBuiltins = disabled_builtins_ptr;
ffi::luau_compile(source.as_ref(), options)
@@ -490,7 +494,7 @@ impl Compiler {
if bytecode.first() == Some(&0) {
// The rest of the bytecode is the error message starting with `:`
// See https://github.com/luau-lang/luau/blob/0.640/Compiler/src/Compiler.cpp#L4336
let message = StdString::from_utf8_lossy(&bytecode[2..]).into_owned();
let message = String::from_utf8_lossy(&bytecode[2..]).into_owned();
return Err(Error::SyntaxError {
incomplete_input: message.ends_with("<eof>"),
message,
@@ -513,7 +517,7 @@ impl Chunk<'_> {
/// - `@` - 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<StdString>) -> Self {
pub fn set_name(mut self, name: impl Into<String>) -> Self {
self.name = name.into();
self
}
@@ -662,19 +666,19 @@ impl Chunk<'_> {
///
/// It does nothing if the chunk is already binary or invalid.
fn compile(&mut self) {
if let Ok(ref source) = self.source {
if self.detect_mode() == ChunkMode::Text {
#[cfg(feature = "luau")]
if let Ok(data) = self.compiler.get_or_insert_with(Default::default).compile(source) {
self.source = Ok(Cow::Owned(data));
self.mode = Some(ChunkMode::Binary);
}
#[cfg(not(feature = "luau"))]
if let Ok(func) = self.lua.lock().load_chunk(None, None, None, source.as_ref()) {
let data = func.dump(false);
self.source = Ok(Cow::Owned(data));
self.mode = Some(ChunkMode::Binary);
}
if let Ok(ref source) = self.source
&& self.detect_mode() == ChunkMode::Text
{
#[cfg(feature = "luau")]
if let Ok(data) = self.compiler.get_or_insert_default().compile(source) {
self.source = Ok(Cow::Owned(data));
self.mode = Some(ChunkMode::Binary);
}
#[cfg(not(feature = "luau"))]
if let Ok(func) = self.lua.lock().load_chunk(None, None, None, source.as_ref()) {
let data = func.dump(false);
self.source = Ok(Cow::Owned(data));
self.mode = Some(ChunkMode::Binary);
}
}
}
@@ -687,33 +691,33 @@ impl Chunk<'_> {
// Try to fetch compiled chunk from cache
let mut text_source = None;
if let Ok(ref source) = self.source {
if self.detect_mode() == ChunkMode::Text {
let lua = self.lua.lock();
if let Some(cache) = lua.priv_app_data_ref::<ChunksCache>() {
if let Some(data) = cache.0.get(source.as_ref()) {
self.source = Ok(Cow::Owned(data.clone()));
self.mode = Some(ChunkMode::Binary);
return self;
}
}
text_source = Some(source.as_ref().to_vec());
if let Ok(ref source) = self.source
&& self.detect_mode() == ChunkMode::Text
{
let lua = self.lua.lock();
if let Some(cache) = lua.priv_app_data_ref::<ChunksCache>()
&& let Some(data) = cache.0.get(source.as_ref())
{
self.source = Ok(Cow::Owned(data.clone()));
self.mode = Some(ChunkMode::Binary);
return self;
}
text_source = Some(source.as_ref().to_vec());
}
// Compile and cache the chunk
if let Some(text_source) = text_source {
self.compile();
if let Ok(ref binary_source) = self.source {
if self.detect_mode() == ChunkMode::Binary {
let lua = self.lua.lock();
if let Some(mut cache) = lua.priv_app_data_mut::<ChunksCache>() {
cache.0.insert(text_source, binary_source.to_vec());
} else {
let mut cache = ChunksCache(HashMap::new());
cache.0.insert(text_source, binary_source.to_vec());
lua.set_priv_app_data(cache);
};
if let Ok(ref binary_source) = self.source
&& self.detect_mode() == ChunkMode::Binary
{
let lua = self.lua.lock();
if let Some(mut cache) = lua.priv_app_data_mut::<ChunksCache>() {
cache.0.insert(text_source, binary_source.to_vec());
} else {
let mut cache = ChunksCache(HashMap::new());
cache.0.insert(text_source, binary_source.to_vec());
lua.set_priv_app_data(cache);
}
}
}
@@ -761,7 +765,7 @@ impl Chunk<'_> {
ChunkMode::Text
}
fn convert_name(name: StdString) -> Result<CString> {
fn convert_name(name: String) -> Result<CString> {
CString::new(name).map_err(|err| Error::runtime(format!("invalid name: {err}")))
}
@@ -783,7 +787,6 @@ impl Chunk<'_> {
///
/// 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) -> impl IntoLua {
WrappedChunk {
+154 -231
View File
@@ -4,20 +4,19 @@ use std::ffi::{CStr, CString, OsStr, OsString};
use std::hash::{BuildHasher, Hash};
use std::os::raw::c_int;
use std::path::{Path, PathBuf};
use std::string::String as StdString;
use std::{mem, slice, str};
use std::{slice, str};
use bstr::{BStr, BString, ByteSlice, ByteVec};
use bstr::{BStr, BString, ByteVec};
use num_traits::cast;
use crate::error::{Error, Result};
use crate::function::Function;
use crate::state::{Lua, RawLua};
use crate::string::{BorrowedBytes, BorrowedStr, String};
use crate::string::{BorrowedBytes, BorrowedStr, LuaString};
use crate::table::Table;
use crate::thread::Thread;
use crate::traits::{FromLua, IntoLua, ShortTypeName as _};
use crate::types::{Either, LightUserData, MaybeSend, RegistryKey};
use crate::types::{Either, LightUserData, MaybeSend, MaybeSync, RegistryKey};
use crate::userdata::{AnyUserData, UserData};
use crate::value::{Nil, Value};
@@ -47,14 +46,14 @@ impl FromLua for Value {
}
}
impl IntoLua for String {
impl IntoLua for LuaString {
#[inline]
fn into_lua(self, _: &Lua) -> Result<Value> {
Ok(Value::String(self))
}
}
impl IntoLua for &String {
impl IntoLua for &LuaString {
#[inline]
fn into_lua(self, _: &Lua) -> Result<Value> {
Ok(Value::String(self.clone()))
@@ -67,16 +66,12 @@ impl IntoLua for &String {
}
}
impl FromLua for String {
impl FromLua for LuaString {
#[inline]
fn from_lua(value: Value, lua: &Lua) -> Result<String> {
fn from_lua(value: Value, lua: &Lua) -> Result<LuaString> {
let ty = value.type_name();
lua.coerce_string(value)?
.ok_or_else(|| Error::FromLuaConversionError {
from: ty,
to: "string".to_string(),
message: Some("expected string or number".to_string()),
})
.ok_or_else(|| Error::from_lua_conversion(ty, "string", "expected string or number".to_string()))
}
unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result<Self> {
@@ -84,98 +79,86 @@ impl FromLua for String {
let type_id = ffi::lua_type(state, idx);
if type_id == ffi::LUA_TSTRING {
ffi::lua_xpush(state, lua.ref_thread(), idx);
return Ok(String(lua.pop_ref_thread()));
return Ok(LuaString(lua.pop_ref_thread()));
}
// Fallback to default
Self::from_lua(lua.stack_value(idx, Some(type_id)), lua.lua())
}
}
impl IntoLua for BorrowedStr<'_> {
impl IntoLua for BorrowedStr {
#[inline]
fn into_lua(self, _: &Lua) -> Result<Value> {
Ok(Value::String(self.borrow.into_owned()))
Ok(Value::String(LuaString(self.vref)))
}
#[inline]
unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> {
lua.push_ref(&self.borrow.0);
lua.push_ref(&self.vref);
Ok(())
}
}
impl IntoLua for &BorrowedStr<'_> {
impl IntoLua for &BorrowedStr {
#[inline]
fn into_lua(self, _: &Lua) -> Result<Value> {
Ok(Value::String(self.borrow.clone().into_owned()))
Ok(Value::String(LuaString(self.vref.clone())))
}
#[inline]
unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> {
lua.push_ref(&self.borrow.0);
lua.push_ref(&self.vref);
Ok(())
}
}
impl FromLua for BorrowedStr<'_> {
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 })
let s = LuaString::from_lua(value, lua)?;
BorrowedStr::try_from(&s)
}
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 })
let s = LuaString::from_stack(idx, lua)?;
BorrowedStr::try_from(&s)
}
}
impl IntoLua for BorrowedBytes<'_> {
impl IntoLua for BorrowedBytes {
#[inline]
fn into_lua(self, _: &Lua) -> Result<Value> {
Ok(Value::String(self.borrow.into_owned()))
Ok(Value::String(LuaString(self.vref)))
}
#[inline]
unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> {
lua.push_ref(&self.borrow.0);
lua.push_ref(&self.vref);
Ok(())
}
}
impl IntoLua for &BorrowedBytes<'_> {
impl IntoLua for &BorrowedBytes {
#[inline]
fn into_lua(self, _: &Lua) -> Result<Value> {
Ok(Value::String(self.borrow.clone().into_owned()))
Ok(Value::String(LuaString(self.vref.clone())))
}
#[inline]
unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> {
lua.push_ref(&self.borrow.0);
lua.push_ref(&self.vref);
Ok(())
}
}
impl FromLua for BorrowedBytes<'_> {
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 })
let s = LuaString::from_lua(value, lua)?;
Ok(BorrowedBytes::from(&s))
}
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 })
let s = LuaString::from_stack(idx, lua)?;
Ok(BorrowedBytes::from(&s))
}
}
@@ -204,11 +187,7 @@ impl FromLua for Table {
fn from_lua(value: Value, _: &Lua) -> Result<Table> {
match value {
Value::Table(table) => Ok(table),
_ => Err(Error::FromLuaConversionError {
from: value.type_name(),
to: "table".to_string(),
message: None,
}),
_ => Err(Error::from_lua_conversion(value.type_name(), "table", None)),
}
}
}
@@ -238,11 +217,7 @@ impl FromLua for Function {
fn from_lua(value: Value, _: &Lua) -> Result<Function> {
match value {
Value::Function(table) => Ok(table),
_ => Err(Error::FromLuaConversionError {
from: value.type_name(),
to: "function".to_string(),
message: None,
}),
_ => Err(Error::from_lua_conversion(value.type_name(), "function", None)),
}
}
}
@@ -272,11 +247,7 @@ impl FromLua for Thread {
fn from_lua(value: Value, _: &Lua) -> Result<Thread> {
match value {
Value::Thread(t) => Ok(t),
_ => Err(Error::FromLuaConversionError {
from: value.type_name(),
to: "thread".to_string(),
message: None,
}),
_ => Err(Error::from_lua_conversion(value.type_name(), "thread", None)),
}
}
}
@@ -306,16 +277,12 @@ impl FromLua for AnyUserData {
fn from_lua(value: Value, _: &Lua) -> Result<AnyUserData> {
match value {
Value::UserData(ud) => Ok(ud),
_ => Err(Error::FromLuaConversionError {
from: value.type_name(),
to: "userdata".to_string(),
message: None,
}),
_ => Err(Error::from_lua_conversion(value.type_name(), "userdata", None)),
}
}
}
impl<T: UserData + MaybeSend + 'static> IntoLua for T {
impl<T: UserData + MaybeSend + MaybeSync + 'static> IntoLua for T {
#[inline]
fn into_lua(self, lua: &Lua) -> Result<Value> {
Ok(Value::UserData(lua.create_userdata(self)?))
@@ -428,11 +395,11 @@ impl FromLua for LightUserData {
fn from_lua(value: Value, _: &Lua) -> Result<Self> {
match value {
Value::LightUserData(ud) => Ok(ud),
_ => Err(Error::FromLuaConversionError {
from: value.type_name(),
to: "lightuserdata".to_string(),
message: None,
}),
_ => Err(Error::from_lua_conversion(
value.type_name(),
"lightuserdata",
None,
)),
}
}
}
@@ -451,11 +418,7 @@ impl FromLua for crate::Vector {
fn from_lua(value: Value, _: &Lua) -> Result<Self> {
match value {
Value::Vector(v) => Ok(v),
_ => Err(Error::FromLuaConversionError {
from: value.type_name(),
to: "vector".to_string(),
message: None,
}),
_ => Err(Error::from_lua_conversion(value.type_name(), "vector", None)),
}
}
}
@@ -488,16 +451,12 @@ impl FromLua for crate::Buffer {
fn from_lua(value: Value, _: &Lua) -> Result<Self> {
match value {
Value::Buffer(buf) => Ok(buf),
_ => Err(Error::FromLuaConversionError {
from: value.type_name(),
to: "buffer".to_string(),
message: None,
}),
_ => Err(Error::from_lua_conversion(value.type_name(), "buffer", None)),
}
}
}
impl IntoLua for StdString {
impl IntoLua for String {
#[inline]
fn into_lua(self, lua: &Lua) -> Result<Value> {
#[cfg(feature = "lua55")]
@@ -519,16 +478,14 @@ impl IntoLua for StdString {
}
}
impl FromLua for StdString {
impl FromLua for String {
#[inline]
fn from_lua(value: Value, lua: &Lua) -> Result<Self> {
let ty = value.type_name();
Ok(lua
.coerce_string(value)?
.ok_or_else(|| Error::FromLuaConversionError {
from: ty,
to: Self::type_name(),
message: Some("expected string or number".to_string()),
.ok_or_else(|| {
Error::from_lua_conversion(ty, Self::type_name(), "expected string or number".to_string())
})?
.to_str()?
.to_owned())
@@ -544,11 +501,7 @@ impl FromLua for StdString {
let bytes = slice::from_raw_parts(data as *const u8, size);
return str::from_utf8(bytes)
.map(|s| s.to_owned())
.map_err(|e| Error::FromLuaConversionError {
from: "string",
to: Self::type_name(),
message: Some(e.to_string()),
});
.map_err(|e| Error::from_lua_conversion("string", Self::type_name(), e.to_string()));
}
// Fallback to default
Self::from_lua(lua.stack_value(idx, Some(type_id)), lua.lua())
@@ -570,7 +523,10 @@ impl IntoLua for &str {
impl IntoLua for Cow<'_, str> {
#[inline]
fn into_lua(self, lua: &Lua) -> Result<Value> {
Ok(Value::String(lua.create_string(self.as_bytes())?))
match self {
Cow::Borrowed(s) => s.into_lua(lua),
Cow::Owned(s) => s.into_lua(lua),
}
}
}
@@ -587,10 +543,8 @@ impl FromLua for Box<str> {
let ty = value.type_name();
Ok(lua
.coerce_string(value)?
.ok_or_else(|| Error::FromLuaConversionError {
from: ty,
to: Self::type_name(),
message: Some("expected string or number".to_string()),
.ok_or_else(|| {
Error::from_lua_conversion(ty, Self::type_name(), "expected string or number".to_string())
})?
.to_str()?
.to_owned()
@@ -614,21 +568,12 @@ impl FromLua for CString {
#[inline]
fn from_lua(value: Value, lua: &Lua) -> Result<Self> {
let ty = value.type_name();
let string = lua
.coerce_string(value)?
.ok_or_else(|| Error::FromLuaConversionError {
from: ty,
to: Self::type_name(),
message: Some("expected string or number".to_string()),
})?;
let string = lua.coerce_string(value)?.ok_or_else(|| {
Error::from_lua_conversion(ty, Self::type_name(), "expected string or number".to_string())
})?;
match CStr::from_bytes_with_nul(&string.as_bytes_with_nul()) {
Ok(s) => Ok(s.into()),
Err(_) => Err(Error::FromLuaConversionError {
from: ty,
to: Self::type_name(),
message: Some("invalid C-style string".to_string()),
}),
Err(err) => Err(Error::from_lua_conversion(ty, Self::type_name(), err.to_string())),
}
}
}
@@ -643,7 +588,10 @@ impl IntoLua for &CStr {
impl IntoLua for Cow<'_, CStr> {
#[inline]
fn into_lua(self, lua: &Lua) -> Result<Value> {
Ok(Value::String(lua.create_string(self.to_bytes())?))
match self {
Cow::Borrowed(s) => s.into_lua(lua),
Cow::Owned(s) => s.into_lua(lua),
}
}
}
@@ -668,10 +616,8 @@ impl FromLua for BString {
Value::Buffer(buf) => Ok(buf.to_vec().into()),
_ => Ok((*lua
.coerce_string(value)?
.ok_or_else(|| Error::FromLuaConversionError {
from: ty,
to: Self::type_name(),
message: Some("expected string or number".to_string()),
.ok_or_else(|| {
Error::from_lua_conversion(ty, Self::type_name(), "expected string or number".to_string())
})?
.as_bytes())
.into()),
@@ -722,23 +668,22 @@ impl FromLua for OsString {
let bs = BString::from_lua(value, lua)?;
Vec::from(bs)
.into_os_string()
.map_err(|err| Error::FromLuaConversionError {
from: ty,
to: "OsString".into(),
message: Some(err.to_string()),
})
.map_err(|err| Error::from_lua_conversion(ty, "OsString", err.to_string()))
}
}
impl IntoLua for &OsStr {
#[cfg(unix)]
#[inline]
fn into_lua(self, lua: &Lua) -> Result<Value> {
let s = <[u8]>::from_os_str(self).ok_or_else(|| Error::ToLuaConversionError {
from: "OsStr".into(),
to: "string",
message: Some("invalid utf-8 encoding".into()),
})?;
Ok(Value::String(lua.create_string(s)?))
use std::os::unix::ffi::OsStrExt;
Ok(Value::String(lua.create_string(self.as_bytes())?))
}
#[cfg(not(unix))]
#[inline]
fn into_lua(self, lua: &Lua) -> Result<Value> {
self.display().to_string().into_lua(lua)
}
}
@@ -776,34 +721,25 @@ 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::Integer(i) => cast(i).and_then(char::from_u32).ok_or_else(|| {
let msg = "integer out of range when converting to char";
Error::from_lua_conversion(ty, "char", msg.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(),
),
}),
_ => {
let msg = "expected string to have exactly one char when converting to char";
Err(Error::from_lua_conversion(ty, "char", msg.to_string()))
}
}
}
_ => Err(Error::FromLuaConversionError {
from: ty,
to: Self::type_name(),
message: Some("expected string or integer".to_string()),
}),
_ => {
let msg = "expected string or integer";
Err(Error::from_lua_conversion(ty, Self::type_name(), msg.to_string()))
}
}
}
}
@@ -854,24 +790,14 @@ macro_rules! lua_convert_int {
if let Some(i) = lua.coerce_integer(value.clone())? {
cast(i)
} else {
cast(
lua.coerce_number(value)?
.ok_or_else(|| Error::FromLuaConversionError {
from: ty,
to: stringify!($x).to_string(),
message: Some(
"expected number or string coercible to number".to_string(),
),
})?,
)
cast(lua.coerce_number(value)?.ok_or_else(|| {
let msg = "expected number or string coercible to number";
Error::from_lua_conversion(ty, stringify!($x), msg.to_string())
})?)
}
}
})
.ok_or_else(|| Error::FromLuaConversionError {
from: ty,
to: stringify!($x).to_string(),
message: Some("out of range".to_owned()),
})
.ok_or_else(|| Error::from_lua_conversion(ty, stringify!($x), "out of range".to_string()))
}
unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result<Self> {
@@ -881,13 +807,18 @@ macro_rules! lua_convert_int {
let mut ok = 0;
let i = ffi::lua_tointegerx(state, idx, &mut ok);
if ok != 0 {
return cast(i).ok_or_else(|| Error::FromLuaConversionError {
from: "integer",
to: stringify!($x).to_string(),
message: Some("out of range".to_owned()),
return cast(i).ok_or_else(|| {
Error::from_lua_conversion("integer", stringify!($x), "out of range".to_string())
});
}
}
#[cfg(feature = "luau")]
if type_id == ffi::LUA_TINTEGER {
let i = ffi::lua_tointeger64(state, idx, std::ptr::null_mut());
return cast(i).ok_or_else(|| {
Error::from_lua_conversion("integer", stringify!($x), "out of range".to_string())
});
}
// Fallback to default
Self::from_lua(lua.stack_value(idx, Some(type_id)), lua.lua())
}
@@ -921,13 +852,10 @@ macro_rules! lua_convert_float {
#[inline]
fn from_lua(value: Value, lua: &Lua) -> Result<Self> {
let ty = value.type_name();
lua.coerce_number(value)?
.map(|n| n as $x)
.ok_or_else(|| Error::FromLuaConversionError {
from: ty,
to: stringify!($x).to_string(),
message: Some("expected number or string coercible to number".to_string()),
})
lua.coerce_number(value)?.map(|n| n as $x).ok_or_else(|| {
let msg = "expected number or string coercible to number";
Error::from_lua_conversion(ty, stringify!($x), msg.to_string())
})
}
unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result<Self> {
@@ -987,18 +915,16 @@ where
},
Value::Table(table) => {
let vec = table.sequence_values().collect::<Result<Vec<_>>>()?;
vec.try_into()
.map_err(|vec: Vec<T>| Error::FromLuaConversionError {
from: "table",
to: Self::type_name(),
message: Some(format!("expected table of length {N}, got {}", vec.len())),
})
vec.try_into().map_err(|vec: Vec<T>| {
let msg = format!("expected table of length {N}, got {}", vec.len());
Error::from_lua_conversion("table", Self::type_name(), msg)
})
}
_ => {
let msg = format!("expected table of length {N}");
let err = Error::from_lua_conversion(value.type_name(), Self::type_name(), msg.to_string());
Err(err)
}
_ => Err(Error::FromLuaConversionError {
from: value.type_name(),
to: Self::type_name(),
message: Some("expected table".to_string()),
}),
}
}
}
@@ -1029,11 +955,11 @@ impl<T: FromLua> FromLua for Vec<T> {
fn from_lua(value: Value, _lua: &Lua) -> Result<Self> {
match value {
Value::Table(table) => table.sequence_values().collect(),
_ => Err(Error::FromLuaConversionError {
from: value.type_name(),
to: Self::type_name(),
message: Some("expected table".to_string()),
}),
_ => Err(Error::from_lua_conversion(
value.type_name(),
Self::type_name(),
"expected table".to_string(),
)),
}
}
}
@@ -1048,14 +974,13 @@ impl<K: Eq + Hash + IntoLua, V: IntoLua, S: BuildHasher> IntoLua for HashMap<K,
impl<K: Eq + Hash + FromLua, V: FromLua, S: BuildHasher + Default> FromLua for HashMap<K, V, S> {
#[inline]
fn from_lua(value: Value, _: &Lua) -> Result<Self> {
if let Value::Table(table) = value {
table.pairs().collect()
} else {
Err(Error::FromLuaConversionError {
from: value.type_name(),
to: Self::type_name(),
message: Some("expected table".to_string()),
})
match value {
Value::Table(table) => table.pairs().collect(),
_ => Err(Error::from_lua_conversion(
value.type_name(),
Self::type_name(),
"expected table".to_string(),
)),
}
}
}
@@ -1070,14 +995,13 @@ impl<K: Ord + IntoLua, V: IntoLua> IntoLua for BTreeMap<K, V> {
impl<K: Ord + FromLua, V: FromLua> FromLua for BTreeMap<K, V> {
#[inline]
fn from_lua(value: Value, _: &Lua) -> Result<Self> {
if let Value::Table(table) = value {
table.pairs().collect()
} else {
Err(Error::FromLuaConversionError {
from: value.type_name(),
to: Self::type_name(),
message: Some("expected table".to_string()),
})
match value {
Value::Table(table) => table.pairs().collect(),
_ => Err(Error::from_lua_conversion(
value.type_name(),
Self::type_name(),
"expected table".to_string(),
)),
}
}
}
@@ -1097,11 +1021,11 @@ impl<T: Eq + Hash + FromLua, S: BuildHasher + Default> FromLua for HashSet<T, S>
match value {
Value::Table(table) if table.raw_len() > 0 => table.sequence_values().collect(),
Value::Table(table) => table.pairs::<T, Value>().map(|res| res.map(|(k, _)| k)).collect(),
_ => Err(Error::FromLuaConversionError {
from: value.type_name(),
to: Self::type_name(),
message: Some("expected table".to_string()),
}),
_ => Err(Error::from_lua_conversion(
value.type_name(),
Self::type_name(),
"expected table".to_string(),
)),
}
}
}
@@ -1121,11 +1045,11 @@ impl<T: Ord + FromLua> FromLua for BTreeSet<T> {
match value {
Value::Table(table) if table.raw_len() > 0 => table.sequence_values().collect(),
Value::Table(table) => table.pairs::<T, Value>().map(|res| res.map(|(k, _)| k)).collect(),
_ => Err(Error::FromLuaConversionError {
from: value.type_name(),
to: Self::type_name(),
message: Some("expected table".to_string()),
}),
_ => Err(Error::from_lua_conversion(
value.type_name(),
Self::type_name(),
"expected table".to_string(),
)),
}
}
}
@@ -1195,11 +1119,11 @@ impl<L: FromLua, R: FromLua> FromLua for Either<L, R> {
// 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,
}),
Err(_) => Err(Error::from_lua_conversion(
value_type_name,
Self::type_name(),
None,
)),
},
}
}
@@ -1211,13 +1135,12 @@ impl<L: FromLua, R: FromLua> FromLua for Either<L, R> {
Err(_) => match R::from_stack(idx, lua).map(Either::Right) {
Ok(r) => Ok(r),
Err(_) => {
let value_type_name =
CStr::from_ptr(ffi::lua_typename(lua.state(), ffi::lua_type(lua.state(), idx)));
Err(Error::FromLuaConversionError {
from: value_type_name.to_str().unwrap(),
to: Self::type_name(),
message: None,
})
let state = lua.state();
let from_type_name = CStr::from_ptr(ffi::lua_typename(state, ffi::lua_type(state, idx)))
.to_str()
.unwrap_or("unknown");
let err = Error::from_lua_conversion(from_type_name, Self::type_name(), None);
Err(err)
}
},
}
+32 -49
View File
@@ -1,3 +1,9 @@
//! Lua debugging interface.
//!
//! This module provides access to the Lua debug interface, allowing inspection of the call stack,
//! and function information. The main types are [`struct@Debug`] for accessing debug information
//! and [`HookTriggers`] for configuring debug hooks.
use std::borrow::Cow;
use std::os::raw::c_int;
@@ -5,7 +11,7 @@ use ffi::{lua_Debug, lua_State};
use crate::function::Function;
use crate::state::RawLua;
use crate::util::{assert_stack, linenumber_to_usize, ptr_to_lossy_str, ptr_to_str, StackGuard};
use crate::util::{StackGuard, assert_stack, linenumber_to_usize, ptr_to_lossy_str, ptr_to_str};
/// Contains information about currently executing Lua code.
///
@@ -133,12 +139,6 @@ impl<'a> Debug<'a> {
}
}
#[doc(hidden)]
#[deprecated(note = "Use `current_line` instead")]
pub fn curr_line(&self) -> i32 {
self.current_line().map(|n| n as i32).unwrap_or(-1)
}
/// Corresponds to the `l` "what" mask. Returns the current line.
pub fn current_line(&self) -> Option<usize> {
unsafe {
@@ -190,15 +190,15 @@ impl<'a> Debug<'a> {
#[cfg(not(feature = "luau"))]
let stack = DebugStack {
num_ups: (*self.ar).nups as _,
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52"))]
num_upvalues: (*self.ar).nups as _,
#[cfg(not(any(feature = "lua51", feature = "luajit")))]
num_params: (*self.ar).nparams as _,
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52"))]
#[cfg(not(any(feature = "lua51", feature = "luajit")))]
is_vararg: (*self.ar).isvararg != 0,
};
#[cfg(feature = "luau")]
let stack = DebugStack {
num_ups: (*self.ar).nupvals,
num_upvalues: (*self.ar).nupvals,
num_params: (*self.ar).nparams,
is_vararg: (*self.ar).isvararg != 0,
};
@@ -208,6 +208,8 @@ impl<'a> Debug<'a> {
}
/// Represents a specific event that triggered the hook.
#[cfg(not(feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DebugEvent {
Call,
@@ -218,6 +220,9 @@ pub enum DebugEvent {
Unknown(c_int),
}
/// Contains the name information of a function in the call stack.
///
/// Returned by the [`Debug::names`] method.
#[derive(Clone, Debug)]
pub struct DebugNames<'a> {
/// A (reasonable) name of the function (`None` if the name cannot be found).
@@ -228,6 +233,9 @@ pub struct DebugNames<'a> {
pub name_what: Option<&'static str>,
}
/// Contains the source information of a function in the call stack.
///
/// Returned by the [`Debug::source`] method.
#[derive(Clone, Debug)]
pub struct DebugSource<'a> {
/// Source of the chunk that created the function.
@@ -243,47 +251,20 @@ pub struct DebugSource<'a> {
pub what: &'static str,
}
/// Contains stack information about a function in the call stack.
///
/// Returned by the [`Debug::stack`] method.
#[derive(Copy, Clone, Debug)]
pub struct DebugStack {
/// Number of upvalues.
pub num_ups: u8,
/// Number of parameters.
#[cfg(any(
feature = "lua55",
feature = "lua54",
feature = "lua53",
feature = "lua52",
feature = "luau"
))]
#[cfg_attr(
docsrs,
doc(cfg(any(
feature = "lua55",
feature = "lua54",
feature = "lua53",
feature = "lua52",
feature = "luau"
)))
)]
/// The number of upvalues of the function.
pub num_upvalues: u8,
/// The number of parameters of the function (always 0 for C).
#[cfg(any(not(any(feature = "lua51", feature = "luajit")), doc))]
#[cfg_attr(docsrs, doc(cfg(not(any(feature = "lua51", feature = "luajit")))))]
pub num_params: u8,
/// Whether the function is a vararg function.
#[cfg(any(
feature = "lua55",
feature = "lua54",
feature = "lua53",
feature = "lua52",
feature = "luau"
))]
#[cfg_attr(
docsrs,
doc(cfg(any(
feature = "lua55",
feature = "lua54",
feature = "lua53",
feature = "lua52",
feature = "luau"
)))
)]
/// Whether the function is a variadic function (always true for C).
#[cfg(any(not(any(feature = "lua51", feature = "luajit")), doc))]
#[cfg_attr(docsrs, doc(cfg(not(any(feature = "lua51", feature = "luajit")))))]
pub is_vararg: bool,
}
@@ -361,6 +342,7 @@ impl HookTriggers {
}
// Compute the mask to pass to `lua_sethook`.
#[cfg(not(feature = "luau"))]
pub(crate) const fn mask(&self) -> c_int {
let mut mask: c_int = 0;
if self.on_calls {
@@ -380,6 +362,7 @@ impl HookTriggers {
// Returns the `count` parameter to pass to `lua_sethook`, if applicable. Otherwise, zero is
// returned.
#[cfg(not(feature = "luau"))]
pub(crate) const fn count(&self) -> c_int {
match self.every_nth_instruction {
Some(n) => n as c_int,
+42 -46
View File
@@ -1,10 +1,14 @@
//! Lua error handling.
//!
//! This module provides the [`Error`] type returned by all fallible `mlua` operations, together
//! with extension traits for adapting Rust errors for use within Lua.
use std::error::Error as StdError;
use std::fmt;
use std::io::Error as IoError;
use std::net::AddrParseError;
use std::result::Result as StdResult;
use std::str::Utf8Error;
use std::string::String as StdString;
use std::sync::Arc;
use crate::private::Sealed;
@@ -22,7 +26,7 @@ pub enum Error {
/// Syntax error while parsing Lua source code.
SyntaxError {
/// The error message as returned by Lua.
message: StdString,
message: String,
/// `true` if the error can likely be fixed by appending more input to the source code.
///
/// This is useful for implementing REPLs as they can query the user for more input if this
@@ -34,20 +38,20 @@ pub enum Error {
/// The Lua VM returns this error when a builtin operation is performed on incompatible types.
/// Among other things, this includes invoking operators on wrong types (such as calling or
/// indexing a `nil` value).
RuntimeError(StdString),
RuntimeError(String),
/// Lua memory error, aka `LUA_ERRMEM`
///
/// The Lua VM returns this error when the allocator does not return the requested memory, aka
/// it is an out-of-memory error.
MemoryError(StdString),
MemoryError(String),
/// Lua garbage collector error, aka `LUA_ERRGCMM`.
///
/// The Lua VM returns this error when there is an error running a `__gc` metamethod.
#[cfg(any(feature = "lua53", feature = "lua52", doc))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua53", feature = "lua52"))))]
GarbageCollectorError(StdString),
GarbageCollectorError(String),
/// Potentially unsafe action in safe mode.
SafetyError(StdString),
SafetyError(String),
/// Memory control is not available.
///
/// This error can only happen when Lua state was not created by us and does not have the
@@ -80,23 +84,14 @@ pub enum Error {
/// (which is stored in the corresponding field).
BadArgument {
/// Function that was called.
to: Option<StdString>,
to: Option<String>,
/// Argument position (usually starts from 1).
pos: usize,
/// Argument name.
name: Option<StdString>,
name: Option<String>,
/// Underlying error returned when converting argument to a Lua value.
cause: Arc<Error>,
},
/// A Rust value could not be converted to a Lua value.
ToLuaConversionError {
/// Name of the Rust type that could not be converted.
from: String,
/// Name of the Lua type that could not be created.
to: &'static str,
/// A message indicating why the conversion failed in more detail.
message: Option<StdString>,
},
/// A Lua value could not be converted to the expected Rust type.
FromLuaConversionError {
/// Name of the Lua type that could not be converted.
@@ -104,7 +99,7 @@ pub enum Error {
/// Name of the Rust type that could not be created.
to: String,
/// A string containing more detailed error information.
message: Option<StdString>,
message: Option<String>,
},
/// [`Thread::resume`] was called on an unresumable coroutine.
///
@@ -154,17 +149,17 @@ pub enum Error {
/// A [`MetaMethod`] operation is restricted (typically for `__gc` or `__metatable`).
///
/// [`MetaMethod`]: crate::MetaMethod
MetaMethodRestricted(StdString),
MetaMethodRestricted(String),
/// A [`MetaMethod`] (eg. `__index` or `__newindex`) has invalid type.
///
/// [`MetaMethod`]: crate::MetaMethod
MetaMethodTypeError {
/// Name of the metamethod.
method: StdString,
method: String,
/// Passed value type.
type_name: &'static str,
/// A string containing more detailed error information.
message: Option<StdString>,
message: Option<String>,
},
/// A [`RegistryKey`] produced from a different Lua state was used.
///
@@ -173,7 +168,7 @@ pub enum Error {
/// A Rust callback returned `Err`, raising the contained `Error` as a Lua error.
CallbackError {
/// Lua call stack backtrace.
traceback: StdString,
traceback: String,
/// Original error returned by the Rust code.
cause: Arc<Error>,
},
@@ -185,11 +180,11 @@ pub enum Error {
/// Serialization error.
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
SerializeError(StdString),
SerializeError(String),
/// Deserialization error.
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
DeserializeError(StdString),
DeserializeError(String),
/// A custom error.
///
/// This can be used for returning user-defined errors from callbacks.
@@ -201,7 +196,7 @@ pub enum Error {
/// An error with additional context.
WithContext {
/// A string containing additional context.
context: StdString,
context: String,
/// Underlying error.
cause: Arc<Error>,
},
@@ -225,7 +220,7 @@ impl fmt::Display for Error {
}
Error::SafetyError(msg) => {
write!(fmt, "safety error: {msg}")
},
}
Error::MemoryControlNotAvailable => {
write!(fmt, "memory control is not available")
}
@@ -238,10 +233,7 @@ impl fmt::Display for Error {
fmt,
"out of Lua stack, too many arguments to a Lua function or too many return values from a callback"
),
Error::BindError => write!(
fmt,
"too many arguments to Function::bind"
),
Error::BindError => write!(fmt, "too many arguments to Function::bind"),
Error::BadArgument { to, pos, name, cause } => {
if let Some(name) = name {
write!(fmt, "bad argument `{name}`")?;
@@ -252,13 +244,6 @@ impl fmt::Display for Error {
write!(fmt, " to `{to}`")?;
}
write!(fmt, ": {cause}")
},
Error::ToLuaConversionError { from, to, message } => {
write!(fmt, "error converting {from} to Lua {to}")?;
match message {
None => Ok(()),
Some(message) => write!(fmt, " ({message})"),
}
}
Error::FromLuaConversionError { from, to, message } => {
write!(fmt, "error converting Lua {from} to {to}")?;
@@ -273,7 +258,11 @@ impl fmt::Display for Error {
Error::UserDataBorrowError => write!(fmt, "error borrowing userdata"),
Error::UserDataBorrowMutError => write!(fmt, "error mutably borrowing userdata"),
Error::MetaMethodRestricted(method) => write!(fmt, "metamethod {method} is restricted"),
Error::MetaMethodTypeError { method, type_name, message } => {
Error::MetaMethodTypeError {
method,
type_name,
message,
} => {
write!(fmt, "metamethod {method} has unsupported type {type_name}")?;
match message {
None => Ok(()),
@@ -286,7 +275,11 @@ impl fmt::Display for Error {
Error::CallbackError { cause, traceback } => {
// Trace errors down to the root
let (mut cause, mut full_traceback) = (cause, None);
while let Error::CallbackError { cause: cause2, traceback: traceback2 } = &**cause {
while let Error::CallbackError {
cause: cause2,
traceback: traceback2,
} = &**cause
{
cause = cause2;
full_traceback = Some(traceback2);
}
@@ -312,11 +305,11 @@ impl fmt::Display for Error {
#[cfg(feature = "serde")]
Error::SerializeError(err) => {
write!(fmt, "serialize error: {err}")
},
}
#[cfg(feature = "serde")]
Error::DeserializeError(err) => {
write!(fmt, "deserialize error: {err}")
},
}
Error::ExternalError(err) => err.fmt(fmt),
Error::WithContext { context, cause } => {
writeln!(fmt, "{context}")?;
@@ -352,7 +345,11 @@ impl Error {
/// Wraps an external error object.
#[inline]
pub fn external<T: Into<Box<DynStdError>>>(err: T) -> Self {
Error::ExternalError(err.into().into())
let boxed = err.into();
match boxed.downcast::<Self>() {
Ok(err) => *err,
Err(boxed) => Error::ExternalError(boxed.into()),
}
}
/// Attempts to downcast the external error object to a concrete type by reference.
@@ -394,6 +391,7 @@ impl Error {
}
}
#[inline]
pub(crate) fn from_lua_conversion(
from: &'static str,
to: impl ToString,
@@ -561,10 +559,8 @@ 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);
static_assertions::assert_not_impl_any!(super::Error: Send, Sync);
#[cfg(feature = "send")]
static_assertions::assert_impl_all!(Error: Send, Sync);
static_assertions::assert_impl_all!(super::Error: Send, Sync);
}
+186 -15
View File
@@ -1,24 +1,99 @@
//! Lua function handling.
//!
//! This module provides types for working with Lua functions from Rust, including
//! both Lua-defined functions and native Rust callbacks.
//!
//! # Calling Functions
//!
//! Use [`Function::call`] to invoke a Lua function synchronously:
//!
//! ```
//! # use mlua::{Function, Lua, Result};
//! # fn main() -> Result<()> {
//! let lua = Lua::new();
//!
//! // Get a built-in function
//! let print: Function = lua.globals().get("print")?;
//! print.call::<()>("Hello from Rust!")?;
//!
//! // Call a function that returns values
//! let tonumber: Function = lua.globals().get("tonumber")?;
//! let n: i32 = tonumber.call("42")?;
//! assert_eq!(n, 42);
//! # Ok(())
//! # }
//! ```
//!
//! For asynchronous execution, use `Function::call_async` (requires `async` feature):
//!
//! ```ignore
//! let result: String = my_async_func.call_async(args).await?;
//! ```
//!
//! # Creating Functions
//!
//! Functions can be created from Rust closures using [`Lua::create_function`]:
//!
//! ```
//! # use mlua::{Lua, Result};
//! # fn main() -> Result<()> {
//! let lua = Lua::new();
//!
//! let greet = lua.create_function(|_, name: String| {
//! Ok(format!("Hello, {}!", name))
//! })?;
//!
//! lua.globals().set("greet", greet)?;
//! let result: String = lua.load(r#"greet("World")"#).eval()?;
//! assert_eq!(result, "Hello, World!");
//! # Ok(())
//! # }
//! ```
//!
//! For simpler cases, use [`Function::wrap`] or [`Function::wrap_raw`] to convert a Rust function
//! directly:
//!
//! ```
//! # use mlua::{Function, Lua, Result};
//! # fn main() -> Result<()> {
//! let lua = Lua::new();
//!
//! fn add(a: i32, b: i32) -> i32 { a + b }
//!
//! lua.globals().set("add", Function::wrap_raw(add))?;
//! let sum: i32 = lua.load("add(2, 3)").eval()?;
//! assert_eq!(sum, 5);
//! # Ok(())
//! # }
//! ```
//!
//! # Function Environments
//!
//! Lua functions have an associated environment table that determines how global
//! variables are resolved. Use [`Function::environment`] and [`Function::set_environment`]
//! to inspect or modify this environment.
use std::cell::RefCell;
use std::os::raw::{c_int, c_void};
use std::result::Result as StdResult;
use std::{mem, ptr, slice};
use crate::error::{Error, Result};
use crate::error::{Error, ExternalError, ExternalResult, Result};
use crate::state::Lua;
use crate::table::Table;
use crate::traits::{FromLuaMulti, IntoLua, IntoLuaMulti, LuaNativeFn, LuaNativeFnMut};
use crate::traits::{FromLuaMulti, IntoLua, IntoLuaMulti};
use crate::types::{Callback, LuaType, MaybeSend, ValueRef};
use crate::util::{
assert_stack, check_stack, linenumber_to_usize, pop_error, ptr_to_lossy_str, ptr_to_str, StackGuard,
StackGuard, assert_stack, check_stack, linenumber_to_usize, pop_error, ptr_to_lossy_str, ptr_to_str,
};
use crate::value::Value;
#[cfg(feature = "async")]
use {
crate::thread::AsyncThread,
crate::traits::LuaNativeAsyncFn,
crate::types::AsyncCallback,
std::future::{self, Future},
std::pin::{pin, Pin},
std::pin::{Pin, pin},
std::task::{Context, Poll},
};
@@ -165,7 +240,7 @@ impl Function {
/// # }
/// ```
///
/// [`AsyncThread`]: crate::AsyncThread
/// [`AsyncThread`]: crate::thread::AsyncThread
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub fn call_async<R>(&self, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
@@ -555,30 +630,32 @@ impl Function {
/// Wraps a Rust function or closure, returning an opaque type that implements [`IntoLua`]
/// trait.
#[inline]
pub fn wrap<F, A, R>(func: F) -> impl IntoLua
pub fn wrap<F, A, R, E>(func: F) -> impl IntoLua
where
F: LuaNativeFn<A, Output = Result<R>> + MaybeSend + 'static,
F: LuaNativeFn<A, Output = StdResult<R, E>> + MaybeSend + 'static,
A: FromLuaMulti,
R: IntoLuaMulti,
E: ExternalError,
{
WrappedFunction(Box::new(move |lua, nargs| unsafe {
let args = A::from_stack_args(nargs, 1, None, lua)?;
func.call(args)?.push_into_stack_multi(lua)
func.call(args).into_lua_err()?.push_into_stack_multi(lua)
}))
}
/// Wraps a Rust mutable closure, returning an opaque type that implements [`IntoLua`] trait.
pub fn wrap_mut<F, A, R>(func: F) -> impl IntoLua
pub fn wrap_mut<F, A, R, E>(func: F) -> impl IntoLua
where
F: LuaNativeFnMut<A, Output = Result<R>> + MaybeSend + 'static,
F: LuaNativeFnMut<A, Output = StdResult<R, E>> + MaybeSend + 'static,
A: FromLuaMulti,
R: IntoLuaMulti,
E: ExternalError,
{
let func = RefCell::new(func);
WrappedFunction(Box::new(move |lua, nargs| unsafe {
let mut func = func.try_borrow_mut().map_err(|_| Error::RecursiveMutCallback)?;
let args = A::from_stack_args(nargs, 1, None, lua)?;
func.call(args)?.push_into_stack_multi(lua)
func.call(args).into_lua_err()?.push_into_stack_multi(lua)
}))
}
@@ -591,6 +668,7 @@ impl Function {
pub fn wrap_raw<F, A>(func: F) -> impl IntoLua
where
F: LuaNativeFn<A> + MaybeSend + 'static,
F::Output: IntoLuaMulti,
A: FromLuaMulti,
{
WrappedFunction(Box::new(move |lua, nargs| unsafe {
@@ -607,6 +685,7 @@ impl Function {
pub fn wrap_raw_mut<F, A>(func: F) -> impl IntoLua
where
F: LuaNativeFnMut<A> + MaybeSend + 'static,
F::Output: IntoLuaMulti,
A: FromLuaMulti,
{
let func = RefCell::new(func);
@@ -621,11 +700,12 @@ impl Function {
/// trait.
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub fn wrap_async<F, A, R>(func: F) -> impl IntoLua
pub fn wrap_async<F, A, R, E>(func: F) -> impl IntoLua
where
F: LuaNativeAsyncFn<A, Output = Result<R>> + MaybeSend + 'static,
F: LuaNativeAsyncFn<A, Output = StdResult<R, E>> + MaybeSend + 'static,
A: FromLuaMulti,
R: IntoLuaMulti,
E: ExternalError,
{
WrappedAsyncFunction(Box::new(move |rawlua, nargs| unsafe {
let args = match A::from_stack_args(nargs, 1, None, rawlua) {
@@ -634,7 +714,7 @@ impl Function {
};
let lua = rawlua.lua();
let fut = func.call(args);
Box::pin(async move { fut.await?.push_into_stack_multi(lua.raw_lua()) })
Box::pin(async move { fut.await.into_lua_err()?.push_into_stack_multi(lua.raw_lua()) })
}))
}
@@ -648,6 +728,7 @@ impl Function {
pub fn wrap_raw_async<F, A>(func: F) -> impl IntoLua
where
F: LuaNativeAsyncFn<A> + MaybeSend + 'static,
F::Output: IntoLuaMulti,
A: FromLuaMulti,
{
WrappedAsyncFunction(Box::new(move |rawlua, nargs| unsafe {
@@ -681,7 +762,9 @@ impl LuaType for Function {
const TYPE_ID: c_int = ffi::LUA_TFUNCTION;
}
/// Future for asynchronous function calls.
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct AsyncCallFuture<R: FromLuaMulti>(Result<AsyncThread<R>>);
@@ -705,6 +788,94 @@ impl<R: FromLuaMulti> Future for AsyncCallFuture<R> {
}
}
/// A trait for types that can be used as Lua functions.
pub trait LuaNativeFn<A: FromLuaMulti> {
type Output;
fn call(&self, args: A) -> Self::Output;
}
/// A trait for types with mutable state that can be used as Lua functions.
pub trait LuaNativeFnMut<A: FromLuaMulti> {
type Output;
fn call(&mut self, args: A) -> Self::Output;
}
/// A trait for types that returns a future and can be used as Lua functions.
#[cfg(feature = "async")]
pub trait LuaNativeAsyncFn<A: FromLuaMulti> {
type Output;
fn call(&self, args: A) -> impl Future<Output = Self::Output> + MaybeSend + 'static;
}
macro_rules! impl_lua_native_fn {
($($A:ident),*) => {
impl<FN, $($A,)* R> LuaNativeFn<($($A,)*)> for FN
where
FN: Fn($($A,)*) -> R + MaybeSend + 'static,
($($A,)*): FromLuaMulti,
{
type Output = R;
#[allow(non_snake_case)]
fn call(&self, args: ($($A,)*)) -> Self::Output {
let ($($A,)*) = args;
self($($A,)*)
}
}
impl<FN, $($A,)* R> LuaNativeFnMut<($($A,)*)> for FN
where
FN: FnMut($($A,)*) -> R + MaybeSend + 'static,
($($A,)*): FromLuaMulti,
{
type Output = R;
#[allow(non_snake_case)]
fn call(&mut self, args: ($($A,)*)) -> Self::Output {
let ($($A,)*) = args;
self($($A,)*)
}
}
#[cfg(feature = "async")]
impl<FN, $($A,)* Fut, R> LuaNativeAsyncFn<($($A,)*)> for FN
where
FN: Fn($($A,)*) -> Fut + MaybeSend + 'static,
($($A,)*): FromLuaMulti,
Fut: Future<Output = R> + MaybeSend + 'static,
{
type Output = R;
#[allow(non_snake_case)]
fn call(&self, args: ($($A,)*)) -> impl Future<Output = Self::Output> + MaybeSend + 'static {
let ($($A,)*) = args;
self($($A,)*)
}
}
};
}
impl_lua_native_fn!();
impl_lua_native_fn!(A);
impl_lua_native_fn!(A, B);
impl_lua_native_fn!(A, B, C);
impl_lua_native_fn!(A, B, C, D);
impl_lua_native_fn!(A, B, C, D, E);
impl_lua_native_fn!(A, B, C, D, E, F);
impl_lua_native_fn!(A, B, C, D, E, F, G);
impl_lua_native_fn!(A, B, C, D, E, F, G, H);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J, K);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J, K, L);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J, K, L, M);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J, K, L, M, N);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P);
#[cfg(test)]
mod assertions {
use super::*;
+61 -47
View File
@@ -61,91 +61,105 @@
//! [`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
//! [`AsyncThread`]: crate::thread::AsyncThread
// 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)]
#![allow(unsafe_op_in_unsafe_fn)]
#[macro_use]
mod macros;
mod buffer;
mod chunk;
mod conversion;
mod debug;
mod error;
mod function;
#[cfg(any(feature = "luau", doc))]
mod luau;
mod memory;
mod multi;
mod scope;
mod state;
mod stdlib;
mod string;
mod table;
mod thread;
mod traits;
mod types;
mod userdata;
mod util;
mod value;
mod vector;
pub mod chunk;
pub mod debug;
pub mod error;
pub mod function;
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub mod luau;
pub mod prelude;
pub mod state;
pub mod string;
pub mod table;
pub mod thread;
pub mod userdata;
pub use bstr::BString;
pub use ffi::{self, lua_CFunction, lua_State};
pub use crate::chunk::{AsChunk, Chunk, ChunkMode};
pub use crate::debug::{Debug, DebugEvent, DebugNames, DebugSource, DebugStack};
pub use crate::error::{Error, ErrorContext, ExternalError, ExternalResult, Result};
pub use crate::function::{Function, FunctionInfo};
#[doc(inline)]
pub use crate::error::{Error, Result};
#[doc(inline)]
pub use crate::function::Function;
pub use crate::multi::{MultiValue, Variadic};
pub use crate::scope::Scope;
pub use crate::state::{GCMode, Lua, LuaOptions, WeakLua};
#[doc(inline)]
pub use crate::state::{Lua, LuaOptions, WeakLua};
pub use crate::stdlib::StdLib;
pub use crate::string::{BorrowedBytes, BorrowedStr, String};
pub use crate::table::{Table, TablePairs, TableSequence};
pub use crate::thread::{Thread, ThreadStatus};
pub use crate::traits::{
FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, LuaNativeFn, LuaNativeFnMut, ObjectLike,
};
#[doc(inline)]
pub use crate::string::{BorrowedBytes, BorrowedStr, LuaString};
#[doc(inline)]
pub use crate::table::Table;
#[doc(inline)]
pub use crate::thread::Thread;
#[doc(inline)]
pub use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, ObjectLike};
pub use crate::types::{
AppDataRef, AppDataRefMut, Either, Integer, LightUserData, MaybeSend, Number, RegistryKey, VmState,
};
pub use crate::userdata::{
AnyUserData, MetaMethod, UserData, UserDataFields, UserDataMetatable, UserDataMethods, UserDataRef,
UserDataRefMut, UserDataRegistry,
AppDataRef, AppDataRefMut, Either, Integer, LightUserData, MaybeSend, MaybeSync, Number, RegistryKey,
VmState,
};
#[doc(inline)]
pub use crate::userdata::AnyUserData;
pub use crate::value::{Nil, Value};
// Re-export some types to keep backward compatibility and avoid breaking changes in the public API.
#[doc(hidden)]
pub use crate::chunk::{AsChunk, Chunk, ChunkMode};
#[cfg(feature = "luau")]
#[doc(hidden)]
pub use crate::chunk::{CompileConstant, Compiler};
#[doc(hidden)]
pub use crate::error::{ErrorContext, ExternalError, ExternalResult};
#[doc(hidden)]
pub use crate::string::LuaString as String;
#[doc(hidden)]
pub use crate::table::{TablePairs, TableSequence};
#[doc(hidden)]
pub use crate::thread::ThreadStatus;
#[doc(hidden)]
pub use crate::userdata::{
MetaMethod, UserData, UserDataFields, UserDataMetatable, UserDataMethods, UserDataOwned, UserDataRef,
UserDataRefMut, UserDataRegistry,
};
#[cfg(not(feature = "luau"))]
#[doc(inline)]
pub use crate::debug::HookTriggers;
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub use crate::{
buffer::Buffer,
chunk::{CompileConstant, Compiler},
function::CoverageInfo,
luau::{HeapDump, NavigateError, Require, TextRequirer},
vector::Vector,
};
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub use crate::{thread::AsyncThread, traits::LuaNativeAsyncFn};
pub use crate::{buffer::Buffer, vector::Vector};
#[cfg(feature = "serde")]
#[doc(hidden)]
pub use crate::serde::{DeserializeOptions, SerializeOptions};
#[cfg(feature = "serde")]
#[doc(inline)]
pub use crate::{
serde::{de::Options as DeserializeOptions, ser::Options as SerializeOptions, LuaSerdeExt},
value::SerializableValue,
};
pub use crate::{serde::LuaSerdeExt, value::SerializableValue};
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
@@ -246,10 +260,10 @@ pub use mlua_derive::FromLua;
///
/// * skip_memory_check - skip memory allocation checks for some operations.
///
/// In module mode, mlua runs in unknown environment and cannot say are there any memory
/// limits or not. As result, some operations that require memory allocation runs in
/// protected mode. Setting this attribute will improve performance of such operations
/// with risk of having uncaught exceptions and memory leaks.
/// In module mode, mlua runs in an unknown environment and cannot tell whether there are any memory
/// limits or not. As a result, some operations that require memory allocation run in protected
/// mode. Setting this attribute will improve performance of such operations with risk of having
/// uncaught exceptions and memory leaks.
///
/// ```ignore
/// #[mlua::lua_module(skip_memory_check)]
+13 -13
View File
@@ -79,10 +79,10 @@ impl HeapDump {
let mut size_by_type = HashMap::new();
let objects = self.data["objects"].as_object()?;
for obj in objects.values() {
if let Some(cat_id) = category_id {
if obj["cat"].as_i64()? != cat_id {
continue;
}
if let Some(cat_id) = category_id
&& obj["cat"].as_i64()? != cat_id
{
continue;
}
update_size(&mut size_by_type, obj["type"].as_str()?, obj["size"].as_u64()?);
}
@@ -123,18 +123,18 @@ impl HeapDump {
if obj["type"] != "userdata" {
continue;
}
if let Some(cat_id) = category_id {
if obj["cat"].as_i64()? != cat_id {
continue;
}
if let Some(cat_id) = category_id
&& obj["cat"].as_i64()? != cat_id
{
continue;
}
// Determine userdata type from metatable
let mut ud_type = "unknown";
if let Some(metatable_addr) = obj["metatable"].as_str() {
if let Some(t) = get_key(objects, &objects[metatable_addr], "__type") {
ud_type = t;
}
if let Some(metatable_addr) = obj["metatable"].as_str()
&& let Some(t) = get_key(objects, &objects[metatable_addr], "__type")
{
ud_type = t;
}
update_size(&mut size_by_userdata, ud_type, obj["size"].as_u64()?);
}
@@ -155,7 +155,7 @@ impl HeapDump {
/// Updates the size mapping for a given key.
fn update_size<K: Eq + Hash>(size_type: &mut HashMap<K, (usize, u64)>, key: K, size: u64) {
let (ref mut count, ref mut total_size) = size_type.entry(key).or_insert((0, 0));
let (count, total_size) = size_type.entry(key).or_insert((0, 0));
*count += 1;
*total_size += size;
}
+10 -3
View File
@@ -1,3 +1,10 @@
//! Luau-specific extensions and types.
//!
//! This module provides Luau-specific functionality including custom [`require`] implementations,
//! heap memory analysis, and Luau VM integration utilities.
//!
//! [`require`]: crate::Lua::create_require_function
use std::ffi::{CStr, CString};
use std::os::raw::c_int;
use std::ptr;
@@ -5,12 +12,12 @@ use std::ptr;
use crate::chunk::ChunkMode;
use crate::error::{Error, Result};
use crate::function::Function;
use crate::state::{callback_error_ext, ExtraData, Lua};
use crate::state::{ExtraData, Lua, callback_error_ext};
use crate::traits::{FromLuaMulti, IntoLua};
use crate::types::MaybeSend;
pub use heap_dump::HeapDump;
pub use require::{NavigateError, Require, TextRequirer};
pub use require::{FsRequirer, NavigateError, Require};
// Since Luau has some missing standard functions, we re-implement them here
@@ -86,7 +93,7 @@ impl Lua {
}
// Enable default `require` implementation
let require = self.create_require_function(require::TextRequirer::new())?;
let require = self.create_require_function(FsRequirer::new())?;
self.globals().raw_set("require", require)?;
Ok(())
+2 -3
View File
@@ -8,12 +8,11 @@ use std::{fmt, mem, ptr};
use crate::error::{Error, Result};
use crate::function::Function;
use crate::state::{callback_error_ext, Lua};
use crate::state::{Lua, callback_error_ext};
use crate::table::Table;
use crate::types::MaybeSend;
// TODO: Rename to FsRequirer
pub use fs::TextRequirer;
pub use fs::FsRequirer;
/// An error that can occur during navigation in the Luau `require-by-string` system.
#[derive(Debug, Clone)]
+10 -10
View File
@@ -12,7 +12,7 @@ use super::{NavigateError, Require};
/// The standard implementation of Luau `require-by-string` navigation.
#[derive(Default, Debug)]
pub struct TextRequirer {
pub struct FsRequirer {
/// An absolute path to the current Luau module (not mapped to a physical file)
abs_path: PathBuf,
/// A relative path to the current Luau module (not mapped to a physical file)
@@ -22,7 +22,7 @@ pub struct TextRequirer {
resolved_path: Option<PathBuf>,
}
impl TextRequirer {
impl FsRequirer {
/// The prefix used for chunk names in the require system.
/// Only chunk names starting with this prefix are allowed to be used in `require`.
const CHUNK_PREFIX: &str = "@";
@@ -36,16 +36,16 @@ impl TextRequirer {
/// The filename for the Luau configuration file.
const LUAU_CONFIG_FILENAME: &str = ".config.luau";
/// Creates a new `TextRequirer` instance.
/// Creates a new `FsRequirer` instance.
pub fn new() -> Self {
Self::default()
}
fn normalize_chunk_name(chunk_name: &str) -> &str {
if let Some((path, line)) = chunk_name.rsplit_once(':') {
if line.parse::<u32>().is_ok() {
return path;
}
if let Some((path, line)) = chunk_name.rsplit_once(':')
&& line.parse::<u32>().is_ok()
{
return path;
}
chunk_name
}
@@ -114,7 +114,7 @@ impl TextRequirer {
}
}
impl Require for TextRequirer {
impl Require for FsRequirer {
fn is_require_allowed(&self, chunk_name: &str) -> bool {
chunk_name.starts_with(Self::CHUNK_PREFIX)
}
@@ -231,7 +231,7 @@ impl Require for TextRequirer {
mod tests {
use std::path::Path;
use super::TextRequirer;
use super::FsRequirer;
#[test]
fn test_path_normalize() {
@@ -267,7 +267,7 @@ mod tests {
// '..' disappears if path is absolute and component is non-erasable
("/../", "/"),
] {
let path = TextRequirer::normalize_path(input.as_ref());
let path = FsRequirer::normalize_path(input.as_ref());
assert_eq!(
&path,
expected.as_ref() as &Path,
-13
View File
@@ -28,9 +28,7 @@ impl 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) {
@@ -39,17 +37,6 @@ impl MemoryState {
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();
}
mem_state as *mut MemoryState
}
#[inline]
pub(crate) fn used_memory(&self) -> usize {
self.used_memory as usize
+1 -1
View File
@@ -1,4 +1,4 @@
use std::collections::{vec_deque, VecDeque};
use std::collections::{VecDeque, vec_deque};
use std::iter::FromIterator;
use std::mem;
use std::ops::{Deref, DerefMut};
+23 -14
View File
@@ -3,35 +3,44 @@
#[doc(no_inline)]
pub use crate::{
AnyUserData as LuaAnyUserData, BorrowedBytes as LuaBorrowedBytes, BorrowedStr as LuaBorrowedStr,
Chunk as LuaChunk, Either as LuaEither, Error as LuaError, ErrorContext as LuaErrorContext,
ExternalError as LuaExternalError, ExternalResult as LuaExternalResult, FromLua, FromLuaMulti,
Function as LuaFunction, FunctionInfo as LuaFunctionInfo, GCMode as LuaGCMode, Integer as LuaInteger,
IntoLua, IntoLuaMulti, LightUserData as LuaLightUserData, Lua, LuaNativeFn, LuaNativeFnMut, LuaOptions,
MetaMethod as LuaMetaMethod, MultiValue as LuaMultiValue, Nil as LuaNil, Number as LuaNumber,
Either as LuaEither, Error as LuaError, FromLua, FromLuaMulti, Function as LuaFunction,
Integer as LuaInteger, IntoLua, IntoLuaMulti, LightUserData as LuaLightUserData, Lua, LuaOptions,
LuaString, MetaMethod as LuaMetaMethod, MultiValue as LuaMultiValue, Nil as LuaNil, Number as LuaNumber,
ObjectLike as LuaObjectLike, RegistryKey as LuaRegistryKey, Result as LuaResult, StdLib as LuaStdLib,
String as LuaString, Table as LuaTable, TablePairs as LuaTablePairs, TableSequence as LuaTableSequence,
Thread as LuaThread, ThreadStatus as LuaThreadStatus, UserData as LuaUserData,
UserDataFields as LuaUserDataFields, UserDataMetatable as LuaUserDataMetatable,
UserDataMethods as LuaUserDataMethods, UserDataRef as LuaUserDataRef,
UserDataRefMut as LuaUserDataRefMut, UserDataRegistry as LuaUserDataRegistry, Value as LuaValue,
Variadic as LuaVariadic, VmState as LuaVmState, WeakLua,
Table as LuaTable, Thread as LuaThread, UserData as LuaUserData, UserDataFields as LuaUserDataFields,
UserDataMetatable as LuaUserDataMetatable, UserDataMethods as LuaUserDataMethods,
UserDataOwned as LuaUserDataOwned, UserDataRef as LuaUserDataRef, UserDataRefMut as LuaUserDataRefMut,
UserDataRegistry as LuaUserDataRegistry, Value as LuaValue, Variadic as LuaVariadic,
VmState as LuaVmState, WeakLua, chunk::AsChunk as AsLuaChunk, chunk::Chunk as LuaChunk,
chunk::ChunkMode as LuaChunkMode, error::ErrorContext as LuaErrorContext,
error::ExternalError as LuaExternalError, error::ExternalResult as LuaExternalResult,
function::FunctionInfo as LuaFunctionInfo, function::LuaNativeFn, function::LuaNativeFnMut,
state::GcIncParams as LuaGcIncParams, state::GcMode as LuaGcMode, table::TablePairs as LuaTablePairs,
table::TableSequence as LuaTableSequence, thread::ThreadStatus as LuaThreadStatus,
};
#[cfg(not(feature = "luau"))]
#[doc(no_inline)]
pub use crate::HookTriggers as LuaHookTriggers;
#[cfg(any(feature = "lua54", feature = "lua55"))]
#[doc(no_inline)]
pub use crate::state::GcGenParams as LuaGcGenParams;
#[cfg(feature = "luau")]
#[doc(no_inline)]
pub use crate::{
CompileConstant as LuaCompileConstant, CoverageInfo as LuaCoverageInfo,
NavigateError as LuaNavigateError, Require as LuaRequire, TextRequirer as LuaTextRequirer,
Vector as LuaVector,
chunk::{CompileConstant as LuaCompileConstant, Compiler as LuaCompiler},
luau::{
FsRequirer as LuaFsRequirer, HeapDump as LuaHeapDump, NavigateError as LuaNavigateError,
Require as LuaRequire,
},
};
#[cfg(feature = "async")]
#[doc(no_inline)]
pub use crate::{AsyncThread as LuaAsyncThread, LuaNativeAsyncFn};
pub use crate::{function::LuaNativeAsyncFn, thread::AsyncThread as LuaAsyncThread};
#[cfg(feature = "serde")]
#[doc(no_inline)]
+1 -1
View File
@@ -8,7 +8,7 @@ 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, check_stack, get_metatable_ptr, get_userdata, take_userdata, StackGuard};
use crate::util::{self, StackGuard, check_stack, get_metatable_ptr, get_userdata, take_userdata};
/// Constructed by the [`Lua::scope`] method, allows temporarily creating Lua userdata and
/// callbacks that are not required to be `Send` or `'static`.
+3 -4
View File
@@ -4,7 +4,6 @@ use std::cell::RefCell;
use std::os::raw::c_void;
use std::rc::Rc;
use std::result::Result as StdResult;
use std::string::String as StdString;
use rustc_hash::FxHashSet;
use serde::de::{self, IntoDeserializer};
@@ -243,14 +242,14 @@ impl<'de> serde::Deserializer<'de> for Deserializer {
Value::Table(table) => {
let _guard = RecursionGuard::new(&table, &self.visited);
let mut iter = table.pairs::<StdString, Value>();
let mut iter = table.pairs::<String, Value>();
let (variant, value) = match iter.next() {
Some(v) => v?,
None => {
return Err(de::Error::invalid_value(
de::Unexpected::Map,
&"map with a single key",
))
));
}
};
@@ -621,7 +620,7 @@ impl<'de> de::MapAccess<'de> for MapDeserializer<'_> {
}
struct EnumDeserializer {
variant: StdString,
variant: String,
value: Option<Value>,
options: Options,
visited: Rc<RefCell<FxHashSet<*const c_void>>>,
+4 -6
View File
@@ -37,8 +37,8 @@ pub trait LuaSerdeExt: Sealed {
fn null(&self) -> Value;
/// A metatable attachable to a Lua table to systematically encode it as Array (instead of Map).
/// As result, encoded Array will contain only sequence part of the table, with the same length
/// as the `#` operator on that table.
/// As a result, encoded Array will contain only sequence part of the table, with the same
/// length as the `#` operator on that table.
///
/// # Example
///
@@ -242,7 +242,5 @@ static ARRAY_METATABLE_REGISTRY_KEY: u8 = 0;
pub mod de;
pub mod ser;
#[doc(inline)]
pub use de::Deserializer;
#[doc(inline)]
pub use ser::Serializer;
pub use de::{Deserializer, Options as DeserializeOptions};
pub use ser::{Options as SerializeOptions, Serializer};
+5 -5
View File
@@ -1,6 +1,6 @@
//! Serialize a Rust data structure into Lua value.
use serde::{ser, Serialize};
use serde::{Serialize, ser};
use super::LuaSerdeExt;
use crate::error::{Error, Result};
@@ -531,10 +531,10 @@ impl ser::SerializeStruct for SerializeStruct<'_> {
Some(table @ Value::Table(_)) => Ok(table),
Some(value @ Value::String(_)) if self.options.detect_serde_json_arbitrary_precision => {
let number_s = value.to_string()?;
if number_s.contains(['.', 'e', 'E']) {
if let Ok(number) = number_s.parse().map(Value::Number) {
return Ok(number);
}
if number_s.contains(['.', 'e', 'E'])
&& let Ok(number) = number_s.parse().map(Value::Number)
{
return Ok(number);
}
Ok(number_s
.parse()
+261 -211
View File
@@ -1,3 +1,8 @@
//! Lua state management.
//!
//! This module provides the main [`Lua`] state handle together with state-specific
//! configuration and garbage collector controls.
use std::any::TypeId;
use std::cell::{BorrowError, BorrowMutError, RefCell};
use std::marker::PhantomData;
@@ -15,16 +20,16 @@ use crate::memory::MemoryState;
use crate::multi::MultiValue;
use crate::scope::Scope;
use crate::stdlib::StdLib;
use crate::string::String;
use crate::string::LuaString;
use crate::table::Table;
use crate::thread::Thread;
use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti};
use crate::types::{
AppDataRef, AppDataRefMut, ArcReentrantMutexGuard, Integer, LuaType, MaybeSend, Number, ReentrantMutex,
ReentrantMutexGuard, RegistryKey, VmState, XRc, XWeak,
AppDataRef, AppDataRefMut, ArcReentrantMutexGuard, Integer, LuaType, MaybeSend, MaybeSync, Number,
ReentrantMutex, ReentrantMutexGuard, RegistryKey, VmState, XRc, XWeak,
};
use crate::userdata::{AnyUserData, UserData, UserDataProxy, UserDataRegistry, UserDataStorage};
use crate::util::{assert_stack, check_stack, protect_lua_closure, push_string, rawset_field, StackGuard};
use crate::util::{StackGuard, assert_stack, check_stack, protect_lua_closure, push_string, rawset_field};
use crate::value::{Nil, Value};
#[cfg(not(feature = "luau"))]
@@ -44,6 +49,7 @@ use {
use serde::Serialize;
pub(crate) use extra::ExtraData;
#[doc(hidden)]
pub use raw::RawLua;
pub(crate) use util::callback_error_ext;
@@ -62,20 +68,126 @@ pub struct WeakLua(XWeak<ReentrantMutex<RawLua>>);
pub(crate) struct LuaGuard(ArcReentrantMutexGuard<RawLua>);
/// Mode of the Lua garbage collector (GC).
///
/// In Lua 5.4 GC can work in two modes: incremental and generational.
/// Previous Lua versions support only incremental GC.
/// Tuning parameters for the incremental GC collector.
///
/// More information can be found in the Lua [documentation].
///
/// [documentation]: https://www.lua.org/manual/5.4/manual.html#2.5
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum GCMode {
Incremental,
/// [documentation]: https://www.lua.org/manual/5.5/manual.html#2.5.1
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Default)]
pub struct GcIncParams {
/// Pause between successive GC cycles, expressed as a percentage of live memory.
#[cfg(not(feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
pub pause: Option<c_int>,
/// Target heap size as a percentage of live data, controlling how aggressively
/// the GC reclaims memory (`LUA_GCSETGOAL`).
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub goal: Option<c_int>,
/// GC work performed per unit of memory allocated.
pub step_multiplier: Option<c_int>,
/// Granularity of each GC step (see Lua reference for details).
#[cfg(any(feature = "lua55", feature = "lua54", feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua55", feature = "lua54", feature = "luau"))))]
pub step_size: Option<c_int>,
}
impl GcIncParams {
/// Sets the `pause` parameter.
#[cfg(not(feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
pub fn pause(mut self, v: c_int) -> Self {
self.pause = Some(v);
self
}
/// Sets the `goal` parameter.
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn goal(mut self, v: c_int) -> Self {
self.goal = Some(v);
self
}
/// Sets the `step_multiplier` parameter.
pub fn step_multiplier(mut self, v: c_int) -> Self {
self.step_multiplier = Some(v);
self
}
/// Sets the `step_size` parameter.
#[cfg(any(feature = "lua55", feature = "lua54", feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua55", feature = "lua54", feature = "luau"))))]
pub fn step_size(mut self, v: c_int) -> Self {
self.step_size = Some(v);
self
}
}
/// Tuning parameters for the generational GC collector (Lua 5.4+).
///
/// More information can be found in the Lua [documentation].
///
/// [documentation]: https://www.lua.org/manual/5.5/manual.html#2.5.2
#[cfg(any(feature = "lua55", feature = "lua54"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua55", feature = "lua54"))))]
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Default)]
pub struct GcGenParams {
/// Frequency of minor (young-generation) collection steps.
pub minor_multiplier: Option<c_int>,
/// Threshold controlling how large the young generation can grow before triggering
/// a shift from minor to major collection.
pub minor_to_major: Option<c_int>,
/// Threshold controlling how much the major collection must shrink the heap before
/// switching back to minor (young-generation) collection.
#[cfg(feature = "lua55")]
#[cfg_attr(docsrs, doc(cfg(feature = "lua55")))]
pub major_to_minor: Option<c_int>,
}
#[cfg(any(feature = "lua55", feature = "lua54"))]
impl GcGenParams {
/// Sets the `minor_multiplier` parameter.
pub fn minor_multiplier(mut self, v: c_int) -> Self {
self.minor_multiplier = Some(v);
self
}
/// Sets the `minor_to_major` threshold.
pub fn minor_to_major(mut self, v: c_int) -> Self {
self.minor_to_major = Some(v);
self
}
/// Sets the `major_to_minor` parameter.
#[cfg(feature = "lua55")]
#[cfg_attr(docsrs, doc(cfg(feature = "lua55")))]
pub fn major_to_minor(mut self, v: c_int) -> Self {
self.major_to_minor = Some(v);
self
}
}
/// Lua garbage collector (GC) operating mode.
///
/// Use [`Lua::gc_set_mode`] to switch the collector mode and/or tune its parameters.
#[non_exhaustive]
#[derive(Clone, Debug)]
pub enum GcMode {
/// Incremental mark-and-sweep
Incremental(GcIncParams),
/// Generational
#[cfg(any(feature = "lua55", feature = "lua54"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua55", feature = "lua54"))))]
Generational,
Generational(GcGenParams),
}
/// Controls Lua interpreter behavior such as Rust panics handling.
@@ -337,30 +449,27 @@ impl Lua {
R::from_stack_multi(nresults, &lua)
}
/// Runs callback with the inner RawLua value. It can be used to manually push and get values on
/// the stack.
/// Calls provided function passing a reference to the [`RawLua`] handle.
///
/// This function is safe because all unsafe actions with RawLua can only be done with unsafe
/// Provided [`RawLua`] handle can be used to manually pushing/popping values to/from the stack.
///
/// # Example
/// ```
/// # use mlua::{Lua, Result, FromLua, IntoLua};
/// # use mlua::{Lua, Result, FromLua, IntoLua, IntoLuaMulti};
/// # fn main() -> Result<()> {
/// let lua = Lua::new();
/// let n: i32 = {
/// let num = 11i32;
/// lua.exec_raw_lua(|lua| {
/// unsafe {
/// <i32 as IntoLua>::push_into_stack(num, lua)?;
/// let nums = (3, 4, 5);
/// lua.exec_raw_lua(|rawlua| unsafe {
/// nums.push_into_stack_multi(rawlua)?;
/// let mut sum = 0;
/// for _ in 0..3 {
/// sum += rawlua.pop::<i32>()?;
/// }
///
/// let n = unsafe {
/// <i32 as FromLua>::from_stack(-1, lua)?
/// };
/// Result::Ok(n)
/// Result::Ok(sum)
/// })
/// }?;
/// assert_eq!(n, 11);
/// assert_eq!(n, 12);
/// # Ok(())
/// # }
/// ```
@@ -437,31 +546,6 @@ impl Lua {
Ok(())
}
#[doc(hidden)]
#[deprecated(since = "0.11.0", note = "Use `register_module` instead")]
#[cfg(not(feature = "luau"))]
#[cfg(not(tarpaulin_include))]
pub fn load_from_function<T: FromLua>(&self, modname: &str, func: Function) -> Result<T> {
let loaded = unsafe {
self.exec_raw::<Table>((), |state| {
ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_LOADED_TABLE);
})?
};
let value = match loaded.raw_get(modname)? {
Value::Nil => {
let result = match func.call(modname)? {
Value::Nil => Value::Boolean(true),
res => res,
};
loaded.raw_set(modname, &result)?;
result
}
res => res,
};
T::from_lua(value, self)
}
/// Unloads module `modname`.
///
/// This method does not support unloading binary Lua modules since they are internally cached
@@ -855,7 +939,6 @@ impl Lua {
{
use std::ffi::CStr;
use std::os::raw::{c_char, c_void};
use std::string::String as StdString;
unsafe extern "C-unwind" fn warn_proc(ud: *mut c_void, msg: *const c_char, tocont: c_int) {
let extra = ud as *mut ExtraData;
@@ -865,7 +948,7 @@ impl Lua {
if XRc::strong_count(&warn_callback) > 2 {
return Ok(());
}
let msg = StdString::from_utf8_lossy(CStr::from_ptr(msg).to_bytes());
let msg = String::from_utf8_lossy(CStr::from_ptr(msg).to_bytes());
warn_callback((*extra).lua(), &msg, tocont != 0)
});
}
@@ -910,8 +993,8 @@ impl Lua {
/// Gets information about the interpreter runtime stack at the given level.
///
/// This function calls callback `f`, passing the [`Debug`] structure that can be used to get
/// information about the function executing at a given level.
/// This function calls callback `f`, passing the [`struct@Debug`] structure that can be used to
/// get information about the function executing at a given level.
/// Level `0` is the current running function, whereas level `n+1` is the function that has
/// called level `n` (except for tail calls, which do not count in the stack).
pub fn inspect_stack<R>(&self, level: usize, f: impl FnOnce(&Debug) -> R) -> Option<R> {
@@ -936,7 +1019,7 @@ impl Lua {
///
/// The `msg` parameter, if provided, is added at the beginning of the traceback.
/// The `level` parameter works the same way as in [`Lua::inspect_stack`].
pub fn traceback(&self, msg: Option<&str>, level: usize) -> Result<String> {
pub fn traceback(&self, msg: Option<&str>, level: usize) -> Result<LuaString> {
let lua = self.lock();
unsafe {
check_stack(lua.state(), 3)?;
@@ -948,7 +1031,7 @@ impl Lua {
// `protect_lua` adds it's own call frame, so we need to increase level by 1
ffi::luaL_traceback(state, state, msg, (level + 1) as c_int);
})?;
Ok(String(lua.pop_ref()))
Ok(LuaString(lua.pop_ref()))
}
}
@@ -999,19 +1082,19 @@ impl Lua {
unsafe { ffi::lua_gc(lua.main_state(), ffi::LUA_GCISRUNNING, 0) != 0 }
}
/// Stop the Lua GC from running
/// Stops 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) };
}
/// Restarts the Lua GC if it is not running
/// 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) };
}
/// Perform a full garbage-collection cycle.
/// Performs a full garbage-collection cycle.
///
/// It may be necessary to call this function twice to collect all currently unreachable
/// objects. Once to finish the current gc cycle, and once to start and finish the next cycle.
@@ -1024,153 +1107,128 @@ impl Lua {
}
}
/// Steps the garbage collector one indivisible step.
/// Performs a basic step of garbage collection.
///
/// Returns `true` if this has finished a collection cycle.
/// In incremental mode, a basic step corresponds to the current step size. In generational
/// mode, a basic step performs a full minor collection or an incremental step, if the collector
/// has scheduled one.
///
/// In incremental mode, returns `true` if this step has finished a collection cycle.
/// In generational mode, returns `true` if the step finished a major collection.
pub fn gc_step(&self) -> Result<bool> {
self.gc_step_kbytes(0)
}
/// Steps the garbage collector as though memory had been allocated.
///
/// if `kbytes` is 0, then this is the same as calling `gc_step`. Returns true if this step has
/// 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(state, 3)?;
protect_lua!(state, 0, 0, |state| {
ffi::lua_gc(state, ffi::LUA_GCSTEP, kbytes) != 0
ffi::lua_gc(state, ffi::LUA_GCSTEP, 0) != 0
})
}
}
/// Sets the `pause` value of the collector.
/// Switches the GC to the given mode with the provided parameters.
///
/// Returns the previous value of `pause`. More information can be found in the Lua
/// [documentation].
/// Returns the previous [`GcMode`]. The returned value's parameter fields are always
/// `None` because Lua's C API does not provide a way to read back current parameter values
/// without changing them.
///
/// For Luau this parameter sets GC goal
/// # Examples
///
/// [documentation]: https://www.lua.org/manual/5.4/manual.html#2.5
pub fn gc_set_pause(&self, pause: c_int) -> c_int {
/// Switch to generational mode (Lua 5.4+):
/// ```ignore
/// let prev = lua.gc_set_mode(GcMode::Generational(GcGenParams::default()));
/// ```
///
/// Switch to incremental mode with custom parameters:
/// ```ignore
/// lua.gc_set_mode(GcMode::Incremental(
/// GcIncParams::default().pause(200).step_multiplier(100)
/// ));
/// ```
pub fn gc_set_mode(&self, mode: GcMode) -> GcMode {
let lua = self.lock();
let state = lua.main_state();
unsafe {
match mode {
#[cfg(feature = "lua55")]
return ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPPAUSE, pause);
#[cfg(not(any(feature = "lua55", feature = "luau")))]
return ffi::lua_gc(state, ffi::LUA_GCSETPAUSE, pause);
GcMode::Incremental(params) => unsafe {
if let Some(v) = params.pause {
ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPPAUSE, v);
}
if let Some(v) = params.step_multiplier {
ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPSTEPMUL, v);
}
if let Some(v) = params.step_size {
ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPSTEPSIZE, v);
}
match ffi::lua_gc(state, ffi::LUA_GCINC) {
ffi::LUA_GCINC => GcMode::Incremental(GcIncParams::default()),
ffi::LUA_GCGEN => GcMode::Generational(GcGenParams::default()),
_ => unreachable!(),
}
},
#[cfg(feature = "lua54")]
GcMode::Incremental(params) => unsafe {
let pause = params.pause.unwrap_or(0);
let step_mul = params.step_multiplier.unwrap_or(0);
let step_size = params.step_size.unwrap_or(0);
match ffi::lua_gc(state, ffi::LUA_GCINC, pause, step_mul, step_size) {
ffi::LUA_GCINC => GcMode::Incremental(GcIncParams::default()),
ffi::LUA_GCGEN => GcMode::Generational(GcGenParams::default()),
_ => unreachable!(),
}
},
#[cfg(any(feature = "lua53", feature = "lua52", feature = "lua51", feature = "luajit"))]
GcMode::Incremental(params) => unsafe {
if let Some(v) = params.pause {
ffi::lua_gc(state, ffi::LUA_GCSETPAUSE, v);
}
if let Some(v) = params.step_multiplier {
ffi::lua_gc(state, ffi::LUA_GCSETSTEPMUL, v);
}
GcMode::Incremental(GcIncParams::default())
},
#[cfg(feature = "luau")]
return ffi::lua_gc(state, ffi::LUA_GCSETGOAL, pause);
}
}
GcMode::Incremental(params) => unsafe {
if let Some(v) = params.goal {
ffi::lua_gc(state, ffi::LUA_GCSETGOAL, v);
}
if let Some(v) = params.step_multiplier {
ffi::lua_gc(state, ffi::LUA_GCSETSTEPMUL, v);
}
if let Some(v) = params.step_size {
ffi::lua_gc(state, ffi::LUA_GCSETSTEPSIZE, v);
}
GcMode::Incremental(GcIncParams::default())
},
/// Sets the `step multiplier` value of the collector.
///
/// 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 {
#[cfg(feature = "lua55")]
return ffi::lua_gc(
lua.main_state(),
ffi::LUA_GCPARAM,
ffi::LUA_GCPSTEPMUL,
step_multiplier,
);
#[cfg(not(feature = "lua55"))]
return ffi::lua_gc(lua.main_state(), ffi::LUA_GCSETSTEPMUL, step_multiplier);
}
}
/// Changes the collector to incremental mode with the given parameters.
///
/// Returns the previous mode (always `GCMode::Incremental` in Lua < 5.4).
/// More information can be found in the Lua [documentation].
///
/// [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();
#[cfg(any(
feature = "lua53",
feature = "lua52",
feature = "lua51",
feature = "luajit",
feature = "luau"
))]
unsafe {
if pause > 0 {
#[cfg(not(feature = "luau"))]
ffi::lua_gc(state, ffi::LUA_GCSETPAUSE, pause);
#[cfg(feature = "luau")]
ffi::lua_gc(state, ffi::LUA_GCSETGOAL, pause);
}
if step_multiplier > 0 {
ffi::lua_gc(state, ffi::LUA_GCSETSTEPMUL, step_multiplier);
}
#[cfg(feature = "luau")]
if step_size > 0 {
ffi::lua_gc(state, ffi::LUA_GCSETSTEPSIZE, step_size);
}
#[cfg(not(feature = "luau"))]
let _ = step_size; // Ignored
return GCMode::Incremental;
}
#[cfg(feature = "lua55")]
let prev_mode = unsafe {
ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPPAUSE, pause);
ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPSTEPMUL, step_multiplier);
ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPSTEPSIZE, step_size);
ffi::lua_gc(state, ffi::LUA_GCINC)
};
#[cfg(feature = "lua54")]
let prev_mode = unsafe { ffi::lua_gc(state, ffi::LUA_GCINC, pause, step_multiplier, step_size) };
#[cfg(any(feature = "lua55", feature = "lua54"))]
match prev_mode {
ffi::LUA_GCINC => GCMode::Incremental,
ffi::LUA_GCGEN => GCMode::Generational,
_ => unreachable!(),
}
}
/// Changes the collector to generational mode with the given parameters.
///
/// Returns the previous mode. More information about the generational GC
/// can be found in the Lua 5.4 [documentation][lua_doc].
///
/// [lua_doc]: https://www.lua.org/manual/5.4/manual.html#2.5.2
#[cfg(any(feature = "lua55", feature = "lua54"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua55", 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();
#[cfg(feature = "lua55")]
let prev_mode = unsafe {
ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPMINORMUL, minor_multiplier);
ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPMINORMAJOR, major_multiplier);
// TODO: LUA_GCPMAJORMINOR
ffi::lua_gc(state, ffi::LUA_GCGEN)
};
#[cfg(not(feature = "lua55"))]
let prev_mode = unsafe { ffi::lua_gc(state, ffi::LUA_GCGEN, minor_multiplier, major_multiplier) };
match prev_mode {
ffi::LUA_GCGEN => GCMode::Generational,
ffi::LUA_GCINC => GCMode::Incremental,
_ => unreachable!(),
GcMode::Generational(params) => unsafe {
if let Some(v) = params.minor_multiplier {
ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPMINORMUL, v);
}
if let Some(v) = params.minor_to_major {
ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPMINORMAJOR, v);
}
if let Some(v) = params.major_to_minor {
ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPMAJORMINOR, v);
}
match ffi::lua_gc(state, ffi::LUA_GCGEN) {
ffi::LUA_GCGEN => GcMode::Generational(GcGenParams::default()),
ffi::LUA_GCINC => GcMode::Incremental(GcIncParams::default()),
_ => unreachable!(),
}
},
#[cfg(feature = "lua54")]
GcMode::Generational(params) => unsafe {
let minor = params.minor_multiplier.unwrap_or(0);
let minor_to_major = params.minor_to_major.unwrap_or(0);
match ffi::lua_gc(state, ffi::LUA_GCGEN, minor, minor_to_major) {
ffi::LUA_GCGEN => GcMode::Generational(GcGenParams::default()),
ffi::LUA_GCINC => GcMode::Incremental(GcIncParams::default()),
_ => unreachable!(),
}
},
}
}
@@ -1205,10 +1263,10 @@ impl Lua {
#[doc(hidden)]
#[allow(clippy::result_unit_err)]
pub fn set_fflag(name: &str, enabled: bool) -> StdResult<(), ()> {
if let Ok(name) = std::ffi::CString::new(name) {
if unsafe { ffi::luau_setfflag(name.as_ptr(), enabled as c_int) != 0 } {
return Ok(());
}
if let Ok(name) = std::ffi::CString::new(name)
&& unsafe { ffi::luau_setfflag(name.as_ptr(), enabled as c_int) != 0 }
{
return Ok(());
}
Err(())
}
@@ -1248,7 +1306,7 @@ impl Lua {
/// 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> {
pub fn create_string(&self, s: impl AsRef<[u8]>) -> Result<LuaString> {
unsafe { self.lock().create_string(s.as_ref()) }
}
@@ -1259,7 +1317,7 @@ impl Lua {
#[cfg(feature = "lua55")]
#[cfg_attr(docsrs, doc(cfg(feature = "lua55")))]
#[inline]
pub fn create_external_string(&self, s: impl Into<Vec<u8>>) -> Result<String> {
pub fn create_external_string(&self, s: impl Into<Vec<u8>>) -> Result<LuaString> {
unsafe { self.lock().create_external_string(s.into()) }
}
@@ -1457,7 +1515,7 @@ impl Lua {
/// }
/// ```
///
/// [`AsyncThread`]: crate::AsyncThread
/// [`AsyncThread`]: crate::thread::AsyncThread
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub fn create_async_function<F, A, FR, R>(&self, func: F) -> Result<Function>
@@ -1493,7 +1551,7 @@ impl Lua {
#[inline]
pub fn create_userdata<T>(&self, data: T) -> Result<AnyUserData>
where
T: UserData + MaybeSend + 'static,
T: UserData + MaybeSend + MaybeSync + 'static,
{
unsafe { self.lock().make_userdata(UserDataStorage::new(data)) }
}
@@ -1504,7 +1562,7 @@ impl Lua {
#[inline]
pub fn create_ser_userdata<T>(&self, data: T) -> Result<AnyUserData>
where
T: UserData + Serialize + MaybeSend + 'static,
T: UserData + Serialize + MaybeSend + MaybeSync + 'static,
{
unsafe { self.lock().make_userdata(UserDataStorage::new_ser(data)) }
}
@@ -1519,7 +1577,7 @@ impl Lua {
#[inline]
pub fn create_any_userdata<T>(&self, data: T) -> Result<AnyUserData>
where
T: MaybeSend + 'static,
T: MaybeSend + MaybeSync + 'static,
{
unsafe { self.lock().make_any_userdata(UserDataStorage::new(data)) }
}
@@ -1532,7 +1590,7 @@ impl Lua {
#[inline]
pub fn create_ser_any_userdata<T>(&self, data: T) -> Result<AnyUserData>
where
T: Serialize + MaybeSend + 'static,
T: Serialize + MaybeSend + MaybeSync + 'static,
{
unsafe { (self.lock()).make_any_userdata(UserDataStorage::new_ser(data)) }
}
@@ -1741,7 +1799,7 @@ impl Lua {
///
/// To succeed, the value must be a string (in which case this is a no-op), an integer, or a
/// number.
pub fn coerce_string(&self, v: Value) -> Result<Option<String>> {
pub fn coerce_string(&self, v: Value) -> Result<Option<LuaString>> {
Ok(match v {
Value::String(s) => Some(s),
v => unsafe {
@@ -1759,7 +1817,7 @@ impl Lua {
})?
};
if !res.is_null() {
Some(String(lua.pop_ref()))
Some(LuaString(lua.pop_ref()))
} else {
None
}
@@ -1785,11 +1843,7 @@ impl Lua {
lua.push_value(&v)?;
let mut isint = 0;
let i = ffi::lua_tointegerx(state, -1, &mut isint);
if isint == 0 {
None
} else {
Some(i)
}
if isint == 0 { None } else { Some(i) }
},
})
}
@@ -1811,11 +1865,7 @@ impl Lua {
lua.push_value(&v)?;
let mut isnum = 0;
let n = ffi::lua_tonumberx(state, -1, &mut isnum);
if isnum == 0 {
None
} else {
Some(n)
}
if isnum == 0 { None } else { Some(n) }
},
})
}
+1 -1
View File
@@ -14,7 +14,7 @@ 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};
use crate::util::{TypeKey, WrappedFailure, get_internal_metatable, push_internal_userdata};
#[cfg(any(feature = "luau", doc))]
use crate::chunk::Compiler;
+62 -38
View File
@@ -10,26 +10,26 @@ use std::sync::Arc;
use crate::chunk::ChunkMode;
use crate::error::{Error, Result};
use crate::function::Function;
use crate::memory::{MemoryState, ALLOCATOR};
use crate::memory::{ALLOCATOR, MemoryState};
use crate::state::util::callback_error_ext;
use crate::stdlib::StdLib;
use crate::string::String;
use crate::string::LuaString;
use crate::table::Table;
use crate::thread::Thread;
use crate::traits::IntoLua;
use crate::traits::{FromLua, IntoLua};
use crate::types::{
AppDataRef, AppDataRefMut, Callback, CallbackUpvalue, DestructedUserdata, Integer, LightUserData,
LuaType, MaybeSend, ReentrantMutex, RegistryKey, ValueRef, XRc,
};
use crate::userdata::{
init_userdata_metatable, AnyUserData, MetaMethod, RawUserDataRegistry, UserData, UserDataRegistry,
UserDataStorage,
AnyUserData, MetaMethod, RawUserDataRegistry, UserData, UserDataRegistry, UserDataStorage,
init_userdata_metatable,
};
use crate::util::{
assert_stack, check_stack, get_destructed_userdata_metatable, get_internal_userdata, get_main_state,
get_metatable_ptr, get_userdata, init_error_registry, init_internal_metatable, pop_error,
push_internal_userdata, push_string, push_table, push_userdata, rawset_field, safe_pcall, safe_xpcall,
short_type_name, StackGuard, WrappedFailure,
StackGuard, WrappedFailure, assert_stack, check_stack, get_destructed_userdata_metatable,
get_internal_userdata, get_main_state, get_metatable_ptr, get_userdata, init_error_registry,
init_internal_metatable, pop_error, push_internal_userdata, push_string, push_table, push_userdata,
rawset_field, safe_pcall, safe_xpcall, short_type_name,
};
use crate::value::{Nil, Value};
@@ -50,7 +50,7 @@ use {
std::task::{Context, Poll, Waker},
};
/// An inner Lua struct which holds a raw Lua state.
/// An internal Lua struct which holds a raw Lua state.
#[doc(hidden)]
pub struct RawLua {
// The state is dynamic and depends on context
@@ -516,34 +516,34 @@ impl RawLua {
}
/// See [`Lua::create_string`]
pub(crate) unsafe fn create_string(&self, s: &[u8]) -> Result<String> {
pub(crate) unsafe fn create_string(&self, s: &[u8]) -> Result<LuaString> {
let state = self.state();
if self.unlikely_memory_error() {
push_string(state, s, false)?;
return Ok(String(self.pop_ref()));
return Ok(LuaString(self.pop_ref()));
}
let _sg = StackGuard::new(state);
check_stack(state, 3)?;
push_string(state, s, true)?;
Ok(String(self.pop_ref()))
Ok(LuaString(self.pop_ref()))
}
/// Creates an external string, that is, a string that uses memory not managed by Lua.
///
/// Modifies the input data to add `\0` terminator.
#[cfg(feature = "lua55")]
pub(crate) unsafe fn create_external_string(&self, bytes: Vec<u8>) -> Result<String> {
pub(crate) unsafe fn create_external_string(&self, bytes: Vec<u8>) -> Result<LuaString> {
let state = self.state();
if self.unlikely_memory_error() {
crate::util::push_external_string(state, bytes, false)?;
return Ok(String(self.pop_ref()));
return Ok(LuaString(self.pop_ref()));
}
let _sg = StackGuard::new(state);
check_stack(state, 3)?;
crate::util::push_external_string(state, bytes, true)?;
Ok(String(self.pop_ref()))
Ok(LuaString(self.pop_ref()))
}
#[cfg(feature = "luau")]
@@ -681,10 +681,10 @@ impl RawLua {
#[cfg(feature = "async")]
pub(crate) unsafe fn recycle_thread(&self, thread: &mut Thread) {
let extra = &mut *self.extra.get();
if extra.thread_pool.len() < extra.thread_pool.capacity() {
if let Some(index) = thread.0.index_count.take() {
extra.thread_pool.push(index);
}
if extra.thread_pool.len() < extra.thread_pool.capacity()
&& let Some(index) = thread.0.index_count.take()
{
extra.thread_pool.push(index);
}
}
@@ -731,14 +731,27 @@ impl RawLua {
/// Pushes a value that implements `IntoLua` onto the Lua stack.
///
/// Uses up to 2 stack spaces to push a single value, does not call `checkstack`.
#[allow(clippy::missing_safety_doc)]
#[inline(always)]
pub unsafe fn push(&self, value: impl IntoLua) -> Result<()> {
value.push_into_stack(self)
}
/// Pops a value that implements [`FromLua`] from the top of the Lua stack.
///
/// Uses up to 1 stack space, does not call `checkstack`.
#[allow(clippy::missing_safety_doc)]
#[inline(always)]
pub unsafe fn pop<R: FromLua>(&self) -> Result<R> {
let v = R::from_stack(-1, self)?;
ffi::lua_pop(self.state(), 1);
Ok(v)
}
/// Pushes a `Value` (by reference) onto the Lua stack.
///
/// Uses 2 stack spaces, does not call `checkstack`.
/// Uses up to 2 stack spaces, does not call `checkstack`.
#[allow(clippy::missing_safety_doc)]
pub unsafe fn push_value(&self, value: &Value) -> Result<()> {
let state = self.state();
match value {
@@ -773,6 +786,7 @@ impl RawLua {
/// Pops a value from the Lua stack.
///
/// Uses up to 1 stack spaces, does not call `checkstack`.
#[allow(clippy::missing_safety_doc)]
#[inline]
pub unsafe fn pop_value(&self) -> Value {
let value = self.stack_value(-1, None);
@@ -803,15 +817,22 @@ impl RawLua {
#[cfg(any(feature = "lua52", feature = "lua51", feature = "luajit", feature = "luau"))]
ffi::LUA_TNUMBER => {
use crate::types::Number;
let n = ffi::lua_tonumber(state, idx);
match num_traits::cast(n) {
Some(i) if n.to_bits() == (i as Number).to_bits() => Value::Integer(i),
Some(i) if n.to_bits() == (i as crate::types::Number).to_bits() => Value::Integer(i),
_ => Value::Number(n),
}
}
#[cfg(feature = "luau")]
ffi::LUA_TINTEGER => {
let i = ffi::lua_tointeger64(state, idx, ptr::null_mut());
match num_traits::cast(i) {
Some(i) => Value::Integer(i),
_ => Value::Number(i as crate::types::Number),
}
}
#[cfg(feature = "luau")]
ffi::LUA_TVECTOR => {
let v = ffi::lua_tovector(state, idx);
@@ -824,7 +845,7 @@ impl RawLua {
ffi::LUA_TSTRING => {
ffi::lua_xpush(state, self.ref_thread(), idx);
Value::String(String(self.pop_ref_thread()))
Value::String(LuaString(self.pop_ref_thread()))
}
ffi::LUA_TTABLE => {
@@ -949,7 +970,7 @@ impl RawLua {
// Check if userdata/metatable is already registered
let type_id = TypeId::of::<T>();
if let Some(&table_id) = (*self.extra.get()).registered_userdata_t.get(&type_id) {
return Ok(table_id as Integer);
return Ok(table_id);
}
// Create a new metatable from `UserData` definition
@@ -968,7 +989,7 @@ impl RawLua {
// Check if userdata/metatable is already registered
let type_id = TypeId::of::<T>();
if let Some(&table_id) = (*self.extra.get()).registered_userdata_t.get(&type_id) {
return Ok(table_id as Integer);
return Ok(table_id);
}
// Check if metatable creation is pending or create an empty metatable otherwise
@@ -983,7 +1004,7 @@ impl RawLua {
unsafe fn make_userdata_with_metatable<T>(
&self,
data: UserDataStorage<T>,
get_metatable_id: impl FnOnce() -> Result<Integer>,
get_metatable_id: impl FnOnce() -> Result<c_int>,
) -> Result<AnyUserData> {
let state = self.state();
let _sg = StackGuard::new(state);
@@ -993,7 +1014,7 @@ impl RawLua {
let mt_id = get_metatable_id()?;
let protect = !self.unlikely_memory_error();
push_userdata(state, data, protect)?;
ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, mt_id);
ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, mt_id as _);
ffi::lua_setmetatable(state, -2);
// Set empty environment for Lua 5.1
@@ -1011,7 +1032,7 @@ impl RawLua {
Ok(AnyUserData(self.pop_ref()))
}
pub(crate) unsafe fn create_userdata_metatable(&self, registry: RawUserDataRegistry) -> Result<Integer> {
pub(crate) unsafe fn create_userdata_metatable(&self, registry: RawUserDataRegistry) -> Result<c_int> {
let state = self.state();
let type_id = registry.type_id;
@@ -1027,7 +1048,7 @@ impl RawLua {
}
self.register_userdata_metatable(mt_ptr, type_id);
Ok(id as Integer)
Ok(id)
}
pub(crate) unsafe fn push_userdata_metatable(&self, mut registry: RawUserDataRegistry) -> Result<()> {
@@ -1126,7 +1147,7 @@ impl RawLua {
#[cfg(feature = "luau")]
if registry.enable_namecall {
let map: &mut rustc_hash::FxHashMap<_, crate::types::CallbackPtr> =
methods_map.get_or_insert_with(Default::default);
methods_map.get_or_insert_default();
for (k, m) in &registry.methods {
map.insert(k.as_bytes().to_vec(), &**m);
}
@@ -1219,13 +1240,11 @@ impl RawLua {
Ok(type_id) => Ok(type_id),
Err(Error::UserDataTypeMismatch) if ffi::lua_type(state, idx) != ffi::LUA_TUSERDATA => {
// Report `FromLuaConversionError` instead
// In Luau `luaL_typename` return heap-allocated string that is valid only for
// the `state` lifetime.
// `lua_typename` is used instead to get a truly static string.
let idx_type_name = CStr::from_ptr(ffi::lua_typename(state, ffi::lua_type(state, idx)));
let idx_type_name = idx_type_name.to_str().unwrap();
let type_name = CStr::from_ptr(ffi::lua_typename(state, ffi::lua_type(state, idx)))
.to_str()
.unwrap_or("unknown");
let message = format!("expected userdata of type '{}'", short_type_name::<T>());
Err(Error::from_lua_conversion(idx_type_name, "userdata", message))
Err(Error::from_lua_conversion(type_name, "userdata", message))
}
Err(err) => Err(err),
}
@@ -1586,6 +1605,11 @@ unsafe fn load_std_libs(state: *mut ffi::lua_State, libs: StdLib) -> Result<()>
requiref(state, ffi::LUA_VECLIBNAME, ffi::luaopen_vector, 1)?;
}
#[cfg(feature = "luau")]
if libs.contains(StdLib::INTEGER) {
requiref(state, ffi::LUA_INTLIBNAME, ffi::luaopen_integer, 1)?;
}
if libs.contains(StdLib::MATH) {
requiref(state, ffi::LUA_MATHLIBNAME, ffi::luaopen_math, 1)?;
}
+2 -2
View File
@@ -1,11 +1,11 @@
use std::os::raw::c_int;
use std::panic::{catch_unwind, AssertUnwindSafe};
use std::panic::{AssertUnwindSafe, catch_unwind};
use std::ptr;
use std::sync::Arc;
use crate::error::{Error, Result};
use crate::state::{ExtraData, RawLua};
use crate::util::{self, get_internal_metatable, WrappedFailure};
use crate::util::{self, WrappedFailure, get_internal_metatable};
struct StateGuard<'a>(&'a RawLua, *mut ffi::lua_State);
+6 -1
View File
@@ -73,10 +73,15 @@ impl StdLib {
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub const VECTOR: StdLib = StdLib(1 << 10);
/// [`integer`](https://luau.org/library#integer-library) library
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub const INTEGER: StdLib = StdLib(1 << 11);
/// [`jit`](http://luajit.org/ext_jit.html) library
#[cfg(any(feature = "luajit", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luajit")))]
pub const JIT: StdLib = StdLib(1 << 11);
pub const JIT: StdLib = StdLib(1 << 12);
/// (**unsafe**) [`ffi`](http://luajit.org/ext_ffi.html) library
#[cfg(any(feature = "luajit", doc))]
+85 -79
View File
@@ -1,9 +1,12 @@
use std::borrow::{Borrow, Cow};
//! Lua string handling.
//!
//! This module provides types for working with Lua strings from Rust.
use std::borrow::Borrow;
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 std::{cmp, fmt, mem, slice, str};
use crate::error::{Error, Result};
use crate::state::Lua;
@@ -21,37 +24,40 @@ use {
///
/// Unlike Rust strings, Lua strings may not be valid UTF-8.
#[derive(Clone)]
pub struct String(pub(crate) ValueRef);
pub struct LuaString(pub(crate) ValueRef);
impl String {
impl LuaString {
/// Get a [`BorrowedStr`] if the Lua string is valid UTF-8.
///
/// The returned `BorrowedStr` holds a strong reference to the Lua state to guarantee the
/// validity of the underlying data.
///
/// # Examples
///
/// ```
/// # use mlua::{Lua, Result, String};
/// # use mlua::{Lua, LuaString, Result};
/// # fn main() -> Result<()> {
/// # let lua = Lua::new();
/// let globals = lua.globals();
///
/// let version: String = globals.get("_VERSION")?;
/// let version: LuaString = globals.get("_VERSION")?;
/// assert!(version.to_str()?.contains("Lua"));
///
/// let non_utf8: String = lua.load(r#" "test\255" "#).eval()?;
/// let non_utf8: LuaString = lua.load(r#" "test\255" "#).eval()?;
/// assert!(non_utf8.to_str().is_err());
/// # Ok(())
/// # }
/// ```
#[inline]
pub fn to_str(&self) -> Result<BorrowedStr<'_>> {
pub fn to_str(&self) -> Result<BorrowedStr> {
BorrowedStr::try_from(self)
}
/// Converts this string to a [`StdString`].
/// Converts this Lua string to a [`String`].
///
/// 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
/// This method returns [`String`] instead of [`Cow<'_, str>`] because lifetime cannot be
/// bound to a weak Lua object.
///
/// [U+FFFD]: std::char::REPLACEMENT_CHARACTER
@@ -70,11 +76,11 @@ impl String {
/// # }
/// ```
#[inline]
pub fn to_string_lossy(&self) -> StdString {
StdString::from_utf8_lossy(&self.as_bytes()).into_owned()
pub fn to_string_lossy(&self) -> String {
String::from_utf8_lossy(&self.as_bytes()).into_owned()
}
/// Returns an object that implements [`Display`] for safely printing a Lua [`String`] that may
/// Returns an object that implements [`Display`] for safely printing a [`LuaString`] that may
/// contain non-Unicode data.
///
/// This may perform lossy conversion.
@@ -86,32 +92,33 @@ impl String {
/// Get the bytes that make up this string.
///
/// The returned slice will not contain the terminating null byte, but will contain any null
/// bytes embedded into the Lua string.
/// The returned `BorrowedStr` holds a strong reference to the Lua state to guarantee the
/// validity of the underlying data. The data will not contain the terminating null byte, but
/// will contain any null bytes embedded into the Lua string.
///
/// # Examples
///
/// ```
/// # use mlua::{Lua, Result, String};
/// # use mlua::{Lua, LuaString, Result};
/// # fn main() -> Result<()> {
/// # let lua = Lua::new();
/// let non_utf8: String = lua.load(r#" "test\255" "#).eval()?;
/// let non_utf8: LuaString = lua.load(r#" "test\255" "#).eval()?;
/// assert!(non_utf8.to_str().is_err()); // oh no :(
/// assert_eq!(non_utf8.as_bytes(), &b"test\xff"[..]);
/// # Ok(())
/// # }
/// ```
#[inline]
pub fn as_bytes(&self) -> BorrowedBytes<'_> {
pub fn as_bytes(&self) -> BorrowedBytes {
BorrowedBytes::from(self)
}
/// Get the bytes that make up this string, including the trailing null byte.
pub fn as_bytes_with_nul(&self) -> BorrowedBytes<'_> {
let BorrowedBytes { buf, borrow, _lua } = BorrowedBytes::from(self);
pub fn as_bytes_with_nul(&self) -> BorrowedBytes {
let BorrowedBytes { buf, vref, _lua } = BorrowedBytes::from(self);
// Include the trailing null 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 }
BorrowedBytes { buf, vref, _lua }
}
// Does not return the terminating null byte
@@ -135,7 +142,7 @@ impl String {
(slice, lua)
}
/// Converts this string to a generic C pointer.
/// Converts this Lua string to a generic C pointer.
///
/// There is no way to convert the pointer back to its original value.
///
@@ -146,7 +153,7 @@ impl String {
}
}
impl fmt::Debug for String {
impl fmt::Debug for LuaString {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let bytes = self.as_bytes();
// Check if the string is valid utf8
@@ -162,12 +169,12 @@ impl fmt::Debug for String {
// 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`.
// This makes our `LuaString` comparable with `Vec<u8>`, `[u8]`, `&str` and `String`.
//
// The only downside is that this disallows a comparison with `Cow<str>`, as that only implements
// `AsRef<str>`, which collides with this impl. Requiring `AsRef<str>` would fix that, but limit us
// in other ways.
impl<T> PartialEq<T> for String
impl<T> PartialEq<T> for LuaString
where
T: AsRef<[u8]> + ?Sized,
{
@@ -176,43 +183,43 @@ where
}
}
impl PartialEq for String {
fn eq(&self, other: &String) -> bool {
impl PartialEq for LuaString {
fn eq(&self, other: &LuaString) -> bool {
self.as_bytes() == other.as_bytes()
}
}
impl Eq for String {}
impl Eq for LuaString {}
impl<T> PartialOrd<T> for String
impl<T> PartialOrd<T> for LuaString
where
T: AsRef<[u8]> + ?Sized,
{
fn partial_cmp(&self, other: &T) -> Option<cmp::Ordering> {
self.as_bytes().partial_cmp(&other.as_ref())
<[u8]>::partial_cmp(&self.as_bytes(), other.as_ref())
}
}
impl PartialOrd for String {
fn partial_cmp(&self, other: &String) -> Option<cmp::Ordering> {
impl PartialOrd for LuaString {
fn partial_cmp(&self, other: &LuaString) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for String {
fn cmp(&self, other: &String) -> cmp::Ordering {
impl Ord for LuaString {
fn cmp(&self, other: &LuaString) -> cmp::Ordering {
self.as_bytes().cmp(&other.as_bytes())
}
}
impl Hash for String {
impl Hash for LuaString {
fn hash<H: Hasher>(&self, state: &mut H) {
self.as_bytes().hash(state);
}
}
#[cfg(feature = "serde")]
impl Serialize for String {
impl Serialize for LuaString {
fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
where
S: Serializer,
@@ -224,7 +231,7 @@ impl Serialize for String {
}
}
struct Display<'a>(&'a String);
struct Display<'a>(&'a LuaString);
impl fmt::Display for Display<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
@@ -234,14 +241,14 @@ impl fmt::Display for Display<'_> {
}
/// A borrowed string (`&str`) that holds a strong reference to the Lua state.
pub struct BorrowedStr<'a> {
pub struct BorrowedStr {
// `buf` points to a readonly memory managed by Lua
pub(crate) buf: &'a str,
pub(crate) borrow: Cow<'a, String>,
pub(crate) buf: &'static str,
pub(crate) vref: ValueRef,
pub(crate) _lua: Lua,
}
impl Deref for BorrowedStr<'_> {
impl Deref for BorrowedStr {
type Target = str;
#[inline(always)]
@@ -250,33 +257,33 @@ impl Deref for BorrowedStr<'_> {
}
}
impl Borrow<str> for BorrowedStr<'_> {
impl Borrow<str> for BorrowedStr {
#[inline(always)]
fn borrow(&self) -> &str {
self.buf
}
}
impl AsRef<str> for BorrowedStr<'_> {
impl AsRef<str> for BorrowedStr {
#[inline(always)]
fn as_ref(&self) -> &str {
self.buf
}
}
impl fmt::Display for BorrowedStr<'_> {
impl fmt::Display for BorrowedStr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.buf.fmt(f)
}
}
impl fmt::Debug for BorrowedStr<'_> {
impl fmt::Debug for BorrowedStr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.buf.fmt(f)
}
}
impl<T> PartialEq<T> for BorrowedStr<'_>
impl<T> PartialEq<T> for BorrowedStr
where
T: AsRef<str>,
{
@@ -285,9 +292,9 @@ where
}
}
impl Eq for BorrowedStr<'_> {}
impl Eq for BorrowedStr {}
impl<T> PartialOrd<T> for BorrowedStr<'_>
impl<T> PartialOrd<T> for BorrowedStr
where
T: AsRef<str>,
{
@@ -296,36 +303,33 @@ where
}
}
impl Ord for BorrowedStr<'_> {
impl Ord for BorrowedStr {
fn cmp(&self, other: &Self) -> cmp::Ordering {
self.buf.cmp(other.buf)
}
}
impl<'a> TryFrom<&'a String> for BorrowedStr<'a> {
impl TryFrom<&LuaString> for BorrowedStr {
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 })
fn try_from(value: &LuaString) -> Result<Self> {
let BorrowedBytes { buf, vref, _lua } = BorrowedBytes::from(value);
let buf =
str::from_utf8(buf).map_err(|e| Error::from_lua_conversion("string", "&str", e.to_string()))?;
Ok(Self { buf, vref, _lua })
}
}
/// A borrowed byte slice (`&[u8]`) that holds a strong reference to the Lua state.
pub struct BorrowedBytes<'a> {
pub struct BorrowedBytes {
// `buf` points to a readonly memory managed by Lua
pub(crate) buf: &'a [u8],
pub(crate) borrow: Cow<'a, String>,
pub(crate) buf: &'static [u8],
pub(crate) vref: ValueRef,
pub(crate) _lua: Lua,
}
impl Deref for BorrowedBytes<'_> {
impl Deref for BorrowedBytes {
type Target = [u8];
#[inline(always)]
@@ -334,27 +338,27 @@ impl Deref for BorrowedBytes<'_> {
}
}
impl Borrow<[u8]> for BorrowedBytes<'_> {
impl Borrow<[u8]> for BorrowedBytes {
#[inline(always)]
fn borrow(&self) -> &[u8] {
self.buf
}
}
impl AsRef<[u8]> for BorrowedBytes<'_> {
impl AsRef<[u8]> for BorrowedBytes {
#[inline(always)]
fn as_ref(&self) -> &[u8] {
self.buf
}
}
impl fmt::Debug for BorrowedBytes<'_> {
impl fmt::Debug for BorrowedBytes {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.buf.fmt(f)
}
}
impl<T> PartialEq<T> for BorrowedBytes<'_>
impl<T> PartialEq<T> for BorrowedBytes
where
T: AsRef<[u8]>,
{
@@ -363,9 +367,9 @@ where
}
}
impl Eq for BorrowedBytes<'_> {}
impl Eq for BorrowedBytes {}
impl<T> PartialOrd<T> for BorrowedBytes<'_>
impl<T> PartialOrd<T> for BorrowedBytes
where
T: AsRef<[u8]>,
{
@@ -374,13 +378,13 @@ where
}
}
impl Ord for BorrowedBytes<'_> {
impl Ord for BorrowedBytes {
fn cmp(&self, other: &Self) -> cmp::Ordering {
self.buf.cmp(other.buf)
}
}
impl<'a> IntoIterator for &'a BorrowedBytes<'_> {
impl<'a> IntoIterator for &'a BorrowedBytes {
type Item = &'a u8;
type IntoIter = slice::Iter<'a, u8>;
@@ -389,18 +393,20 @@ impl<'a> IntoIterator for &'a BorrowedBytes<'_> {
}
}
impl<'a> From<&'a String> for BorrowedBytes<'a> {
impl From<&LuaString> for BorrowedBytes {
#[inline]
fn from(value: &'a String) -> Self {
fn from(value: &LuaString) -> Self {
let (buf, _lua) = unsafe { value.to_slice() };
let borrow = Cow::Borrowed(value);
Self { buf, borrow, _lua }
let vref = value.0.clone();
// SAFETY: The `buf` is valid for the lifetime of the Lua state and occupied slot index
let buf = unsafe { mem::transmute::<&[u8], &'static [u8]>(buf) };
Self { buf, vref, _lua }
}
}
struct WrappedString<T: AsRef<[u8]>>(T);
impl String {
impl LuaString {
/// Wraps bytes, returning an opaque type that implements [`IntoLua`] trait.
///
/// This function uses [`Lua::create_string`] under the hood.
@@ -415,7 +421,7 @@ impl<T: AsRef<[u8]>> IntoLua for WrappedString<T> {
}
}
impl LuaType for String {
impl LuaType for LuaString {
const TYPE_ID: c_int = ffi::LUA_TSTRING;
}
@@ -424,9 +430,9 @@ mod assertions {
use super::*;
#[cfg(not(feature = "send"))]
static_assertions::assert_not_impl_any!(String: Send);
static_assertions::assert_not_impl_any!(LuaString: Send);
#[cfg(feature = "send")]
static_assertions::assert_impl_all!(String: Send, Sync);
static_assertions::assert_impl_all!(LuaString: Send, Sync);
#[cfg(feature = "send")]
static_assertions::assert_impl_all!(BorrowedBytes: Send, Sync);
#[cfg(feature = "send")]
+168 -19
View File
@@ -1,15 +1,167 @@
//! Lua table handling.
//!
//! Tables are Lua's primary data structure, used for arrays, dictionaries, objects, modules,
//! and more. This module provides types for creating and manipulating Lua tables from Rust.
//!
//! # Basic Operations
//!
//! Tables support key-value access similar to Rust's `HashMap`:
//!
//! ```
//! # use mlua::{Lua, Result};
//! # fn main() -> Result<()> {
//! let lua = Lua::new();
//! let table = lua.create_table()?;
//!
//! // Set and get values
//! table.set("key", "value")?;
//! let value: String = table.get("key")?;
//! assert_eq!(value, "value");
//!
//! // Keys and values can be any Lua-compatible type
//! table.set(1, "first")?;
//! table.set("nested", lua.create_table()?)?;
//! # Ok(())
//! # }
//! ```
//!
//! # Array Operations
//!
//! Tables can be used as arrays with 1-based indexing:
//!
//! ```
//! # use mlua::{Lua, Result};
//! # fn main() -> Result<()> {
//! let lua = Lua::new();
//! let array = lua.create_table()?;
//!
//! // Push values to the end (like Vec::push)
//! array.push("first")?;
//! array.push("second")?;
//! array.push("third")?;
//!
//! // Pop from the end
//! let last: String = array.pop()?;
//! assert_eq!(last, "third");
//!
//! // Get length
//! assert_eq!(array.raw_len(), 2);
//! # Ok(())
//! # }
//! ```
//!
//! # Iteration
//!
//! Iterate over all key-value pairs with [`Table::pairs`]:
//!
//! ```
//! # use mlua::{Lua, Result, Value};
//! # fn main() -> Result<()> {
//! let lua = Lua::new();
//! let table = lua.create_table()?;
//! table.set("a", 1)?;
//! table.set("b", 2)?;
//!
//! for pair in table.pairs::<String, i32>() {
//! let (key, value) = pair?;
//! println!("{key} = {value}");
//! }
//! # Ok(())
//! # }
//! ```
//!
//! For array portions, use [`Table::sequence_values`]:
//!
//! ```
//! # use mlua::{Lua, Result};
//! # fn main() -> Result<()> {
//! let lua = Lua::new();
//! let array = lua.create_sequence_from(["a", "b", "c"])?;
//!
//! for value in array.sequence_values::<String>() {
//! println!("{}", value?);
//! }
//! # Ok(())
//! # }
//! ```
//!
//! # Raw vs Normal Access
//!
//! Methods prefixed with `raw_` (like [`Table::raw_get`], [`Table::raw_set`]) bypass
//! metamethods, directly accessing the table's contents. Normal methods may trigger
//! `__index`, `__newindex`, and other metamethods:
//!
//! ```
//! # use mlua::{Lua, Result};
//! # fn main() -> Result<()> {
//! let lua = Lua::new();
//!
//! // raw_set bypasses __newindex metamethod
//! let t = lua.create_table()?;
//! t.raw_set("key", "value")?;
//!
//! // raw_get bypasses __index metamethod
//! let v: String = t.raw_get("key")?;
//! # Ok(())
//! # }
//! ```
//!
//! # Metatables
//!
//! Tables can have metatables that customize their behavior:
//!
//! ```
//! # use mlua::{Lua, Result};
//! # fn main() -> Result<()> {
//! let lua = Lua::new();
//!
//! let table = lua.create_table()?;
//! let metatable = lua.create_table()?;
//!
//! // Set a default value via __index
//! metatable.set("__index", lua.create_function(|_, _: ()| Ok("default"))?)?;
//! table.set_metatable(Some(metatable))?;
//!
//! // Accessing missing keys returns "default"
//! let value: String = table.get("missing")?;
//! assert_eq!(value, "default");
//! # Ok(())
//! # }
//! ```
//!
//! # Global Table
//!
//! The Lua global environment is itself a table, accessible via [`Lua::globals`]:
//!
//! ```
//! # use mlua::{Lua, Result};
//! # fn main() -> Result<()> {
//! let lua = Lua::new();
//! let globals = lua.globals();
//!
//! // Set a global variable
//! globals.set("my_var", 42)?;
//!
//! // Now accessible from Lua code
//! let result: i32 = lua.load("my_var + 8").eval()?;
//! assert_eq!(result, 50);
//! # Ok(())
//! # }
//! ```
//!
//! [`Lua::globals`]: crate::Lua::globals
use std::collections::HashSet;
use std::fmt;
use std::marker::PhantomData;
use std::os::raw::c_void;
use std::string::String as StdString;
use crate::error::{Error, Result};
use crate::function::Function;
use crate::state::{LuaGuard, RawLua, WeakLua};
use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, ObjectLike};
use crate::types::{Integer, ValueRef};
use crate::util::{assert_stack, check_stack, get_metatable_ptr, StackGuard};
use crate::util::{StackGuard, assert_stack, check_stack, get_metatable_ptr};
use crate::value::{Nil, Value};
#[cfg(feature = "async")]
@@ -226,15 +378,15 @@ impl Table {
// 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() {
if mt.contains_key("__eq")? {
return mt.get::<Function>("__eq")?.call((self, other));
}
if let Some(mt) = self.metatable()
&& let Some(eq_func) = mt.get::<Option<Function>>("__eq")?
{
return eq_func.call((self, other));
}
if let Some(mt) = other.metatable() {
if mt.contains_key("__eq")? {
return mt.get::<Function>("__eq")?.call((self, other));
}
if let Some(mt) = other.metatable()
&& let Some(eq_func) = mt.get::<Option<Function>>("__eq")?
{
return eq_func.call((self, other));
}
Ok(false)
@@ -626,10 +778,8 @@ impl Table {
ffi::lua_pushnil(state);
while ffi::lua_next(state, -2) != 0 {
let k = K::from_stack(-2, &lua)?;
let v = V::from_stack(-1, &lua)?;
let v = lua.pop::<V>()?;
f(k, v)?;
// Keep key for next iteration
ffi::lua_pop(state, 1);
}
}
Ok(())
@@ -702,8 +852,7 @@ impl Table {
if len.is_none() && t == ffi::LUA_TNIL {
break;
}
f(V::from_stack(-1, &lua)?)?;
ffi::lua_pop(state, 1);
f(lua.pop::<V>()?)?;
}
}
Ok(())
@@ -1008,7 +1157,7 @@ impl ObjectLike for Table {
}
#[inline]
fn to_string(&self) -> Result<StdString> {
fn to_string(&self) -> Result<String> {
Value::Table(Table(self.0.clone())).to_string()
}
@@ -1070,7 +1219,7 @@ impl Serialize for SerializableTable<'_> {
where
S: Serializer,
{
use crate::serde::de::{check_value_for_skip, MapPairs, RecursionGuard};
use crate::serde::de::{MapPairs, RecursionGuard, check_value_for_skip};
use crate::value::SerializableValue;
let convert_result = |res: Result<()>, serialize_err: Option<S::Error>| match res {
@@ -1098,7 +1247,7 @@ impl Serialize for SerializableTable<'_> {
seq.serialize_element(&SerializableValue::new(&value, options, Some(visited)))
.map_err(|err| {
serialize_err = Some(err);
Error::SerializeError(StdString::new())
Error::SerializeError(String::new())
})
});
convert_result(res, serialize_err)?;
@@ -1123,7 +1272,7 @@ impl Serialize for SerializableTable<'_> {
)
.map_err(|err| {
serialize_err = Some(err);
Error::SerializeError(StdString::new())
Error::SerializeError(String::new())
})
};
+68 -10
View File
@@ -1,3 +1,40 @@
//! Lua thread (coroutine) handling.
//!
//! This module provides types for creating and working with Lua coroutines from Rust.
//! Coroutines allow cooperative multitasking within a single Lua state by suspending and
//! resuming execution at well-defined yield points.
//!
//! # Basic Usage
//!
//! Threads are created via [`Lua::create_thread`] and driven by calling [`Thread::resume`]:
//!
//! ```rust
//! # use mlua::{Lua, Result, Thread};
//! # fn main() -> Result<()> {
//! let lua = Lua::new();
//! let thread: Thread = lua.load(r#"
//! coroutine.create(function(a, b)
//! coroutine.yield(a + b)
//! return a * b
//! end)
//! "#).eval()?;
//!
//! assert_eq!(thread.resume::<i32>((3, 4))?, 7);
//! assert_eq!(thread.resume::<i32>(())?, 12);
//! # Ok(())
//! # }
//! ```
//!
//! # Async Support
//!
//! When the `async` feature is enabled, a [`Thread`] can be converted into an [`AsyncThread`]
//! via [`Thread::into_async`], which implements both [`Future`] and [`Stream`].
//! This integrates Lua coroutines naturally with Rust async runtimes such as Tokio.
//!
//! [`Lua::create_thread`]: crate::Lua::create_thread
//! [`Future`]: std::future::Future
//! [`Stream`]: futures_util::stream::Stream
use std::fmt;
use std::os::raw::{c_int, c_void};
@@ -6,7 +43,7 @@ use crate::function::Function;
use crate::state::RawLua;
use crate::traits::{FromLuaMulti, IntoLuaMulti};
use crate::types::{LuaType, ValueRef};
use crate::util::{check_stack, error_traceback_thread, pop_error, StackGuard};
use crate::util::{StackGuard, check_stack, error_traceback_thread, pop_error};
#[cfg(not(feature = "luau"))]
use crate::{
@@ -69,7 +106,7 @@ impl ThreadStatusInner {
}
/// Handle to an internal Lua thread (coroutine).
#[derive(Clone)]
#[derive(Clone, PartialEq)]
pub struct Thread(pub(crate) ValueRef, pub(crate) *mut ffi::lua_State);
#[cfg(feature = "send")]
@@ -92,7 +129,6 @@ pub struct AsyncThread<R> {
impl Thread {
/// Returns reference to the Lua state that this thread is associated with.
#[doc(hidden)]
#[inline(always)]
pub fn state(&self) -> *mut ffi::lua_State {
self.1
@@ -259,6 +295,31 @@ impl Thread {
}
}
/// Returns `true` if this thread is resumable (meaning it can be resumed by calling
/// [`Thread::resume`]).
#[inline(always)]
pub fn is_resumable(&self) -> bool {
self.status() == ThreadStatus::Resumable
}
/// Returns `true` if this thread is currently running.
#[inline(always)]
pub fn is_running(&self) -> bool {
self.status() == ThreadStatus::Running
}
/// Returns `true` if this thread has finished executing.
#[inline(always)]
pub fn is_finished(&self) -> bool {
self.status() == ThreadStatus::Finished
}
/// Returns `true` if this thread has raised a Lua error during execution.
#[inline(always)]
pub fn is_error(&self) -> bool {
self.status() == ThreadStatus::Error
}
/// 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.
@@ -295,7 +356,7 @@ impl Thread {
/// Resets a thread
///
/// In [Lua 5.4]: cleans its call stack and closes all pending to-be-closed variables.
/// Returns a error in case of either the original error that stopped the thread or errors
/// Returns an error in case of either the original error that stopped the thread or errors
/// in closing methods.
///
/// In Luau: resets to the initial state of a newly created Lua thread.
@@ -449,6 +510,8 @@ impl Thread {
/// 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.
///
/// [`Lua::sandbox`]: crate::Lua::sandbox
///
/// # Examples
///
/// ```
@@ -502,12 +565,6 @@ impl fmt::Debug for Thread {
}
}
impl PartialEq for Thread {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl LuaType for Thread {
const TYPE_ID: c_int = ffi::LUA_TTHREAD;
}
@@ -523,6 +580,7 @@ impl<R> AsyncThread<R> {
#[cfg(feature = "async")]
impl<R> Drop for AsyncThread<R> {
fn drop(&mut self) {
#[allow(clippy::collapsible_if)]
if self.recycle {
if let Some(lua) = self.thread.0.lua.try_lock() {
unsafe {
+8 -96
View File
@@ -1,17 +1,20 @@
//! Core conversion and extension traits.
//!
//! This module provides the fundamental traits for converting values between Rust and Lua,
//! and for defining native Lua callable functions.
use std::os::raw::c_int;
use std::string::String as StdString;
use std::sync::Arc;
use crate::error::{Error, Result};
use crate::multi::MultiValue;
use crate::private::Sealed;
use crate::state::{Lua, RawLua, WeakLua};
use crate::types::MaybeSend;
use crate::util::{check_stack, parse_lookup_path, short_type_name};
use crate::value::Value;
#[cfg(feature = "async")]
use {crate::function::AsyncCallFuture, std::future::Future};
use crate::function::AsyncCallFuture;
/// Trait for types convertible to [`Value`].
pub trait IntoLua: Sized {
@@ -236,7 +239,7 @@ pub trait ObjectLike: Sealed {
/// Converts the object to a string in a human-readable format.
///
/// This might invoke the `__tostring` metamethod.
fn to_string(&self) -> Result<StdString>;
fn to_string(&self) -> Result<String>;
/// Converts the object to a Lua value.
fn to_value(&self) -> Value;
@@ -246,100 +249,9 @@ pub trait ObjectLike: Sealed {
fn weak_lua(&self) -> &WeakLua;
}
/// A trait for types that can be used as Lua functions.
pub trait LuaNativeFn<A: FromLuaMulti> {
type Output: IntoLuaMulti;
fn call(&self, args: A) -> Self::Output;
}
/// A trait for types with mutable state that can be used as Lua functions.
pub trait LuaNativeFnMut<A: FromLuaMulti> {
type Output: IntoLuaMulti;
fn call(&mut self, args: A) -> Self::Output;
}
/// A trait for types that returns a future and can be used as Lua functions.
#[cfg(feature = "async")]
pub trait LuaNativeAsyncFn<A: FromLuaMulti> {
type Output: IntoLuaMulti;
fn call(&self, args: A) -> impl Future<Output = Self::Output> + MaybeSend + 'static;
}
macro_rules! impl_lua_native_fn {
($($A:ident),*) => {
impl<FN, $($A,)* R> LuaNativeFn<($($A,)*)> for FN
where
FN: Fn($($A,)*) -> R + MaybeSend + 'static,
($($A,)*): FromLuaMulti,
R: IntoLuaMulti,
{
type Output = R;
#[allow(non_snake_case)]
fn call(&self, args: ($($A,)*)) -> Self::Output {
let ($($A,)*) = args;
self($($A,)*)
}
}
impl<FN, $($A,)* R> LuaNativeFnMut<($($A,)*)> for FN
where
FN: FnMut($($A,)*) -> R + MaybeSend + 'static,
($($A,)*): FromLuaMulti,
R: IntoLuaMulti,
{
type Output = R;
#[allow(non_snake_case)]
fn call(&mut self, args: ($($A,)*)) -> Self::Output {
let ($($A,)*) = args;
self($($A,)*)
}
}
#[cfg(feature = "async")]
impl<FN, $($A,)* Fut, R> LuaNativeAsyncFn<($($A,)*)> for FN
where
FN: Fn($($A,)*) -> Fut + MaybeSend + 'static,
($($A,)*): FromLuaMulti,
Fut: Future<Output = R> + MaybeSend + 'static,
R: IntoLuaMulti,
{
type Output = R;
#[allow(non_snake_case)]
fn call(&self, args: ($($A,)*)) -> impl Future<Output = Self::Output> + MaybeSend + 'static {
let ($($A,)*) = args;
self($($A,)*)
}
}
};
}
impl_lua_native_fn!();
impl_lua_native_fn!(A);
impl_lua_native_fn!(A, B);
impl_lua_native_fn!(A, B, C);
impl_lua_native_fn!(A, B, C, D);
impl_lua_native_fn!(A, B, C, D, E);
impl_lua_native_fn!(A, B, C, D, E, F);
impl_lua_native_fn!(A, B, C, D, E, F, G);
impl_lua_native_fn!(A, B, C, D, E, F, G, H);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J, K);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J, K, L);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J, K, L, M);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J, K, L, M, N);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P);
pub(crate) trait ShortTypeName {
#[inline(always)]
fn type_name() -> StdString {
fn type_name() -> String {
short_type_name::<Self>()
}
}
+12
View File
@@ -128,6 +128,18 @@ pub trait MaybeSend {}
#[cfg(not(feature = "send"))]
impl<T> MaybeSend for T {}
/// A trait that adds `Sync` requirement if `send` feature is enabled.
#[cfg(feature = "send")]
pub trait MaybeSync: Sync {}
#[cfg(feature = "send")]
impl<T: Sync> MaybeSync for T {}
/// A trait that adds `Sync` requirement if `send` feature is enabled.
#[cfg(not(feature = "send"))]
pub trait MaybeSync {}
#[cfg(not(feature = "send"))]
impl<T> MaybeSync for T {}
pub(crate) struct DestructedUserdata;
pub(crate) trait LuaType {
+1 -1
View File
@@ -69,7 +69,7 @@ mod inner {
#[inline(always)]
fn deref(&self) -> &Self::Target {
&self.0 .0
&self.0.0
}
}
}
+4 -4
View File
@@ -55,10 +55,10 @@ impl Drop for ValueRef {
if let Some(ValueRefIndex(index)) = self.index_count.take() {
// It's guaranteed that the inner value returns exactly once.
// This means in particular that the value is not dropped.
if XRc::into_inner(index).is_some() {
if let Some(lua) = self.lua.try_lock() {
unsafe { lua.drop_ref(self) };
}
if XRc::into_inner(index).is_some()
&& let Some(lua) = self.lua.try_lock()
{
unsafe { lua.drop_ref(self) }
}
}
}
+93 -43
View File
@@ -1,18 +1,22 @@
//! Lua userdata handling.
//!
//! This module provides types for creating and working with Lua userdata from Rust.
use std::any::TypeId;
use std::ffi::CStr;
use std::fmt;
use std::hash::Hash;
use std::os::raw::{c_char, c_void};
use std::string::String as StdString;
use crate::Either;
use crate::error::{Error, Result};
use crate::function::Function;
use crate::state::Lua;
use crate::string::String;
use crate::string::LuaString;
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, short_type_name, take_userdata, StackGuard};
use crate::types::{MaybeSend, MaybeSync, ValueRef};
use crate::util::{StackGuard, check_stack, get_userdata, push_string, short_type_name, take_userdata};
use crate::value::Value;
#[cfg(feature = "async")]
@@ -26,12 +30,12 @@ use {
// Re-export for convenience
pub(crate) use cell::UserDataStorage;
pub use r#ref::{UserDataRef, UserDataRefMut};
pub use r#ref::{UserDataOwned, 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,
TypeIdHints, borrow_userdata_scoped, borrow_userdata_scoped_mut, collect_userdata,
init_userdata_metatable,
};
/// Kinds of metamethods that can be overridden.
@@ -124,6 +128,11 @@ pub enum MetaMethod {
///
/// This is not an operator, but will be called by methods such as `tostring` and `print`.
ToString,
/// The `__todebugstring` metamethod for debug purposes.
///
/// This is an mlua-specific metamethod that can be used to provide debug representation for
/// userdata.
ToDebugString,
/// The `__pairs` metamethod.
///
/// This is not an operator, but it will be called by the built-in `pairs` function.
@@ -185,7 +194,7 @@ impl PartialEq<MetaMethod> for &str {
}
}
impl PartialEq<MetaMethod> for StdString {
impl PartialEq<MetaMethod> for String {
fn eq(&self, other: &MetaMethod) -> bool {
self == other.name()
}
@@ -233,6 +242,7 @@ impl MetaMethod {
MetaMethod::NewIndex => "__newindex",
MetaMethod::Call => "__call",
MetaMethod::ToString => "__tostring",
MetaMethod::ToDebugString => "__todebugstring",
#[cfg(any(
feature = "lua55",
@@ -279,7 +289,7 @@ impl AsRef<str> for MetaMethod {
}
}
impl From<MetaMethod> for StdString {
impl From<MetaMethod> for String {
#[inline]
fn from(method: MetaMethod) -> Self {
method.name().to_owned()
@@ -295,7 +305,7 @@ pub trait UserDataMethods<T> {
///
/// If `add_meta_method` is used to set the `__index` metamethod, the `__index` metamethod will
/// be used as a fall-back if no regular method is found.
fn add_method<M, A, R>(&mut self, name: impl Into<StdString>, method: M)
fn add_method<M, A, R>(&mut self, name: impl Into<String>, method: M)
where
M: Fn(&Lua, &T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
@@ -306,7 +316,7 @@ pub trait UserDataMethods<T> {
/// Refer to [`add_method`] for more information about the implementation.
///
/// [`add_method`]: UserDataMethods::add_method
fn add_method_mut<M, A, R>(&mut self, name: impl Into<StdString>, method: M)
fn add_method_mut<M, A, R>(&mut self, name: impl Into<String>, method: M)
where
M: FnMut(&Lua, &mut T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
@@ -319,8 +329,7 @@ pub trait UserDataMethods<T> {
///
/// The method can be called only once per userdata instance, subsequent calls will result in a
/// [`Error::UserDataDestructed`] error.
#[doc(hidden)]
fn add_method_once<M, A, R>(&mut self, name: impl Into<StdString>, method: M)
fn add_method_once<M, A, R>(&mut self, name: impl Into<String>, method: M)
where
T: 'static,
M: Fn(&Lua, T, A) -> Result<R> + MaybeSend + 'static,
@@ -342,7 +351,7 @@ pub trait UserDataMethods<T> {
/// [`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 Into<StdString>, method: M)
fn add_async_method<M, A, MR, R>(&mut self, name: impl Into<String>, method: M)
where
T: 'static,
M: Fn(Lua, UserDataRef<T>, A) -> MR + MaybeSend + 'static,
@@ -357,7 +366,7 @@ pub trait UserDataMethods<T> {
/// [`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 Into<StdString>, method: M)
fn add_async_method_mut<M, A, MR, R>(&mut self, name: impl Into<String>, method: M)
where
T: 'static,
M: Fn(Lua, UserDataRefMut<T>, A) -> MR + MaybeSend + 'static,
@@ -374,8 +383,7 @@ pub trait UserDataMethods<T> {
/// [`Error::UserDataDestructed`] error.
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
#[doc(hidden)]
fn add_async_method_once<M, A, MR, R>(&mut self, name: impl Into<StdString>, method: M)
fn add_async_method_once<M, A, MR, R>(&mut self, name: impl Into<String>, method: M)
where
T: 'static,
M: Fn(Lua, T, A) -> MR + MaybeSend + 'static,
@@ -398,7 +406,7 @@ pub trait UserDataMethods<T> {
/// 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 Into<StdString>, function: F)
fn add_function<F, A, R>(&mut self, name: impl Into<String>, function: F)
where
F: Fn(&Lua, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
@@ -409,7 +417,7 @@ pub trait UserDataMethods<T> {
/// This is a version of [`add_function`] that accepts a `FnMut` argument.
///
/// [`add_function`]: UserDataMethods::add_function
fn add_function_mut<F, A, R>(&mut self, name: impl Into<StdString>, function: F)
fn add_function_mut<F, A, R>(&mut self, name: impl Into<String>, function: F)
where
F: FnMut(&Lua, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
@@ -423,7 +431,7 @@ pub trait UserDataMethods<T> {
/// [`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 Into<StdString>, function: F)
fn add_async_function<F, A, FR, R>(&mut self, name: impl Into<String>, function: F)
where
F: Fn(Lua, A) -> FR + MaybeSend + 'static,
A: FromLuaMulti,
@@ -438,7 +446,7 @@ pub trait UserDataMethods<T> {
/// side has a metatable. To prevent this, use [`add_meta_function`].
///
/// [`add_meta_function`]: UserDataMethods::add_meta_function
fn add_meta_method<M, A, R>(&mut self, name: impl Into<StdString>, method: M)
fn add_meta_method<M, A, R>(&mut self, name: impl Into<String>, method: M)
where
M: Fn(&Lua, &T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
@@ -452,7 +460,7 @@ pub trait UserDataMethods<T> {
/// side has a metatable. To prevent this, use [`add_meta_function`].
///
/// [`add_meta_function`]: UserDataMethods::add_meta_function
fn add_meta_method_mut<M, A, R>(&mut self, name: impl Into<StdString>, method: M)
fn add_meta_method_mut<M, A, R>(&mut self, name: impl Into<String>, method: M)
where
M: FnMut(&Lua, &mut T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
@@ -468,7 +476,7 @@ pub trait UserDataMethods<T> {
docsrs,
doc(cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau")))))
)]
fn add_async_meta_method<M, A, MR, R>(&mut self, name: impl Into<StdString>, method: M)
fn add_async_meta_method<M, A, MR, R>(&mut self, name: impl Into<String>, method: M)
where
T: 'static,
M: Fn(Lua, UserDataRef<T>, A) -> MR + MaybeSend + 'static,
@@ -484,7 +492,7 @@ pub trait UserDataMethods<T> {
/// [`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 Into<StdString>, method: M)
fn add_async_meta_method_mut<M, A, MR, R>(&mut self, name: impl Into<String>, method: M)
where
T: 'static,
M: Fn(Lua, UserDataRefMut<T>, A) -> MR + MaybeSend + 'static,
@@ -497,7 +505,7 @@ pub trait UserDataMethods<T> {
/// Metamethods for binary operators can be triggered if either the left or right argument to
/// the binary operator has a metatable, so the first argument here is not necessarily a
/// userdata of type `T`.
fn add_meta_function<F, A, R>(&mut self, name: impl Into<StdString>, function: F)
fn add_meta_function<F, A, R>(&mut self, name: impl Into<String>, function: F)
where
F: Fn(&Lua, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
@@ -508,7 +516,7 @@ pub trait UserDataMethods<T> {
/// This is a version of [`add_meta_function`] that accepts a `FnMut` argument.
///
/// [`add_meta_function`]: UserDataMethods::add_meta_function
fn add_meta_function_mut<F, A, R>(&mut self, name: impl Into<StdString>, function: F)
fn add_meta_function_mut<F, A, R>(&mut self, name: impl Into<String>, function: F)
where
F: FnMut(&Lua, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
@@ -524,7 +532,7 @@ pub trait UserDataMethods<T> {
docsrs,
doc(cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau")))))
)]
fn add_async_meta_function<F, A, FR, R>(&mut self, name: impl Into<StdString>, function: F)
fn add_async_meta_function<F, A, FR, R>(&mut self, name: impl Into<String>, function: F)
where
F: Fn(Lua, A) -> FR + MaybeSend + 'static,
A: FromLuaMulti,
@@ -543,7 +551,7 @@ pub trait UserDataFields<T> {
///
/// If `add_meta_method` is used to set the `__index` metamethod, it will
/// be used as a fall-back if no regular field or method are found.
fn add_field<V>(&mut self, name: impl Into<StdString>, value: V)
fn add_field<V>(&mut self, name: impl Into<String>, value: V)
where
V: IntoLua + 'static;
@@ -554,7 +562,7 @@ pub trait UserDataFields<T> {
///
/// If `add_meta_method` is used to set the `__index` metamethod, the `__index` metamethod will
/// be used as a fall-back if no regular field or method are found.
fn add_field_method_get<M, R>(&mut self, name: impl Into<StdString>, method: M)
fn add_field_method_get<M, R>(&mut self, name: impl Into<String>, method: M)
where
M: Fn(&Lua, &T) -> Result<R> + MaybeSend + 'static,
R: IntoLua;
@@ -567,21 +575,21 @@ pub trait UserDataFields<T> {
///
/// If `add_meta_method` is used to set the `__newindex` metamethod, the `__newindex` metamethod
/// will be used as a fall-back if no regular field is found.
fn add_field_method_set<M, A>(&mut self, name: impl Into<StdString>, method: M)
fn add_field_method_set<M, A>(&mut self, name: impl Into<String>, method: M)
where
M: FnMut(&Lua, &mut T, A) -> Result<()> + MaybeSend + 'static,
A: FromLua;
/// Add a regular field getter as a function which accepts a generic [`AnyUserData`] of type `T`
/// argument.
fn add_field_function_get<F, R>(&mut self, name: impl Into<StdString>, function: F)
fn add_field_function_get<F, R>(&mut self, name: impl Into<String>, function: F)
where
F: Fn(&Lua, AnyUserData) -> Result<R> + MaybeSend + 'static,
R: IntoLua;
/// Add a regular field setter as a function which accepts a generic [`AnyUserData`] of type `T`
/// first argument.
fn add_field_function_set<F, A>(&mut self, name: impl Into<StdString>, function: F)
fn add_field_function_set<F, A>(&mut self, name: impl Into<String>, function: F)
where
F: FnMut(&Lua, AnyUserData, A) -> Result<()> + MaybeSend + 'static,
A: FromLua;
@@ -594,7 +602,7 @@ pub trait UserDataFields<T> {
///
/// `mlua` will trigger an error on an attempt to define a protected metamethod,
/// like `__gc` or `__metatable`.
fn add_meta_field<V>(&mut self, name: impl Into<StdString>, value: V)
fn add_meta_field<V>(&mut self, name: impl Into<String>, value: V)
where
V: IntoLua + 'static;
@@ -606,7 +614,7 @@ pub trait UserDataFields<T> {
///
/// `mlua` will trigger an error on an attempt to define a protected metamethod,
/// like `__gc` or `__metatable`.
fn add_meta_field_with<F, R>(&mut self, name: impl Into<StdString>, f: F)
fn add_meta_field_with<F, R>(&mut self, name: impl Into<String>, f: F)
where
F: FnOnce(&Lua) -> Result<R> + 'static,
R: IntoLua;
@@ -706,7 +714,7 @@ pub trait UserData: Sized {
///
/// [`is`]: crate::AnyUserData::is
/// [`borrow`]: crate::AnyUserData::borrow
#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, PartialEq)]
pub struct AnyUserData(pub(crate) ValueRef);
impl AnyUserData {
@@ -1021,8 +1029,8 @@ impl AnyUserData {
/// Returns a type name of this userdata (from a metatable field).
///
/// If no type name is set, returns `None`.
pub fn type_name(&self) -> Result<Option<StdString>> {
/// If no type name is set, returns `userdata`.
pub fn type_name(&self) -> Result<LuaString> {
let lua = self.0.lua.lock();
let state = lua.state();
unsafe {
@@ -1039,8 +1047,8 @@ impl AnyUserData {
ffi::luaL_getmetafield(state, -1, MetaMethod::Type.as_cstr().as_ptr())
};
match name_type {
ffi::LUA_TSTRING => Ok(Some(String(lua.pop_ref()).to_str()?.to_owned())),
_ => Ok(None),
ffi::LUA_TSTRING => Ok(LuaString(lua.pop_ref())),
_ => lua.create_string(b"userdata"),
}
}
}
@@ -1076,6 +1084,48 @@ impl AnyUserData {
};
is_serializable().unwrap_or(false)
}
unsafe fn invoke_tostring_dbg(&self) -> Result<Option<String>> {
let lua = self.0.lua.lock();
let state = lua.state();
let _guard = StackGuard::new(state);
check_stack(state, 3)?;
lua.push_ref(&self.0);
protect_lua!(state, 1, 1, fn(state) {
// Try `__todebugstring` metamethod first, then `__tostring`
#[allow(clippy::collapsible_if)]
if ffi::luaL_callmeta(state, -1, cstr!("__todebugstring")) == 0 {
if ffi::luaL_callmeta(state, -1, cstr!("__tostring")) == 0 {
ffi::lua_pushnil(state);
}
}
})?;
Ok(lua.pop_value().as_string().map(|s| s.to_string_lossy()))
}
pub(crate) fn fmt_pretty(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
// Try converting to a (debug) string first, with fallback to `__name/__type`
match unsafe { self.invoke_tostring_dbg() } {
Ok(Some(s)) => write!(fmt, "{s}"),
_ => {
let name = self.type_name().ok();
let name = (name.as_ref())
.map(|s| Either::Left(s.display()))
.unwrap_or(Either::Right("userdata"));
write!(fmt, "{name}: {:?}", self.to_pointer())
}
}
}
}
impl fmt::Debug for AnyUserData {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
if fmt.alternate() {
return self.fmt_pretty(fmt);
}
fmt.debug_tuple("AnyUserData").field(&self.0).finish()
}
}
/// Handle to a [`AnyUserData`] metatable.
@@ -1126,13 +1176,13 @@ impl UserDataMetatable {
/// It skips restricted metamethods, such as `__gc` or `__metatable`.
///
/// This struct is created by the [`UserDataMetatable::pairs`] method.
pub struct UserDataMetatablePairs<'a, V>(TablePairs<'a, StdString, V>);
pub struct UserDataMetatablePairs<'a, V>(TablePairs<'a, String, V>);
impl<V> Iterator for UserDataMetatablePairs<'_, V>
where
V: FromLua,
{
type Item = Result<(StdString, V)>;
type Item = Result<(String, V)>;
fn next(&mut self) -> Option<Self::Item> {
loop {
@@ -1172,7 +1222,7 @@ impl AnyUserData {
/// Wraps any Rust type, returning an opaque type that implements [`IntoLua`] trait.
///
/// This function uses [`Lua::create_any_userdata`] under the hood.
pub fn wrap<T: MaybeSend + 'static>(data: T) -> impl IntoLua {
pub fn wrap<T: MaybeSend + MaybeSync + 'static>(data: T) -> impl IntoLua {
WrappedUserdata(move |lua| lua.create_any_userdata(data))
}
@@ -1182,7 +1232,7 @@ impl AnyUserData {
/// This function uses [`Lua::create_ser_any_userdata`] under the hood.
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
pub fn wrap_ser<T: Serialize + MaybeSend + 'static>(data: T) -> impl IntoLua {
pub fn wrap_ser<T: Serialize + MaybeSend + MaybeSync + 'static>(data: T) -> impl IntoLua {
WrappedUserdata(move |lua| lua.create_ser_any_userdata(data))
}
}
+33 -65
View File
@@ -1,4 +1,4 @@
use std::cell::{RefCell, UnsafeCell};
use std::cell::RefCell;
#[cfg(feature = "serde")]
use serde::ser::{Serialize, Serializer};
@@ -6,14 +6,14 @@ use serde::ser::{Serialize, Serializer};
use crate::error::{Error, Result};
use crate::types::XRc;
use super::lock::{RawLock, UserDataLock};
use super::lock::{RawLock, RwLock, UserDataLock};
use super::r#ref::{UserDataRef, UserDataRefMut};
#[cfg(all(feature = "serde", not(feature = "send")))]
type DynSerialize = dyn erased_serde::Serialize;
#[cfg(all(feature = "serde", feature = "send"))]
type DynSerialize = dyn erased_serde::Serialize + Send;
type DynSerialize = dyn erased_serde::Serialize + Send + Sync;
pub(crate) enum UserDataStorage<T> {
Owned(UserDataVariant<T>),
@@ -23,9 +23,9 @@ pub(crate) enum UserDataStorage<T> {
// A enum for storing userdata values.
// It's stored inside a Lua VM and protected by the outer `ReentrantMutex`.
pub(crate) enum UserDataVariant<T> {
Default(XRc<UserDataCell<T>>),
Default(XRc<RwLock<T>>),
#[cfg(feature = "serde")]
Serializable(XRc<UserDataCell<Box<DynSerialize>>>, bool), // bool is `is_sync`
Serializable(XRc<RwLock<Box<DynSerialize>>>),
}
impl<T> Clone for UserDataVariant<T> {
@@ -34,7 +34,7 @@ impl<T> Clone for UserDataVariant<T> {
match self {
Self::Default(inner) => Self::Default(XRc::clone(inner)),
#[cfg(feature = "serde")]
Self::Serializable(inner, is_sync) => Self::Serializable(XRc::clone(inner), *is_sync),
Self::Serializable(inner) => Self::Serializable(XRc::clone(inner)),
}
}
}
@@ -42,10 +42,12 @@ impl<T> Clone for UserDataVariant<T> {
impl<T> UserDataVariant<T> {
#[inline(always)]
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.
// Shared (read) lock is always correct for in-place borrows:
// - this method is called internally while the Lua mutex is held, ensuring exclusive Lua-level
// access per call frame
// - with `send` feature, all owned userdata satisfies `T: Sync`, so simultaneous shared references
// from multiple threads are sound
// - without `send` feature, single-threaded execution makes shared lock safe for any `T`
let _guard = (self.raw_lock().try_lock_shared_guarded()).map_err(|_| Error::UserDataBorrowError)?;
Ok(f(unsafe { &*self.as_ptr() }))
}
@@ -78,10 +80,12 @@ impl<T> UserDataVariant<T> {
return Err(Error::UserDataBorrowMutError);
}
Ok(match self {
Self::Default(inner) => XRc::into_inner(inner).unwrap().value.into_inner(),
Self::Default(inner) => XRc::into_inner(inner).unwrap().into_inner(),
#[cfg(feature = "serde")]
Self::Serializable(inner, _) => unsafe {
let raw = Box::into_raw(XRc::into_inner(inner).unwrap().value.into_inner());
Self::Serializable(inner) => unsafe {
// The serde variant erases `T` to `Box<DynSerialize>`, so we
// must cast the raw pointer back to recover the concrete type.
let raw = Box::into_raw(XRc::into_inner(inner).unwrap().into_inner());
*Box::from_raw(raw as *mut T)
},
})
@@ -92,25 +96,25 @@ impl<T> UserDataVariant<T> {
match self {
Self::Default(inner) => XRc::strong_count(inner),
#[cfg(feature = "serde")]
Self::Serializable(inner, _) => XRc::strong_count(inner),
Self::Serializable(inner) => XRc::strong_count(inner),
}
}
#[inline(always)]
pub(super) fn raw_lock(&self) -> &RawLock {
match self {
Self::Default(inner) => &inner.raw_lock,
Self::Default(inner) => unsafe { inner.raw() },
#[cfg(feature = "serde")]
Self::Serializable(inner, _) => &inner.raw_lock,
Self::Serializable(inner) => unsafe { inner.raw() },
}
}
#[inline(always)]
pub(super) fn as_ptr(&self) -> *mut T {
match self {
Self::Default(inner) => inner.value.get(),
Self::Default(inner) => inner.data_ptr(),
#[cfg(feature = "serde")]
Self::Serializable(inner, _) => unsafe { &mut **(inner.value.get() as *mut Box<T>) },
Self::Serializable(inner) => unsafe { (&mut **inner.data_ptr()) as *mut DynSerialize as *mut T },
}
}
}
@@ -119,51 +123,16 @@ 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(variant @ UserDataVariant::Serializable(inner, is_sync)) => unsafe {
#[cfg(feature = "send")]
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 _ = is_sync;
let _guard = (variant.raw_lock().try_lock_shared_guarded())
.map_err(|_| serde::ser::Error::custom(Error::UserDataBorrowError))?;
(*inner.value.get()).serialize(serializer)
}
Self::Owned(variant @ UserDataVariant::Serializable(inner)) => unsafe {
let _guard = (variant.raw_lock().try_lock_shared_guarded())
.map_err(|_| serde::ser::Error::custom(Error::UserDataBorrowError))?;
(*inner.data_ptr()).serialize(serializer)
},
_ => Err(serde::ser::Error::custom("cannot serialize <userdata>")),
}
}
}
/// A type that provides interior mutability for a userdata value (thread-safe).
pub(crate) struct UserDataCell<T> {
raw_lock: RawLock,
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> {
#[inline(always)]
fn new(value: T) -> Self {
UserDataCell {
raw_lock: RawLock::INIT,
value: UnsafeCell::new(value),
}
}
}
pub(crate) enum ScopedUserDataVariant<T> {
Ref(*const T),
RefMut(RefCell<*mut T>),
@@ -173,10 +142,10 @@ pub(crate) enum ScopedUserDataVariant<T> {
impl<T> Drop for ScopedUserDataVariant<T> {
#[inline]
fn drop(&mut self) {
if let Self::Boxed(value) = self {
if let Ok(value) = value.try_borrow_mut() {
unsafe { drop(Box::from_raw(*value)) };
}
if let Self::Boxed(value) = self
&& let Ok(value) = value.try_borrow_mut()
{
unsafe { drop(Box::from_raw(*value)) }
}
}
}
@@ -184,7 +153,7 @@ impl<T> Drop for ScopedUserDataVariant<T> {
impl<T: 'static> UserDataStorage<T> {
#[inline(always)]
pub(crate) fn new(data: T) -> Self {
Self::Owned(UserDataVariant::Default(XRc::new(UserDataCell::new(data))))
Self::Owned(UserDataVariant::Default(XRc::new(RwLock::new(data))))
}
#[inline(always)]
@@ -201,11 +170,10 @@ impl<T: 'static> UserDataStorage<T> {
#[inline(always)]
pub(crate) fn new_ser(data: T) -> Self
where
T: Serialize + crate::types::MaybeSend,
T: Serialize + crate::types::MaybeSend + crate::types::MaybeSync,
{
let data = Box::new(data) as Box<DynSerialize>;
let is_sync = super::util::is_sync::<T>();
let variant = UserDataVariant::Serializable(XRc::new(UserDataCell::new(data)), is_sync);
let variant = UserDataVariant::Serializable(XRc::new(RwLock::new(data)));
Self::Owned(variant)
}
+44 -19
View File
@@ -1,6 +1,4 @@
pub(crate) trait UserDataLock {
const INIT: Self;
fn is_locked(&self) -> bool;
fn try_lock_shared(&self) -> bool;
fn try_lock_exclusive(&self) -> bool;
@@ -48,12 +46,12 @@ impl<L: UserDataLock + ?Sized> Drop for LockGuard<'_, L> {
}
}
pub(crate) use lock_impl::RawLock;
pub(crate) use lock_impl::{RawLock, RwLock};
#[cfg(not(feature = "send"))]
#[cfg(not(tarpaulin_include))]
mod lock_impl {
use std::cell::Cell;
use std::cell::{Cell, UnsafeCell};
// Positive values represent the number of read references.
// Negative values represent the number of write references (only one allowed).
@@ -62,9 +60,6 @@ mod lock_impl {
const UNUSED: isize = 0;
impl super::UserDataLock for RawLock {
#[allow(clippy::declare_interior_mutable_const)]
const INIT: Self = Cell::new(UNUSED);
#[inline(always)]
fn is_locked(&self) -> bool {
self.get() != UNUSED
@@ -72,7 +67,7 @@ mod lock_impl {
#[inline(always)]
fn try_lock_shared(&self) -> bool {
let flag = self.get().wrapping_add(1);
let flag = self.get().checked_add(1).expect("userdata lock count overflow");
if flag <= UNUSED {
return false;
}
@@ -104,41 +99,71 @@ mod lock_impl {
self.set(flag + 1);
}
}
/// A cheap single-threaded read-write lock pairing a `parking_lot::RwLock` type.
pub(crate) struct RwLock<T> {
lock: RawLock,
data: UnsafeCell<T>,
}
impl<T> RwLock<T> {
/// Creates a new `RwLock` containing the given value.
#[inline(always)]
pub(crate) fn new(value: T) -> Self {
RwLock {
lock: RawLock::new(UNUSED),
data: UnsafeCell::new(value),
}
}
/// Returns a reference to the underlying raw lock.
#[inline(always)]
pub(crate) unsafe fn raw(&self) -> &RawLock {
&self.lock
}
/// Returns a raw pointer to the underlying data.
#[inline(always)]
pub(crate) fn data_ptr(&self) -> *mut T {
self.data.get()
}
/// Consumes this `RwLock`, returning the underlying data.
#[inline(always)]
pub(crate) fn into_inner(self) -> T {
self.data.into_inner()
}
}
}
#[cfg(feature = "send")]
mod lock_impl {
use parking_lot::lock_api::RawRwLock;
pub(crate) type RawLock = parking_lot::RawRwLock;
pub(crate) use parking_lot::{RawRwLock as RawLock, RwLock};
impl super::UserDataLock for RawLock {
#[allow(clippy::declare_interior_mutable_const)]
const INIT: Self = <Self as parking_lot::lock_api::RawRwLock>::INIT;
#[inline(always)]
fn is_locked(&self) -> bool {
RawRwLock::is_locked(self)
parking_lot::lock_api::RawRwLock::is_locked(self)
}
#[inline(always)]
fn try_lock_shared(&self) -> bool {
RawRwLock::try_lock_shared(self)
parking_lot::lock_api::RawRwLock::try_lock_shared(self)
}
#[inline(always)]
fn try_lock_exclusive(&self) -> bool {
RawRwLock::try_lock_exclusive(self)
parking_lot::lock_api::RawRwLock::try_lock_exclusive(self)
}
#[inline(always)]
unsafe fn unlock_shared(&self) {
RawRwLock::unlock_shared(self)
parking_lot::lock_api::RawRwLock::unlock_shared(self)
}
#[inline(always)]
unsafe fn unlock_exclusive(&self) {
RawRwLock::unlock_exclusive(self)
parking_lot::lock_api::RawRwLock::unlock_exclusive(self)
}
}
}
+2 -4
View File
@@ -1,12 +1,10 @@
use std::string::String as StdString;
use crate::Function;
use crate::error::{Error, Result};
use crate::state::WeakLua;
use crate::table::Table;
use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, ObjectLike};
use crate::userdata::AnyUserData;
use crate::value::Value;
use crate::Function;
#[cfg(feature = "async")]
use crate::function::AsyncCallFuture;
@@ -88,7 +86,7 @@ impl ObjectLike for AnyUserData {
}
#[inline]
fn to_string(&self) -> Result<StdString> {
fn to_string(&self) -> Result<String> {
Value::UserData(self.clone()).to_string()
}
+75 -13
View File
@@ -1,4 +1,4 @@
use std::any::{type_name, TypeId};
use std::any::{TypeId, type_name};
use std::ops::{Deref, DerefMut};
use std::os::raw::c_int;
use std::{fmt, mem};
@@ -7,12 +7,11 @@ use crate::error::{Error, Result};
use crate::state::{Lua, RawLua};
use crate::traits::FromLua;
use crate::userdata::AnyUserData;
use crate::util::get_userdata;
use crate::util::{check_stack, get_userdata, take_userdata};
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 {
@@ -63,11 +62,10 @@ impl<T> TryFrom<UserDataVariant<T>> for UserDataRef<T> {
#[inline]
fn try_from(variant: UserDataVariant<T>) -> Result<Self> {
let guard = if cfg!(not(feature = "send")) || is_sync::<T>() {
variant.raw_lock().try_lock_shared_guarded()
} else {
variant.raw_lock().try_lock_exclusive_guarded()
};
// Shared (read) lock is always correct:
// - with `send` feature, `T: Sync` is guaranteed by the `MaybeSync` bound on userdata creation
// - without `send` feature, single-threaded access makes shared lock safe for any `T`
let guard = variant.raw_lock().try_lock_shared_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))
@@ -442,15 +440,75 @@ impl<T> DerefMut for UserDataRefMutInner<T> {
}
}
/// A wrapper type that takes ownership of a userdata value.
///
/// It implements [`FromLua`] and can be used to receive a typed userdata from Lua by taking
/// ownership of it.
/// The original Lua userdata is marked as destructed and cannot be used further.
pub struct UserDataOwned<T>(pub T);
impl<T> Deref for UserDataOwned<T> {
type Target = T;
#[inline]
fn deref(&self) -> &T {
&self.0
}
}
impl<T> DerefMut for UserDataOwned<T> {
#[inline]
fn deref_mut(&mut self) -> &mut T {
&mut self.0
}
}
impl<T: fmt::Debug> fmt::Debug for UserDataOwned<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
(**self).fmt(f)
}
}
impl<T: fmt::Display> fmt::Display for UserDataOwned<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
(**self).fmt(f)
}
}
impl<T: 'static> FromLua for UserDataOwned<T> {
fn from_lua(value: Value, _: &Lua) -> Result<Self> {
try_value_to_userdata::<T>(value)?.take().map(UserDataOwned)
}
unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result<Self> {
let state = lua.state();
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);
if (*ud).has_exclusive_access() {
check_stack(state, 1)?;
take_userdata::<UserDataStorage<T>>(state, idx)
.into_inner()
.map(UserDataOwned)
} else {
Err(Error::UserDataBorrowMutError)
}
}
_ => Err(Error::UserDataTypeMismatch),
}
}
}
#[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>())),
}),
_ => Err(Error::from_lua_conversion(
value.type_name(),
"userdata",
format!("expected userdata of type {}", type_name::<T>()),
)),
}
}
@@ -466,6 +524,10 @@ mod assertions {
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!(UserDataOwned<()>: Send, Sync);
#[cfg(feature = "send")]
static_assertions::assert_not_impl_all!(UserDataOwned<std::rc::Rc<()>>: Send, Sync);
#[cfg(not(feature = "send"))]
static_assertions::assert_not_impl_all!(UserDataRef<()>: Send, Sync);
+32 -27
View File
@@ -4,15 +4,14 @@ use std::any::TypeId;
use std::cell::RefCell;
use std::marker::PhantomData;
use std::os::raw::c_void;
use std::string::String as StdString;
use crate::error::{Error, Result};
use crate::state::{Lua, LuaGuard};
use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti};
use crate::types::{Callback, MaybeSend};
use crate::userdata::{
borrow_userdata_scoped, borrow_userdata_scoped_mut, AnyUserData, MetaMethod, TypeIdHints, UserData,
UserDataFields, UserDataMethods, UserDataStorage,
AnyUserData, MetaMethod, TypeIdHints, UserData, UserDataFields, UserDataMethods, UserDataStorage,
borrow_userdata_scoped, borrow_userdata_scoped_mut,
};
use crate::util::short_type_name;
use crate::value::Value;
@@ -55,7 +54,7 @@ pub(crate) struct RawUserDataRegistry {
pub(crate) destructor: ffi::lua_CFunction,
pub(crate) type_id: Option<TypeId>,
pub(crate) type_name: StdString,
pub(crate) type_name: String,
#[cfg(feature = "luau")]
pub(crate) enable_namecall: bool,
@@ -368,7 +367,7 @@ impl<T> UserDataRegistry<T> {
method: name.to_string(),
type_name: value.type_name(),
message: Some("expected nil, table or function".to_string()),
})
});
}
}
}
@@ -382,12 +381,12 @@ impl<T> UserDataRegistry<T> {
}
// Returns function name for the type `T`, without the module path
fn get_function_name<T>(name: &str) -> StdString {
fn get_function_name<T>(name: &str) -> String {
format!("{}.{name}", short_type_name::<T>())
}
impl<T> UserDataFields<T> for UserDataRegistry<T> {
fn add_field<V>(&mut self, name: impl Into<StdString>, value: V)
fn add_field<V>(&mut self, name: impl Into<String>, value: V)
where
V: IntoLua + 'static,
{
@@ -395,7 +394,7 @@ impl<T> UserDataFields<T> for UserDataRegistry<T> {
self.raw.fields.push((name, value.into_lua(self.lua.lua())));
}
fn add_field_method_get<M, R>(&mut self, name: impl Into<StdString>, method: M)
fn add_field_method_get<M, R>(&mut self, name: impl Into<String>, method: M)
where
M: Fn(&Lua, &T) -> Result<R> + MaybeSend + 'static,
R: IntoLua,
@@ -405,7 +404,7 @@ impl<T> UserDataFields<T> for UserDataRegistry<T> {
self.raw.field_getters.push((name, callback));
}
fn add_field_method_set<M, A>(&mut self, name: impl Into<StdString>, method: M)
fn add_field_method_set<M, A>(&mut self, name: impl Into<String>, method: M)
where
M: FnMut(&Lua, &mut T, A) -> Result<()> + MaybeSend + 'static,
A: FromLua,
@@ -415,7 +414,7 @@ impl<T> UserDataFields<T> for UserDataRegistry<T> {
self.raw.field_setters.push((name, callback));
}
fn add_field_function_get<F, R>(&mut self, name: impl Into<StdString>, function: F)
fn add_field_function_get<F, R>(&mut self, name: impl Into<String>, function: F)
where
F: Fn(&Lua, AnyUserData) -> Result<R> + MaybeSend + 'static,
R: IntoLua,
@@ -425,7 +424,7 @@ impl<T> UserDataFields<T> for UserDataRegistry<T> {
self.raw.field_getters.push((name, callback));
}
fn add_field_function_set<F, A>(&mut self, name: impl Into<StdString>, mut function: F)
fn add_field_function_set<F, A>(&mut self, name: impl Into<String>, mut function: F)
where
F: FnMut(&Lua, AnyUserData, A) -> Result<()> + MaybeSend + 'static,
A: FromLua,
@@ -435,7 +434,7 @@ impl<T> UserDataFields<T> for UserDataRegistry<T> {
self.raw.field_setters.push((name, callback));
}
fn add_meta_field<V>(&mut self, name: impl Into<StdString>, value: V)
fn add_meta_field<V>(&mut self, name: impl Into<String>, value: V)
where
V: IntoLua + 'static,
{
@@ -445,7 +444,7 @@ impl<T> UserDataFields<T> for UserDataRegistry<T> {
self.raw.meta_fields.push((name, field));
}
fn add_meta_field_with<F, R>(&mut self, name: impl Into<StdString>, f: F)
fn add_meta_field_with<F, R>(&mut self, name: impl Into<String>, f: F)
where
F: FnOnce(&Lua) -> Result<R> + 'static,
R: IntoLua,
@@ -458,7 +457,7 @@ impl<T> UserDataFields<T> for UserDataRegistry<T> {
}
impl<T> UserDataMethods<T> for UserDataRegistry<T> {
fn add_method<M, A, R>(&mut self, name: impl Into<StdString>, method: M)
fn add_method<M, A, R>(&mut self, name: impl Into<String>, method: M)
where
M: Fn(&Lua, &T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
@@ -469,7 +468,7 @@ impl<T> UserDataMethods<T> for UserDataRegistry<T> {
self.raw.methods.push((name, callback));
}
fn add_method_mut<M, A, R>(&mut self, name: impl Into<StdString>, method: M)
fn add_method_mut<M, A, R>(&mut self, name: impl Into<String>, method: M)
where
M: FnMut(&Lua, &mut T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
@@ -481,7 +480,7 @@ impl<T> UserDataMethods<T> for UserDataRegistry<T> {
}
#[cfg(feature = "async")]
fn add_async_method<M, A, MR, R>(&mut self, name: impl Into<StdString>, method: M)
fn add_async_method<M, A, MR, R>(&mut self, name: impl Into<String>, method: M)
where
T: 'static,
M: Fn(Lua, UserDataRef<T>, A) -> MR + MaybeSend + 'static,
@@ -495,7 +494,7 @@ impl<T> UserDataMethods<T> for UserDataRegistry<T> {
}
#[cfg(feature = "async")]
fn add_async_method_mut<M, A, MR, R>(&mut self, name: impl Into<StdString>, method: M)
fn add_async_method_mut<M, A, MR, R>(&mut self, name: impl Into<String>, method: M)
where
T: 'static,
M: Fn(Lua, UserDataRefMut<T>, A) -> MR + MaybeSend + 'static,
@@ -508,7 +507,7 @@ impl<T> UserDataMethods<T> for UserDataRegistry<T> {
self.raw.async_methods.push((name, callback));
}
fn add_function<F, A, R>(&mut self, name: impl Into<StdString>, function: F)
fn add_function<F, A, R>(&mut self, name: impl Into<String>, function: F)
where
F: Fn(&Lua, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
@@ -519,7 +518,7 @@ impl<T> UserDataMethods<T> for UserDataRegistry<T> {
self.raw.methods.push((name, callback));
}
fn add_function_mut<F, A, R>(&mut self, name: impl Into<StdString>, function: F)
fn add_function_mut<F, A, R>(&mut self, name: impl Into<String>, function: F)
where
F: FnMut(&Lua, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
@@ -531,7 +530,7 @@ impl<T> UserDataMethods<T> for UserDataRegistry<T> {
}
#[cfg(feature = "async")]
fn add_async_function<F, A, FR, R>(&mut self, name: impl Into<StdString>, function: F)
fn add_async_function<F, A, FR, R>(&mut self, name: impl Into<String>, function: F)
where
F: Fn(Lua, A) -> FR + MaybeSend + 'static,
A: FromLuaMulti,
@@ -543,7 +542,7 @@ impl<T> UserDataMethods<T> for UserDataRegistry<T> {
self.raw.async_methods.push((name, callback));
}
fn add_meta_method<M, A, R>(&mut self, name: impl Into<StdString>, method: M)
fn add_meta_method<M, A, R>(&mut self, name: impl Into<String>, method: M)
where
M: Fn(&Lua, &T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
@@ -554,7 +553,7 @@ impl<T> UserDataMethods<T> for UserDataRegistry<T> {
self.raw.meta_methods.push((name, callback));
}
fn add_meta_method_mut<M, A, R>(&mut self, name: impl Into<StdString>, method: M)
fn add_meta_method_mut<M, A, R>(&mut self, name: impl Into<String>, method: M)
where
M: FnMut(&Lua, &mut T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
@@ -566,7 +565,7 @@ impl<T> UserDataMethods<T> for UserDataRegistry<T> {
}
#[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))]
fn add_async_meta_method<M, A, MR, R>(&mut self, name: impl Into<StdString>, method: M)
fn add_async_meta_method<M, A, MR, R>(&mut self, name: impl Into<String>, method: M)
where
T: 'static,
M: Fn(Lua, UserDataRef<T>, A) -> MR + MaybeSend + 'static,
@@ -580,7 +579,7 @@ impl<T> UserDataMethods<T> for UserDataRegistry<T> {
}
#[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))]
fn add_async_meta_method_mut<M, A, MR, R>(&mut self, name: impl Into<StdString>, method: M)
fn add_async_meta_method_mut<M, A, MR, R>(&mut self, name: impl Into<String>, method: M)
where
T: 'static,
M: Fn(Lua, UserDataRefMut<T>, A) -> MR + MaybeSend + 'static,
@@ -593,7 +592,7 @@ impl<T> UserDataMethods<T> for UserDataRegistry<T> {
self.raw.async_meta_methods.push((name, callback));
}
fn add_meta_function<F, A, R>(&mut self, name: impl Into<StdString>, function: F)
fn add_meta_function<F, A, R>(&mut self, name: impl Into<String>, function: F)
where
F: Fn(&Lua, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
@@ -604,7 +603,7 @@ impl<T> UserDataMethods<T> for UserDataRegistry<T> {
self.raw.meta_methods.push((name, callback));
}
fn add_meta_function_mut<F, A, R>(&mut self, name: impl Into<StdString>, function: F)
fn add_meta_function_mut<F, A, R>(&mut self, name: impl Into<String>, function: F)
where
F: FnMut(&Lua, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
@@ -616,7 +615,7 @@ impl<T> UserDataMethods<T> for UserDataRegistry<T> {
}
#[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))]
fn add_async_meta_function<F, A, FR, R>(&mut self, name: impl Into<StdString>, function: F)
fn add_async_meta_function<F, A, FR, R>(&mut self, name: impl Into<String>, function: F)
where
F: Fn(Lua, A) -> FR + MaybeSend + 'static,
A: FromLuaMulti,
@@ -655,6 +654,12 @@ macro_rules! lua_userdata_impl {
// A special proxy object for UserData
pub(crate) struct UserDataProxy<T>(pub(crate) PhantomData<T>);
// `UserDataProxy` holds no real `T` value, only a type marker, so it is always safe to send/share.
#[cfg(feature = "send")]
unsafe impl<T> Send for UserDataProxy<T> {}
#[cfg(feature = "send")]
unsafe impl<T> Sync for UserDataProxy<T> {}
lua_userdata_impl!(UserDataProxy<T>);
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
-31
View File
@@ -1,6 +1,4 @@
use std::any::TypeId;
use std::cell::Cell;
use std::marker::PhantomData;
use std::os::raw::c_int;
use std::ptr;
@@ -11,35 +9,6 @@ use crate::error::{Error, Result};
use crate::types::CallbackPtr;
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 {
+4 -4
View File
@@ -2,15 +2,15 @@ use std::any::Any;
use std::fmt::Write as _;
use std::mem::MaybeUninit;
use std::os::raw::{c_int, c_void};
use std::panic::{catch_unwind, resume_unwind, AssertUnwindSafe};
use std::panic::{AssertUnwindSafe, catch_unwind, resume_unwind};
use std::ptr;
use std::sync::Arc;
use crate::error::{Error, Result};
use crate::memory::MemoryState;
use crate::util::{
check_stack, get_internal_userdata, init_internal_metatable, push_internal_userdata, push_string,
push_table, rawset_field, to_string, TypeKey, DESTRUCTED_USERDATA_METATABLE,
DESTRUCTED_USERDATA_METATABLE, TypeKey, check_stack, get_internal_userdata, init_internal_metatable,
push_internal_userdata, push_string, push_table, rawset_field, to_string,
};
static WRAPPED_FAILURE_TYPE_KEY: u8 = 0;
@@ -197,7 +197,7 @@ where
F: FnOnce(*mut ffi::lua_State) -> R,
R: Copy,
{
struct Params<F, R: Copy> {
struct Params<F, R> {
function: Option<F>,
result: MaybeUninit<R>,
nresults: c_int,
+6 -10
View File
@@ -6,16 +6,16 @@ use std::{ptr, slice, str};
use crate::error::{Error, Result};
pub(crate) use error::{
error_traceback, error_traceback_thread, init_error_registry, pop_error, protect_lua_call,
protect_lua_closure, WrappedFailure,
WrappedFailure, error_traceback, error_traceback_thread, init_error_registry, pop_error,
protect_lua_call, protect_lua_closure,
};
pub(crate) use path::parse_path as parse_lookup_path;
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, push_internal_userdata, push_userdata, take_userdata,
DESTRUCTED_USERDATA_METATABLE,
DESTRUCTED_USERDATA_METATABLE, get_destructed_userdata_metatable, get_internal_metatable,
get_internal_userdata, get_userdata, init_internal_metatable, push_internal_userdata, push_userdata,
take_userdata,
};
#[cfg(not(feature = "luau"))]
@@ -264,11 +264,7 @@ pub(crate) unsafe fn get_main_state(state: *mut ffi::lua_State) -> Option<*mut f
// Check the current state first
let is_main_state = ffi::lua_pushthread(state) == 1;
ffi::lua_pop(state, 1);
if is_main_state {
Some(state)
} else {
None
}
if is_main_state { Some(state) } else { None }
}
#[cfg(feature = "luau")]
Some(ffi::lua_mainthread(state))
+1 -1
View File
@@ -196,7 +196,7 @@ fn unquote_string<'a>(path: &'a str, chars: &mut Peekable<CharIndices<'a>>) -> R
#[cfg(test)]
mod tests {
use super::{parse_path, PathKey};
use super::{PathKey, parse_path};
#[test]
fn test_parse_path() {
+1 -1
View File
@@ -3,7 +3,7 @@ use std::{mem, ptr};
use crate::error::Result;
use crate::userdata::collect_userdata;
use crate::util::{check_stack, get_metatable_ptr, push_table, rawset_field, TypeKey};
use crate::util::{TypeKey, check_stack, get_metatable_ptr, push_table, rawset_field};
// Pushes the userdata and attaches a metatable with __gc method.
// Internally uses 3 stack spaces, does not call checkstack.
+19 -28
View File
@@ -1,19 +1,18 @@
use std::cmp::Ordering;
use std::collections::HashSet;
use std::os::raw::c_void;
use std::string::String as StdString;
use std::{fmt, ptr, str};
use num_traits::FromPrimitive;
use crate::error::{Error, Result};
use crate::function::Function;
use crate::string::{BorrowedStr, String};
use crate::string::{BorrowedStr, LuaString};
use crate::table::Table;
use crate::thread::Thread;
use crate::types::{Integer, LightUserData, Number, ValueRef};
use crate::userdata::AnyUserData;
use crate::util::{check_stack, StackGuard};
use crate::util::{StackGuard, check_stack};
#[cfg(feature = "serde")]
use {
@@ -50,7 +49,7 @@ pub enum Value {
/// An interned string, managed by Lua.
///
/// Unlike Rust strings, Lua strings may not be valid UTF-8.
String(String),
String(LuaString),
/// Reference to a Lua table.
Table(Table),
/// Reference to a Lua function (or closure).
@@ -129,7 +128,7 @@ impl Value {
#[inline]
pub fn to_pointer(&self) -> *const c_void {
match self {
Value::String(String(vref)) => {
Value::String(LuaString(vref)) => {
// In Lua < 5.4 (excluding Luau), string pointers are NULL
// Use alternative approach
let lua = vref.lua.lock();
@@ -151,8 +150,8 @@ impl Value {
///
/// This might invoke the `__tostring` metamethod for non-primitive types (eg. tables,
/// functions).
pub fn to_string(&self) -> Result<StdString> {
unsafe fn invoke_to_string(vref: &ValueRef) -> Result<StdString> {
pub fn to_string(&self) -> Result<String> {
unsafe fn invoke_tostring(vref: &ValueRef) -> Result<String> {
let lua = vref.lua.lock();
let state = lua.state();
let _guard = StackGuard::new(state);
@@ -162,7 +161,7 @@ impl Value {
protect_lua!(state, 1, 1, fn(state) {
ffi::luaL_tolstring(state, -1, ptr::null_mut());
})?;
Ok(String(lua.pop_ref()).to_str()?.to_string())
Ok(LuaString(lua.pop_ref()).to_str()?.to_string())
}
match self {
@@ -179,9 +178,9 @@ impl Value {
| Value::Function(Function(vref))
| Value::Thread(Thread(vref, ..))
| Value::UserData(AnyUserData(vref))
| Value::Other(vref) => unsafe { invoke_to_string(vref) },
| Value::Other(vref) => unsafe { invoke_tostring(vref) },
#[cfg(feature = "luau")]
Value::Buffer(crate::Buffer(vref)) => unsafe { invoke_to_string(vref) },
Value::Buffer(crate::Buffer(vref)) => unsafe { invoke_tostring(vref) },
Value::Error(err) => Ok(err.to_string()),
}
}
@@ -336,17 +335,17 @@ impl Value {
self.as_number()
}
/// Returns `true` if the value is a Lua [`String`].
/// Returns `true` if the value is a [`LuaString`].
#[inline]
pub fn is_string(&self) -> bool {
self.as_string().is_some()
}
/// Cast the value to Lua [`String`].
/// Cast the value to a [`LuaString`].
///
/// If the value is a Lua [`String`], returns it or `None` otherwise.
/// If the value is a [`LuaString`], returns it or `None` otherwise.
#[inline]
pub fn as_string(&self) -> Option<&String> {
pub fn as_string(&self) -> Option<&LuaString> {
match self {
Value::String(s) => Some(s),
_ => None,
@@ -355,26 +354,26 @@ impl Value {
/// Cast the value to [`BorrowedStr`].
///
/// If the value is a Lua [`String`], try to convert it to [`BorrowedStr`] or return `None`
/// If the value is a [`LuaString`], try to convert it to [`BorrowedStr`] or return `None`
/// otherwise.
#[deprecated(
since = "0.11.0",
note = "This method does not follow Rust naming convention. Use `as_string().and_then(|s| s.to_str().ok())` instead."
)]
#[inline]
pub fn as_str(&self) -> Option<BorrowedStr<'_>> {
pub fn as_str(&self) -> Option<BorrowedStr> {
self.as_string().and_then(|s| s.to_str().ok())
}
/// Cast the value to [`StdString`].
/// Cast the value to [`String`].
///
/// If the value is a Lua [`String`], converts it to [`StdString`] or returns `None` otherwise.
/// If the value is a [`LuaString`], converts it to [`String`] or returns `None` otherwise.
#[deprecated(
since = "0.11.0",
note = "This method does not follow Rust naming convention. Use `as_string().map(|s| s.to_string_lossy())` instead."
)]
#[inline]
pub fn as_string_lossy(&self) -> Option<StdString> {
pub fn as_string_lossy(&self) -> Option<String> {
self.as_string().map(|s| s.to_string_lossy())
}
@@ -562,15 +561,7 @@ impl Value {
t @ Value::Table(_) => write!(fmt, "table: {:?}", t.to_pointer()),
f @ Value::Function(_) => write!(fmt, "function: {:?}", f.to_pointer()),
t @ Value::Thread(_) => write!(fmt, "thread: {:?}", t.to_pointer()),
u @ Value::UserData(ud) => {
// Try `__name/__type` first then `__tostring`
let name = ud.type_name().ok().flatten();
let s = name
.map(|name| format!("{name}: {:?}", u.to_pointer()))
.or_else(|| u.to_string().ok())
.unwrap_or_else(|| format!("userdata: {:?}", u.to_pointer()));
write!(fmt, "{s}")
}
Value::UserData(ud) => ud.fmt_pretty(fmt),
#[cfg(feature = "luau")]
buf @ Value::Buffer(_) => write!(fmt, "buffer: {:?}", buf.to_pointer()),
Value::Error(e) if recursive => write!(fmt, "{e:?}"),
+5 -6
View File
@@ -1,6 +1,5 @@
#![cfg(feature = "async")]
use std::string::String as StdString;
use std::sync::Arc;
use std::time::Duration;
@@ -8,7 +7,7 @@ use futures_util::stream::TryStreamExt;
use tokio::sync::Mutex;
use mlua::{
Error, Function, Lua, LuaOptions, MultiValue, ObjectLike, Result, StdLib, Table, ThreadStatus, UserData,
Error, Function, Lua, LuaOptions, MultiValue, ObjectLike, Result, StdLib, Table, UserData,
UserDataMethods, UserDataRef, Value,
};
@@ -40,9 +39,9 @@ async fn test_async_function() -> Result<()> {
async fn test_async_function_wrap() -> Result<()> {
let lua = Lua::new();
let f = Function::wrap_async(|s: StdString| async move {
let f = Function::wrap_async(|s: String| async move {
tokio::task::yield_now().await;
Ok(s)
Ok::<_, Error>(s)
});
lua.globals().set("f", f)?;
let res: String = lua.load(r#"f("hello")"#).eval_async().await?;
@@ -68,7 +67,7 @@ async fn test_async_function_wrap() -> Result<()> {
async fn test_async_function_wrap_raw() -> Result<()> {
let lua = Lua::new();
let f = Function::wrap_raw_async(|s: StdString| async move {
let f = Function::wrap_raw_async(|s: String| async move {
tokio::task::yield_now().await;
s
});
@@ -715,7 +714,7 @@ fn test_async_yield_with() -> Result<()> {
assert_eq!(thread.resume::<(i32, i32)>((10, 11))?, (21, 110));
assert_eq!(thread.resume::<(i32, i32)>((11, 12))?, (23, 132));
assert_eq!(thread.resume::<(i32, i32)>((12, 13))?, (0, 0));
assert_eq!(thread.status(), ThreadStatus::Finished);
assert!(thread.is_finished());
Ok(())
}
+2 -1
View File
@@ -1,3 +1,4 @@
#[cfg(not(target_os = "wasi"))]
use std::{fs, io};
use mlua::{Chunk, ChunkMode, Lua, Result};
@@ -85,7 +86,7 @@ fn test_chunk_macro() -> Result<()> {
data.raw_set("num", 1)?;
let ud = mlua::AnyUserData::wrap("hello");
let f = mlua::Function::wrap(|| Ok(()));
let f = mlua::Function::wrap(|| Ok::<_, mlua::Error>(()));
lua.globals().set("g", 123)?;
+11 -11
View File
@@ -1,28 +1,28 @@
error[E0277]: the type `UnsafeCell<mlua::state::raw::RawLua>` may contain interior mutability and a reference may not be safely transferable across a catch_unwind boundary
error[E0277]: the type `UnsafeCell<mlua::state::RawLua>` may contain interior mutability and a reference may not be safely transferable 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 transferable across a catch_unwind boundary
| ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<mlua::state::RawLua>` may contain interior mutability and a reference may not be safely transferable 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>`
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>`
= help: within `Lua`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<mlua::state::RawLua>`
note: required because it appears within the type `lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::RawLua>`
--> $CARGO/lock_api-$VERSION/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>>`
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::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>>>`
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::RawLua>>>`
--> $RUST/core/src/marker.rs
|
| pub struct PhantomData<T: PointeeSized>;
| ^^^^^^^^^^^
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>>`
note: required because it appears within the type `Arc<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::RawLua>>`
--> $RUST/alloc/src/sync.rs
|
| pub struct Arc<
@@ -63,22 +63,22 @@ note: required because it appears within the type `lock_api::remutex::RawReentra
|
| 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>`
note: required because it appears within the type `lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::RawLua>`
--> $CARGO/lock_api-$VERSION/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>>`
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::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>>>`
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::RawLua>>>`
--> $RUST/core/src/marker.rs
|
| pub struct PhantomData<T: PointeeSized>;
| ^^^^^^^^^^^
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>>`
note: required because it appears within the type `Arc<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::RawLua>>`
--> $RUST/alloc/src/sync.rs
|
| pub struct Arc<
+12 -12
View File
@@ -1,24 +1,24 @@
error[E0277]: the type `UnsafeCell<mlua::state::raw::RawLua>` may contain interior mutability and a reference may not be safely transferable across a catch_unwind boundary
error[E0277]: the type `UnsafeCell<mlua::state::RawLua>` may contain interior mutability and a reference may not be safely transferable 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 transferable across a catch_unwind boundary
| ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<mlua::state::RawLua>` may contain interior mutability and a reference may not be safely transferable 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>`
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>`
= help: within `alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::RawLua>>`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<mlua::state::RawLua>`
note: required because it appears within the type `lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::RawLua>`
--> $CARGO/lock_api-$VERSION/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>>`
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::RawLua>>`
--> $RUST/alloc/src/sync.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>>`
= note: required for `NonNull<alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::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::RawLua>>`
--> $RUST/alloc/src/sync.rs
|
| pub struct Weak<
@@ -57,7 +57,7 @@ error[E0277]: the type `UnsafeCell<usize>` may contain interior mutability and a
| |
| 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>`
= help: within `alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::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
|
@@ -68,18 +68,18 @@ note: required because it appears within the type `lock_api::remutex::RawReentra
|
| 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>`
note: required because it appears within the type `lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::RawLua>`
--> $CARGO/lock_api-$VERSION/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>>`
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::RawLua>>`
--> $RUST/alloc/src/sync.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>>`
= note: required for `NonNull<alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::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::RawLua>>`
--> $RUST/alloc/src/sync.rs
|
| pub struct Weak<
+17 -13
View File
@@ -49,7 +49,7 @@ fn test_string_from_lua() -> Result<()> {
let lua = Lua::new();
// From stack
let f = lua.create_function(|_, s: mlua::String| Ok(s))?;
let f = lua.create_function(|_, s: mlua::LuaString| Ok(s))?;
let s = f.call::<String>("hello, world!")?;
assert_eq!(s, "hello, world!");
@@ -708,9 +708,10 @@ fn test_either_from_lua() -> Result<()> {
},
err => panic!("expected `Error::BadArgument`, got {err:?}"),
}
assert!(err
.to_string()
.starts_with("bad argument #1: error converting Lua string to Either<i32, Table>"),);
assert!(
err.to_string()
.starts_with("bad argument #1: error converting Lua string to Either<i32, Table>"),
);
}
err => panic!("expected `Error::CallbackError`, got {err:?}"),
}
@@ -736,15 +737,18 @@ fn test_char_from_lua() -> Result<()> {
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")));
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(())
}
+13
View File
@@ -77,6 +77,19 @@ fn test_error_chain() -> Result<()> {
Ok(())
}
#[test]
fn test_external_error() {
// `Error::external` should preserve `mlua::Error`
let runtime_err = Error::runtime("test error");
let converted = Error::external(runtime_err);
assert!(matches!(converted, Error::RuntimeError(ref msg) if msg == "test error"));
// Other errors should become `ExternalError`
let converted = Error::external(io::Error::other("other error"));
assert!(matches!(converted, Error::ExternalError(_)));
assert!(converted.downcast_ref::<io::Error>().is_some());
}
#[cfg(feature = "anyhow")]
#[test]
fn test_error_anyhow() -> Result<()> {
+40 -8
View File
@@ -1,4 +1,7 @@
use mlua::{Error, Function, Lua, Result, String, Table, Variadic};
use std::fmt;
use std::result::Result as StdResult;
use mlua::{Error, Function, Lua, LuaString, Result, Table, Variadic};
#[test]
fn test_function_call() -> Result<()> {
@@ -267,7 +270,7 @@ fn test_function_coverage() -> Result<()> {
assert_eq!(
report[0],
mlua::CoverageInfo {
mlua::function::CoverageInfo {
function: None,
line_defined: 1,
depth: 0,
@@ -276,7 +279,7 @@ fn test_function_coverage() -> Result<()> {
);
assert_eq!(
report[1],
mlua::CoverageInfo {
mlua::function::CoverageInfo {
function: Some("abc".into()),
line_defined: 4,
depth: 1,
@@ -285,7 +288,7 @@ fn test_function_coverage() -> Result<()> {
);
assert_eq!(
report[2],
mlua::CoverageInfo {
mlua::function::CoverageInfo {
function: None,
line_defined: 12,
depth: 1,
@@ -294,7 +297,7 @@ fn test_function_coverage() -> Result<()> {
);
assert_eq!(
report[3],
mlua::CoverageInfo {
mlua::function::CoverageInfo {
function: None,
line_defined: 13,
depth: 2,
@@ -343,7 +346,7 @@ fn test_function_deep_clone() -> Result<()> {
fn test_function_wrap() -> Result<()> {
let lua = Lua::new();
let f = Function::wrap(|s: String, n| Ok(s.to_str().unwrap().repeat(n)));
let f = Function::wrap(|s: LuaString, n| Ok::<_, Error>(s.to_str().unwrap().repeat(n)));
lua.globals().set("f", f)?;
lua.load(r#"assert(f("hello", 2) == "hellohello")"#)
.exec()
@@ -361,11 +364,40 @@ fn test_function_wrap() -> Result<()> {
.exec()
.unwrap();
// Return external error
#[derive(Debug)]
struct MyError(String);
impl fmt::Display for MyError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "MyError: {}", self.0)
}
}
impl std::error::Error for MyError {}
let fext = Function::wrap(|s: String| -> StdResult<String, MyError> {
if s == "bad" {
return Err(MyError("bad input".into()));
}
Ok(format!("ok: {s}"))
});
lua.globals().set("fext", fext)?;
lua.load(r#"assert(fext("hello") == "ok: hello")"#)
.exec()
.unwrap();
lua.load(
r#"
local ok, err = pcall(fext, "bad")
assert(not ok and tostring(err):find("MyError: bad input"))
"#,
)
.exec()
.unwrap();
// Mutable callback
let mut i = 0;
let fmut = Function::wrap_mut(move || {
i += 1;
Ok(i)
Ok::<_, Error>(i)
});
lua.globals().set("fmut", fmut)?;
lua.load(r#"fmut(); fmut(); assert(fmut() == 3)"#).exec().unwrap();
@@ -385,7 +417,7 @@ fn test_function_wrap() -> Result<()> {
// Check recursive mut callback error
let fmut = Function::wrap_mut(|f: Function| match f.call::<()>(&f) {
Err(Error::CallbackError { cause, .. }) => match cause.as_ref() {
Error::RecursiveMutCallback { .. } => Ok(()),
Error::RecursiveMutCallback { .. } => Ok::<_, Error>(()),
other => panic!("incorrect result: {other:?}"),
},
other => panic!("incorrect result: {other:?}"),
+5 -4
View File
@@ -3,7 +3,8 @@
use std::sync::atomic::{AtomicI64, Ordering};
use std::sync::{Arc, Mutex};
use mlua::{DebugEvent, Error, HookTriggers, Lua, Result, ThreadStatus, Value, VmState};
use mlua::debug::DebugEvent;
use mlua::{Error, HookTriggers, Lua, Result, Value, VmState};
#[test]
fn test_hook_triggers() {
@@ -280,14 +281,14 @@ fn test_hook_yield() -> Result<()> {
assert!(co.resume::<()>(()).is_ok());
assert!(co.resume::<()>(()).is_ok());
assert!(co.resume::<()>(()).is_ok());
assert!(co.status() == ThreadStatus::Finished);
assert!(co.is_finished());
}
#[cfg(any(feature = "lua51", feature = "lua52", feature = "luajit"))]
{
assert!(
matches!(co.resume::<()>(()), Err(Error::RuntimeError(err)) if err.contains("attempt to yield from a hook"))
);
assert!(co.status() == ThreadStatus::Error);
assert!(co.is_error());
}
Ok(())
@@ -320,7 +321,7 @@ fn test_global_hook() -> Result<()> {
thread.resume::<()>(()).unwrap();
lua.remove_global_hook();
thread.resume::<()>(()).unwrap();
assert_eq!(thread.status(), ThreadStatus::Finished);
assert!(thread.is_finished());
assert_eq!(counter.load(Ordering::Relaxed), 3);
Ok(())
+22 -4
View File
@@ -3,11 +3,11 @@
use std::cell::Cell;
use std::fmt::Debug;
use std::os::raw::c_void;
use std::sync::atomic::{AtomicBool, AtomicPtr, AtomicU64, Ordering};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicPtr, AtomicU64, Ordering};
use mlua::{
Compiler, Error, Function, Lua, LuaOptions, Result, StdLib, Table, ThreadStatus, Value, Vector, VmState,
Compiler, Error, Function, Lua, LuaOptions, ObjectLike, Result, StdLib, Table, Value, Vector, VmState,
};
#[test]
@@ -324,11 +324,11 @@ fn test_interrupts() -> Result<()> {
.into_function()?,
)?;
co.resume::<()>(())?;
assert_eq!(co.status(), ThreadStatus::Resumable);
assert!(co.is_resumable());
let result: i32 = co.resume(())?;
assert_eq!(result, 6);
assert_eq!(yield_count.load(Ordering::Relaxed), 7);
assert_eq!(co.status(), ThreadStatus::Finished);
assert!(co.is_finished());
// Test no yielding at non-yieldable points
yield_count.store(0, Ordering::Relaxed);
@@ -535,5 +535,23 @@ fn test_heap_dump() -> Result<()> {
Ok(())
}
#[test]
fn test_integer64_type() -> Result<()> {
let lua = Lua::new();
_ = Lua::set_fflag("LuauIntegerType", true);
let integer_lib = lua.globals().get::<Table>("integer")?;
let n = integer_lib.call_function::<i64>("create", 42)?;
assert_eq!(n, 42);
let n: i64 = lua.load("return 42i").eval()?;
assert_eq!(n, 42);
let n: i64 = lua.load("return -42i").eval()?;
assert_eq!(n, -42);
Ok(())
}
#[path = "luau/require.rs"]
mod require;
+16 -11
View File
@@ -1,7 +1,8 @@
use std::io::Result as IoResult;
use std::result::Result as StdResult;
use mlua::{Error, FromLua, IntoLua, Lua, MultiValue, NavigateError, Require, Result, TextRequirer, Value};
use mlua::luau::{FsRequirer, NavigateError, Require};
use mlua::{Error, FromLua, IntoLua, Lua, MultiValue, Result, Value};
fn run_require(lua: &Lua, path: impl IntoLua) -> Result<Value> {
lua.load(r#"return require(...)"#).call(path)
@@ -42,8 +43,10 @@ fn test_require_errors() {
// Pass non-string to require
let res = run_require(&lua, true);
assert!(res.is_err());
assert!((res.unwrap_err().to_string())
.contains("bad argument #1 to 'require' (string expected, got boolean)"));
assert!(
(res.unwrap_err().to_string())
.contains("bad argument #1 to 'require' (string expected, got boolean)")
);
// Require from loadstring
let res = lua
@@ -63,7 +66,7 @@ fn test_require_errors() {
assert!((res.unwrap_err().to_string()).contains("@ is not a valid alias"));
// Test throwing mlua::Error
struct MyRequire(TextRequirer);
struct MyRequire(FsRequirer);
impl Require for MyRequire {
fn is_require_allowed(&self, chunk_name: &str) -> bool {
@@ -107,9 +110,7 @@ fn test_require_errors() {
}
}
let require = lua
.create_require_function(MyRequire(TextRequirer::new()))
.unwrap();
let require = lua.create_require_function(MyRequire(FsRequirer::new())).unwrap();
lua.globals().set("require", require).unwrap();
let res = lua.load(r#"return require('./a/relative/path')"#).exec();
assert!((res.unwrap_err().to_string()).contains("test error"));
@@ -169,8 +170,10 @@ fn test_require_without_config() {
"./tests/luau/require/without_config/ambiguous_file_requirer",
);
assert!(res.is_err());
assert!((res.unwrap_err().to_string())
.contains("could not resolve child component \"dependency\" (ambiguous)"));
assert!(
(res.unwrap_err().to_string())
.contains("could not resolve child component \"dependency\" (ambiguous)")
);
// RequireWithDirectoryAmbiguity
let res = run_require(
@@ -178,8 +181,10 @@ fn test_require_without_config() {
"./tests/luau/require/without_config/ambiguous_directory_requirer",
);
assert!(res.is_err());
assert!((res.unwrap_err().to_string())
.contains("could not resolve child component \"dependency\" (ambiguous)"));
assert!(
(res.unwrap_err().to_string())
.contains("could not resolve child component \"dependency\" (ambiguous)")
);
// CheckCachedResult
let res = run_require(&lua, "./tests/luau/require/without_config/validate_cache").unwrap();
+24 -4
View File
@@ -1,6 +1,10 @@
use std::sync::Arc;
use mlua::{Error, GCMode, Lua, Result, UserData};
use mlua::state::{GcIncParams, GcMode};
use mlua::{Error, Lua, Result, UserData};
#[cfg(any(feature = "lua54", feature = "lua55"))]
use mlua::state::GcGenParams;
#[test]
fn test_memory_limit() -> Result<()> {
@@ -74,8 +78,14 @@ fn test_gc_control() -> Result<()> {
#[cfg(any(feature = "lua55", feature = "lua54"))]
{
assert_eq!(lua.gc_gen(0, 0), GCMode::Incremental);
assert_eq!(lua.gc_inc(0, 0, 0), GCMode::Generational);
assert!(matches!(
lua.gc_set_mode(GcMode::Generational(GcGenParams::default())),
GcMode::Incremental(_)
));
assert!(matches!(
lua.gc_set_mode(GcMode::Incremental(GcIncParams::default())),
GcMode::Generational(_)
));
}
#[cfg(any(
@@ -93,7 +103,17 @@ fn test_gc_control() -> Result<()> {
assert!(lua.gc_is_running());
}
assert_eq!(lua.gc_inc(200, 100, 13), GCMode::Incremental);
assert!(matches!(
lua.gc_set_mode(GcMode::Incremental({
let p = GcIncParams::default().step_multiplier(100);
#[cfg(not(feature = "luau"))]
let p = p.pause(200);
#[cfg(feature = "luau")]
let p = p.goal(200);
p
})),
GcMode::Incremental(_)
));
struct MyUserdata(#[allow(unused)] Arc<()>);
impl UserData for MyUserdata {}
+4 -2
View File
@@ -1,4 +1,6 @@
use mlua::{Error, ExternalError, Integer, IntoLuaMulti, Lua, MultiValue, Result, String, Value, Variadic};
use mlua::{
Error, ExternalError, Integer, IntoLuaMulti, Lua, LuaString, MultiValue, Result, Value, Variadic,
};
#[test]
fn test_result_conversions() -> Result<()> {
@@ -81,7 +83,7 @@ fn test_multivalue_by_ref() -> Result<()> {
Value::Boolean(true),
]);
let f = lua.create_function(|_, (i, s, b): (i32, String, bool)| {
let f = lua.create_function(|_, (i, s, b): (i32, LuaString, bool)| {
assert_eq!(i, 3);
assert_eq!(s.to_str()?, "hello");
assert_eq!(b, true);
+7 -8
View File
@@ -1,10 +1,9 @@
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,
AnyUserData, Error, Function, Lua, LuaString, MetaMethod, ObjectLike, Result, UserData, UserDataFields,
UserDataMethods, UserDataRegistry,
};
@@ -437,15 +436,15 @@ fn test_scope_userdata_ref_mut() -> Result<()> {
fn test_scope_any_userdata() -> Result<()> {
let lua = Lua::new();
fn register(reg: &mut UserDataRegistry<&mut StdString>) {
reg.add_method_mut("push", |_, this, s: String| {
fn register(reg: &mut UserDataRegistry<&mut String>) {
reg.add_method_mut("push", |_, this, s: LuaString| {
this.push_str(&s.to_str()?);
Ok(())
});
reg.add_meta_method("__tostring", |_, data, ()| Ok((*data).clone()));
}
let mut data = StdString::from("foo");
let mut data = String::from("foo");
lua.scope(|scope| {
let ud = scope.create_any_userdata(&mut data, register)?;
lua.globals().set("ud", ud)?;
@@ -527,11 +526,11 @@ fn test_scope_any_userdata_ref_mut() -> Result<()> {
fn test_scope_destructors() -> Result<()> {
let lua = Lua::new();
lua.register_userdata_type::<Arc<StdString>>(|reg| {
lua.register_userdata_type::<Arc<String>>(|reg| {
reg.add_meta_method("__tostring", |_, data, ()| Ok(data.to_string()));
})?;
let arc_str = Arc::new(StdString::from("foo"));
let arc_str = Arc::new(String::from("foo"));
let ud = lua.create_any_userdata(arc_str.clone())?;
lua.scope(|scope| {
@@ -544,7 +543,7 @@ fn test_scope_destructors() -> Result<()> {
// Try destructing the userdata while it's borrowed
let ud = lua.create_any_userdata(arc_str.clone())?;
ud.borrow_scoped::<Arc<StdString>, _>(|arc_str| {
ud.borrow_scoped::<Arc<String>, _>(|arc_str| {
assert_eq!(arc_str.as_str(), "foo");
lua.scope(|scope| {
scope.add_destructor(|| {
+5 -51
View File
@@ -1,58 +1,14 @@
#![cfg(feature = "send")]
use std::cell::UnsafeCell;
use std::marker::PhantomData;
use std::string::String as StdString;
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_send_only() -> Result<()> {
let lua = Lua::new();
// This type is `Send` but not `Sync`.
struct MyUserData(StdString, PhantomData<UnsafeCell<()>>);
assert_impl_all!(MyUserData: Send);
assert_not_impl_all!(MyUserData: 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_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 ud = lua.globals().get::<UserDataRef<MyUserData>>("ud")?;
std::thread::scope(|s| {
s.spawn(|| {
let res = lua.globals().get::<UserDataRef<MyUserData>>("ud");
assert!(matches!(res, Err(Error::UserDataBorrowError)));
});
});
drop(ud);
lua.load("ud:method()").exec().unwrap();
Ok(())
}
use mlua::{AnyUserData, Lua, ObjectLike, Result, UserData, UserDataMethods, UserDataRef};
use static_assertions::assert_impl_all;
#[test]
fn test_userdata_multithread_access_sync() -> Result<()> {
let lua = Lua::new();
// This type is `Send` and `Sync`.
struct MyUserData(StdString);
struct MyUserData(String);
assert_impl_all!(MyUserData: Send, Sync);
impl UserData for MyUserData {
@@ -75,13 +31,11 @@ fn test_userdata_multithread_access_sync() -> Result<()> {
std::thread::scope(|s| {
s.spawn(|| {
// Getting another shared reference for `Sync` type is allowed.
// FIXME: does not work due to https://github.com/rust-lang/rust/pull/135634
// let _ = lua.globals().get::<UserDataRef<MyUserData>>("ud").unwrap();
let _ = lua.globals().get::<UserDataRef<MyUserData>>("ud").unwrap();
});
});
// FIXME: does not work due to https://github.com/rust-lang/rust/pull/135634
// lua.load("ud:method()").exec().unwrap();
lua.load("ud:method()").exec().unwrap();
Ok(())
}
+10 -10
View File
@@ -1,11 +1,11 @@
use std::borrow::Cow;
use std::collections::HashSet;
use mlua::{Lua, Result, String};
use mlua::{Lua, LuaString, Result};
#[test]
fn test_string_compare() {
fn with_str<F: FnOnce(String)>(s: &str, f: F) {
fn with_str<F: FnOnce(LuaString)>(s: &str, f: F) {
f(Lua::new().create_string(s).unwrap());
}
@@ -42,9 +42,9 @@ fn test_string_views() -> Result<()> {
.exec()?;
let globals = lua.globals();
let ok: String = globals.get("ok")?;
let err: String = globals.get("err")?;
let empty: String = globals.get("empty")?;
let ok: LuaString = globals.get("ok")?;
let err: LuaString = globals.get("err")?;
let empty: LuaString = globals.get("empty")?;
assert_eq!(ok.to_str()?, "null bytes are valid utf-8, wh\0 knew?");
assert_eq!(ok.to_string_lossy(), "null bytes are valid utf-8, wh\0 knew?");
@@ -74,7 +74,7 @@ fn test_string_from_bytes() -> Result<()> {
fn test_string_hash() -> Result<()> {
let lua = Lua::new();
let set: HashSet<String> = lua.load(r#"{"hello", "world", "abc", 321}"#).eval()?;
let set: HashSet<LuaString> = lua.load(r#"{"hello", "world", "abc", 321}"#).eval()?;
assert_eq!(set.len(), 4);
assert!(set.contains(&lua.create_string("hello")?));
assert!(set.contains(&lua.create_string("world")?));
@@ -133,13 +133,13 @@ fn test_string_display() -> Result<()> {
fn test_string_wrap() -> Result<()> {
let lua = Lua::new();
let s = String::wrap("hello, world");
let s = LuaString::wrap("hello, world");
lua.globals().set("s", s)?;
assert_eq!(lua.globals().get::<String>("s")?, "hello, world");
assert_eq!(lua.globals().get::<LuaString>("s")?, "hello, world");
let s2 = String::wrap("hello, world (owned)".to_string());
let s2 = LuaString::wrap("hello, world (owned)".to_string());
lua.globals().set("s2", s2)?;
assert_eq!(lua.globals().get::<String>("s2")?, "hello, world (owned)");
assert_eq!(lua.globals().get::<LuaString>("s2")?, "hello, world (owned)");
Ok(())
}
+31 -26
View File
@@ -1,14 +1,13 @@
use std::collections::HashMap;
#[cfg(not(target_arch = "wasm32"))]
use std::iter::FromIterator;
use std::panic::{catch_unwind, AssertUnwindSafe};
use std::string::String as StdString;
use std::panic::{AssertUnwindSafe, catch_unwind};
use std::sync::Arc;
use std::{error, f32, f64, fmt};
use mlua::{
ffi, ChunkMode, Error, ExternalError, Function, Lua, LuaOptions, Nil, Result, StdLib, String, Table,
UserData, Value, Variadic,
ChunkMode, Error, ExternalError, Function, Lua, LuaOptions, Nil, Result, StdLib, Table, UserData, Value,
Variadic, ffi,
};
#[test]
@@ -155,7 +154,7 @@ fn test_replace_globals() -> Result<()> {
globals.set("foo", "bar")?;
lua.set_globals(globals.clone())?;
let val = lua.load("return foo").eval::<StdString>()?;
let val = lua.load("return foo").eval::<String>()?;
assert_eq!(val, "bar");
// Updating globals in sandboxed Lua state is not allowed
@@ -398,7 +397,7 @@ fn test_error() -> Result<()> {
fn test_panic() -> Result<()> {
fn make_lua(options: LuaOptions) -> Result<Lua> {
let lua = Lua::new_with(StdLib::ALL_SAFE, options)?;
let rust_panic_function = lua.create_function(|_, msg: Option<StdString>| -> Result<()> {
let rust_panic_function = lua.create_function(|_, msg: Option<String>| -> Result<()> {
if let Some(msg) = msg {
panic!("{}", msg)
}
@@ -496,7 +495,7 @@ fn test_panic() -> Result<()> {
.exec()
}) {
Ok(r) => panic!("no panic was detected: {:?}", r),
Err(p) => assert!(*p.downcast::<StdString>().unwrap() == "rust panic from lua"),
Err(p) => assert!(*p.downcast::<String>().unwrap() == "rust panic from lua"),
}
// Test disabling `catch_rust_panics` option / xpcall correctness
@@ -520,7 +519,7 @@ fn test_panic() -> Result<()> {
.exec()
}) {
Ok(r) => panic!("no panic was detected: {:?}", r),
Err(p) => assert!(*p.downcast::<StdString>().unwrap() == "rust panic from lua"),
Err(p) => assert!(*p.downcast::<String>().unwrap() == "rust panic from lua"),
}
Ok(())
@@ -684,10 +683,12 @@ fn test_pcall_xpcall() -> Result<()> {
))]
assert_eq!(globals.get::<std::string::String>("xpcall_error")?, "testerror");
#[cfg(feature = "lua51")]
assert!(globals
.get::<String>("xpcall_error")?
.to_str()?
.ends_with(": testerror"));
assert!(
globals
.get::<mlua::LuaString>("xpcall_error")?
.to_str()?
.ends_with(": testerror")
);
// Make sure that weird xpcall error recursion at least doesn't cause unsafety or panics.
lua.load(
@@ -1070,10 +1071,11 @@ fn test_ref_stack_exhaustion() {
Ok(())
})) {
Ok(_) => panic!("no panic was detected"),
Err(p) => assert!(p
.downcast::<StdString>()
.unwrap()
.starts_with("cannot create a Lua reference, out of auxiliary stack space")),
Err(p) => assert!(
p.downcast::<String>()
.unwrap()
.starts_with("cannot create a Lua reference, out of auxiliary stack space")
),
}
}
@@ -1218,7 +1220,11 @@ fn test_context_thread_51() -> Result<()> {
fn test_jit_version() -> Result<()> {
let lua = Lua::new();
let jit: Table = lua.globals().get("jit")?;
assert!(jit.get::<String>("version")?.to_str()?.contains("LuaJIT"));
assert!(
jit.get::<mlua::LuaString>("version")?
.to_str()?
.contains("LuaJIT")
);
Ok(())
}
@@ -1318,7 +1324,7 @@ fn test_inspect_stack() -> Result<()> {
// Not inside any function
assert!(lua.inspect_stack(0, |_| ()).is_none());
let logline = lua.create_function(|lua, msg: StdString| {
let logline = lua.create_function(|lua, msg: String| {
let r = lua
.inspect_stack(1, |debug| {
let source = debug.source().short_src;
@@ -1368,7 +1374,7 @@ fn test_inspect_stack() -> Result<()> {
local function baz(a, b, c, ...)
return stack_info()
end
assert(baz() == 'DebugStack { num_ups: 1, num_params: 3, is_vararg: true }')
assert(baz() == 'DebugStack { num_upvalues: 1, num_params: 3, is_vararg: true }')
"#,
)
.exec()?;
@@ -1381,7 +1387,7 @@ fn test_inspect_stack() -> Result<()> {
local function baz(a, b, c, ...)
return stack_info()
end
assert(baz() == 'DebugStack { num_ups: 1 }')
assert(baz() == 'DebugStack { num_upvalues: 1 }')
"#,
)
.exec()?;
@@ -1422,9 +1428,8 @@ fn test_traceback() -> Result<()> {
assert!(traceback.contains("stack traceback:"));
// Test traceback inside a function
let get_traceback = lua.create_function(|lua, (msg, level): (Option<StdString>, usize)| {
lua.traceback(msg.as_deref(), level)
})?;
let get_traceback = lua
.create_function(|lua, (msg, level): (Option<String>, usize)| lua.traceback(msg.as_deref(), level))?;
lua.globals().set("get_traceback", get_traceback)?;
lua.load(
@@ -1504,10 +1509,10 @@ fn test_multi_states() -> Result<()> {
#[cfg(any(feature = "lua55", feature = "lua54"))]
fn test_warnings() -> Result<()> {
let lua = Lua::new();
lua.set_app_data::<Vec<(StdString, bool)>>(Vec::new());
lua.set_app_data::<Vec<(String, bool)>>(Vec::new());
lua.set_warning_function(|lua, msg, incomplete| {
lua.app_data_mut::<Vec<(StdString, bool)>>()
lua.app_data_mut::<Vec<(String, bool)>>()
.unwrap()
.push((msg.to_string(), incomplete));
Ok(())
@@ -1521,7 +1526,7 @@ fn test_warnings() -> Result<()> {
lua.remove_warning_function();
lua.warning("one more warning", false);
let messages = lua.app_data_ref::<Vec<(StdString, bool)>>().unwrap();
let messages = lua.app_data_ref::<Vec<(String, bool)>>().unwrap();
assert_eq!(
*messages,
vec![
+18 -18
View File
@@ -1,6 +1,6 @@
use std::panic::catch_unwind;
use mlua::{Error, Function, IntoLua, Lua, Result, Thread, ThreadStatus, Value};
use mlua::{Error, Function, IntoLua, Lua, Result, Thread, Value};
#[test]
fn test_thread() -> Result<()> {
@@ -21,17 +21,17 @@ fn test_thread() -> Result<()> {
.eval()?,
)?;
assert_eq!(thread.status(), ThreadStatus::Resumable);
assert!(thread.is_resumable());
assert_eq!(thread.resume::<i64>(0)?, 0);
assert_eq!(thread.status(), ThreadStatus::Resumable);
assert!(thread.is_resumable());
assert_eq!(thread.resume::<i64>(1)?, 1);
assert_eq!(thread.status(), ThreadStatus::Resumable);
assert!(thread.is_resumable());
assert_eq!(thread.resume::<i64>(2)?, 3);
assert_eq!(thread.status(), ThreadStatus::Resumable);
assert!(thread.is_resumable());
assert_eq!(thread.resume::<i64>(3)?, 6);
assert_eq!(thread.status(), ThreadStatus::Resumable);
assert!(thread.is_resumable());
assert_eq!(thread.resume::<i64>(4)?, 10);
assert_eq!(thread.status(), ThreadStatus::Finished);
assert!(thread.is_finished());
let accumulate = lua.create_thread(
lua.load(
@@ -50,9 +50,9 @@ fn test_thread() -> Result<()> {
accumulate.resume::<()>(i)?;
}
assert_eq!(accumulate.resume::<i64>(4)?, 10);
assert_eq!(accumulate.status(), ThreadStatus::Resumable);
assert!(accumulate.is_resumable());
assert!(accumulate.resume::<()>("error").is_err());
assert_eq!(accumulate.status(), ThreadStatus::Error);
assert!(accumulate.is_error());
let thread = lua
.load(
@@ -65,7 +65,7 @@ fn test_thread() -> Result<()> {
"#,
)
.eval::<Thread>()?;
assert_eq!(thread.status(), ThreadStatus::Resumable);
assert!(thread.is_resumable());
assert_eq!(thread.resume::<i64>(())?, 42);
let thread: Thread = lua
@@ -92,7 +92,7 @@ fn test_thread() -> Result<()> {
// Already running thread must be unresumable
let thread = lua.create_thread(lua.create_function(|lua, ()| {
assert_eq!(lua.current_thread().status(), ThreadStatus::Running);
assert!(lua.current_thread().is_running());
let result = lua.current_thread().resume::<()>(());
assert!(
matches!(result, Err(Error::CoroutineUnresumable)),
@@ -123,12 +123,12 @@ fn test_thread_reset() -> Result<()> {
assert!(thread.reset(func.clone()).is_ok());
for _ in 0..2 {
assert_eq!(thread.status(), ThreadStatus::Resumable);
assert!(thread.is_resumable());
let _ = thread.resume::<AnyUserData>(MyUserData(arc.clone()))?;
assert_eq!(thread.status(), ThreadStatus::Resumable);
assert!(thread.is_resumable());
assert_eq!(Arc::strong_count(&arc), 2);
thread.resume::<()>(())?;
assert_eq!(thread.status(), ThreadStatus::Finished);
assert!(thread.is_finished());
thread.reset(func.clone())?;
lua.gc_collect()?;
assert_eq!(Arc::strong_count(&arc), 1);
@@ -138,21 +138,21 @@ fn test_thread_reset() -> Result<()> {
let func: Function = lua.load(r#"function(ud) error("test error") end"#).eval()?;
let thread = lua.create_thread(func.clone())?;
let _ = thread.resume::<AnyUserData>(MyUserData(arc.clone()));
assert_eq!(thread.status(), ThreadStatus::Error);
assert!(thread.is_error());
assert_eq!(Arc::strong_count(&arc), 2);
#[cfg(any(feature = "lua55", feature = "lua54"))]
{
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));
assert!(thread.is_finished());
assert!(thread.reset(func.clone()).is_ok());
assert_eq!(thread.status(), ThreadStatus::Resumable);
assert!(thread.is_resumable());
}
#[cfg(any(feature = "lua55", feature = "lua54", feature = "luau"))]
{
assert!(thread.reset(func.clone()).is_ok());
assert_eq!(thread.status(), ThreadStatus::Resumable);
assert!(thread.is_resumable());
}
// Try reset running thread
+10 -7
View File
@@ -1,6 +1,6 @@
use std::os::raw::c_void;
use mlua::{Function, LightUserData, Lua, Number, Result, String as LuaString, Thread};
use mlua::{Error, Function, LightUserData, Lua, LuaString, Number, Result, Thread};
#[test]
fn test_lightuserdata() -> Result<()> {
@@ -30,7 +30,7 @@ fn test_boolean_type_metatable() -> Result<()> {
let lua = Lua::new();
let mt = lua.create_table()?;
mt.set("__add", Function::wrap(|a, b| Ok(a || b)))?;
mt.set("__add", Function::wrap(|a, b| Ok::<_, mlua::Error>(a || b)))?;
assert_eq!(lua.type_metatable::<bool>(), None);
lua.set_type_metatable::<bool>(Some(mt.clone()));
assert_eq!(lua.type_metatable::<bool>().unwrap(), mt);
@@ -51,7 +51,7 @@ fn test_lightuserdata_type_metatable() -> Result<()> {
mt.set(
"__add",
Function::wrap(|a: LightUserData, b: LightUserData| {
Ok(LightUserData((a.0 as usize + b.0 as usize) as *mut c_void))
Ok::<_, Error>(LightUserData((a.0 as usize + b.0 as usize) as *mut c_void))
}),
)?;
lua.set_type_metatable::<LightUserData>(Some(mt.clone()));
@@ -79,7 +79,10 @@ fn test_number_type_metatable() -> Result<()> {
let lua = Lua::new();
let mt = lua.create_table()?;
mt.set("__call", Function::wrap(|n1: f64, n2: f64| Ok(n1 * n2)))?;
mt.set(
"__call",
Function::wrap(|n1: f64, n2: f64| Ok::<_, Error>(n1 * n2)),
)?;
lua.set_type_metatable::<Number>(Some(mt.clone()));
assert_eq!(lua.type_metatable::<Number>().unwrap(), mt);
@@ -96,7 +99,7 @@ fn test_string_type_metatable() -> Result<()> {
let mt = lua.create_table()?;
mt.set(
"__add",
Function::wrap(|a: String, b: String| Ok(format!("{a}{b}"))),
Function::wrap(|a: String, b: String| Ok::<_, Error>(format!("{a}{b}"))),
)?;
lua.set_type_metatable::<LuaString>(Some(mt.clone()));
assert_eq!(lua.type_metatable::<LuaString>().unwrap(), mt);
@@ -113,7 +116,7 @@ fn test_function_type_metatable() -> Result<()> {
let mt = lua.create_table()?;
mt.set(
"__index",
Function::wrap(|_: Function, key: String| Ok(format!("function.{key}"))),
Function::wrap(|_: Function, key: String| Ok::<_, Error>(format!("function.{key}"))),
)?;
lua.set_type_metatable::<Function>(Some(mt.clone()));
assert_eq!(lua.type_metatable::<Function>(), Some(mt));
@@ -132,7 +135,7 @@ fn test_thread_type_metatable() -> Result<()> {
let mt = lua.create_table()?;
mt.set(
"__index",
Function::wrap(|_: Thread, key: String| Ok(format!("thread.{key}"))),
Function::wrap(|_: Thread, key: String| Ok::<_, Error>(format!("thread.{key}"))),
)?;
lua.set_type_metatable::<Thread>(Some(mt.clone()));
assert_eq!(lua.type_metatable::<Thread>(), Some(mt));
+112 -24
View File
@@ -1,14 +1,13 @@
use std::any::TypeId;
use std::collections::HashMap;
use std::string::String as StdString;
use std::sync::Arc;
#[cfg(any(feature = "lua55", feature = "lua54"))]
use std::sync::atomic::{AtomicI64, Ordering};
use mlua::{
AnyUserData, Error, ExternalError, Function, Lua, MetaMethod, Nil, ObjectLike, Result, String, UserData,
UserDataFields, UserDataMethods, UserDataRef, UserDataRegistry, Value, Variadic,
AnyUserData, Error, ExternalError, Function, Lua, LuaString, MetaMethod, Nil, ObjectLike, Result,
UserData, UserDataFields, UserDataMethods, UserDataOwned, UserDataRef, UserDataRegistry, Value, Variadic,
};
#[test]
@@ -131,7 +130,7 @@ fn test_metamethods() -> Result<()> {
MetaMethod::Eq,
|_, (lhs, rhs): (UserDataRef<Self>, UserDataRef<Self>)| Ok(lhs.0 == rhs.0),
);
methods.add_meta_method(MetaMethod::Index, |_, data, index: String| {
methods.add_meta_method(MetaMethod::Index, |_, data, index: LuaString| {
if index.to_str()? == "inner" {
Ok(data.0)
} else {
@@ -291,8 +290,8 @@ fn test_gc_userdata() -> Result<()> {
let lua = Lua::new();
lua.globals().set("userdata", MyUserdata { id: 123 })?;
assert!(lua
.load(
assert!(
lua.load(
r#"
local tbl = setmetatable({
userdata = userdata
@@ -308,7 +307,8 @@ fn test_gc_userdata() -> Result<()> {
"#
)
.exec()
.is_err());
.is_err()
);
Ok(())
}
@@ -491,8 +491,8 @@ fn test_user_values() -> Result<()> {
ud.set_nth_user_value(1, "hello")?;
ud.set_nth_user_value(2, "world")?;
ud.set_nth_user_value(65535, 321)?;
assert_eq!(ud.nth_user_value::<String>(1)?, "hello");
assert_eq!(ud.nth_user_value::<String>(2)?, "world");
assert_eq!(ud.nth_user_value::<LuaString>(1)?, "hello");
assert_eq!(ud.nth_user_value::<LuaString>(2)?, "world");
assert_eq!(ud.nth_user_value::<Value>(3)?, Value::Nil);
assert_eq!(ud.nth_user_value::<i32>(65535)?, 321);
@@ -582,8 +582,8 @@ fn test_fields() -> Result<()> {
});
// Use userdata "uservalue" storage
fields.add_field_function_get("uval", |_, ud| ud.user_value::<Option<String>>());
fields.add_field_function_set("uval", |_, ud, s: Option<String>| ud.set_user_value(s));
fields.add_field_function_get("uval", |_, ud| ud.user_value::<Option<LuaString>>());
fields.add_field_function_set("uval", |_, ud, s: Option<LuaString>| ud.set_user_value(s));
fields.add_meta_field(MetaMethod::Index, HashMap::from([("f", 321)]));
fields.add_meta_field_with(MetaMethod::NewIndex, |lua| {
@@ -630,9 +630,11 @@ fn test_fields() -> Result<()> {
}
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_meta_method(MetaMethod::Index, |_, _, name: StdString| match &*name {
"y" => Ok(Some(-1)),
_ => Ok(None),
methods.add_meta_method(MetaMethod::Index, |_, _, name: LuaString| {
match name.to_str()?.as_ref() {
"y" => Ok(Some(-1)),
_ => Ok(None),
}
});
}
}
@@ -659,7 +661,7 @@ fn test_metatable() -> Result<()> {
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_function("my_type_name", |_, data: AnyUserData| {
let metatable = data.metatable()?;
metatable.get::<String>(MetaMethod::Type)
metatable.get::<LuaString>(MetaMethod::Type)
});
}
}
@@ -723,7 +725,47 @@ fn test_metatable() -> Result<()> {
let ud = lua.create_userdata(MyUserData3)?;
let metatable = ud.metatable()?;
assert_eq!(metatable.get::<String>(MetaMethod::Type)?.to_str()?, "CustomName");
assert_eq!(
metatable.get::<LuaString>(MetaMethod::Type)?.to_str()?,
"CustomName"
);
Ok(())
}
#[test]
fn test_userdata_type_name() -> Result<()> {
struct MyUserData;
impl UserData for MyUserData {}
struct MyUserdataCustom;
impl UserData for MyUserdataCustom {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
fields.add_meta_field_with(MetaMethod::Type, |_| Ok("MyCustomName"));
}
}
// mlua always sets __name/__type; override with a non-string to test the "userdata" fallback
struct MyUserdataInvalid;
impl UserData for MyUserdataInvalid {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
fields.add_meta_field_with(MetaMethod::Type, |_| Ok(42_i64));
}
}
let lua = Lua::new();
// Default is the Rust type name
let ud = lua.create_userdata(MyUserData)?;
assert_eq!(ud.type_name()?, "MyUserData");
// Custom name from metatable
let ud = lua.create_userdata(MyUserdataCustom)?;
assert_eq!(ud.type_name()?, "MyCustomName");
// Invalid type name should fallback to "userdata"
let ud = lua.create_userdata(MyUserdataInvalid)?;
assert_eq!(ud.type_name()?.to_str()?, "userdata");
Ok(())
}
@@ -776,16 +818,16 @@ fn test_userdata_proxy() -> Result<()> {
fn test_any_userdata() -> Result<()> {
let lua = Lua::new();
lua.register_userdata_type::<StdString>(|reg| {
lua.register_userdata_type::<String>(|reg| {
reg.add_method("get", |_, this, ()| Ok(this.clone()));
reg.add_method_mut("concat", |_, this, s: String| {
reg.add_method_mut("concat", |_, this, s: LuaString| {
this.push_str(&s.to_string_lossy());
Ok(())
});
})?;
let ud = lua.create_any_userdata("hello".to_string())?;
assert_eq!(&*ud.borrow::<StdString>()?, "hello");
assert_eq!(&*ud.borrow::<String>()?, "hello");
lua.globals().set("ud", ud)?;
lua.load(
@@ -805,7 +847,7 @@ fn test_any_userdata() -> Result<()> {
fn test_any_userdata_wrap() -> Result<()> {
let lua = Lua::new();
lua.register_userdata_type::<StdString>(|reg| {
lua.register_userdata_type::<String>(|reg| {
reg.add_method("get", |_, this, ()| Ok(this.clone()));
})?;
@@ -857,7 +899,7 @@ fn test_userdata_object_like() -> Result<()> {
r => panic!("expected RuntimeError, got {r:?}"),
}
assert_eq!(ud.call::<String>(())?, "called");
assert_eq!(ud.call::<LuaString>(())?, "called");
ud.call_method::<()>("add", 2)?;
assert_eq!(ud.get::<u32>("n")?, 323);
@@ -1371,11 +1413,11 @@ fn test_userdata_namecall() -> Result<()> {
struct MyUserData;
impl UserData for MyUserData {
fn register(registry: &mut mlua::UserDataRegistry<Self>) {
fn register(registry: &mut UserDataRegistry<Self>) {
registry.add_method("method", |_, _, ()| Ok("method called"));
registry.add_field_method_get("field", |_, _| Ok("field value"));
registry.add_meta_method(MetaMethod::Index, |_, _, key: StdString| Ok(key));
registry.add_meta_method(MetaMethod::Index, |_, _, key: LuaString| Ok(key));
registry.enable_namecall();
}
@@ -1413,7 +1455,53 @@ fn test_userdata_get_path() -> Result<()> {
}
let ud = lua.create_userdata(MyUd)?;
assert_eq!(ud.get_path::<String>(".value")?, "userdata_value");
assert_eq!(ud.get_path::<LuaString>(".value")?, "userdata_value");
Ok(())
}
#[test]
fn test_userdata_owned() -> Result<()> {
#[derive(Debug)]
struct MyUserdata(Arc<i64>);
impl UserData for MyUserdata {
fn register(registry: &mut UserDataRegistry<Self>) {
registry.add_method("num", |_, this, ()| Ok(*this.0));
}
}
let lua = Lua::new();
let rc = Arc::new(42);
// It takes ownership and destructs the Lua userdata
let ud = lua.create_userdata(MyUserdata(rc.clone()))?;
assert_eq!(Arc::strong_count(&rc), 2);
let owned: UserDataOwned<MyUserdata> = lua.convert(&ud)?;
assert_eq!(*owned.0.0, 42);
drop(owned);
assert_eq!(Arc::strong_count(&rc), 1);
match ud.borrow::<MyUserdata>() {
Err(Error::UserDataDestructed) => {}
r => panic!("expected UserDataDestructed, got {:?}", r),
}
// Cannot take while borrowed
let rc = Arc::new(7);
let ud = lua.create_userdata(MyUserdata(rc.clone()))?;
let borrowed = ud.borrow::<MyUserdata>()?;
match lua.convert::<UserDataOwned<MyUserdata>>(&ud) {
Err(Error::UserDataBorrowMutError) => {}
r => panic!("expected UserDataBorrowMutError, got {:?}", r),
}
drop(borrowed);
// Works as a function parameter
let f = lua.create_function(|_, owned: UserDataOwned<MyUserdata>| Ok(*owned.0.0))?;
let rc = Arc::new(55);
let ud = lua.create_userdata(MyUserdata(rc.clone()))?;
assert_eq!(f.call::<i64>(ud)?, 55);
assert_eq!(Arc::strong_count(&rc), 1); // dropped after call
Ok(())
}
+37 -8
View File
@@ -1,9 +1,11 @@
use std::collections::HashMap;
use std::os::raw::c_void;
use std::ptr;
use std::string::String as StdString;
use mlua::{Error, LightUserData, Lua, MultiValue, Result, UserData, UserDataMethods, Value};
use mlua::{
AnyUserData, Error, LightUserData, Lua, MultiValue, Result, UserData, UserDataMethods, UserDataRegistry,
Value,
};
#[test]
fn test_value_eq() -> Result<()> {
@@ -178,7 +180,7 @@ fn test_value_to_string() -> Result<()> {
assert!(thread.to_string()?.starts_with("thread:"));
assert_eq!(thread.type_name(), "thread");
lua.register_userdata_type::<StdString>(|reg| {
lua.register_userdata_type::<String>(|reg| {
reg.add_meta_method("__tostring", |_, this, ()| Ok(this.clone()));
})?;
let ud: Value = Value::UserData(lua.create_any_userdata(String::from("string userdata"))?);
@@ -213,12 +215,37 @@ fn test_value_to_string() -> Result<()> {
fn test_debug_format() -> Result<()> {
let lua = Lua::new();
lua.register_userdata_type::<HashMap<i32, StdString>>(|_| {})?;
lua.register_userdata_type::<HashMap<i32, String>>(|_| {})?;
let ud = lua
.create_any_userdata::<HashMap<i32, StdString>>(HashMap::new())
.create_any_userdata::<HashMap<i32, String>>(HashMap::new())
.map(Value::UserData)?;
assert!(format!("{ud:#?}").starts_with("HashMap<i32, String>:"));
struct ToDebugUserData;
impl UserData for ToDebugUserData {
fn register(registry: &mut UserDataRegistry<Self>) {
registry.add_meta_method("__tostring", |_, _, ()| Ok("regular-string"));
registry.add_meta_method("__todebugstring", |_, _, ()| Ok("debug-string"));
}
}
let debug_ud = Value::UserData(lua.create_userdata(ToDebugUserData)?);
assert_eq!(debug_ud.to_string()?, "regular-string");
assert_eq!(format!("{debug_ud:#?}"), "debug-string");
struct ToStringUserData;
impl UserData for ToStringUserData {
fn register(registry: &mut UserDataRegistry<Self>) {
registry.add_meta_method("__tostring", |_, _, ()| Ok("regular-string"));
}
}
let tostring_only_ud = Value::UserData(lua.create_userdata(ToStringUserData)?);
assert_eq!(format!("{tostring_only_ud:#?}"), "regular-string");
// Check that `AnyUsedata` pretty debug format is same as for `Value::UserData`
let any_ud: AnyUserData = lua.create_userdata(ToDebugUserData)?;
let value_ud = Value::UserData(any_ud.clone());
assert_eq!(format!("{any_ud:#?}"), format!("{value_ud:#?}"));
Ok(())
}
@@ -259,9 +286,11 @@ fn test_value_conversions() -> Result<()> {
assert!(Value::Table(lua.create_table()?).is_table());
assert!(Value::Table(lua.create_table()?).as_table().is_some());
assert!(Value::Function(lua.create_function(|_, ()| Ok(())).unwrap()).is_function());
assert!(Value::Function(lua.create_function(|_, ()| Ok(())).unwrap())
.as_function()
.is_some());
assert!(
Value::Function(lua.create_function(|_, ()| Ok(())).unwrap())
.as_function()
.is_some()
);
assert!(Value::Thread(lua.create_thread(lua.load("function() end").eval()?)?).is_thread());
assert!(
Value::Thread(lua.create_thread(lua.load("function() end").eval()?)?)