Compare commits

..

87 Commits

Author SHA1 Message Date
Alex Orlenko 0711c614c7 v0.12.0-rc.2 2026-06-06 14:28:31 +01:00
Alex Orlenko 743325f7d6 clippy 2026-06-06 14:28:31 +01:00
Alex Orlenko 0b365e92a9 Support to_alias_override/to_alias_fallback in Require trait (Luau) 2026-06-06 14:11:59 +01:00
Alex Orlenko 15fb63b2a2 Support thread create/resume/yield callbacks for all Lua/Luau versions
Introduce new `Lua::set_thread_event_callback` function to register callback for
thread events.
Thread collection callback is removed as unlikely very usefil and has too many limitations.
2026-06-01 00:25:12 +01:00
Alex Orlenko 0849d05c83 mlua_derive: Update docs 2026-05-31 13:45:48 +01:00
Alex Orlenko 38c05b850e mlua_derive: Improve error reporting for #[lua] attributes 2026-05-31 13:32:33 +01:00
Alex Orlenko 8d1841f8cf Fix tests (Luau 0.723 integer type) 2026-05-31 13:22:50 +01:00
Alex Orlenko e9271d2e32 mlua_derive: Use correct Span in metamethod name validation error 2026-05-31 13:17:40 +01:00
Alex Orlenko e263220fb3 Add documentation for #derive(UserData) 2026-05-31 13:05:03 +01:00
Alex Orlenko e6d16815d7 mlua_derive: Improve context-aware validation for #[lua] attr 2026-05-31 12:56:15 +01:00
Alex Orlenko ae88e8acf8 mlua_derive: Detect (and reject) all generic type parameters in UserData derive 2026-05-31 12:31:35 +01:00
Alex Orlenko fcab60bac4 mlua_derive: Fix #[lua(meta, field)] case 2026-05-31 12:28:06 +01:00
Alex Orlenko 208a70f407 Move inlined #[lua_module] proc macro doc to docs/lua_module.md 2026-05-31 11:17:51 +01:00
Alex Orlenko ca360f9019 Move inlined chunk! macro doc to docs/chunk.md 2026-05-31 11:16:40 +01:00
Alex Orlenko a7c5a24a7b mlua_derive: Deny #[lua = "..."] syntax 2026-05-30 22:23:03 +01:00
Alex Orlenko b7c98ad9bb mlua_derive: Switch from #[userdata] to [derive(UserData)] 2026-05-30 13:11:26 +01:00
Alex Orlenko 1f3dafa564 mlua_derive: Reject static field functions with args 2026-05-30 12:46:31 +01:00
Alex Orlenko 1d4a756436 mlua_derive: Support async userdata methods in macro 2026-05-29 00:56:33 +01:00
Alex Orlenko 6e7d6c78ed mlua_derive: Group functionality in modules
Create new `chunk`, `userdata`, `module` modules.
2026-05-26 23:23:09 +01:00
Alex Orlenko 023e4c61d8 mlua_derive: Fix compilation / remove "if let" guards 2026-05-26 23:23:09 +01:00
Alex Orlenko 92bd06d3c1 mlua_derive: Refactor Capture implementation for chunk! 2026-05-26 23:23:09 +01:00
Alex Orlenko d8544bf038 mlua_derive: Optimize Captures::add 2026-05-26 23:23:09 +01:00
Alex Orlenko 7114c03489 mlua_derive: Remove unneeded Span::line hack for chunk! since we use Rust 1.95+ 2026-05-26 23:23:09 +01:00
Alex Orlenko f4cacc524e mlua_derive: Bump MSRV to 1.88 2026-05-26 23:23:09 +01:00
Alex Orlenko cc7f7ce7b7 mlua_derive: show better error message when $ is not followed by ident 2026-05-26 23:23:09 +01:00
Alex Orlenko 72de602ec3 Fix modules compilation after switching to 2024 edition in mlua_derive 2026-05-26 23:23:09 +01:00
Alex Orlenko 1573dd1242 Add #[mlua::userdata] and #[mlua::userdata_impl] macros 2026-05-26 23:23:09 +01:00
Alex Orlenko 39d3201848 Prevent XRc overflow when dropping RawLua with foreign Lua state
Calling `lua_close` triggers GC collection of the `ExtraData` that
cascades to `RawLua::drop` where extra is already decremented to 0,
causing a subtraction overflow.
2026-05-26 23:18:24 +01:00
莯凛 4aa6214b45 feat: implement Not for StdLib (#699) 2026-04-30 22:56:50 +01:00
Alex Orlenko 72824a468a Remove custom PartialEq for LuaString and use derived one
Lua can compare strings using `lua_rawequal` and it's more efficient
than always compare bytes.
Under the hood Lua compare pointers for interned strings and content for long ones.
Close #694
2026-04-22 22:30:45 +01:00
Alex Orlenko c54b90623c Fix String::to_pointer return NULL in Lua <5.4
Lua 5.4+ can return string pointer when calling `lua_topointer`.
In earlier Lua versions this API always returns NULL.
Let's unify this behavior.
2026-04-22 22:23:25 +01:00
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
104 changed files with 4479 additions and 1473 deletions
+2 -2
View File
@@ -1,8 +1,8 @@
name: Documentation (dev) name: Documentation (main)
on: on:
push: push:
branches: [dev] branches: [main]
workflow_dispatch: workflow_dispatch:
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
+28
View File
@@ -1,3 +1,31 @@
## v0.12.0-rc.2 (Jun 06, 2026)
- Add `#[derive(UserData)]` and `#[mlua::userdata_impl]` macros
- Support thread create/resume/yield callbacks for all Lua versions (including Luau)
- Support `to_alias_override`/`to_alias_fallback` in `Require` trait (Luau)
- Prevent `XRc` overflow when dropping `RawLua` with foreign Lua state
- implement `Not` for `StdLib` (#699)
- Fix `String::to_pointer` return NULL in Lua <5.4
## 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) ## v0.11.6 (Jan 27, 2026)
- Added Lua 5.5 support (`lua55` feature flag) - Added Lua 5.5 support (`lua55` feature flag)
+8 -7
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "mlua" name = "mlua"
version = "0.12.0-dev.1" # remember to update mlua_derive version = "0.12.0-rc.2" # remember to update mlua_derive
authors = ["Aleksandr Orlenko <zxteam@pm.me>", "kyren <catherine@kyju.org>"] authors = ["Aleksandr Orlenko <zxteam@pm.me>", "kyren <catherine@kyju.org>"]
rust-version = "1.88" rust-version = "1.88"
edition = "2024" edition = "2024"
@@ -42,7 +42,7 @@ async = ["dep:futures-util"]
send = ["error-send"] send = ["error-send"]
error-send = [] error-send = []
serde = ["dep:serde", "dep:erased-serde", "dep:serde-value", "bstr/serde"] serde = ["dep:serde", "dep:erased-serde", "dep:serde-value", "bstr/serde"]
macros = ["mlua_derive/macros"] macros = ["mlua_derive/macros", "dep:inventory"]
anyhow = ["dep:anyhow", "error-send"] anyhow = ["dep:anyhow", "error-send"]
userdata-wrappers = ["parking_lot/send_guard"] userdata-wrappers = ["parking_lot/send_guard"]
@@ -50,7 +50,7 @@ userdata-wrappers = ["parking_lot/send_guard"]
serialize = ["serde"] serialize = ["serde"]
[dependencies] [dependencies]
mlua_derive = { version = "=0.11.0", optional = true, path = "mlua_derive" } mlua_derive = { version = "=0.12.0-rc.1", optional = true, path = "mlua_derive" }
bstr = { version = "1.0", features = ["std"], default-features = false } bstr = { version = "1.0", features = ["std"], default-features = false }
either = "1.0" either = "1.0"
num-traits = { version = "0.2.14" } num-traits = { version = "0.2.14" }
@@ -61,9 +61,10 @@ erased-serde = { version = "0.4", optional = true }
serde-value = { version = "0.7", optional = true } serde-value = { version = "0.7", optional = true }
parking_lot = { version = "0.12", features = ["arc_lock"] } parking_lot = { version = "0.12", features = ["arc_lock"] }
anyhow = { version = "1.0", optional = true } anyhow = { version = "1.0", optional = true }
inventory = { version = "0.3", optional = true }
libc = "0.2" 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] [dev-dependencies]
trybuild = "1.0" trybuild = "1.0"
@@ -77,10 +78,10 @@ static_assertions = "1.0"
hyper = { version = "1.2", features = ["full"] } hyper = { version = "1.2", features = ["full"] }
hyper-util = { version = "0.1.3", features = ["full"] } hyper-util = { version = "0.1.3", features = ["full"] }
http-body-util = "0.1.1" http-body-util = "0.1.1"
reqwest = { version = "0.12", features = ["json"] } reqwest = { version = "0.13", features = ["json"] }
tempfile = "3" tempfile = "3"
criterion = { version = "0.7", features = ["async_tokio"] } criterion = { version = "0.8", features = ["async_tokio"] }
rustyline = "17.0" rustyline = "18.0"
tokio = { version = "1.0", features = ["full"] } tokio = { version = "1.0", features = ["full"] }
[lints.rust] [lints.rust]
+3 -1
View File
@@ -17,6 +17,8 @@
[Benchmarks]: https://github.com/khvzak/script-bench-rs [Benchmarks]: https://github.com/khvzak/script-bench-rs
[FAQ]: FAQ.md [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 `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. _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`. 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 ### 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`: Add to `Cargo.toml`:
+149
View File
@@ -0,0 +1,149 @@
Implements the [`UserData`] trait for a Rust type.
This derive macro generates an implementation of [`UserData`] that exposes
struct fields to Lua and integrates with `#[mlua::userdata_impl]` for
registering methods.
Named fields are exposed as readable and writable fields in Lua by default.
Use `#[lua(...)]` on individual fields or methods to control how they are
registered.
```rust,ignore
use mlua::{Lua, Result, UserData};
#[derive(UserData)]
struct Rectangle {
length: u32,
width: u32,
}
#[mlua::userdata_impl]
impl Rectangle {
#[lua(infallible)]
fn new(length: u32, width: u32) -> Self {
Self { length, width }
}
#[lua(getter, name = "area", infallible)]
fn calculate_area(&self) -> u32 {
self.length * self.width
}
fn diagonal(&self) -> Result<f64> {
Ok(((self.length.pow(2) + self.width.pow(2)) as f64).sqrt())
}
}
```
# Struct field attributes
Each named field can be annotated with `#[lua(...)]`:
| Attribute | Description |
| -------------- | ----------------------------------------------------- |
| `get` | Expose a getter. The field becomes readable from Lua. |
| `set` | Expose a setter. The field becomes writable from Lua. |
| `skip` | Do not expose this field. |
| `name = "..."` | Override the Lua-facing name for the field. |
If neither `get` nor `set` is specified, both are enabled.
Fields exposed as readable (via `get` or by default) must implement `Clone`.
The generated getter clones the field value when accessed from Lua.
# Methods registration
Use `#[mlua::userdata_impl]` on an `impl` block to register methods,
metamethods, and constants. All public items in the block are registered
automatically.
## Method detection
The receiver type determines how a method is registered:
| Receiver | Registration |
| ----------- | ----------------- |
| `&self` | `add_method` |
| `&mut self` | `add_method_mut` |
| `self` | `add_method_once` |
| None | `add_function` |
A first parameter of type `&Lua` (or `&mlua::Lua`) is treated as the
Lua state reference and passed automatically.
## Method and constant attributes
Each item in the impl block can be annotated with `#[lua(...)]`:
| Attribute | Applies to | Description |
| -------------- | ------------------ | -------------------------------------------------------------------------------------- |
| `skip` | Methods, constants | Exclude this item from registration. |
| `name = "..."` | Methods, constants | Override the Lua-facing name. |
| `infallible` | Methods | Wrap the return value in `Ok(...)`. |
| `getter` | Methods | Register as a field getter. Must take `&self` and no Lua-facing arguments. |
| `setter` | Methods | Register as a field setter. Must take `&[mut] self` and one value argument. |
| `field` | Methods, constants | Register as a static field. Methods must take no receiver and no Lua-facing arguments. |
| `meta` | Methods, constants | Register as a metamethod. May be combined with `field` for meta static fields. |
At most one of `getter`, `setter`, `field` may be specified on a method.
## Constants
Constants in an `#[mlua::userdata_impl]` block are registered as static
fields:
```rust,ignore
#[mlua::userdata_impl]
impl MyType {
const VERSION: &str = "1.0";
const COUNT: u32 = 42;
}
```
Use `#[lua(meta)]` on a constant to register it as a meta static field.
## Metamethods
Annotate a method with `#[lua(meta)]` to register it as a Lua metamethod.
The metamethod name is inferred from the function name when it starts with
`__`. Use `name = "..."` to specify the name explicitly.
```rust,ignore
#[mlua::userdata_impl]
impl MyType {
#[lua(meta, infallible)]
fn __add(&self, other: &Self) -> Self { ... }
#[lua(meta, name = "__call", infallible)]
fn construct(lua: &Lua, value: u32) -> Self { ... }
}
```
## Reference parameters
Reference parameters in method signatures are automatically mapped to
the appropriate callback wrapper types:
| Parameter type | Callback type |
| -------------- | ------------------- |
| `&str` | `BorrowedStr` |
| `&[u8]` | `BorrowedBytes` |
| `&T` | `UserDataRef<T>` |
| `&mut T` | `UserDataRefMut<T>` |
## Async methods
Async methods are supported and registered via the corresponding async
variants (`add_async_method`, `add_async_method_mut`, etc.).
# Limitations
Generics are not supported. Wrap a generic type in a concrete newtype
instead.
Union types cannot derive `UserData`.
Enum types are accepted but generate no field registrations. All method
registration must be done via `#[mlua::userdata_impl]`.
[`UserData`]: crate::UserData
+52
View File
@@ -0,0 +1,52 @@
Create a type that implements [`AsChunk`] and can capture Rust variables.
This macro allows to write Lua code directly in Rust code.
Rust variables can be referenced from Lua using `$` prefix, as shown in the example below.
User's Rust types needs to implement [`UserData`] or [`IntoLua`] traits.
Captured variables are **moved** into the chunk.
```rust
use mlua::{Lua, Result, chunk};
fn main() -> Result<()> {
let lua = Lua::new();
let name = "Rustacean";
lua.load(chunk! {
print("hello, " .. $name)
}).exec()
}
```
## Syntax issues
Since the Rust tokenizer will tokenize Lua code, this imposes some restrictions.
The main thing to remember is:
- Use double quoted strings (`""`) instead of single quoted strings (`''`).
(Single quoted strings only work if they contain a single character, since in Rust,
`'a'` is a character literal).
- Using Lua comments `--` is not desirable in **stable** Rust and can have bad side effects.
This is because procedural macros have Line/Column information available only in
**nightly** Rust. Instead, Lua chunks represented as a big single line of code in stable Rust.
As workaround, Rust comments `//` can be used.
Other minor limitations:
- Certain escape codes in string literals don't work. (Specifically: `\a`, `\b`, `\f`, `\v`,
`\123` (octal escape codes), `\u`, and `\U`).
These are accepted: : `\\`, `\n`, `\t`, `\r`, `\xAB` (hex escape codes), and `\0`.
- The `//` (floor division) operator is unusable, as its start a comment.
Everything else should work.
[`AsChunk`]: crate::chunk::AsChunk
[`UserData`]: crate::UserData
[`IntoLua`]: crate::IntoLua
+41
View File
@@ -0,0 +1,41 @@
Registers Lua module entrypoint.
You can register multiple entrypoints as required.
```rust,ignore
use mlua::{Lua, Result, Table};
#[mlua::lua_module]
fn my_module(lua: &Lua) -> Result<Table> {
let exports = lua.create_table()?;
exports.set("hello", "world")?;
Ok(exports)
}
```
Internally in the code above the compiler defines C function `luaopen_my_module`.
You can also pass options to the attribute:
* name - name of the module, defaults to the name of the function
```rust,ignore
#[mlua::lua_module(name = "alt_module")]
fn my_module(lua: &Lua) -> Result<Table> {
...
}
```
* skip_memory_check - skip memory allocation checks for some operations.
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.
```rust,ignore
#[mlua::lua_module(skip_memory_check)]
fn my_module(lua: &Lua) -> Result<Table> {
...
}
```
+3 -3
View File
@@ -1,6 +1,6 @@
## mlua v0.10 release notes ## 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]. 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 [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. 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 [`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`. 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. `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: 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 #### 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 ```rust
let func = lua.create_function(|_, _a: i32| Ok(()))?; 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. - `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. protected mode. Setting this attribute will improve performance of such operations with risk of having uncaught exceptions and memory leaks.
#### Improved Windows target #### Improved Windows target
+28 -27
View File
@@ -1,45 +1,46 @@
use mlua::{Lua, MetaMethod, Result, UserData, chunk}; use mlua::{Lua, Result, UserData, chunk};
#[derive(Default)] #[derive(Default, UserData)]
struct Rectangle { struct Rectangle {
length: u32, length: u32,
width: u32, width: u32,
} }
impl UserData for Rectangle { #[mlua::userdata_impl]
fn add_fields<F: mlua::UserDataFields<Self>>(fields: &mut F) { impl Rectangle {
fields.add_field_method_get("length", |_, this| Ok(this.length)); const NAME: &str = "Rectangle";
fields.add_field_method_set("length", |_, this, val| {
this.length = val; #[lua(infallible)]
Ok(()) fn new(length: u32, width: u32) -> Self {
}); Self { length, width }
fields.add_field_method_get("width", |_, this| Ok(this.width));
fields.add_field_method_set("width", |_, this, val| {
this.width = val;
Ok(())
});
} }
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) { #[lua(getter, name = "area", infallible)]
methods.add_method("area", |_, this, ()| Ok(this.length * this.width)); fn calculate_area(&self) -> u32 {
methods.add_method("diagonal", |_, this, ()| { self.length * self.width
Ok((this.length.pow(2) as f64 + this.width.pow(2) as f64).sqrt()) }
});
// Constructor fn diagonal(&self) -> Result<f64> {
methods.add_meta_function(MetaMethod::Call, |_, ()| Ok(Rectangle::default())); Ok((self.length.pow(2) as f64 + self.width.pow(2) as f64).sqrt())
}
// Constructor via `__call` metamethod
#[lua(meta, infallible)]
fn __call(length: u32, width: u32) -> Self {
Rectangle::new(length, width)
} }
} }
fn main() -> Result<()> { fn main() -> Result<()> {
let lua = Lua::new(); let lua = Lua::new();
let rectangle = Rectangle::default(); lua.globals().set("Rectangle", lua.create_proxy::<Rectangle>()?)?;
lua.load(chunk! { lua.load(chunk! {
local rect = $rectangle() local rect = Rectangle(10, 5)
rect.width = 10 rect.width = rect.width + 5
rect.length = 5 rect.length = rect.length + 5
assert(rect:area() == 50) assert(rect.NAME == "Rectangle")
assert(rect:diagonal() - 11.1803 < 0.0001) assert(rect.area == 150)
assert(math.floor(rect:diagonal()) == 18)
}) })
.exec() .exec()
} }
+4 -4
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "mlua-sys" name = "mlua-sys"
version = "0.10.0" version = "0.11.0-rc.1"
authors = ["Aleksandr Orlenko <zxteam@pm.me>"] authors = ["Aleksandr Orlenko <zxteam@pm.me>"]
rust-version = "1.88" rust-version = "1.88"
edition = "2024" edition = "2024"
@@ -41,9 +41,9 @@ libc = "0.2"
cc = "1.0" cc = "1.0"
cfg-if = "1.0" cfg-if = "1.0"
pkg-config = "0.3.17" pkg-config = "0.3.17"
lua-src = { version = ">= 550.0.0, < 550.1.0", optional = true } lua-src = { version = ">= 550.1.0, < 550.2.0", optional = true }
luajit-src = { version = ">= 210.6.0, < 210.7.0", optional = true } luajit-src = { version = ">= 210.7.0, < 210.8.0", optional = true }
luau0-src = { version = "0.18.0", optional = true } luau0-src = { version = "0.20.0", optional = true }
[lints.rust] [lints.rust]
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(raw_dylib)'] } unexpected_cfgs = { level = "allow", check-cfg = ['cfg(raw_dylib)'] }
+2
View File
@@ -38,8 +38,10 @@ unsafe extern "C-unwind" {
#[link_name = "luaL_checkinteger"] #[link_name = "luaL_checkinteger"]
pub fn luaL_checkinteger_(L: *mut lua_State, narg: c_int) -> c_int; 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"] #[link_name = "luaL_optinteger"]
pub fn luaL_optinteger_(L: *mut lua_State, narg: c_int, def: c_int) -> c_int; 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_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; 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_TLIGHTUSERDATA: c_int = 2;
pub const LUA_TNUMBER: c_int = 3; 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_TSTRING: c_int = 6;
pub const LUA_TTABLE: c_int = 6; pub const LUA_TTABLE: c_int = 7;
pub const LUA_TFUNCTION: c_int = 7; pub const LUA_TFUNCTION: c_int = 8;
pub const LUA_TUSERDATA: c_int = 8; pub const LUA_TUSERDATA: c_int = 9;
pub const LUA_TTHREAD: c_int = 9; pub const LUA_TTHREAD: c_int = 10;
pub const LUA_TBUFFER: c_int = 10; pub const LUA_TBUFFER: c_int = 11;
/// Guaranteed number of Lua stack slots available to a C function. /// Guaranteed number of Lua stack slots available to a C function.
pub const LUA_MINSTACK: c_int = 20; 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_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_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_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_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_tostringatom(L: *mut lua_State, idx: c_int, atom: *mut c_int) -> *const c_char;
pub fn lua_tolstringatom( pub fn lua_tolstringatom(
@@ -182,6 +184,7 @@ unsafe extern "C-unwind" {
pub fn lua_pushnumber(L: *mut lua_State, n: lua_Number); pub fn lua_pushnumber(L: *mut lua_State, n: lua_Number);
#[link_name = "lua_pushinteger"] #[link_name = "lua_pushinteger"]
pub fn lua_pushinteger_(L: *mut lua_State, n: c_int); 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); pub fn lua_pushunsigned(L: *mut lua_State, n: lua_Unsigned);
#[cfg(not(feature = "luau-vector4"))] #[cfg(not(feature = "luau-vector4"))]
pub fn lua_pushvector(L: *mut lua_State, x: c_float, y: c_float, z: c_float); 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 (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)] #[inline(always)]
pub unsafe fn lua_isvector(L: *mut lua_State, n: c_int) -> c_int { pub unsafe fn lua_isvector(L: *mut lua_State, n: c_int) -> c_int {
(lua_type(L, n) == LUA_TVECTOR) as 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, 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" { unsafe extern "C-unwind" {
pub fn lua_stackdepth(L: *mut lua_State) -> c_int; 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; 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_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; 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) /// 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)>, 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 /// gets called when a string is created to assign an atom id
pub useratom: Option<unsafe extern "C-unwind" fn(s: *const c_char, l: usize) -> i16>, 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 /// gets called when BREAK instruction is encountered
pub debugbreak: Option<unsafe extern "C-unwind" fn(L: *mut lua_State, ar: *mut lua_Debug)>, 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_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_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_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_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); 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_MATHLIBNAME: *const c_char = cstr!("math");
pub const LUA_DBLIBNAME: *const c_char = cstr!("debug"); pub const LUA_DBLIBNAME: *const c_char = cstr!("debug");
pub const LUA_VECLIBNAME: *const c_char = cstr!("vector"); pub const LUA_VECLIBNAME: *const c_char = cstr!("vector");
pub const LUA_INTLIBNAME: *const c_char = cstr!("integer");
unsafe extern "C-unwind" { unsafe extern "C-unwind" {
pub fn luaopen_base(L: *mut lua_State) -> c_int; 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_math(L: *mut lua_State) -> c_int;
pub fn luaopen_debug(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_vector(L: *mut lua_State) -> c_int;
pub fn luaopen_integer(L: *mut lua_State) -> c_int;
// open all builtin libraries // open all builtin libraries
pub fn luaL_openlibs(L: *mut lua_State); pub fn luaL_openlibs(L: *mut lua_State);
+4 -5
View File
@@ -1,8 +1,9 @@
[package] [package]
name = "mlua_derive" name = "mlua_derive"
version = "0.11.0" version = "0.12.0-rc.1"
authors = ["Aleksandr Orlenko <zxteam@pm.me>"] authors = ["Aleksandr Orlenko <zxteam@pm.me>"]
edition = "2021" rust-version = "1.88"
edition = "2024"
description = "Procedural macros for the mlua crate." description = "Procedural macros for the mlua crate."
repository = "https://github.com/mlua-rs/mlua" repository = "https://github.com/mlua-rs/mlua"
keywords = ["lua", "mlua"] keywords = ["lua", "mlua"]
@@ -12,7 +13,7 @@ license = "MIT"
proc-macro = true proc-macro = true
[features] [features]
macros = ["proc-macro-error2", "itertools", "regex", "once_cell"] macros = ["proc-macro-error2", "itertools"]
[dependencies] [dependencies]
quote = "1.0" quote = "1.0"
@@ -20,5 +21,3 @@ proc-macro2 = { version = "1.0", features = ["span-locations"] }
proc-macro-error2 = { version = "2.0.1", optional = true } proc-macro-error2 = { version = "2.0.1", optional = true }
syn = { version = "2.0", features = ["full"] } syn = { version = "2.0", features = ["full"] }
itertools = { version = "0.14", optional = true } itertools = { version = "0.14", optional = true }
regex = { version = "1.4", optional = true }
once_cell = { version = "1.0", optional = true }
-105
View File
@@ -1,105 +0,0 @@
use proc_macro::{TokenStream, TokenTree};
use crate::token::{Pos, Token, Tokens};
#[derive(Debug, Clone)]
pub(crate) struct Capture {
key: Token,
rust: TokenTree,
}
impl Capture {
fn new(key: Token, rust: TokenTree) -> Self {
Self { key, rust }
}
/// Token string inside `chunk!`
pub(crate) fn key(&self) -> &Token {
&self.key
}
/// As rust variable, e.g. `x`
pub(crate) fn as_rust(&self) -> &TokenTree {
&self.rust
}
}
#[derive(Debug)]
pub(crate) struct Captures(Vec<Capture>);
impl Captures {
pub(crate) fn new() -> Self {
Self(Vec::new())
}
pub(crate) fn add(&mut self, token: &Token) -> Capture {
let tt = token.tree();
let key = token.clone();
match self.0.iter().find(|arg| arg.key() == &key) {
Some(arg) => arg.clone(),
None => {
let arg = Capture::new(key, tt.clone());
self.0.push(arg.clone());
arg
}
}
}
pub(crate) fn captures(&self) -> &[Capture] {
&self.0
}
}
#[derive(Debug)]
pub(crate) struct Chunk {
source: String,
caps: Captures,
}
impl Chunk {
pub(crate) fn new(tokens: TokenStream) -> Self {
let tokens = Tokens::retokenize(tokens);
let mut source = String::new();
let mut caps = Captures::new();
let mut pos: Option<Pos> = None;
for t in tokens {
if t.is_cap() {
caps.add(&t);
}
let (line, col) = (t.start().line, t.start().column);
let (prev_line, prev_col) = pos
.take()
.map(|lc| (lc.line, lc.column))
.unwrap_or_else(|| (line, col));
#[allow(clippy::comparison_chain)]
if line > prev_line {
source.push('\n');
} else if line == prev_line {
for _ in 0..col.saturating_sub(prev_col) {
source.push(' ');
}
}
source.push_str(&t.to_string());
pos = Some(t.end());
}
Self {
source: source.trim_end().to_string(),
caps,
}
}
pub(crate) fn source(&self) -> &str {
&self.source
}
pub(crate) fn captures(&self) -> &[Capture] {
self.caps.captures()
}
}
+160
View File
@@ -0,0 +1,160 @@
use std::ops::Deref;
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::{ToTokens, quote};
use self::token::{Pos, Token, Tokens};
mod token;
#[derive(Debug, Clone)]
pub(crate) struct Capture(Token);
impl Deref for Capture {
type Target = Token;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Capture {
fn new(token: &Token) -> Self {
Self(token.clone())
}
pub(crate) fn name(&self) -> String {
self.0.to_string()
}
}
impl ToTokens for Capture {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let ts: TokenStream = self.0.tree().clone().into();
tokens.extend(TokenStream2::from(ts));
}
}
#[derive(Debug)]
pub(crate) struct Captures(Vec<Capture>);
impl Captures {
pub(crate) fn new() -> Self {
Self(Vec::new())
}
pub(crate) fn add(&mut self, token: &Token) {
if self.0.iter().any(|arg| &**arg == token) {
return;
}
self.0.push(Capture::new(token));
}
pub(crate) fn captures(&self) -> &[Capture] {
&self.0
}
}
#[derive(Debug)]
pub(crate) struct Chunk {
source: String,
caps: Captures,
}
impl Chunk {
pub(crate) fn new(tokens: TokenStream) -> Self {
let tokens = Tokens::retokenize(tokens);
let mut source = String::new();
let mut caps = Captures::new();
let mut prev_end: Option<Pos> = None;
for t in tokens {
if t.is_cap() {
caps.add(&t);
}
let (line, col) = (t.start().line, t.start().column);
if let Some(prev) = prev_end {
if line > prev.line {
source.push('\n');
source.push_str(&" ".repeat(col.saturating_sub(1)));
} else if line == prev.line {
source.push_str(&" ".repeat(col.saturating_sub(prev.column)));
}
} else {
source.push_str(&" ".repeat(col.saturating_sub(1)));
}
source.push_str(&t.to_string());
prev_end = Some(t.end());
}
Self {
source: source.trim_end().to_string(),
caps,
}
}
pub(crate) fn captures(&self) -> &[Capture] {
self.caps.captures()
}
pub(crate) fn expand(&self) -> TokenStream2 {
let source = &self.source;
let caps_len = self.captures().len();
let caps = self.captures().iter().map(|cap| {
let cap_name = cap.name();
quote! { env.raw_set(#cap_name, #cap)?; }
});
quote! {{
use mlua::{AsChunk, ChunkMode, Lua, Result, Table};
use ::std::borrow::Cow;
use ::std::cell::Cell;
use ::std::io::Result as IoResult;
struct InnerChunk<F: FnOnce(&Lua) -> Result<Table>>(Cell<Option<F>>);
impl<F> AsChunk for InnerChunk<F>
where
F: FnOnce(&Lua) -> Result<Table>,
{
fn environment(&self, lua: &Lua) -> Result<Option<Table>> {
if #caps_len > 0 {
if let Some(make_env) = self.0.take() {
return make_env(lua).map(Some);
}
}
Ok(None)
}
fn mode(&self) -> Option<ChunkMode> {
Some(ChunkMode::Text)
}
fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>> {
Ok(Cow::Borrowed((#source).as_bytes()))
}
}
let make_env = move |lua: &Lua| -> Result<Table> {
let globals = lua.globals();
let env = lua.create_table()?;
let meta = lua.create_table()?;
meta.raw_set("__index", &globals)?;
meta.raw_set("__newindex", &globals)?;
// Add captured variables
#(#caps)*
env.set_metatable(Some(meta))?;
Ok(env)
};
InnerChunk(Cell::new(Some(make_env)))
}}
}
}
@@ -3,10 +3,8 @@ use std::fmt::{self, Display, Formatter};
use std::vec::IntoIter; use std::vec::IntoIter;
use itertools::Itertools; use itertools::Itertools;
use once_cell::sync::Lazy;
use proc_macro::{Delimiter, Span, TokenStream, TokenTree}; use proc_macro::{Delimiter, Span, TokenStream, TokenTree};
use proc_macro2::Span as Span2; use proc_macro2::Span as Span2;
use regex::Regex;
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub(crate) struct Pos { pub(crate) struct Pos {
@@ -39,46 +37,16 @@ fn span_pos(span: &Span) -> (Pos, Pos) {
let start = span2.start(); let start = span2.start();
let end = span2.end(); let end = span2.end();
// In stable, line/column information is not provided // Rust 1.88 stabilized Span APIs, so this branch must be unreachable
// and set to 0 (line is 1-indexed)
if start.line == 0 || end.line == 0 { if start.line == 0 || end.line == 0 {
return fallback_span_pos(span); proc_macro_error2::abort_call_site!(
"cannot retrieve span location information; mlua requires nightly Rust or stable >= 1.88"
);
} }
(Pos::new(start.line, start.column), Pos::new(end.line, end.column)) (Pos::new(start.line, start.column), Pos::new(end.line, end.column))
} }
fn parse_pos(span: &Span) -> Option<(usize, usize)> {
// Workaround to somehow retrieve location information in span in stable rust :(
static RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"bytes\(([0-9]+)\.\.([0-9]+)\)").unwrap());
match RE.captures(&format!("{span:?}")) {
Some(caps) => match (caps.get(1), caps.get(2)) {
(Some(start), Some(end)) => Some((
match start.as_str().parse() {
Ok(v) => v,
_ => return None,
},
match end.as_str().parse() {
Ok(v) => v,
_ => return None,
},
)),
_ => None,
},
None => None,
}
}
fn fallback_span_pos(span: &Span) -> (Pos, Pos) {
let (start, end) = match parse_pos(span) {
Some(v) => v,
None => proc_macro_error2::abort_call_site!("Cannot retrieve span information; please use nightly"),
};
(Pos::new(1, start), Pos::new(1, end))
}
/// Attribute of token. /// Attribute of token.
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum TokenAttr { enum TokenAttr {
@@ -108,8 +76,9 @@ impl Eq for Token {}
impl Token { impl Token {
fn new(tree: TokenTree) -> Self { fn new(tree: TokenTree) -> Self {
let (start, end) = span_pos(&tree.span()); let (start, end) = span_pos(&tree.span());
let source = tree.span().source_text().unwrap_or_else(|| tree.to_string());
Self { Self {
source: tree.to_string(), source,
start, start,
end, end,
tree, tree,
@@ -168,14 +137,17 @@ impl Tokens {
Tokens( Tokens(
tt.into_iter() tt.into_iter()
.flat_map(Tokens::from) .flat_map(Tokens::from)
.peekable()
.batching(|iter| { .batching(|iter| {
// Find variable tokens // Find variable tokens: `$` + `ident` => `$ident`
let t = iter.next()?; let t = iter.next()?;
if t.is("$") { if t.is("$") {
// `$` + `ident` => `$ident` if let Some(next) = iter.next()
let t = iter.next().expect("$ must trail an identifier"); && matches!(next.tree, TokenTree::Ident(_))
Some(t.attr(TokenAttr::Cap)) {
Some(next.attr(TokenAttr::Cap))
} else {
proc_macro_error2::abort!(t.tree.span(), "`$` must be followed by an identifier");
}
} else { } else {
Some(t) Some(t)
} }
+13 -13
View File
@@ -1,6 +1,6 @@
use proc_macro::TokenStream; use proc_macro::TokenStream;
use quote::quote; use quote::quote;
use syn::{parse_macro_input, DeriveInput}; use syn::{DeriveInput, parse_macro_input};
pub fn from_lua(input: TokenStream) -> TokenStream { pub fn from_lua(input: TokenStream) -> TokenStream {
let DeriveInput { ident, generics, .. } = parse_macro_input!(input as DeriveInput); let DeriveInput { ident, generics, .. } = parse_macro_input!(input as DeriveInput);
@@ -13,19 +13,19 @@ pub fn from_lua(input: TokenStream) -> TokenStream {
}; };
quote! { quote! {
impl #impl_generics ::mlua::FromLua for #ident #ty_generics #where_clause { impl #impl_generics ::mlua::FromLua for #ident #ty_generics #where_clause {
#[inline] #[inline]
fn from_lua(value: ::mlua::Value, _: &::mlua::Lua) -> ::mlua::Result<Self> { fn from_lua(value: ::mlua::Value, _: &::mlua::Lua) -> ::mlua::Result<Self> {
match value { match value {
::mlua::Value::UserData(ud) => Ok(ud.borrow::<Self>()?.clone()), ::mlua::Value::UserData(ud) => Ok(ud.borrow::<Self>()?.clone()),
_ => Err(::mlua::Error::FromLuaConversionError { _ => Err(::mlua::Error::FromLuaConversionError {
from: value.type_name(), from: value.type_name(),
to: #ident_str.to_string(), to: #ident_str.to_string(),
message: None, message: None,
}), }),
} }
}
} }
}
} }
.into() .into()
} }
+27 -131
View File
@@ -1,148 +1,30 @@
use proc_macro::TokenStream; use proc_macro::TokenStream;
use proc_macro2::{Ident, Span};
use quote::quote; mod module;
use syn::meta::ParseNestedMeta;
use syn::{parse_macro_input, ItemFn, LitStr, Result};
#[cfg(feature = "macros")] #[cfg(feature = "macros")]
use { use {crate::chunk::Chunk, proc_macro_error2::proc_macro_error};
crate::chunk::Chunk, proc_macro::TokenTree, proc_macro2::TokenStream as TokenStream2,
proc_macro_error2::proc_macro_error,
};
#[derive(Default)] #[cfg(feature = "macros")]
struct ModuleAttributes { macro_rules! try_compile {
name: Option<Ident>, ($expr:expr) => {
skip_memory_check: bool, match $expr {
} Ok(val) => val,
Err(err) => return err.to_compile_error().into(),
impl ModuleAttributes {
fn parse(&mut self, meta: ParseNestedMeta) -> Result<()> {
if meta.path.is_ident("name") {
match meta.value() {
Ok(value) => {
self.name = Some(value.parse::<LitStr>()?.parse()?);
}
Err(_) => {
return Err(meta.error("`name` attribute must have a value"));
}
}
} else if meta.path.is_ident("skip_memory_check") {
if meta.value().is_ok() {
return Err(meta.error("`skip_memory_check` attribute have no values"));
}
self.skip_memory_check = true;
} else {
return Err(meta.error("unsupported module attribute"));
} }
Ok(()) };
}
} }
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn lua_module(attr: TokenStream, item: TokenStream) -> TokenStream { pub fn lua_module(attr: TokenStream, item: TokenStream) -> TokenStream {
let mut args = ModuleAttributes::default(); module::lua_module(attr, item)
if !attr.is_empty() {
let args_parser = syn::meta::parser(|meta| args.parse(meta));
parse_macro_input!(attr with args_parser);
}
let func = parse_macro_input!(item as ItemFn);
let func_name = &func.sig.ident;
let module_name = args.name.unwrap_or_else(|| func_name.clone());
let ext_entrypoint_name = Ident::new(&format!("luaopen_{module_name}"), Span::call_site());
let skip_memory_check = if args.skip_memory_check {
quote! { lua.skip_memory_check(true); }
} else {
quote! {}
};
let wrapped = quote! {
mlua::require_module_feature!();
#func
#[no_mangle]
unsafe extern "C-unwind" fn #ext_entrypoint_name(state: *mut mlua::lua_State) -> ::std::os::raw::c_int {
mlua::Lua::entrypoint1(state, move |lua| {
#skip_memory_check
#func_name(lua)
})
}
};
wrapped.into()
}
#[cfg(feature = "macros")]
fn to_ident(tt: &TokenTree) -> TokenStream2 {
let s: TokenStream = tt.clone().into();
s.into()
} }
#[cfg(feature = "macros")] #[cfg(feature = "macros")]
#[proc_macro] #[proc_macro]
#[proc_macro_error] #[proc_macro_error]
pub fn chunk(input: TokenStream) -> TokenStream { pub fn chunk(input: TokenStream) -> TokenStream {
let chunk = Chunk::new(input); Chunk::new(input).expand().into()
let source = chunk.source();
let caps_len = chunk.captures().len();
let caps = chunk.captures().iter().map(|cap| {
let cap_name = cap.as_rust().to_string();
let cap = to_ident(cap.as_rust());
quote! { env.raw_set(#cap_name, #cap)?; }
});
let wrapped_code = quote! {{
use mlua::{AsChunk, ChunkMode, Lua, Result, Table};
use ::std::borrow::Cow;
use ::std::cell::Cell;
use ::std::io::Result as IoResult;
struct InnerChunk<F: FnOnce(&Lua) -> Result<Table>>(Cell<Option<F>>);
impl<F> AsChunk for InnerChunk<F>
where
F: FnOnce(&Lua) -> Result<Table>,
{
fn environment(&self, lua: &Lua) -> Result<Option<Table>> {
if #caps_len > 0 {
if let Some(make_env) = self.0.take() {
return make_env(lua).map(Some);
}
}
Ok(None)
}
fn mode(&self) -> Option<ChunkMode> {
Some(ChunkMode::Text)
}
fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>> {
Ok(Cow::Borrowed((#source).as_bytes()))
}
}
let make_env = move |lua: &Lua| -> Result<Table> {
let globals = lua.globals();
let env = lua.create_table()?;
let meta = lua.create_table()?;
meta.raw_set("__index", &globals)?;
meta.raw_set("__newindex", &globals)?;
// Add captured variables
#(#caps)*
env.set_metatable(Some(meta))?;
Ok(env)
};
InnerChunk(Cell::new(Some(make_env)))
}};
wrapped_code.into()
} }
#[cfg(feature = "macros")] #[cfg(feature = "macros")]
@@ -151,9 +33,23 @@ pub fn from_lua(input: TokenStream) -> TokenStream {
from_lua::from_lua(input) from_lua::from_lua(input)
} }
/// Derive macro for implementing `UserData` for a Rust type.
#[cfg(feature = "macros")]
#[proc_macro_derive(UserData, attributes(lua))]
pub fn userdata(item: TokenStream) -> TokenStream {
userdata::userdata_type(item)
}
/// Attribute macro for exposing impl block methods to Lua userdata.
#[cfg(feature = "macros")]
#[proc_macro_attribute]
pub fn userdata_impl(attr: TokenStream, item: TokenStream) -> TokenStream {
userdata::userdata_impl::userdata_impl(attr, item)
}
#[cfg(feature = "macros")] #[cfg(feature = "macros")]
mod chunk; mod chunk;
#[cfg(feature = "macros")] #[cfg(feature = "macros")]
mod from_lua; mod from_lua;
#[cfg(feature = "macros")] #[cfg(feature = "macros")]
mod token; mod userdata;
+68
View File
@@ -0,0 +1,68 @@
use proc_macro::TokenStream;
use proc_macro2::{Ident, Span};
use quote::quote;
use syn::meta::ParseNestedMeta;
use syn::{ItemFn, LitStr, Result, parse_macro_input};
#[derive(Default)]
struct ModuleAttributes {
name: Option<Ident>,
skip_memory_check: bool,
}
impl ModuleAttributes {
fn parse(&mut self, meta: ParseNestedMeta) -> Result<()> {
if meta.path.is_ident("name") {
match meta.value() {
Ok(value) => {
self.name = Some(value.parse::<LitStr>()?.parse()?);
}
Err(_) => {
return Err(meta.error("`name` attribute must have a value"));
}
}
} else if meta.path.is_ident("skip_memory_check") {
if meta.value().is_ok() {
return Err(meta.error("`skip_memory_check` attribute have no values"));
}
self.skip_memory_check = true;
} else {
return Err(meta.error("unsupported module attribute"));
}
Ok(())
}
}
pub fn lua_module(attr: TokenStream, item: TokenStream) -> TokenStream {
let mut args = ModuleAttributes::default();
if !attr.is_empty() {
let args_parser = syn::meta::parser(|meta| args.parse(meta));
parse_macro_input!(attr with args_parser);
}
let func = parse_macro_input!(item as ItemFn);
let func_name = &func.sig.ident;
let module_name = args.name.unwrap_or_else(|| func_name.clone());
let ext_entrypoint_name = Ident::new(&format!("luaopen_{module_name}"), Span::call_site());
let skip_memory_check = if args.skip_memory_check {
quote! { lua.skip_memory_check(true); }
} else {
quote! {}
};
let wrapped = quote! {
mlua::require_module_feature!();
#func
#[unsafe(no_mangle)]
unsafe extern "C-unwind" fn #ext_entrypoint_name(state: *mut mlua::lua_State) -> ::std::os::raw::c_int {
mlua::Lua::entrypoint1(state, move |lua| {
#skip_memory_check
#func_name(lua)
})
}
};
wrapped.into()
}
+93
View File
@@ -0,0 +1,93 @@
use proc_macro2::Span;
use syn::meta::ParseNestedMeta;
use syn::{Ident, LitStr, Result};
/// Parsed `#[lua(...)]` attribute.
///
/// Some flags are context-dependent:
/// - Struct fields: `get`, `set`, `name`, `skip`
/// - Impl methods: `getter`, `setter`, `field`, `meta`, `infallible`, `name`, `skip`
#[derive(Default)]
pub(crate) struct LuaAttr {
pub(crate) span: Option<Span>,
pub(crate) name: Option<String>,
pub(crate) infallible: bool,
pub(crate) skip: bool,
// Struct field context flags
pub(crate) get: bool,
pub(crate) set: bool,
// Impl method context flags
pub(crate) getter: bool,
pub(crate) setter: bool,
pub(crate) field: bool,
pub(crate) meta: bool,
}
impl LuaAttr {
pub(crate) fn parse_inner(&mut self, meta: ParseNestedMeta) -> Result<()> {
match &meta.path {
path if path.is_ident("skip") => {
if meta.value().is_ok() {
return Err(meta.error("`skip` does not take a value"));
}
self.skip = true;
}
path if path.is_ident("infallible") => {
if meta.value().is_ok() {
return Err(meta.error("`infallible` does not take a value"));
}
self.infallible = true;
}
path if path.is_ident("get") => self.get = true,
path if path.is_ident("set") => self.set = true,
path if path.is_ident("getter") => self.getter = true,
path if path.is_ident("setter") => self.setter = true,
path if path.is_ident("field") => self.field = true,
path if path.is_ident("meta") => self.meta = true,
path if path.is_ident("name") => {
let value = meta.value()?;
let lit: LitStr = value.parse()?;
self.name = Some(lit.value());
}
_ => {
return Err(meta.error(
"unsupported lua attribute, expected: ".to_string()
+ "`skip`, `infallible`, `get`, `set`, `getter`, `setter`, `field`, `meta`, `name`",
));
}
}
Ok(())
}
/// Returns the effective Lua name.
pub(crate) fn name(&self, ident: &Ident) -> String {
self.name.clone().unwrap_or_else(|| ident.to_string())
}
/// Returns the span to use for error reporting.
pub(crate) fn span(&self) -> Span {
self.span.unwrap_or_else(Span::call_site)
}
/// Returns the effective Lua metamethod name.
///
/// If `name` is set via attribute, use it. Otherwise, if the function name
/// starts with `__`, use that. Returns an error if neither is available.
pub(crate) fn effective_meta_name(&self, fn_ident: &Ident) -> Result<String> {
if let Some(ref name) = self.name {
return Ok(name.clone());
}
let fn_name = fn_ident.to_string();
if fn_name.starts_with("__") {
return Ok(fn_name);
}
Err(syn::Error::new(
fn_ident.span(),
format!(
"could not infer metamethod name from `{fn_name}`, either add `name = \"...\"` to `#[lua(meta, ...)]` or prefix the function with `__`"
),
))
}
}
+165
View File
@@ -0,0 +1,165 @@
mod attr;
pub(crate) mod userdata_impl;
use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::spanned::Spanned;
use syn::{Attribute, Data, DeriveInput, Error, Fields, FieldsNamed, Meta, parse_macro_input};
use self::attr::LuaAttr;
/// Wrap registration tokens with any `#[cfg]`/`#[cfg_attr]` attributes from the original item.
pub(crate) fn with_cfg(tokens: proc_macro2::TokenStream, attrs: &[Attribute]) -> proc_macro2::TokenStream {
let cfgs: Vec<_> = (attrs.iter())
.filter(|attr| attr.path().is_ident("cfg") || attr.path().is_ident("cfg_attr"))
.collect();
if cfgs.is_empty() {
return tokens;
}
quote! {
#(#cfgs)*
#tokens
}
}
/// Parse all `#[lua(...)]` attributes on a field, merging them into one `LuaAttr`.
fn parse_field_lua_attr(attrs: &[Attribute]) -> syn::Result<LuaAttr> {
let mut lua_attr = LuaAttr::default();
for attr in attrs {
if !attr.path().is_ident("lua") {
continue;
}
match &attr.meta {
Meta::List(_) => {
lua_attr.span = Some(attr.span());
attr.parse_nested_meta(|meta| lua_attr.parse_inner(meta))?;
validate_field_lua_attr(&lua_attr)?;
}
Meta::Path(_) => {}
Meta::NameValue(_) => {
return Err(syn::Error::new_spanned(
attr,
"`#[lua = \"...\"]` is not supported: use `#[lua(attr = \"...\")]`",
));
}
}
}
Ok(lua_attr)
}
fn validate_field_lua_attr(attr: &LuaAttr) -> syn::Result<()> {
for (set, name) in [
(attr.getter, "getter"),
(attr.setter, "setter"),
(attr.field, "field"),
(attr.meta, "meta"),
(attr.infallible, "infallible"),
] {
if set {
return Err(syn::Error::new(
attr.span(),
format!("`{name}` is not valid for struct fields"),
));
}
}
Ok(())
}
pub fn userdata_type(item: TokenStream) -> TokenStream {
let input = parse_macro_input!(item as DeriveInput);
let type_name = &input.ident;
let named_fields: Option<&FieldsNamed> = match &input.data {
Data::Struct(data) => match &data.fields {
Fields::Named(fields) => Some(fields),
Fields::Unnamed(_) | Fields::Unit => None,
},
Data::Enum(_) => None,
Data::Union(_) => {
return Error::new_spanned(&input, "`#[derive(UserData)]` cannot be applied to unions")
.to_compile_error()
.into();
}
};
// Check for generic parameters (not supported)
let has_generics = !input.generics.params.is_empty();
if has_generics {
return Error::new_spanned(
&input.generics,
"`#[derive(UserData)]` does not support generic type parameters. Wrap the generic type in a concrete newtype instead."
)
.to_compile_error()
.into();
}
let mut field_registrations = Vec::new();
if let Some(fields) = &named_fields {
for field in &fields.named {
let field_name = field.ident.as_ref().unwrap();
let lua_attr = try_compile!(parse_field_lua_attr(&field.attrs));
if lua_attr.skip {
continue;
}
let lua_name = lua_attr.name.unwrap_or_else(|| field_name.to_string());
// Assume get/set by default (unless explicitly specified)
let (has_get, has_set) = if lua_attr.get || lua_attr.set {
(lua_attr.get, lua_attr.set)
} else {
(true, true)
};
if has_get {
let tokens = quote! {
registry.add_field_method_get(#lua_name, |_lua, this| Ok(this.#field_name.clone()));
};
field_registrations.push(with_cfg(tokens, &field.attrs));
}
if has_set {
let tokens = quote! {
registry.add_field_method_set(#lua_name, |_lua, this, val| {
this.#field_name = val;
Ok(())
});
};
field_registrations.push(with_cfg(tokens, &field.attrs));
}
}
}
let registration_type_name = format_ident!("__MluaUserDataRegistration_{type_name}");
let register_fields_fn_name = format_ident!("__mlua_register_{type_name}_fields");
let output = quote! {
#[doc(hidden)]
#[allow(non_camel_case_types)]
struct #registration_type_name {
register: fn(&mut ::mlua::userdata::UserDataRegistry<#type_name>),
}
::mlua::__inventory::collect!(#registration_type_name);
#[allow(non_snake_case)]
fn #register_fields_fn_name(registry: &mut ::mlua::userdata::UserDataRegistry<#type_name>) {
use ::mlua::userdata::UserDataFields as _;
#(#field_registrations)*
}
::mlua::__inventory::submit! {
#registration_type_name { register: #register_fields_fn_name }
}
impl ::mlua::userdata::UserData for #type_name {
fn register(registry: &mut ::mlua::userdata::UserDataRegistry<Self>) {
for item in ::mlua::__inventory::iter::<#registration_type_name> {
(item.register)(registry);
}
}
}
};
output.into()
}
+730
View File
@@ -0,0 +1,730 @@
use std::sync::atomic::{AtomicUsize, Ordering};
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
use syn::spanned::Spanned;
use syn::{
Attribute, FnArg, Ident, ImplItem, ItemImpl, Meta, Signature, Type, parse_macro_input, parse_quote,
};
use super::attr::LuaAttr;
use super::with_cfg;
/// `&T` reference types that mlua provides as wrapper types via `FromLua`.
static BORROW_WRAPPERS: &[(&str, &str)] = &[
("str", "::mlua::string::BorrowedStr"),
("[u8]", "::mlua::string::BorrowedBytes"),
];
enum SelfKind {
Ref(RefKind),
Owned,
None,
}
enum RefKind {
Ref,
Mut,
}
struct ArgInfo {
ident: Ident,
userdata_ref: Option<RefKind>,
callback_type: Type,
}
struct MethodInfo {
self_kind: SelfKind,
has_lua: bool,
args: Vec<ArgInfo>,
}
/// Extract the inner type from a reference type.
fn ref_inner_type(ty: &Type) -> Type {
match ty {
Type::Reference(ref_ty) => (*ref_ty.elem).clone(),
_ => ty.clone(),
}
}
/// Check if the type is `&Lua` or `&mlua::Lua`.
fn is_lua_ref(ty: &Type) -> bool {
let Type::Reference(ref_ty) = ty else { return false };
match &*ref_ty.elem {
Type::Path(p) if p.path.segments.len() == 1 => p.path.segments[0].ident == "Lua",
Type::Path(p) if p.path.segments.len() == 2 => {
p.path.segments[0].ident == "mlua" && p.path.segments[1].ident == "Lua"
}
_ => false,
}
}
/// Classify a `&[mut] T` parameter, returning the callback wrapper type.
///
/// Known borrow types come from the mapping table `BORROW_WRAPPERS`.
/// Everything else gets `UserDataRef[Mut]<T>`.
fn classify_ref_type(ty: &Type) -> Option<Type> {
let Type::Reference(ref_ty) = ty else { return None };
// Check known borrow wrappers:
// - For `&T` check the path name
// - For `&[T]` unpack the slice and format the element as `[T]` for lookup
if ref_ty.mutability.is_none() {
let lookup_name: Option<String> = match &*ref_ty.elem {
Type::Path(path) => path.path.segments.last().map(|seg| seg.ident.to_string()),
Type::Slice(slice) => {
if let Type::Path(path) = &*slice.elem {
path.path.segments.last().map(|seg| format!("[{}]", seg.ident))
} else {
None
}
}
_ => None,
};
if let Some(ref name) = lookup_name {
for &(inner, wrapper) in BORROW_WRAPPERS {
if name == inner {
let wrapper = syn::parse_str(wrapper).expect("invalid wrapper type");
return Some(wrapper);
}
}
}
}
// Mutable references to slices are not supported.
if matches!(&*ref_ty.elem, Type::Slice(_)) && ref_ty.mutability.is_some() {
return None;
}
let inner = ref_inner_type(ty);
if ref_ty.mutability.is_none() {
Some(parse_quote! { ::mlua::userdata::UserDataRef<#inner> })
} else {
Some(parse_quote! { ::mlua::userdata::UserDataRefMut<#inner> })
}
}
/// Analyze method signature.
///
/// Determine `self` kind and collect the callback arguments.
/// Auto-detects `&Lua` as the first non-self parameter.
fn analyze_self_and_args(sig: &Signature) -> syn::Result<MethodInfo> {
let mut self_kind = SelfKind::None;
let mut has_lua = false;
let mut args = Vec::new();
let mut check_first_typed = true;
for param in &sig.inputs {
match param {
FnArg::Receiver(recv) if recv.reference.is_some() && recv.mutability.is_some() => {
self_kind = SelfKind::Ref(RefKind::Mut);
}
FnArg::Receiver(recv) if recv.reference.is_some() => {
self_kind = SelfKind::Ref(RefKind::Ref);
}
FnArg::Receiver(_) => {
self_kind = SelfKind::Owned;
}
FnArg::Typed(typed) => {
if check_first_typed && is_lua_ref(&typed.ty) {
has_lua = true;
check_first_typed = false;
continue;
}
check_first_typed = false;
if let syn::Pat::Ident(pat_ident) = &*typed.pat {
let arg_type = &*typed.ty;
let ref_kind = match arg_type {
Type::Reference(r) if r.mutability.is_some() => Some(RefKind::Mut),
Type::Reference(_) => Some(RefKind::Ref),
_ => None,
};
let callback_type = match &ref_kind {
Some(_) => match classify_ref_type(arg_type) {
Some(ty) => ty,
None => {
return Err(syn::Error::new_spanned(
arg_type,
"this reference type is not supported as a callback parameter",
));
}
},
None => arg_type.clone(),
};
args.push(ArgInfo {
ident: pat_ident.ident.clone(),
userdata_ref: ref_kind,
callback_type,
});
}
}
}
}
Ok(MethodInfo {
self_kind,
has_lua,
args,
})
}
fn strip_item_attrs(attrs: &[Attribute]) -> Vec<Attribute> {
(attrs.iter())
.filter(|attr| !attr.path().is_ident("lua"))
.cloned()
.collect()
}
fn parse_lua_attr(attrs: &[Attribute]) -> syn::Result<LuaAttr> {
let mut lua_attr = LuaAttr::default();
for attr in attrs {
if !attr.path().is_ident("lua") {
continue;
}
match &attr.meta {
Meta::List(_) => {
lua_attr.span = Some(attr.span());
attr.parse_nested_meta(|meta| lua_attr.parse_inner(meta))?;
validate_lua_attr(&lua_attr)?;
}
Meta::Path(_) => {}
Meta::NameValue(_) => {
return Err(syn::Error::new_spanned(
attr,
"`#[lua = \"...\"]` is not supported: use `#[lua(attr = \"...\")]`",
));
}
}
}
Ok(lua_attr)
}
fn validate_lua_attr(attr: &LuaAttr) -> syn::Result<()> {
for (set, name) in [(attr.get, "get"), (attr.set, "set")] {
if set {
return Err(syn::Error::new(
attr.span(),
format!("`{name}` is not valid for methods"),
));
}
}
Ok(())
}
pub fn userdata_impl(attr: TokenStream, item: TokenStream) -> TokenStream {
if !attr.is_empty() {
return syn::Error::new_spanned(
proc_macro2::TokenStream::from(attr),
"`#[userdata_impl]` does not accept arguments",
)
.to_compile_error()
.into();
}
let mut input = parse_macro_input!(item as ItemImpl);
let type_path = match &*input.self_ty {
Type::Path(type_path) => &type_path.path,
_ => {
return syn::Error::new_spanned(&input.self_ty, "`#[userdata_impl]` requires a simple path type")
.to_compile_error()
.into();
}
};
let type_name = (type_path.segments)
.last()
.map(|seg| seg.ident.clone())
.ok_or_else(|| syn::Error::new_spanned(&input.self_ty, "cannot determine type name"));
let type_name = try_compile!(type_name);
static COUNTER: AtomicUsize = AtomicUsize::new(0);
let unique_suffix = COUNTER.fetch_add(1, Ordering::Relaxed);
let register_fn_name = format_ident!("__mlua_register_{type_name}_{unique_suffix}");
let registration_type_name = format_ident!("__MluaUserDataRegistration_{type_name}");
let mut registration_calls = Vec::new();
for item in &input.items {
match item {
ImplItem::Const(const_item) => {
let lua_attr = try_compile!(parse_lua_attr(&const_item.attrs));
if lua_attr.skip {
continue;
}
if lua_attr.getter || lua_attr.setter {
return syn::Error::new(
lua_attr.span(),
"const items do not support `getter` or `setter`",
)
.to_compile_error()
.into();
}
let const_name = &const_item.ident;
let lua_name = lua_attr.name(const_name);
if lua_attr.meta {
let tokens = quote! {
registry.add_meta_field(#lua_name, #type_path::#const_name);
};
registration_calls.push(with_cfg(tokens, &const_item.attrs));
} else {
let tokens = quote! {
registry.add_field(#lua_name, #type_path::#const_name);
};
registration_calls.push(with_cfg(tokens, &const_item.attrs));
}
}
ImplItem::Fn(method) => {
let lua_attr = try_compile!(parse_lua_attr(&method.attrs));
if lua_attr.skip {
continue;
}
// Validate mutually exclusive role flags.
// `getter`, `setter`, `field` are exclusive.
// `meta` on its own means a metamethod.
// `meta` combined with `field` means a meta static field.
// `meta` with `getter` or `setter` is invalid.
let primary = [lua_attr.getter, lua_attr.setter, lua_attr.field];
let primary_count = primary.iter().filter(|&&x| x).count();
if primary_count > 1 {
return syn::Error::new(
lua_attr.span(),
"at most one of `getter`, `setter`, `field` can be specified",
)
.to_compile_error()
.into();
}
if lua_attr.meta && primary_count == 1 && !lua_attr.field {
return syn::Error::new(lua_attr.span(), "`meta` can only be combined with `field`")
.to_compile_error()
.into();
}
let fn_name = &method.sig.ident;
let info = try_compile!(analyze_self_and_args(&method.sig));
let is_async = method.sig.asyncness.is_some();
if lua_attr.getter {
if is_async {
return syn::Error::new_spanned(&method.sig, "async field getter is not supported")
.to_compile_error()
.into();
}
if !matches!(info.self_kind, SelfKind::Ref(RefKind::Ref)) {
return syn::Error::new_spanned(&method.sig, "field getter must take `&self`")
.to_compile_error()
.into();
}
if !info.args.is_empty() {
return syn::Error::new_spanned(
&method.sig,
"field getter must not take additional arguments",
)
.to_compile_error()
.into();
}
let tokens = gen_field_getter(type_path, fn_name, &lua_attr, &info);
registration_calls.push(with_cfg(tokens, &method.attrs));
continue;
}
if lua_attr.setter {
if is_async {
return syn::Error::new_spanned(&method.sig, "async field setter is not supported")
.to_compile_error()
.into();
}
if !matches!(info.self_kind, SelfKind::Ref(_)) {
return syn::Error::new_spanned(&method.sig, "field setter must take `&[mut] self`")
.to_compile_error()
.into();
}
if info.args.len() != 1 {
return syn::Error::new_spanned(
&method.sig,
"field setter must take exactly one value argument",
)
.to_compile_error()
.into();
}
let tokens = gen_field_setter(type_path, fn_name, &lua_attr, &info);
registration_calls.push(with_cfg(tokens, &method.attrs));
continue;
}
if lua_attr.field {
if is_async {
return syn::Error::new_spanned(&method.sig, "async field function is not supported")
.to_compile_error()
.into();
}
if !matches!(info.self_kind, SelfKind::None) {
return syn::Error::new_spanned(&method.sig, "field function must not take `self`")
.to_compile_error()
.into();
}
if !info.args.is_empty() {
return syn::Error::new_spanned(
&method.sig,
"field function must not take arguments",
)
.to_compile_error()
.into();
}
let lua_name = lua_attr.name(fn_name);
if lua_attr.meta {
let tokens = quote! {
registry.add_meta_field(#lua_name, #type_path::#fn_name());
};
registration_calls.push(with_cfg(tokens, &method.attrs));
} else {
let tokens = quote! {
registry.add_field(#lua_name, #type_path::#fn_name());
};
registration_calls.push(with_cfg(tokens, &method.attrs));
}
continue;
}
if lua_attr.meta {
if matches!(info.self_kind, SelfKind::Owned) {
return syn::Error::new_spanned(
&method.sig,
"meta methods cannot take `self`, use `&[mut] self` instead",
)
.to_compile_error()
.into();
}
if is_async {
let tokens = gen_async_meta(type_path, fn_name, &lua_attr, &info);
registration_calls.push(with_cfg(tokens, &method.attrs));
} else {
let tokens = gen_meta(type_path, fn_name, &lua_attr, &info);
registration_calls.push(with_cfg(tokens, &method.attrs));
}
continue;
}
if is_async {
let tokens = gen_async_regular_method(type_path, fn_name, &lua_attr, &info);
registration_calls.push(with_cfg(tokens, &method.attrs));
} else {
let tokens = gen_regular_method(type_path, fn_name, &lua_attr, &info);
registration_calls.push(with_cfg(tokens, &method.attrs));
}
}
_ => {}
}
}
for item in &mut input.items {
match item {
ImplItem::Const(c) => c.attrs = strip_item_attrs(&c.attrs),
ImplItem::Fn(m) => m.attrs = strip_item_attrs(&m.attrs),
_ => {}
}
}
input.attrs = strip_item_attrs(&input.attrs);
let output = quote! {
#[allow(non_snake_case)]
fn #register_fn_name(registry: &mut ::mlua::userdata::UserDataRegistry<#type_path>) {
use ::mlua::userdata::{UserDataFields as _, UserDataMethods as _};
#(#registration_calls)*
}
::mlua::__inventory::submit! {
#registration_type_name { register: #register_fn_name }
}
#input
};
output.into()
}
/// Generate the closure argument destructuring pattern.
fn gen_closure_destructure(info: &MethodInfo) -> TokenStream2 {
if info.args.is_empty() {
return quote! { () };
}
let idents: Vec<_> = (info.args)
.iter()
.map(|a| {
let ident = &a.ident;
if matches!(a.userdata_ref, Some(RefKind::Mut)) {
quote! { mut #ident }
} else {
quote! { #ident }
}
})
.collect();
let types: Vec<_> = info.args.iter().map(|a| &a.callback_type).collect();
quote! { (#(#idents),*): (#(#types),*) }
}
/// Generate call arguments for invoking the original method.
fn gen_call_args(info: &MethodInfo) -> TokenStream2 {
let mut call_args: Vec<TokenStream2> = Vec::new();
match info.self_kind {
SelfKind::None => {}
_ => call_args.push(quote! { this }),
}
if info.has_lua {
call_args.push(quote! { lua });
}
for arg in &info.args {
let ident = &arg.ident;
match arg.userdata_ref {
Some(RefKind::Ref) => call_args.push(quote! { &*#ident }),
Some(RefKind::Mut) => call_args.push(quote! { &mut *#ident }),
None => call_args.push(quote! { #ident }),
}
}
quote! { #(#call_args),* }
}
/// Generate call arguments for invoking the original async method.
fn gen_async_call_args(info: &MethodInfo) -> TokenStream2 {
let mut call_args: Vec<TokenStream2> = Vec::new();
match info.self_kind {
SelfKind::None => {}
SelfKind::Ref(RefKind::Ref) => call_args.push(quote! { &this }),
SelfKind::Ref(RefKind::Mut) => call_args.push(quote! { &mut this }),
SelfKind::Owned => call_args.push(quote! { this }),
}
if info.has_lua {
call_args.push(quote! { lua });
}
for arg in &info.args {
let ident = &arg.ident;
match arg.userdata_ref {
Some(RefKind::Ref) => call_args.push(quote! { &*#ident }),
Some(RefKind::Mut) => call_args.push(quote! { &mut *#ident }),
None => call_args.push(quote! { #ident }),
}
}
quote! { #(#call_args),* }
}
/// Generate the closure params for the registration callback.
fn gen_closure_params(info: &MethodInfo) -> TokenStream2 {
let destructure = gen_closure_destructure(info);
match info.self_kind {
SelfKind::None => quote! { |lua, #destructure| },
_ => quote! { |lua, this, #destructure| },
}
}
/// Generate the closure params for an async registration callback.
fn gen_async_closure_params(info: &MethodInfo) -> TokenStream2 {
let destructure = gen_closure_destructure(info);
match info.self_kind {
SelfKind::None => quote! { |lua, #destructure| },
SelfKind::Ref(RefKind::Mut) => quote! { |lua, mut this, #destructure| },
_ => quote! { |lua, this, #destructure| },
}
}
fn gen_field_getter(
type_path: &syn::Path,
fn_name: &Ident,
lua_attr: &LuaAttr,
info: &MethodInfo,
) -> TokenStream2 {
let lua_name = lua_attr.name(fn_name);
let call_args = gen_call_args(info);
if lua_attr.infallible {
return quote! {
registry.add_field_method_get(#lua_name, |lua, this| {
let _ = lua; // silence unused variable warning
Ok(#type_path::#fn_name(#call_args))
});
};
}
quote! {
registry.add_field_method_get(#lua_name, |lua, this| {
let _ = lua; // silence unused variable warning
#type_path::#fn_name(#call_args)
});
}
}
fn gen_field_setter(
type_path: &syn::Path,
fn_name: &Ident,
lua_attr: &LuaAttr,
info: &MethodInfo,
) -> TokenStream2 {
let lua_name = lua_attr.name(fn_name);
let call_args = gen_call_args(info);
if lua_attr.infallible {
let val_ident = info.args.first().map(|a| &a.ident);
return quote! {
registry.add_field_method_set(#lua_name, |lua, this, #val_ident| {
let _ = lua; // silence unused variable warning
Ok(#type_path::#fn_name(#call_args))
});
};
}
let val_ident = info.args.first().map(|a| &a.ident);
quote! {
registry.add_field_method_set(#lua_name, |lua, this, #val_ident| {
let _ = lua; // silence unused variable warning
#type_path::#fn_name(#call_args)
});
}
}
fn gen_meta(type_path: &syn::Path, fn_name: &Ident, lua_attr: &LuaAttr, info: &MethodInfo) -> TokenStream2 {
let meta_name = match lua_attr.effective_meta_name(fn_name) {
Ok(name) => name,
Err(err) => return err.to_compile_error(),
};
let closure_params = if matches!(info.self_kind, SelfKind::None) {
// Lua always passes `self` to the stack arg, just ignore it.
if info.args.is_empty() {
quote! { |lua, _this: ::mlua::AnyUserData| }
} else {
let idents: Vec<_> = info.args.iter().map(|a| &a.ident).collect();
let types: Vec<_> = info.args.iter().map(|a| &a.callback_type).collect();
quote! { |lua, (_this, #(#idents),*): (::mlua::AnyUserData, #(#types),*) | }
}
} else {
gen_closure_params(info)
};
let call_args = gen_call_args(info);
let fn_path = quote! { #type_path::#fn_name };
let body = if lua_attr.infallible {
quote! { Ok(#fn_path(#call_args)) }
} else {
quote! { #fn_path(#call_args) }
};
match info.self_kind {
SelfKind::None => quote! {
registry.add_meta_function(#meta_name, #closure_params { #body });
},
SelfKind::Ref(RefKind::Mut) => quote! {
registry.add_meta_method_mut(#meta_name, #closure_params { #body });
},
_ => quote! {
registry.add_meta_method(#meta_name, #closure_params { #body });
},
}
}
fn gen_regular_method(
type_path: &syn::Path,
fn_name: &Ident,
lua_attr: &LuaAttr,
info: &MethodInfo,
) -> TokenStream2 {
let fn_path = quote! { #type_path::#fn_name };
let closure_params = gen_closure_params(info);
let call_args = gen_call_args(info);
let lua_name = lua_attr.name(fn_name);
let body = if lua_attr.infallible {
quote! { Ok(#fn_path(#call_args)) }
} else {
quote! { #fn_path(#call_args) }
};
match info.self_kind {
SelfKind::Ref(RefKind::Ref) => quote! {
registry.add_method(#lua_name, #closure_params { #body });
},
SelfKind::Ref(RefKind::Mut) => quote! {
registry.add_method_mut(#lua_name, #closure_params { #body });
},
SelfKind::Owned => quote! {
registry.add_method_once(#lua_name, #closure_params { #body });
},
SelfKind::None => quote! {
registry.add_function(#lua_name, #closure_params { #body });
},
}
}
fn gen_async_regular_method(
type_path: &syn::Path,
fn_name: &Ident,
lua_attr: &LuaAttr,
info: &MethodInfo,
) -> TokenStream2 {
let fn_path = quote! { #type_path::#fn_name };
let closure_params = gen_async_closure_params(info);
let call_args = gen_async_call_args(info);
let lua_name = lua_attr.name(fn_name);
let body = if lua_attr.infallible {
quote! { async move { Ok(#fn_path(#call_args).await) } }
} else {
quote! { async move { #fn_path(#call_args).await } }
};
match info.self_kind {
SelfKind::Ref(RefKind::Ref) => quote! {
registry.add_async_method(#lua_name, #closure_params #body);
},
SelfKind::Ref(RefKind::Mut) => quote! {
registry.add_async_method_mut(#lua_name, #closure_params #body);
},
SelfKind::Owned => quote! {
registry.add_async_method_once(#lua_name, #closure_params #body);
},
SelfKind::None => quote! {
registry.add_async_function(#lua_name, #closure_params #body);
},
}
}
fn gen_async_meta(
type_path: &syn::Path,
fn_name: &Ident,
lua_attr: &LuaAttr,
info: &MethodInfo,
) -> TokenStream2 {
let meta_name = match lua_attr.effective_meta_name(fn_name) {
Ok(name) => name,
Err(err) => return err.to_compile_error(),
};
let closure_params = if matches!(info.self_kind, SelfKind::None) {
if info.args.is_empty() {
quote! { |lua, _this: ::mlua::AnyUserData| }
} else {
let idents: Vec<_> = info.args.iter().map(|a| &a.ident).collect();
let types: Vec<_> = info.args.iter().map(|a| &a.callback_type).collect();
quote! { |lua, (_this, #(#idents),*): (::mlua::AnyUserData, #(#types),*) | }
}
} else {
gen_async_closure_params(info)
};
let call_args = gen_async_call_args(info);
let fn_path = quote! { #type_path::#fn_name };
let body = if lua_attr.infallible {
quote! { async move { Ok(#fn_path(#call_args).await) } }
} else {
quote! { async move { #fn_path(#call_args).await } }
};
match info.self_kind {
SelfKind::None => quote! {
registry.add_async_meta_function(#meta_name, #closure_params #body);
},
SelfKind::Ref(RefKind::Mut) => quote! {
registry.add_async_meta_method_mut(#meta_name, #closure_params #body);
},
_ => quote! {
registry.add_async_meta_method(#meta_name, #closure_params #body);
},
}
}
+1 -1
View File
@@ -59,7 +59,7 @@ impl Buffer {
/// Returns an adaptor implementing [`io::Read`], [`io::Write`] and [`io::Seek`] over the /// Returns an adaptor implementing [`io::Read`], [`io::Write`] and [`io::Seek`] over the
/// buffer. /// 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 { pub fn cursor(self) -> impl io::Read + io::Write + io::Seek {
BufferCursor(self, 0) BufferCursor(self, 0)
} }
+8 -1
View File
@@ -1,3 +1,10 @@
//! 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::borrow::Cow;
use std::collections::HashMap; use std::collections::HashMap;
use std::ffi::CString; use std::ffi::CString;
@@ -153,6 +160,7 @@ pub enum ChunkMode {
/// Represents a constant value that can be used by Luau compiler. /// Represents a constant value that can be used by Luau compiler.
#[cfg(any(feature = "luau", doc))] #[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
#[non_exhaustive]
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum CompileConstant { pub enum CompileConstant {
Nil, Nil,
@@ -779,7 +787,6 @@ impl Chunk<'_> {
/// ///
/// The resulted `IntoLua` implementation will convert the chunk into a Lua function without /// The resulted `IntoLua` implementation will convert the chunk into a Lua function without
/// executing it. /// executing it.
#[doc(hidden)]
#[track_caller] #[track_caller]
pub fn wrap(chunk: impl AsChunk) -> impl IntoLua { pub fn wrap(chunk: impl AsChunk) -> impl IntoLua {
WrappedChunk { WrappedChunk {
+36 -35
View File
@@ -4,7 +4,7 @@ use std::ffi::{CStr, CString, OsStr, OsString};
use std::hash::{BuildHasher, Hash}; use std::hash::{BuildHasher, Hash};
use std::os::raw::c_int; use std::os::raw::c_int;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::{mem, slice, str}; use std::{slice, str};
use bstr::{BStr, BString, ByteVec}; use bstr::{BStr, BString, ByteVec};
use num_traits::cast; use num_traits::cast;
@@ -16,7 +16,7 @@ use crate::string::{BorrowedBytes, BorrowedStr, LuaString};
use crate::table::Table; use crate::table::Table;
use crate::thread::Thread; use crate::thread::Thread;
use crate::traits::{FromLua, IntoLua, ShortTypeName as _}; 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::userdata::{AnyUserData, UserData};
use crate::value::{Nil, Value}; use crate::value::{Nil, Value};
@@ -86,91 +86,79 @@ impl FromLua for LuaString {
} }
} }
impl IntoLua for BorrowedStr<'_> { impl IntoLua for BorrowedStr {
#[inline] #[inline]
fn into_lua(self, _: &Lua) -> Result<Value> { fn into_lua(self, _: &Lua) -> Result<Value> {
Ok(Value::String(self.borrow.into_owned())) Ok(Value::String(LuaString(self.vref)))
} }
#[inline] #[inline]
unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> {
lua.push_ref(&self.borrow.0); lua.push_ref(&self.vref);
Ok(()) Ok(())
} }
} }
impl IntoLua for &BorrowedStr<'_> { impl IntoLua for &BorrowedStr {
#[inline] #[inline]
fn into_lua(self, _: &Lua) -> Result<Value> { fn into_lua(self, _: &Lua) -> Result<Value> {
Ok(Value::String(self.borrow.clone().into_owned())) Ok(Value::String(LuaString(self.vref.clone())))
} }
#[inline] #[inline]
unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> {
lua.push_ref(&self.borrow.0); lua.push_ref(&self.vref);
Ok(()) Ok(())
} }
} }
impl FromLua for BorrowedStr<'_> { impl FromLua for BorrowedStr {
fn from_lua(value: Value, lua: &Lua) -> Result<Self> { fn from_lua(value: Value, lua: &Lua) -> Result<Self> {
let s = LuaString::from_lua(value, lua)?; let s = LuaString::from_lua(value, lua)?;
let BorrowedStr { buf, _lua, .. } = BorrowedStr::try_from(&s)?; BorrowedStr::try_from(&s)
let buf = unsafe { mem::transmute::<&str, &'static str>(buf) };
let borrow = Cow::Owned(s);
Ok(Self { buf, borrow, _lua })
} }
unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result<Self> { unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result<Self> {
let s = LuaString::from_stack(idx, lua)?; let s = LuaString::from_stack(idx, lua)?;
let BorrowedStr { buf, _lua, .. } = BorrowedStr::try_from(&s)?; BorrowedStr::try_from(&s)
let buf = unsafe { mem::transmute::<&str, &'static str>(buf) };
let borrow = Cow::Owned(s);
Ok(Self { buf, borrow, _lua })
} }
} }
impl IntoLua for BorrowedBytes<'_> { impl IntoLua for BorrowedBytes {
#[inline] #[inline]
fn into_lua(self, _: &Lua) -> Result<Value> { fn into_lua(self, _: &Lua) -> Result<Value> {
Ok(Value::String(self.borrow.into_owned())) Ok(Value::String(LuaString(self.vref)))
} }
#[inline] #[inline]
unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> {
lua.push_ref(&self.borrow.0); lua.push_ref(&self.vref);
Ok(()) Ok(())
} }
} }
impl IntoLua for &BorrowedBytes<'_> { impl IntoLua for &BorrowedBytes {
#[inline] #[inline]
fn into_lua(self, _: &Lua) -> Result<Value> { fn into_lua(self, _: &Lua) -> Result<Value> {
Ok(Value::String(self.borrow.clone().into_owned())) Ok(Value::String(LuaString(self.vref.clone())))
} }
#[inline] #[inline]
unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> {
lua.push_ref(&self.borrow.0); lua.push_ref(&self.vref);
Ok(()) Ok(())
} }
} }
impl FromLua for BorrowedBytes<'_> { impl FromLua for BorrowedBytes {
fn from_lua(value: Value, lua: &Lua) -> Result<Self> { fn from_lua(value: Value, lua: &Lua) -> Result<Self> {
let s = LuaString::from_lua(value, lua)?; let s = LuaString::from_lua(value, lua)?;
let BorrowedBytes { buf, _lua, .. } = BorrowedBytes::from(&s); Ok(BorrowedBytes::from(&s))
let buf = unsafe { mem::transmute::<&[u8], &'static [u8]>(buf) };
let borrow = Cow::Owned(s);
Ok(Self { buf, borrow, _lua })
} }
unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result<Self> { unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result<Self> {
let s = LuaString::from_stack(idx, lua)?; let s = LuaString::from_stack(idx, lua)?;
let BorrowedBytes { buf, _lua, .. } = BorrowedBytes::from(&s); Ok(BorrowedBytes::from(&s))
let buf = unsafe { mem::transmute::<&[u8], &'static [u8]>(buf) };
let borrow = Cow::Owned(s);
Ok(Self { buf, borrow, _lua })
} }
} }
@@ -294,7 +282,7 @@ impl FromLua for AnyUserData {
} }
} }
impl<T: UserData + MaybeSend + 'static> IntoLua for T { impl<T: UserData + MaybeSend + MaybeSync + 'static> IntoLua for T {
#[inline] #[inline]
fn into_lua(self, lua: &Lua) -> Result<Value> { fn into_lua(self, lua: &Lua) -> Result<Value> {
Ok(Value::UserData(lua.create_userdata(self)?)) Ok(Value::UserData(lua.create_userdata(self)?))
@@ -535,7 +523,10 @@ impl IntoLua for &str {
impl IntoLua for Cow<'_, str> { impl IntoLua for Cow<'_, str> {
#[inline] #[inline]
fn into_lua(self, lua: &Lua) -> Result<Value> { 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),
}
} }
} }
@@ -597,7 +588,10 @@ impl IntoLua for &CStr {
impl IntoLua for Cow<'_, CStr> { impl IntoLua for Cow<'_, CStr> {
#[inline] #[inline]
fn into_lua(self, lua: &Lua) -> Result<Value> { 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),
}
} }
} }
@@ -818,6 +812,13 @@ macro_rules! lua_convert_int {
}); });
} }
} }
#[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 // Fallback to default
Self::from_lua(lua.stack_value(idx, Some(type_id)), lua.lua()) Self::from_lua(lua.stack_value(idx, Some(type_id)), lua.lua())
} }
+2 -2
View File
@@ -1,8 +1,8 @@
//! Lua debugging interface. //! Lua debugging interface.
//! //!
//! This module provides access to the Lua debug interface, allowing inspection of the call stack, //! This module provides access to the Lua debug interface, allowing inspection of the call stack,
//! and function information. The main types are [`Debug`] for accessing debug information and //! and function information. The main types are [`struct@Debug`] for accessing debug information
//! [`HookTriggers`] for configuring debug hooks. //! and [`HookTriggers`] for configuring debug hooks.
use std::borrow::Cow; use std::borrow::Cow;
use std::os::raw::c_int; use std::os::raw::c_int;
+13 -6
View File
@@ -1,3 +1,8 @@
//! 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::error::Error as StdError;
use std::fmt; use std::fmt;
use std::io::Error as IoError; use std::io::Error as IoError;
@@ -285,7 +290,7 @@ impl fmt::Display for Error {
// Try to find local traceback within the full traceback // Try to find local traceback within the full traceback
if let Some(pos) = full_traceback.find(traceback) { if let Some(pos) = full_traceback.find(traceback) {
write!(fmt, "{}", &full_traceback[..pos])?; write!(fmt, "{}", &full_traceback[..pos])?;
writeln!(fmt, ">{}", &full_traceback[pos..].trim_end())?; writeln!(fmt, ">{}", full_traceback[pos..].trim_end())?;
} else { } else {
writeln!(fmt, "{}", full_traceback.trim_end())?; writeln!(fmt, "{}", full_traceback.trim_end())?;
} }
@@ -340,7 +345,11 @@ impl Error {
/// Wraps an external error object. /// Wraps an external error object.
#[inline] #[inline]
pub fn external<T: Into<Box<DynStdError>>>(err: T) -> Self { 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. /// Attempts to downcast the external error object to a concrete type by reference.
@@ -550,10 +559,8 @@ impl<'a> Iterator for Chain<'a> {
#[cfg(test)] #[cfg(test)]
mod assertions { mod assertions {
use super::*;
#[cfg(not(feature = "error-send"))] #[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")] #[cfg(feature = "send")]
static_assertions::assert_impl_all!(Error: Send, Sync); static_assertions::assert_impl_all!(super::Error: Send, Sync);
} }
+107 -19
View File
@@ -3,12 +3,6 @@
//! This module provides types for working with Lua functions from Rust, including //! This module provides types for working with Lua functions from Rust, including
//! both Lua-defined functions and native Rust callbacks. //! both Lua-defined functions and native Rust callbacks.
//! //!
//! # Main Types
//!
//! - [`Function`] - A handle to a Lua function that can be called from Rust.
//! - [`FunctionInfo`] - Debug information about a function (name, source, line numbers, etc.).
//! - [`CoverageInfo`] - Code coverage data for Luau functions (requires `luau` feature).
//!
//! # Calling Functions //! # Calling Functions
//! //!
//! Use [`Function::call`] to invoke a Lua function synchronously: //! Use [`Function::call`] to invoke a Lua function synchronously:
@@ -81,12 +75,13 @@
use std::cell::RefCell; use std::cell::RefCell;
use std::os::raw::{c_int, c_void}; use std::os::raw::{c_int, c_void};
use std::result::Result as StdResult;
use std::{mem, ptr, slice}; use std::{mem, ptr, slice};
use crate::error::{Error, Result}; use crate::error::{Error, ExternalError, ExternalResult, Result};
use crate::state::Lua; use crate::state::Lua;
use crate::table::Table; 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::types::{Callback, LuaType, MaybeSend, ValueRef};
use crate::util::{ use crate::util::{
StackGuard, assert_stack, check_stack, linenumber_to_usize, pop_error, ptr_to_lossy_str, ptr_to_str, StackGuard, assert_stack, check_stack, linenumber_to_usize, pop_error, ptr_to_lossy_str, ptr_to_str,
@@ -96,7 +91,6 @@ use crate::value::Value;
#[cfg(feature = "async")] #[cfg(feature = "async")]
use { use {
crate::thread::AsyncThread, crate::thread::AsyncThread,
crate::traits::LuaNativeAsyncFn,
crate::types::AsyncCallback, crate::types::AsyncCallback,
std::future::{self, Future}, std::future::{self, Future},
std::pin::{Pin, pin}, std::pin::{Pin, pin},
@@ -246,7 +240,7 @@ impl Function {
/// # } /// # }
/// ``` /// ```
/// ///
/// [`AsyncThread`]: crate::AsyncThread /// [`AsyncThread`]: crate::thread::AsyncThread
#[cfg(feature = "async")] #[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))] #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub fn call_async<R>(&self, args: impl IntoLuaMulti) -> AsyncCallFuture<R> pub fn call_async<R>(&self, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
@@ -636,30 +630,32 @@ impl Function {
/// Wraps a Rust function or closure, returning an opaque type that implements [`IntoLua`] /// Wraps a Rust function or closure, returning an opaque type that implements [`IntoLua`]
/// trait. /// trait.
#[inline] #[inline]
pub fn wrap<F, A, R>(func: F) -> impl IntoLua pub fn wrap<F, A, R, E>(func: F) -> impl IntoLua
where where
F: LuaNativeFn<A, Output = Result<R>> + MaybeSend + 'static, F: LuaNativeFn<A, Output = StdResult<R, E>> + MaybeSend + 'static,
A: FromLuaMulti, A: FromLuaMulti,
R: IntoLuaMulti, R: IntoLuaMulti,
E: ExternalError,
{ {
WrappedFunction(Box::new(move |lua, nargs| unsafe { WrappedFunction(Box::new(move |lua, nargs| unsafe {
let args = A::from_stack_args(nargs, 1, None, lua)?; 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. /// 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 where
F: LuaNativeFnMut<A, Output = Result<R>> + MaybeSend + 'static, F: LuaNativeFnMut<A, Output = StdResult<R, E>> + MaybeSend + 'static,
A: FromLuaMulti, A: FromLuaMulti,
R: IntoLuaMulti, R: IntoLuaMulti,
E: ExternalError,
{ {
let func = RefCell::new(func); let func = RefCell::new(func);
WrappedFunction(Box::new(move |lua, nargs| unsafe { WrappedFunction(Box::new(move |lua, nargs| unsafe {
let mut func = func.try_borrow_mut().map_err(|_| Error::RecursiveMutCallback)?; let mut func = func.try_borrow_mut().map_err(|_| Error::RecursiveMutCallback)?;
let args = A::from_stack_args(nargs, 1, None, lua)?; 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)
})) }))
} }
@@ -672,6 +668,7 @@ impl Function {
pub fn wrap_raw<F, A>(func: F) -> impl IntoLua pub fn wrap_raw<F, A>(func: F) -> impl IntoLua
where where
F: LuaNativeFn<A> + MaybeSend + 'static, F: LuaNativeFn<A> + MaybeSend + 'static,
F::Output: IntoLuaMulti,
A: FromLuaMulti, A: FromLuaMulti,
{ {
WrappedFunction(Box::new(move |lua, nargs| unsafe { WrappedFunction(Box::new(move |lua, nargs| unsafe {
@@ -688,6 +685,7 @@ impl Function {
pub fn wrap_raw_mut<F, A>(func: F) -> impl IntoLua pub fn wrap_raw_mut<F, A>(func: F) -> impl IntoLua
where where
F: LuaNativeFnMut<A> + MaybeSend + 'static, F: LuaNativeFnMut<A> + MaybeSend + 'static,
F::Output: IntoLuaMulti,
A: FromLuaMulti, A: FromLuaMulti,
{ {
let func = RefCell::new(func); let func = RefCell::new(func);
@@ -702,11 +700,12 @@ impl Function {
/// trait. /// trait.
#[cfg(feature = "async")] #[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(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 where
F: LuaNativeAsyncFn<A, Output = Result<R>> + MaybeSend + 'static, F: LuaNativeAsyncFn<A, Output = StdResult<R, E>> + MaybeSend + 'static,
A: FromLuaMulti, A: FromLuaMulti,
R: IntoLuaMulti, R: IntoLuaMulti,
E: ExternalError,
{ {
WrappedAsyncFunction(Box::new(move |rawlua, nargs| unsafe { WrappedAsyncFunction(Box::new(move |rawlua, nargs| unsafe {
let args = match A::from_stack_args(nargs, 1, None, rawlua) { let args = match A::from_stack_args(nargs, 1, None, rawlua) {
@@ -715,7 +714,7 @@ impl Function {
}; };
let lua = rawlua.lua(); let lua = rawlua.lua();
let fut = func.call(args); 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()) })
})) }))
} }
@@ -729,6 +728,7 @@ impl Function {
pub fn wrap_raw_async<F, A>(func: F) -> impl IntoLua pub fn wrap_raw_async<F, A>(func: F) -> impl IntoLua
where where
F: LuaNativeAsyncFn<A> + MaybeSend + 'static, F: LuaNativeAsyncFn<A> + MaybeSend + 'static,
F::Output: IntoLuaMulti,
A: FromLuaMulti, A: FromLuaMulti,
{ {
WrappedAsyncFunction(Box::new(move |rawlua, nargs| unsafe { WrappedAsyncFunction(Box::new(move |rawlua, nargs| unsafe {
@@ -788,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)] #[cfg(test)]
mod assertions { mod assertions {
use super::*; use super::*;
+67 -124
View File
@@ -61,6 +61,7 @@
//! [`Future`]: std::future::Future //! [`Future`]: std::future::Future
//! [`serde::Serialize`]: https://docs.serde.rs/serde/ser/trait.Serialize.html //! [`serde::Serialize`]: https://docs.serde.rs/serde/ser/trait.Serialize.html
//! [`serde::Deserialize`]: https://docs.serde.rs/serde/de/trait.Deserialize.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* // Deny warnings inside doc tests / examples. When this isn't present, rustdoc doesn't show *any*
// warnings at all. // warnings at all.
@@ -72,77 +73,96 @@
mod macros; mod macros;
mod buffer; mod buffer;
mod chunk;
mod conversion; mod conversion;
mod error;
#[cfg(any(feature = "luau", doc))]
mod luau;
mod memory; mod memory;
mod multi; mod multi;
mod scope; mod scope;
mod state;
mod stdlib; mod stdlib;
mod string;
mod thread;
mod traits; mod traits;
mod types; mod types;
mod userdata;
mod util; mod util;
mod value; mod value;
mod vector; mod vector;
pub mod chunk;
pub mod debug; pub mod debug;
pub mod error;
pub mod function; pub mod function;
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub mod luau;
pub mod prelude; pub mod prelude;
pub mod state;
pub mod string;
pub mod table; pub mod table;
pub mod thread;
pub mod userdata;
pub use bstr::BString; pub use bstr::BString;
pub use ffi::{self, lua_CFunction, lua_State}; pub use ffi::{self, lua_CFunction, lua_State};
#[cfg(feature = "macros")]
#[doc(hidden)]
pub use inventory as __inventory;
pub use crate::chunk::{AsChunk, Chunk, ChunkMode}; #[doc(inline)]
pub use crate::error::{Error, ErrorContext, ExternalError, ExternalResult, Result}; pub use crate::error::{Error, Result};
#[doc(inline)]
pub use crate::function::Function; pub use crate::function::Function;
pub use crate::multi::{MultiValue, Variadic}; pub use crate::multi::{MultiValue, Variadic};
pub use crate::scope::Scope; 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::stdlib::StdLib;
pub use crate::string::{BorrowedBytes, BorrowedStr, LuaString, LuaString as String}; #[doc(inline)]
pub use crate::string::{BorrowedBytes, BorrowedStr, LuaString};
#[doc(inline)]
pub use crate::table::Table; pub use crate::table::Table;
pub use crate::thread::{Thread, ThreadStatus}; #[doc(inline)]
pub use crate::traits::{ pub use crate::thread::Thread;
FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, LuaNativeFn, LuaNativeFnMut, ObjectLike, #[doc(inline)]
}; pub use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, ObjectLike};
pub use crate::types::{ pub use crate::types::{
AppDataRef, AppDataRefMut, Either, Integer, LightUserData, MaybeSend, Number, RegistryKey, VmState, AppDataRef, AppDataRefMut, Either, Integer, LightUserData, MaybeSend, MaybeSync, Number, RegistryKey,
}; VmState,
pub use crate::userdata::{
AnyUserData, MetaMethod, UserData, UserDataFields, UserDataMetatable, UserDataMethods, UserDataRef,
UserDataRefMut, UserDataRegistry,
}; };
#[doc(inline)]
pub use crate::userdata::AnyUserData;
pub use crate::value::{Nil, Value}; 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::{ThreadEvent, ThreadStatus, ThreadTriggers};
#[doc(hidden)]
pub use crate::userdata::{
MetaMethod, UserData, UserDataFields, UserDataMetatable, UserDataMethods, UserDataOwned, UserDataRef,
UserDataRefMut, UserDataRegistry,
};
#[cfg(not(feature = "luau"))] #[cfg(not(feature = "luau"))]
#[doc(inline)]
pub use crate::debug::HookTriggers; pub use crate::debug::HookTriggers;
#[cfg(any(feature = "luau", doc))] #[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub use crate::{ pub use crate::{buffer::Buffer, vector::Vector};
buffer::Buffer,
chunk::{CompileConstant, Compiler},
luau::{HeapDump, NavigateError, Require, TextRequirer},
vector::Vector,
};
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub use crate::{thread::AsyncThread, traits::LuaNativeAsyncFn};
#[cfg(feature = "serde")]
#[doc(hidden)]
pub use crate::serde::{DeserializeOptions, SerializeOptions};
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
#[doc(inline)] #[doc(inline)]
pub use crate::{ pub use crate::{serde::LuaSerdeExt, value::SerializableValue};
serde::{LuaSerdeExt, de::Options as DeserializeOptions, ser::Options as SerializeOptions},
value::SerializableValue,
};
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))] #[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
@@ -153,54 +173,7 @@ pub mod serde;
#[macro_use] #[macro_use]
extern crate mlua_derive; extern crate mlua_derive;
/// Create a type that implements [`AsChunk`] and can capture Rust variables. #[doc = include_str!("../docs/chunk.md")]
///
/// This macro allows to write Lua code directly in Rust code.
///
/// Rust variables can be referenced from Lua using `$` prefix, as shown in the example below.
/// User's Rust types needs to implement [`UserData`] or [`IntoLua`] traits.
///
/// Captured variables are **moved** into the chunk.
///
/// ```
/// use mlua::{Lua, Result, chunk};
///
/// fn main() -> Result<()> {
/// let lua = Lua::new();
/// let name = "Rustacean";
/// lua.load(chunk! {
/// print("hello, " .. $name)
/// }).exec()
/// }
/// ```
///
/// ## Syntax issues
///
/// Since the Rust tokenizer will tokenize Lua code, this imposes some restrictions.
/// The main thing to remember is:
///
/// - Use double quoted strings (`""`) instead of single quoted strings (`''`).
///
/// (Single quoted strings only work if they contain a single character, since in Rust,
/// `'a'` is a character literal).
///
/// - Using Lua comments `--` is not desirable in **stable** Rust and can have bad side effects.
///
/// This is because procedural macros have Line/Column information available only in
/// **nightly** Rust. Instead, Lua chunks represented as a big single line of code in stable Rust.
///
/// As workaround, Rust comments `//` can be used.
///
/// Other minor limitations:
///
/// - Certain escape codes in string literals don't work. (Specifically: `\a`, `\b`, `\f`, `\v`,
/// `\123` (octal escape codes), `\u`, and `\U`).
///
/// These are accepted: : `\\`, `\n`, `\t`, `\r`, `\xAB` (hex escape codes), and `\0`.
///
/// - The `//` (floor division) operator is unusable, as its start a comment.
///
/// Everything else should work.
#[cfg(feature = "macros")] #[cfg(feature = "macros")]
#[cfg_attr(docsrs, doc(cfg(feature = "macros")))] #[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
pub use mlua_derive::chunk; pub use mlua_derive::chunk;
@@ -213,47 +186,17 @@ pub use mlua_derive::chunk;
#[cfg_attr(docsrs, doc(cfg(feature = "macros")))] #[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
pub use mlua_derive::FromLua; pub use mlua_derive::FromLua;
/// Registers Lua module entrypoint. #[doc = include_str!("../docs/UserData.md")]
/// #[cfg(feature = "macros")]
/// You can register multiple entrypoints as required. #[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
/// pub use mlua_derive::UserData;
/// ```ignore
/// use mlua::{Lua, Result, Table}; #[doc(hidden)]
/// #[cfg(feature = "macros")]
/// #[mlua::lua_module] #[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
/// fn my_module(lua: &Lua) -> Result<Table> { pub use mlua_derive::userdata_impl;
/// let exports = lua.create_table()?;
/// exports.set("hello", "world")?; #[doc = include_str!("../docs/lua_module.md")]
/// Ok(exports)
/// }
/// ```
///
/// Internally in the code above the compiler defines C function `luaopen_my_module`.
///
/// You can also pass options to the attribute:
///
/// * name - name of the module, defaults to the name of the function
///
/// ```ignore
/// #[mlua::lua_module(name = "alt_module")]
/// fn my_module(lua: &Lua) -> Result<Table> {
/// ...
/// }
/// ```
///
/// * 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.
///
/// ```ignore
/// #[mlua::lua_module(skip_memory_check)]
/// fn my_module(lua: &Lua) -> Result<Table> {
/// ...
/// }
/// ```
#[cfg(all(feature = "mlua_derive", any(feature = "module", doc)))] #[cfg(all(feature = "mlua_derive", any(feature = "module", doc)))]
#[cfg_attr(docsrs, doc(cfg(feature = "module")))] #[cfg_attr(docsrs, doc(cfg(feature = "module")))]
pub use mlua_derive::lua_module; pub use mlua_derive::lua_module;
+9 -2
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::ffi::{CStr, CString};
use std::os::raw::c_int; use std::os::raw::c_int;
use std::ptr; use std::ptr;
@@ -10,7 +17,7 @@ use crate::traits::{FromLuaMulti, IntoLua};
use crate::types::MaybeSend; use crate::types::MaybeSend;
pub use heap_dump::HeapDump; 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 // Since Luau has some missing standard functions, we re-implement them here
@@ -86,7 +93,7 @@ impl Lua {
} }
// Enable default `require` implementation // 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)?; self.globals().raw_set("require", require)?;
Ok(()) Ok(())
+45 -4
View File
@@ -12,8 +12,7 @@ use crate::state::{Lua, callback_error_ext};
use crate::table::Table; use crate::table::Table;
use crate::types::MaybeSend; use crate::types::MaybeSend;
// TODO: Rename to FsRequirer pub use fs::FsRequirer;
pub use fs::TextRequirer;
/// An error that can occur during navigation in the Luau `require-by-string` system. /// An error that can occur during navigation in the Luau `require-by-string` system.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@@ -67,6 +66,24 @@ pub trait Require {
/// configuration file. /// configuration file.
fn jump_to_alias(&mut self, path: &str) -> StdResult<(), NavigateError>; fn jump_to_alias(&mut self, path: &str) -> StdResult<(), NavigateError>;
/// Provides an initial alias override opportunity prior to searching for
/// configuration files.
///
/// If `Ok(())` is returned, alias resolution stops here and the internal state
/// must point at the aliased location.
fn to_alias_override(&mut self, _alias: &str) -> StdResult<(), NavigateError> {
Err(NavigateError::NotFound)
}
/// Provides a final opportunity to resolve an alias if it cannot be found in
/// configuration files.
///
/// If `Ok(())` is returned, alias resolution stops here and the internal state
/// must point at the aliased location.
fn to_alias_fallback(&mut self, _alias: &str) -> StdResult<(), NavigateError> {
Err(NavigateError::NotFound)
}
// Navigate to parent directory // Navigate to parent directory
fn to_parent(&mut self) -> StdResult<(), NavigateError>; fn to_parent(&mut self) -> StdResult<(), NavigateError>;
@@ -193,6 +210,30 @@ pub(super) unsafe extern "C-unwind" fn init_config(config: *mut ffi::luarequire_
}) })
} }
unsafe extern "C-unwind" fn to_alias_override(
state: *mut ffi::lua_State,
ctx: *mut c_void,
alias_unprefixed: *const c_char,
) -> ffi::luarequire_NavigateResult {
let mut this = try_borrow_mut!(state, ctx);
let alias = CStr::from_ptr(alias_unprefixed).to_string_lossy();
callback_error_ext(state, ptr::null_mut(), true, move |_, _| {
this.to_alias_override(&alias).into_nav_result()
})
}
unsafe extern "C-unwind" fn to_alias_fallback(
state: *mut ffi::lua_State,
ctx: *mut c_void,
alias_unprefixed: *const c_char,
) -> ffi::luarequire_NavigateResult {
let mut this = try_borrow_mut!(state, ctx);
let alias = CStr::from_ptr(alias_unprefixed).to_string_lossy();
callback_error_ext(state, ptr::null_mut(), true, move |_, _| {
this.to_alias_fallback(&alias).into_nav_result()
})
}
unsafe extern "C-unwind" fn to_parent( unsafe extern "C-unwind" fn to_parent(
state: *mut ffi::lua_State, state: *mut ffi::lua_State,
ctx: *mut c_void, ctx: *mut c_void,
@@ -299,8 +340,8 @@ pub(super) unsafe extern "C-unwind" fn init_config(config: *mut ffi::luarequire_
(*config).is_require_allowed = is_require_allowed; (*config).is_require_allowed = is_require_allowed;
(*config).reset = reset; (*config).reset = reset;
(*config).jump_to_alias = jump_to_alias; (*config).jump_to_alias = jump_to_alias;
(*config).to_alias_override = None; (*config).to_alias_override = Some(to_alias_override);
(*config).to_alias_fallback = None; (*config).to_alias_fallback = Some(to_alias_fallback);
(*config).to_parent = to_parent; (*config).to_parent = to_parent;
(*config).to_child = to_child; (*config).to_child = to_child;
(*config).is_module_present = is_module_present; (*config).is_module_present = is_module_present;
+6 -6
View File
@@ -12,7 +12,7 @@ use super::{NavigateError, Require};
/// The standard implementation of Luau `require-by-string` navigation. /// The standard implementation of Luau `require-by-string` navigation.
#[derive(Default, Debug)] #[derive(Default, Debug)]
pub struct TextRequirer { pub struct FsRequirer {
/// An absolute path to the current Luau module (not mapped to a physical file) /// An absolute path to the current Luau module (not mapped to a physical file)
abs_path: PathBuf, abs_path: PathBuf,
/// A relative path to the current Luau module (not mapped to a physical file) /// 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>, resolved_path: Option<PathBuf>,
} }
impl TextRequirer { impl FsRequirer {
/// The prefix used for chunk names in the require system. /// The prefix used for chunk names in the require system.
/// Only chunk names starting with this prefix are allowed to be used in `require`. /// Only chunk names starting with this prefix are allowed to be used in `require`.
const CHUNK_PREFIX: &str = "@"; const CHUNK_PREFIX: &str = "@";
@@ -36,7 +36,7 @@ impl TextRequirer {
/// The filename for the Luau configuration file. /// The filename for the Luau configuration file.
const LUAU_CONFIG_FILENAME: &str = ".config.luau"; const LUAU_CONFIG_FILENAME: &str = ".config.luau";
/// Creates a new `TextRequirer` instance. /// Creates a new `FsRequirer` instance.
pub fn new() -> Self { pub fn new() -> Self {
Self::default() Self::default()
} }
@@ -114,7 +114,7 @@ impl TextRequirer {
} }
} }
impl Require for TextRequirer { impl Require for FsRequirer {
fn is_require_allowed(&self, chunk_name: &str) -> bool { fn is_require_allowed(&self, chunk_name: &str) -> bool {
chunk_name.starts_with(Self::CHUNK_PREFIX) chunk_name.starts_with(Self::CHUNK_PREFIX)
} }
@@ -231,7 +231,7 @@ impl Require for TextRequirer {
mod tests { mod tests {
use std::path::Path; use std::path::Path;
use super::TextRequirer; use super::FsRequirer;
#[test] #[test]
fn test_path_normalize() { fn test_path_normalize() {
@@ -267,7 +267,7 @@ mod tests {
// '..' disappears if path is absolute and component is non-erasable // '..' 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!( assert_eq!(
&path, &path,
expected.as_ref() as &Path, expected.as_ref() as &Path,
+24 -14
View File
@@ -3,34 +3,44 @@
#[doc(no_inline)] #[doc(no_inline)]
pub use crate::{ pub use crate::{
AnyUserData as LuaAnyUserData, BorrowedBytes as LuaBorrowedBytes, BorrowedStr as LuaBorrowedStr, AnyUserData as LuaAnyUserData, BorrowedBytes as LuaBorrowedBytes, BorrowedStr as LuaBorrowedStr,
Chunk as LuaChunk, Either as LuaEither, Error as LuaError, ErrorContext as LuaErrorContext, Either as LuaEither, Error as LuaError, FromLua, FromLuaMulti, Function as LuaFunction,
ExternalError as LuaExternalError, ExternalResult as LuaExternalResult, FromLua, FromLuaMulti, Integer as LuaInteger, IntoLua, IntoLuaMulti, LightUserData as LuaLightUserData, Lua, LuaOptions,
Function as LuaFunction, GCMode as LuaGCMode, Integer as LuaInteger, IntoLua, IntoLuaMulti, LuaString, MetaMethod as LuaMetaMethod, MultiValue as LuaMultiValue, Nil as LuaNil, Number as LuaNumber,
LightUserData as LuaLightUserData, Lua, LuaNativeFn, LuaNativeFnMut, 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, ObjectLike as LuaObjectLike, RegistryKey as LuaRegistryKey, Result as LuaResult, StdLib as LuaStdLib,
Table as LuaTable, Thread as LuaThread, ThreadStatus as LuaThreadStatus, UserData as LuaUserData, Table as LuaTable, Thread as LuaThread, UserData as LuaUserData, UserDataFields as LuaUserDataFields,
UserDataFields as LuaUserDataFields, UserDataMetatable as LuaUserDataMetatable, UserDataMetatable as LuaUserDataMetatable, UserDataMethods as LuaUserDataMethods,
UserDataMethods as LuaUserDataMethods, UserDataRef as LuaUserDataRef, UserDataOwned as LuaUserDataOwned, UserDataRef as LuaUserDataRef, UserDataRefMut as LuaUserDataRefMut,
UserDataRefMut as LuaUserDataRefMut, UserDataRegistry as LuaUserDataRegistry, Value as LuaValue, UserDataRegistry as LuaUserDataRegistry, Value as LuaValue, Variadic as LuaVariadic,
Variadic as LuaVariadic, VmState as LuaVmState, WeakLua, function::FunctionInfo as LuaFunctionInfo, VmState as LuaVmState, WeakLua, chunk::AsChunk as AsLuaChunk, chunk::Chunk as LuaChunk,
table::TablePairs as LuaTablePairs, table::TableSequence as LuaTableSequence, 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"))] #[cfg(not(feature = "luau"))]
#[doc(no_inline)] #[doc(no_inline)]
pub use crate::HookTriggers as LuaHookTriggers; 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")] #[cfg(feature = "luau")]
#[doc(no_inline)] #[doc(no_inline)]
pub use crate::{ pub use crate::{
CompileConstant as LuaCompileConstant, NavigateError as LuaNavigateError, Require as LuaRequire, Vector as LuaVector,
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")] #[cfg(feature = "async")]
#[doc(no_inline)] #[doc(no_inline)]
pub use crate::{AsyncThread as LuaAsyncThread, LuaNativeAsyncFn}; pub use crate::{function::LuaNativeAsyncFn, thread::AsyncThread as LuaAsyncThread};
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
#[doc(no_inline)] #[doc(no_inline)]
+4 -6
View File
@@ -37,8 +37,8 @@ pub trait LuaSerdeExt: Sealed {
fn null(&self) -> Value; fn null(&self) -> Value;
/// A metatable attachable to a Lua table to systematically encode it as Array (instead of Map). /// 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 a result, encoded Array will contain only sequence part of the table, with the same
/// as the `#` operator on that table. /// length as the `#` operator on that table.
/// ///
/// # Example /// # Example
/// ///
@@ -242,7 +242,5 @@ static ARRAY_METATABLE_REGISTRY_KEY: u8 = 0;
pub mod de; pub mod de;
pub mod ser; pub mod ser;
#[doc(inline)] pub use de::{Deserializer, Options as DeserializeOptions};
pub use de::Deserializer; pub use ser::{Options as SerializeOptions, Serializer};
#[doc(inline)]
pub use ser::Serializer;
+311 -257
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::any::TypeId;
use std::cell::{BorrowError, BorrowMutError, RefCell}; use std::cell::{BorrowError, BorrowMutError, RefCell};
use std::marker::PhantomData; use std::marker::PhantomData;
@@ -17,11 +22,11 @@ use crate::scope::Scope;
use crate::stdlib::StdLib; use crate::stdlib::StdLib;
use crate::string::LuaString; use crate::string::LuaString;
use crate::table::Table; use crate::table::Table;
use crate::thread::Thread; use crate::thread::{Thread, ThreadEvent, ThreadTriggers};
use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti}; use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti};
use crate::types::{ use crate::types::{
AppDataRef, AppDataRefMut, ArcReentrantMutexGuard, Integer, LuaType, MaybeSend, Number, ReentrantMutex, AppDataRef, AppDataRefMut, ArcReentrantMutexGuard, Integer, LuaType, MaybeSend, MaybeSync, Number,
ReentrantMutexGuard, RegistryKey, VmState, XRc, XWeak, ReentrantMutex, ReentrantMutexGuard, RegistryKey, VmState, XRc, XWeak,
}; };
use crate::userdata::{AnyUserData, UserData, UserDataProxy, UserDataRegistry, UserDataStorage}; use crate::userdata::{AnyUserData, UserData, UserDataProxy, UserDataRegistry, UserDataStorage};
use crate::util::{StackGuard, assert_stack, check_stack, protect_lua_closure, push_string, rawset_field}; use crate::util::{StackGuard, assert_stack, check_stack, protect_lua_closure, push_string, rawset_field};
@@ -44,6 +49,7 @@ use {
use serde::Serialize; use serde::Serialize;
pub(crate) use extra::ExtraData; pub(crate) use extra::ExtraData;
#[doc(hidden)]
pub use raw::RawLua; pub use raw::RawLua;
pub(crate) use util::callback_error_ext; pub(crate) use util::callback_error_ext;
@@ -62,20 +68,126 @@ pub struct WeakLua(XWeak<ReentrantMutex<RawLua>>);
pub(crate) struct LuaGuard(ArcReentrantMutexGuard<RawLua>); pub(crate) struct LuaGuard(ArcReentrantMutexGuard<RawLua>);
/// Mode of the Lua garbage collector (GC). /// Tuning parameters for the incremental GC collector.
///
/// In Lua 5.4 GC can work in two modes: incremental and generational.
/// Previous Lua versions support only incremental GC.
/// ///
/// More information can be found in the Lua [documentation]. /// More information can be found in the Lua [documentation].
/// ///
/// [documentation]: https://www.lua.org/manual/5.4/manual.html#2.5 /// [documentation]: https://www.lua.org/manual/5.5/manual.html#2.5.1
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[non_exhaustive]
pub enum GCMode { #[derive(Clone, Copy, Debug, Default)]
Incremental, 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(any(feature = "lua55", feature = "lua54"))]
#[cfg_attr(docsrs, doc(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. /// Controls Lua interpreter behavior such as Rust panics handling.
@@ -337,30 +449,27 @@ impl Lua {
R::from_stack_multi(nresults, &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 /// Calls provided function passing a reference to the [`RawLua`] handle.
/// the stack.
/// ///
/// 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 /// # Example
/// ``` /// ```
/// # use mlua::{Lua, Result, FromLua, IntoLua}; /// # use mlua::{Lua, Result, FromLua, IntoLua, IntoLuaMulti};
/// # fn main() -> Result<()> { /// # fn main() -> Result<()> {
/// let lua = Lua::new(); /// let lua = Lua::new();
/// let n: i32 = { /// let n: i32 = {
/// let num = 11i32; /// let nums = (3, 4, 5);
/// lua.exec_raw_lua(|lua| { /// lua.exec_raw_lua(|rawlua| unsafe {
/// unsafe { /// nums.push_into_stack_multi(rawlua)?;
/// <i32 as IntoLua>::push_into_stack(num, lua)?; /// let mut sum = 0;
/// for _ in 0..3 {
/// sum += rawlua.pop::<i32>()?;
/// } /// }
/// /// Result::Ok(sum)
/// let n = unsafe {
/// <i32 as FromLua>::from_stack(-1, lua)?
/// };
/// Result::Ok(n)
/// }) /// })
/// }?; /// }?;
/// assert_eq!(n, 11); /// assert_eq!(n, 12);
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
@@ -437,31 +546,6 @@ impl Lua {
Ok(()) 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`. /// Unloads module `modname`.
/// ///
/// This method does not support unloading binary Lua modules since they are internally cached /// This method does not support unloading binary Lua modules since they are internally cached
@@ -758,92 +842,87 @@ impl Lua {
} }
} }
/// Sets a thread creation callback that will be called when a thread is created. /// Sets a callback invoked when thread lifecycle events occur.
#[cfg(any(feature = "luau", doc))] ///
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))] /// `triggers` controls which events trigger the callback, see [`ThreadTriggers`] for more
pub fn set_thread_creation_callback<F>(&self, callback: F) /// details.
///
/// Only one callback can be registered at a time. Calling this again replaces the previous
/// callback and its triggers.
///
/// # Example
///
/// Subscribe only to yield events:
///
/// ```
/// # use mlua::{Lua, Result, ThreadTriggers, ThreadEvent};
/// # fn main() -> Result<()> {
/// let lua = Lua::new();
/// lua.set_thread_event_callback(
/// ThreadTriggers::ON_YIELD,
/// |_lua, event| {
/// if let ThreadEvent::Yield(thread) = event {
/// println!("thread yielded");
/// }
/// Ok(())
/// },
/// );
/// # Ok(())
/// # }
/// ```
pub fn set_thread_event_callback<F>(&self, triggers: ThreadTriggers, callback: F)
where where
F: Fn(&Lua, Thread) -> Result<()> + MaybeSend + 'static, F: Fn(&Lua, ThreadEvent) -> Result<()> + MaybeSend + 'static,
{ {
let lua = self.lock(); let lua = self.lock();
unsafe { unsafe {
(*lua.extra.get()).thread_creation_callback = Some(XRc::new(callback)); (*lua.extra.get()).thread_triggers = triggers;
(*ffi::lua_callbacks(lua.main_state())).userthread = Some(Self::userthread_proc); (*lua.extra.get()).thread_event_callback = Some(XRc::new(callback));
#[cfg(feature = "luau")]
{
let proc = Self::userthread_proc as _;
(*ffi::lua_callbacks(lua.main_state())).userthread = triggers.on_create.then_some(proc);
}
} }
} }
/// Sets a thread collection callback that will be called when a thread is destroyed. /// Removes the thread event callback previously set by [`Lua::set_thread_event_callback`].
/// ///
/// Luau GC does not support exceptions during collection, so the callback must be /// This function has no effect if a callback was not previously set.
/// non-panicking. If the callback panics, the program will be aborted. pub fn remove_thread_event_callback(&self) {
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn set_thread_collection_callback<F>(&self, callback: F)
where
F: Fn(crate::LightUserData) + MaybeSend + 'static,
{
let lua = self.lock(); let lua = self.lock();
let extra = lua.extra.get();
unsafe { unsafe {
(*lua.extra.get()).thread_collection_callback = Some(XRc::new(callback)); (*extra).thread_triggers = ThreadTriggers::new();
(*ffi::lua_callbacks(lua.main_state())).userthread = Some(Self::userthread_proc); (*extra).thread_event_callback = None;
#[cfg(feature = "luau")]
{
(*ffi::lua_callbacks(lua.main_state())).userthread = None;
}
} }
} }
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
unsafe extern "C-unwind" fn userthread_proc(parent: *mut ffi::lua_State, child: *mut ffi::lua_State) { unsafe extern "C-unwind" fn userthread_proc(parent: *mut ffi::lua_State, child: *mut ffi::lua_State) {
// Only handle thread creation
if parent.is_null() {
return;
}
let extra = ExtraData::get(child); let extra = ExtraData::get(child);
if !parent.is_null() { if !(*extra).thread_triggers.on_create {
// Thread is created return;
let callback = match (*extra).thread_creation_callback {
Some(ref cb) => cb.clone(),
None => return,
};
if XRc::strong_count(&callback) > 2 {
return; // Don't allow recursion
}
ffi::lua_pushthread(child);
ffi::lua_xmove(child, (*extra).ref_thread, 1);
let value = Thread((*extra).raw_lua().pop_ref_thread(), child);
callback_error_ext(parent, extra, false, move |extra, _| {
callback((*extra).lua(), value)
})
} else {
// Thread is about to be collected
let callback = match (*extra).thread_collection_callback {
Some(ref cb) => cb.clone(),
None => return,
};
// We need to wrap the callback call in non-unwind function as it's not safe to unwind when
// Luau GC is running.
// This will trigger `abort()` if the callback panics.
unsafe extern "C" fn run_callback(
callback: *const crate::types::ThreadCollectionCallback,
value: *mut ffi::lua_State,
) {
(*callback)(crate::LightUserData(value as _));
}
(*extra).running_gc = true;
run_callback(&callback, child);
(*extra).running_gc = false;
}
}
/// Removes any thread creation or collection callbacks previously set by
/// [`Lua::set_thread_creation_callback`] or [`Lua::set_thread_collection_callback`].
///
/// This function has no effect if a thread callbacks were not previously set.
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn remove_thread_callbacks(&self) {
let lua = self.lock();
unsafe {
let extra = lua.extra.get();
(*extra).thread_creation_callback = None;
(*extra).thread_collection_callback = None;
(*ffi::lua_callbacks(lua.main_state())).userthread = None;
} }
let callback = match &(*extra).thread_event_callback {
Some(cb) if XRc::strong_count(cb) == 1 => cb.clone(),
_ => return,
};
ffi::lua_pushthread(child);
ffi::lua_xmove(child, (*extra).ref_thread, 1);
let thread = Thread((*extra).raw_lua().pop_ref_thread(), child);
callback_error_ext(parent, extra, false, move |extra, _| {
callback((*extra).lua(), ThreadEvent::Create(thread))
})
} }
/// Sets the warning function to be used by Lua to emit warnings. /// Sets the warning function to be used by Lua to emit warnings.
@@ -909,8 +988,8 @@ impl Lua {
/// Gets information about the interpreter runtime stack at the given level. /// 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 /// This function calls callback `f`, passing the [`struct@Debug`] structure that can be used to
/// information about the function executing at a given level. /// 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 /// 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). /// 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> { pub fn inspect_stack<R>(&self, level: usize, f: impl FnOnce(&Debug) -> R) -> Option<R> {
@@ -998,19 +1077,19 @@ impl Lua {
unsafe { ffi::lua_gc(lua.main_state(), ffi::LUA_GCISRUNNING, 0) != 0 } unsafe { ffi::lua_gc(lua.main_state(), ffi::LUA_GCISRUNNING, 0) != 0 }
} }
/// Stop the Lua GC from running /// Stops the Lua GC from running.
pub fn gc_stop(&self) { pub fn gc_stop(&self) {
let lua = self.lock(); let lua = self.lock();
unsafe { ffi::lua_gc(lua.main_state(), ffi::LUA_GCSTOP, 0) }; unsafe { ffi::lua_gc(lua.main_state(), ffi::LUA_GCSTOP, 0) };
} }
/// Restarts the Lua GC if it is not running /// Restarts the Lua GC if it is not running.
pub fn gc_restart(&self) { pub fn gc_restart(&self) {
let lua = self.lock(); let lua = self.lock();
unsafe { ffi::lua_gc(lua.main_state(), ffi::LUA_GCRESTART, 0) }; unsafe { ffi::lua_gc(lua.main_state(), ffi::LUA_GCRESTART, 0) };
} }
/// Perform a full garbage-collection cycle. /// Performs a full garbage-collection cycle.
/// ///
/// It may be necessary to call this function twice to collect all currently unreachable /// 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. /// objects. Once to finish the current gc cycle, and once to start and finish the next cycle.
@@ -1023,153 +1102,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> { 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 lua = self.lock();
let state = lua.main_state(); let state = lua.main_state();
unsafe { unsafe {
check_stack(state, 3)?; check_stack(state, 3)?;
protect_lua!(state, 0, 0, |state| { 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 /// Returns the previous [`GcMode`]. The returned value's parameter fields are always
/// [documentation]. /// `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 /// Switch to generational mode (Lua 5.4+):
pub fn gc_set_pause(&self, pause: c_int) -> c_int { /// ```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 lua = self.lock();
let state = lua.main_state(); let state = lua.main_state();
unsafe {
match mode {
#[cfg(feature = "lua55")] #[cfg(feature = "lua55")]
return ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPPAUSE, pause); GcMode::Incremental(params) => unsafe {
if let Some(v) = params.pause {
#[cfg(not(any(feature = "lua55", feature = "luau")))] ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPPAUSE, v);
return ffi::lua_gc(state, ffi::LUA_GCSETPAUSE, pause); }
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")] #[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")] #[cfg(feature = "lua55")]
return ffi::lua_gc( GcMode::Generational(params) => unsafe {
lua.main_state(), if let Some(v) = params.minor_multiplier {
ffi::LUA_GCPARAM, ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPMINORMUL, v);
ffi::LUA_GCPSTEPMUL, }
step_multiplier, if let Some(v) = params.minor_to_major {
); ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPMINORMAJOR, v);
}
#[cfg(not(feature = "lua55"))] if let Some(v) = params.major_to_minor {
return ffi::lua_gc(lua.main_state(), ffi::LUA_GCSETSTEPMUL, step_multiplier); 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()),
/// Changes the collector to incremental mode with the given parameters. ffi::LUA_GCINC => GcMode::Incremental(GcIncParams::default()),
/// _ => unreachable!(),
/// Returns the previous mode (always `GCMode::Incremental` in Lua < 5.4). }
/// More information can be found in the Lua [documentation]. },
/// #[cfg(feature = "lua54")]
/// [documentation]: https://www.lua.org/manual/5.4/manual.html#2.5.1 GcMode::Generational(params) => unsafe {
pub fn gc_inc(&self, pause: c_int, step_multiplier: c_int, step_size: c_int) -> GCMode { let minor = params.minor_multiplier.unwrap_or(0);
let lua = self.lock(); let minor_to_major = params.minor_to_major.unwrap_or(0);
let state = lua.main_state(); match ffi::lua_gc(state, ffi::LUA_GCGEN, minor, minor_to_major) {
ffi::LUA_GCGEN => GcMode::Generational(GcGenParams::default()),
#[cfg(any( ffi::LUA_GCINC => GcMode::Incremental(GcIncParams::default()),
feature = "lua53", _ => unreachable!(),
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
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!(),
} }
} }
@@ -1456,7 +1510,7 @@ impl Lua {
/// } /// }
/// ``` /// ```
/// ///
/// [`AsyncThread`]: crate::AsyncThread /// [`AsyncThread`]: crate::thread::AsyncThread
#[cfg(feature = "async")] #[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))] #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub fn create_async_function<F, A, FR, R>(&self, func: F) -> Result<Function> pub fn create_async_function<F, A, FR, R>(&self, func: F) -> Result<Function>
@@ -1492,7 +1546,7 @@ impl Lua {
#[inline] #[inline]
pub fn create_userdata<T>(&self, data: T) -> Result<AnyUserData> pub fn create_userdata<T>(&self, data: T) -> Result<AnyUserData>
where where
T: UserData + MaybeSend + 'static, T: UserData + MaybeSend + MaybeSync + 'static,
{ {
unsafe { self.lock().make_userdata(UserDataStorage::new(data)) } unsafe { self.lock().make_userdata(UserDataStorage::new(data)) }
} }
@@ -1503,7 +1557,7 @@ impl Lua {
#[inline] #[inline]
pub fn create_ser_userdata<T>(&self, data: T) -> Result<AnyUserData> pub fn create_ser_userdata<T>(&self, data: T) -> Result<AnyUserData>
where where
T: UserData + Serialize + MaybeSend + 'static, T: UserData + Serialize + MaybeSend + MaybeSync + 'static,
{ {
unsafe { self.lock().make_userdata(UserDataStorage::new_ser(data)) } unsafe { self.lock().make_userdata(UserDataStorage::new_ser(data)) }
} }
@@ -1518,7 +1572,7 @@ impl Lua {
#[inline] #[inline]
pub fn create_any_userdata<T>(&self, data: T) -> Result<AnyUserData> pub fn create_any_userdata<T>(&self, data: T) -> Result<AnyUserData>
where where
T: MaybeSend + 'static, T: MaybeSend + MaybeSync + 'static,
{ {
unsafe { self.lock().make_any_userdata(UserDataStorage::new(data)) } unsafe { self.lock().make_any_userdata(UserDataStorage::new(data)) }
} }
@@ -1531,7 +1585,7 @@ impl Lua {
#[inline] #[inline]
pub fn create_ser_any_userdata<T>(&self, data: T) -> Result<AnyUserData> pub fn create_ser_any_userdata<T>(&self, data: T) -> Result<AnyUserData>
where where
T: Serialize + MaybeSend + 'static, T: Serialize + MaybeSend + MaybeSync + 'static,
{ {
unsafe { (self.lock()).make_any_userdata(UserDataStorage::new_ser(data)) } unsafe { (self.lock()).make_any_userdata(UserDataStorage::new_ser(data)) }
} }
+6 -9
View File
@@ -12,7 +12,8 @@ use rustc_hash::FxHashMap;
use crate::error::Result; use crate::error::Result;
use crate::state::RawLua; use crate::state::RawLua;
use crate::stdlib::StdLib; use crate::stdlib::StdLib;
use crate::types::{AppData, ReentrantMutex, XRc}; use crate::thread::ThreadTriggers;
use crate::types::{AppData, ReentrantMutex, ThreadEventCallback, XRc};
use crate::userdata::RawUserDataRegistry; use crate::userdata::RawUserDataRegistry;
use crate::util::{TypeKey, WrappedFailure, get_internal_metatable, push_internal_userdata}; use crate::util::{TypeKey, WrappedFailure, get_internal_metatable, push_internal_userdata};
@@ -81,10 +82,8 @@ pub(crate) struct ExtraData {
pub(super) warn_callback: Option<crate::types::WarnCallback>, pub(super) warn_callback: Option<crate::types::WarnCallback>,
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
pub(super) interrupt_callback: Option<crate::types::InterruptCallback>, pub(super) interrupt_callback: Option<crate::types::InterruptCallback>,
#[cfg(feature = "luau")] pub(super) thread_triggers: ThreadTriggers,
pub(super) thread_creation_callback: Option<crate::types::ThreadCreationCallback>, pub(super) thread_event_callback: Option<ThreadEventCallback>,
#[cfg(feature = "luau")]
pub(super) thread_collection_callback: Option<crate::types::ThreadCollectionCallback>,
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
pub(crate) running_gc: bool, pub(crate) running_gc: bool,
@@ -186,10 +185,8 @@ impl ExtraData {
warn_callback: None, warn_callback: None,
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
interrupt_callback: None, interrupt_callback: None,
#[cfg(feature = "luau")] thread_triggers: ThreadTriggers::default(),
thread_creation_callback: None, thread_event_callback: None,
#[cfg(feature = "luau")]
thread_collection_callback: None,
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
sandboxed: false, sandboxed: false,
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
+70 -18
View File
@@ -1,7 +1,7 @@
use std::any::TypeId; use std::any::TypeId;
use std::cell::{Cell, UnsafeCell}; use std::cell::{Cell, UnsafeCell};
use std::ffi::CStr; use std::ffi::CStr;
use std::mem; use std::mem::{self, ManuallyDrop};
use std::os::raw::{c_char, c_int, c_void}; use std::os::raw::{c_char, c_int, c_void};
use std::panic::resume_unwind; use std::panic::resume_unwind;
use std::ptr::{self, NonNull}; use std::ptr::{self, NonNull};
@@ -15,11 +15,11 @@ use crate::state::util::callback_error_ext;
use crate::stdlib::StdLib; use crate::stdlib::StdLib;
use crate::string::LuaString; use crate::string::LuaString;
use crate::table::Table; use crate::table::Table;
use crate::thread::Thread; use crate::thread::{Thread, ThreadTriggers};
use crate::traits::IntoLua; use crate::traits::{FromLua, IntoLua};
use crate::types::{ use crate::types::{
AppDataRef, AppDataRefMut, Callback, CallbackUpvalue, DestructedUserdata, Integer, LightUserData, AppDataRef, AppDataRefMut, Callback, CallbackUpvalue, DestructedUserdata, Integer, LightUserData,
LuaType, MaybeSend, ReentrantMutex, RegistryKey, ValueRef, XRc, LuaType, MaybeSend, ReentrantMutex, RegistryKey, ThreadEventCallback, ValueRef, XRc,
}; };
use crate::userdata::{ use crate::userdata::{
AnyUserData, MetaMethod, RawUserDataRegistry, UserData, UserDataRegistry, UserDataStorage, AnyUserData, MetaMethod, RawUserDataRegistry, UserData, UserDataRegistry, UserDataStorage,
@@ -50,13 +50,13 @@ use {
std::task::{Context, Poll, Waker}, 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)] #[doc(hidden)]
pub struct RawLua { pub struct RawLua {
// The state is dynamic and depends on context // The state is dynamic and depends on context
pub(super) state: Cell<*mut ffi::lua_State>, pub(super) state: Cell<*mut ffi::lua_State>,
pub(super) main_state: Option<NonNull<ffi::lua_State>>, pub(super) main_state: Option<NonNull<ffi::lua_State>>,
pub(super) extra: XRc<UnsafeCell<ExtraData>>, pub(super) extra: ManuallyDrop<XRc<UnsafeCell<ExtraData>>>,
owned: bool, owned: bool,
} }
@@ -82,6 +82,9 @@ impl Drop for RawLua {
if !mem_state.is_null() { if !mem_state.is_null() {
drop(Box::from_raw(mem_state)); drop(Box::from_raw(mem_state));
} }
// Drop the `ExtraData` reference after `lua_close` has collected the registry entry
ManuallyDrop::drop(&mut self.extra);
} }
} }
} }
@@ -245,7 +248,7 @@ impl RawLua {
state: Cell::new(state), state: Cell::new(state),
// Make sure that we don't store current state as main state (if it's not available) // Make sure that we don't store current state as main state (if it's not available)
main_state: get_main_state(state).and_then(NonNull::new), main_state: get_main_state(state).and_then(NonNull::new),
extra: XRc::clone(&extra), extra: ManuallyDrop::new(XRc::clone(&extra)),
owned, owned,
})); }));
(*extra.get()).set_lua(&rawlua); (*extra.get()).set_lua(&rawlua);
@@ -640,7 +643,7 @@ impl RawLua {
let protect = !self.unlikely_memory_error(); let protect = !self.unlikely_memory_error();
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
let protect = protect || (*self.extra.get()).thread_creation_callback.is_some(); let protect = protect || self.thread_event_triggers().on_create;
let thread_state = if !protect { let thread_state = if !protect {
ffi::lua_newthread(state) ffi::lua_newthread(state)
@@ -653,6 +656,19 @@ impl RawLua {
self.set_thread_hook(thread_state, HookKind::Global)?; self.set_thread_hook(thread_state, HookKind::Global)?;
let thread = Thread(self.pop_ref(), thread_state); let thread = Thread(self.pop_ref(), thread_state);
// Exec creation callback for non-Luau (Luau handles this via `userthread_proc`)
#[cfg(not(feature = "luau"))]
if self.thread_event_triggers().on_create {
let extra = self.extra.get();
if let Some(ref cb) = (*extra).thread_event_callback
&& XRc::strong_count(cb) == 1
{
let cb = cb.clone();
cb((*extra).lua(), crate::thread::ThreadEvent::Create(thread.clone()))?;
}
}
ffi::lua_xpush(self.ref_thread(), thread_state, func.0.index); ffi::lua_xpush(self.ref_thread(), thread_state, func.0.index);
Ok(thread) Ok(thread)
} }
@@ -688,6 +704,16 @@ impl RawLua {
} }
} }
#[inline(always)]
pub(crate) unsafe fn thread_event_triggers(&self) -> ThreadTriggers {
(*self.extra.get()).thread_triggers
}
#[inline(always)]
pub(crate) unsafe fn thread_event_callback(&self) -> Option<ThreadEventCallback> {
(*self.extra.get()).thread_event_callback.clone()
}
/// Pushes a primitive type value onto the Lua stack. /// Pushes a primitive type value onto the Lua stack.
pub(crate) unsafe fn push_primitive_type<T: LuaType>(&self) -> bool { pub(crate) unsafe fn push_primitive_type<T: LuaType>(&self) -> bool {
match T::TYPE_ID { match T::TYPE_ID {
@@ -731,14 +757,27 @@ impl RawLua {
/// Pushes a value that implements `IntoLua` onto the Lua stack. /// 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`. /// Uses up to 2 stack spaces to push a single value, does not call `checkstack`.
#[allow(clippy::missing_safety_doc)]
#[inline(always)] #[inline(always)]
pub unsafe fn push(&self, value: impl IntoLua) -> Result<()> { pub unsafe fn push(&self, value: impl IntoLua) -> Result<()> {
value.push_into_stack(self) 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. /// 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<()> { pub unsafe fn push_value(&self, value: &Value) -> Result<()> {
let state = self.state(); let state = self.state();
match value { match value {
@@ -773,6 +812,7 @@ impl RawLua {
/// Pops a value from the Lua stack. /// Pops a value from the Lua stack.
/// ///
/// Uses up to 1 stack spaces, does not call `checkstack`. /// Uses up to 1 stack spaces, does not call `checkstack`.
#[allow(clippy::missing_safety_doc)]
#[inline] #[inline]
pub unsafe fn pop_value(&self) -> Value { pub unsafe fn pop_value(&self) -> Value {
let value = self.stack_value(-1, None); let value = self.stack_value(-1, None);
@@ -803,15 +843,22 @@ impl RawLua {
#[cfg(any(feature = "lua52", feature = "lua51", feature = "luajit", feature = "luau"))] #[cfg(any(feature = "lua52", feature = "lua51", feature = "luajit", feature = "luau"))]
ffi::LUA_TNUMBER => { ffi::LUA_TNUMBER => {
use crate::types::Number;
let n = ffi::lua_tonumber(state, idx); let n = ffi::lua_tonumber(state, idx);
match num_traits::cast(n) { 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), _ => 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")] #[cfg(feature = "luau")]
ffi::LUA_TVECTOR => { ffi::LUA_TVECTOR => {
let v = ffi::lua_tovector(state, idx); let v = ffi::lua_tovector(state, idx);
@@ -949,7 +996,7 @@ impl RawLua {
// Check if userdata/metatable is already registered // Check if userdata/metatable is already registered
let type_id = TypeId::of::<T>(); let type_id = TypeId::of::<T>();
if let Some(&table_id) = (*self.extra.get()).registered_userdata_t.get(&type_id) { 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 // Create a new metatable from `UserData` definition
@@ -968,7 +1015,7 @@ impl RawLua {
// Check if userdata/metatable is already registered // Check if userdata/metatable is already registered
let type_id = TypeId::of::<T>(); let type_id = TypeId::of::<T>();
if let Some(&table_id) = (*self.extra.get()).registered_userdata_t.get(&type_id) { 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 // Check if metatable creation is pending or create an empty metatable otherwise
@@ -983,7 +1030,7 @@ impl RawLua {
unsafe fn make_userdata_with_metatable<T>( unsafe fn make_userdata_with_metatable<T>(
&self, &self,
data: UserDataStorage<T>, data: UserDataStorage<T>,
get_metatable_id: impl FnOnce() -> Result<Integer>, get_metatable_id: impl FnOnce() -> Result<c_int>,
) -> Result<AnyUserData> { ) -> Result<AnyUserData> {
let state = self.state(); let state = self.state();
let _sg = StackGuard::new(state); let _sg = StackGuard::new(state);
@@ -993,7 +1040,7 @@ impl RawLua {
let mt_id = get_metatable_id()?; let mt_id = get_metatable_id()?;
let protect = !self.unlikely_memory_error(); let protect = !self.unlikely_memory_error();
push_userdata(state, data, protect)?; 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); ffi::lua_setmetatable(state, -2);
// Set empty environment for Lua 5.1 // Set empty environment for Lua 5.1
@@ -1011,7 +1058,7 @@ impl RawLua {
Ok(AnyUserData(self.pop_ref())) 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 state = self.state();
let type_id = registry.type_id; let type_id = registry.type_id;
@@ -1027,7 +1074,7 @@ impl RawLua {
} }
self.register_userdata_metatable(mt_ptr, type_id); 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<()> { pub(crate) unsafe fn push_userdata_metatable(&self, mut registry: RawUserDataRegistry) -> Result<()> {
@@ -1584,6 +1631,11 @@ unsafe fn load_std_libs(state: *mut ffi::lua_State, libs: StdLib) -> Result<()>
requiref(state, ffi::LUA_VECLIBNAME, ffi::luaopen_vector, 1)?; 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) { if libs.contains(StdLib::MATH) {
requiref(state, ffi::LUA_MATHLIBNAME, ffi::luaopen_math, 1)?; requiref(state, ffi::LUA_MATHLIBNAME, ffi::luaopen_math, 1)?;
} }
+14 -2
View File
@@ -1,4 +1,4 @@
use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign}; use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Not};
/// Flags describing the set of lua standard libraries to load. /// Flags describing the set of lua standard libraries to load.
#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] #[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
@@ -73,10 +73,15 @@ impl StdLib {
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub const VECTOR: StdLib = StdLib(1 << 10); 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 /// [`jit`](http://luajit.org/ext_jit.html) library
#[cfg(any(feature = "luajit", doc))] #[cfg(any(feature = "luajit", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luajit")))] #[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 /// (**unsafe**) [`ffi`](http://luajit.org/ext_ffi.html) library
#[cfg(any(feature = "luajit", doc))] #[cfg(any(feature = "luajit", doc))]
@@ -139,3 +144,10 @@ impl BitXorAssign for StdLib {
*self = StdLib(self.0 ^ rhs.0) *self = StdLib(self.0 ^ rhs.0)
} }
} }
impl Not for StdLib {
type Output = Self;
fn not(self) -> Self::Output {
StdLib(!self.0)
}
}
+56 -49
View File
@@ -1,8 +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::hash::{Hash, Hasher};
use std::ops::Deref; use std::ops::Deref;
use std::os::raw::{c_int, c_void}; use std::os::raw::{c_int, c_void};
use std::{cmp, fmt, slice, str}; use std::{cmp, fmt, mem, slice, str};
use crate::error::{Error, Result}; use crate::error::{Error, Result};
use crate::state::Lua; use crate::state::Lua;
@@ -19,12 +23,15 @@ use {
/// Handle to an internal Lua string. /// Handle to an internal Lua string.
/// ///
/// Unlike Rust strings, Lua strings may not be valid UTF-8. /// Unlike Rust strings, Lua strings may not be valid UTF-8.
#[derive(Clone)] #[derive(Clone, PartialEq)]
pub struct LuaString(pub(crate) ValueRef); pub struct LuaString(pub(crate) ValueRef);
impl LuaString { impl LuaString {
/// Get a [`BorrowedStr`] if the Lua string is valid UTF-8. /// 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 /// # Examples
/// ///
/// ``` /// ```
@@ -42,7 +49,7 @@ impl LuaString {
/// # } /// # }
/// ``` /// ```
#[inline] #[inline]
pub fn to_str(&self) -> Result<BorrowedStr<'_>> { pub fn to_str(&self) -> Result<BorrowedStr> {
BorrowedStr::try_from(self) BorrowedStr::try_from(self)
} }
@@ -85,8 +92,9 @@ impl LuaString {
/// Get the bytes that make up this string. /// Get the bytes that make up this string.
/// ///
/// The returned slice will not contain the terminating null byte, but will contain any null /// The returned `BorrowedStr` holds a strong reference to the Lua state to guarantee the
/// bytes embedded into the Lua string. /// 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 /// # Examples
/// ///
@@ -101,16 +109,16 @@ impl LuaString {
/// # } /// # }
/// ``` /// ```
#[inline] #[inline]
pub fn as_bytes(&self) -> BorrowedBytes<'_> { pub fn as_bytes(&self) -> BorrowedBytes {
BorrowedBytes::from(self) BorrowedBytes::from(self)
} }
/// Get the bytes that make up this string, including the trailing null byte. /// Get the bytes that make up this string, including the trailing null byte.
pub fn as_bytes_with_nul(&self) -> BorrowedBytes<'_> { pub fn as_bytes_with_nul(&self) -> BorrowedBytes {
let BorrowedBytes { buf, borrow, _lua } = BorrowedBytes::from(self); let BorrowedBytes { buf, vref, _lua } = BorrowedBytes::from(self);
// Include the trailing null byte (it's always present but excluded by default) // 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) }; 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 // Does not return the terminating null byte
@@ -141,7 +149,10 @@ impl LuaString {
/// Typically this function is used only for hashing and debug information. /// Typically this function is used only for hashing and debug information.
#[inline] #[inline]
pub fn to_pointer(&self) -> *const c_void { pub fn to_pointer(&self) -> *const c_void {
self.0.to_pointer() // In Lua < 5.4 (excluding Luau), string pointers are NULL
// Use alternative approach
let lua = self.0.lua.lock();
unsafe { ffi::lua_tostring(lua.ref_thread(), self.0.index) as *const c_void }
} }
} }
@@ -175,12 +186,6 @@ where
} }
} }
impl PartialEq for LuaString {
fn eq(&self, other: &LuaString) -> bool {
self.as_bytes() == other.as_bytes()
}
}
impl Eq for LuaString {} impl Eq for LuaString {}
impl<T> PartialOrd<T> for LuaString impl<T> PartialOrd<T> for LuaString
@@ -233,14 +238,14 @@ impl fmt::Display for Display<'_> {
} }
/// A borrowed string (`&str`) that holds a strong reference to the Lua state. /// 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 // `buf` points to a readonly memory managed by Lua
pub(crate) buf: &'a str, pub(crate) buf: &'static str,
pub(crate) borrow: Cow<'a, LuaString>, pub(crate) vref: ValueRef,
pub(crate) _lua: Lua, pub(crate) _lua: Lua,
} }
impl Deref for BorrowedStr<'_> { impl Deref for BorrowedStr {
type Target = str; type Target = str;
#[inline(always)] #[inline(always)]
@@ -249,33 +254,33 @@ impl Deref for BorrowedStr<'_> {
} }
} }
impl Borrow<str> for BorrowedStr<'_> { impl Borrow<str> for BorrowedStr {
#[inline(always)] #[inline(always)]
fn borrow(&self) -> &str { fn borrow(&self) -> &str {
self.buf self.buf
} }
} }
impl AsRef<str> for BorrowedStr<'_> { impl AsRef<str> for BorrowedStr {
#[inline(always)] #[inline(always)]
fn as_ref(&self) -> &str { fn as_ref(&self) -> &str {
self.buf self.buf
} }
} }
impl fmt::Display for BorrowedStr<'_> { impl fmt::Display for BorrowedStr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.buf.fmt(f) self.buf.fmt(f)
} }
} }
impl fmt::Debug for BorrowedStr<'_> { impl fmt::Debug for BorrowedStr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.buf.fmt(f) self.buf.fmt(f)
} }
} }
impl<T> PartialEq<T> for BorrowedStr<'_> impl<T> PartialEq<T> for BorrowedStr
where where
T: AsRef<str>, T: AsRef<str>,
{ {
@@ -284,9 +289,9 @@ where
} }
} }
impl Eq for BorrowedStr<'_> {} impl Eq for BorrowedStr {}
impl<T> PartialOrd<T> for BorrowedStr<'_> impl<T> PartialOrd<T> for BorrowedStr
where where
T: AsRef<str>, T: AsRef<str>,
{ {
@@ -295,33 +300,33 @@ where
} }
} }
impl Ord for BorrowedStr<'_> { impl Ord for BorrowedStr {
fn cmp(&self, other: &Self) -> cmp::Ordering { fn cmp(&self, other: &Self) -> cmp::Ordering {
self.buf.cmp(other.buf) self.buf.cmp(other.buf)
} }
} }
impl<'a> TryFrom<&'a LuaString> for BorrowedStr<'a> { impl TryFrom<&LuaString> for BorrowedStr {
type Error = Error; type Error = Error;
#[inline] #[inline]
fn try_from(value: &'a LuaString) -> Result<Self> { fn try_from(value: &LuaString) -> Result<Self> {
let BorrowedBytes { buf, borrow, _lua } = BorrowedBytes::from(value); let BorrowedBytes { buf, vref, _lua } = BorrowedBytes::from(value);
let buf = let buf =
str::from_utf8(buf).map_err(|e| Error::from_lua_conversion("string", "&str", e.to_string()))?; str::from_utf8(buf).map_err(|e| Error::from_lua_conversion("string", "&str", e.to_string()))?;
Ok(Self { buf, borrow, _lua }) Ok(Self { buf, vref, _lua })
} }
} }
/// A borrowed byte slice (`&[u8]`) that holds a strong reference to the Lua state. /// 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 // `buf` points to a readonly memory managed by Lua
pub(crate) buf: &'a [u8], pub(crate) buf: &'static [u8],
pub(crate) borrow: Cow<'a, LuaString>, pub(crate) vref: ValueRef,
pub(crate) _lua: Lua, pub(crate) _lua: Lua,
} }
impl Deref for BorrowedBytes<'_> { impl Deref for BorrowedBytes {
type Target = [u8]; type Target = [u8];
#[inline(always)] #[inline(always)]
@@ -330,27 +335,27 @@ impl Deref for BorrowedBytes<'_> {
} }
} }
impl Borrow<[u8]> for BorrowedBytes<'_> { impl Borrow<[u8]> for BorrowedBytes {
#[inline(always)] #[inline(always)]
fn borrow(&self) -> &[u8] { fn borrow(&self) -> &[u8] {
self.buf self.buf
} }
} }
impl AsRef<[u8]> for BorrowedBytes<'_> { impl AsRef<[u8]> for BorrowedBytes {
#[inline(always)] #[inline(always)]
fn as_ref(&self) -> &[u8] { fn as_ref(&self) -> &[u8] {
self.buf self.buf
} }
} }
impl fmt::Debug for BorrowedBytes<'_> { impl fmt::Debug for BorrowedBytes {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.buf.fmt(f) self.buf.fmt(f)
} }
} }
impl<T> PartialEq<T> for BorrowedBytes<'_> impl<T> PartialEq<T> for BorrowedBytes
where where
T: AsRef<[u8]>, T: AsRef<[u8]>,
{ {
@@ -359,9 +364,9 @@ where
} }
} }
impl Eq for BorrowedBytes<'_> {} impl Eq for BorrowedBytes {}
impl<T> PartialOrd<T> for BorrowedBytes<'_> impl<T> PartialOrd<T> for BorrowedBytes
where where
T: AsRef<[u8]>, T: AsRef<[u8]>,
{ {
@@ -370,13 +375,13 @@ where
} }
} }
impl Ord for BorrowedBytes<'_> { impl Ord for BorrowedBytes {
fn cmp(&self, other: &Self) -> cmp::Ordering { fn cmp(&self, other: &Self) -> cmp::Ordering {
self.buf.cmp(other.buf) self.buf.cmp(other.buf)
} }
} }
impl<'a> IntoIterator for &'a BorrowedBytes<'_> { impl<'a> IntoIterator for &'a BorrowedBytes {
type Item = &'a u8; type Item = &'a u8;
type IntoIter = slice::Iter<'a, u8>; type IntoIter = slice::Iter<'a, u8>;
@@ -385,12 +390,14 @@ impl<'a> IntoIterator for &'a BorrowedBytes<'_> {
} }
} }
impl<'a> From<&'a LuaString> for BorrowedBytes<'a> { impl From<&LuaString> for BorrowedBytes {
#[inline] #[inline]
fn from(value: &'a LuaString) -> Self { fn from(value: &LuaString) -> Self {
let (buf, _lua) = unsafe { value.to_slice() }; let (buf, _lua) = unsafe { value.to_slice() };
let borrow = Cow::Borrowed(value); let vref = value.0.clone();
Self { buf, borrow, _lua } // 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 }
} }
} }
+2 -11
View File
@@ -3,12 +3,6 @@
//! Tables are Lua's primary data structure, used for arrays, dictionaries, objects, modules, //! 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. //! and more. This module provides types for creating and manipulating Lua tables from Rust.
//! //!
//! # Main Types
//!
//! - [`Table`] - A handle to a Lua table.
//! - [`TablePairs`] - An iterator over key-value pairs in a table.
//! - [`TableSequence`] - An iterator over the array (sequence) portion of a table.
//!
//! # Basic Operations //! # Basic Operations
//! //!
//! Tables support key-value access similar to Rust's `HashMap`: //! Tables support key-value access similar to Rust's `HashMap`:
@@ -784,10 +778,8 @@ impl Table {
ffi::lua_pushnil(state); ffi::lua_pushnil(state);
while ffi::lua_next(state, -2) != 0 { while ffi::lua_next(state, -2) != 0 {
let k = K::from_stack(-2, &lua)?; let k = K::from_stack(-2, &lua)?;
let v = V::from_stack(-1, &lua)?; let v = lua.pop::<V>()?;
f(k, v)?; f(k, v)?;
// Keep key for next iteration
ffi::lua_pop(state, 1);
} }
} }
Ok(()) Ok(())
@@ -860,8 +852,7 @@ impl Table {
if len.is_none() && t == ffi::LUA_TNIL { if len.is_none() && t == ffi::LUA_TNIL {
break; break;
} }
f(V::from_stack(-1, &lua)?)?; f(lua.pop::<V>()?)?;
ffi::lua_pop(state, 1);
} }
} }
Ok(()) Ok(())
+216 -13
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::fmt;
use std::os::raw::{c_int, c_void}; use std::os::raw::{c_int, c_void};
@@ -5,7 +42,7 @@ use crate::error::{Error, Result};
use crate::function::Function; use crate::function::Function;
use crate::state::RawLua; use crate::state::RawLua;
use crate::traits::{FromLuaMulti, IntoLuaMulti}; use crate::traits::{FromLuaMulti, IntoLuaMulti};
use crate::types::{LuaType, ValueRef}; use crate::types::{LuaType, ValueRef, XRc};
use crate::util::{StackGuard, check_stack, error_traceback_thread, pop_error}; use crate::util::{StackGuard, check_stack, error_traceback_thread, pop_error};
#[cfg(not(feature = "luau"))] #[cfg(not(feature = "luau"))]
@@ -26,6 +63,85 @@ use {
}, },
}; };
/// Controls which thread lifecycle events trigger the callback.
#[derive(Clone, Copy, Debug, Default)]
#[non_exhaustive]
pub struct ThreadTriggers {
/// Trigger the callback when a new thread is created.
pub on_create: bool,
/// Trigger the callback before a thread is resumed (via [`Thread::resume`]).
pub on_resume: bool,
/// Trigger the callback after a thread yields.
pub on_yield: bool,
}
impl ThreadTriggers {
/// An instance of [`ThreadTriggers`] with `on_create` trigger set.
pub const ON_CREATE: Self = Self::new().on_create();
/// An instance of [`ThreadTriggers`] with `on_resume` trigger set.
pub const ON_RESUME: Self = Self::new().on_resume();
/// An instance of [`ThreadTriggers`] with `on_yield` trigger set.
pub const ON_YIELD: Self = Self::new().on_yield();
/// Returns a new instance of `ThreadTriggers` with all triggers disabled.
pub const fn new() -> Self {
Self {
on_create: false,
on_resume: false,
on_yield: false,
}
}
/// Returns an instance of `ThreadTriggers` with `on_create` trigger set.
pub const fn on_create(mut self) -> Self {
self.on_create = true;
self
}
/// Returns an instance of `ThreadTriggers` with `on_resume` trigger set.
pub const fn on_resume(mut self) -> Self {
self.on_resume = true;
self
}
/// Returns an instance of `ThreadTriggers` with `on_yield` trigger set.
pub const fn on_yield(mut self) -> Self {
self.on_yield = true;
self
}
}
impl std::ops::BitOr for ThreadTriggers {
type Output = Self;
fn bitor(mut self, rhs: Self) -> Self::Output {
self.on_create |= rhs.on_create;
self.on_resume |= rhs.on_resume;
self.on_yield |= rhs.on_yield;
self
}
}
impl std::ops::BitOrAssign for ThreadTriggers {
fn bitor_assign(&mut self, rhs: Self) {
*self = *self | rhs;
}
}
/// Represents a thread (coroutine) event.
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum ThreadEvent {
/// A new thread was created.
Create(Thread),
/// A thread is about to be resumed via [`Thread::resume`].
Resume(Thread),
/// A thread has just yielded.
Yield(Thread),
}
/// Status of a Lua thread (coroutine). /// Status of a Lua thread (coroutine).
#[derive(Debug, Copy, Clone, Eq, PartialEq)] #[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum ThreadStatus { pub enum ThreadStatus {
@@ -61,7 +177,6 @@ impl ThreadStatusInner {
matches!(self, ThreadStatusInner::New(_) | ThreadStatusInner::Yielded(_)) matches!(self, ThreadStatusInner::New(_) | ThreadStatusInner::Yielded(_))
} }
#[cfg(feature = "async")]
#[inline(always)] #[inline(always)]
fn is_yielded(self) -> bool { fn is_yielded(self) -> bool {
matches!(self, ThreadStatusInner::Yielded(_)) matches!(self, ThreadStatusInner::Yielded(_))
@@ -69,7 +184,7 @@ impl ThreadStatusInner {
} }
/// Handle to an internal Lua thread (coroutine). /// Handle to an internal Lua thread (coroutine).
#[derive(Clone)] #[derive(Clone, PartialEq)]
pub struct Thread(pub(crate) ValueRef, pub(crate) *mut ffi::lua_State); pub struct Thread(pub(crate) ValueRef, pub(crate) *mut ffi::lua_State);
#[cfg(feature = "send")] #[cfg(feature = "send")]
@@ -92,7 +207,6 @@ pub struct AsyncThread<R> {
impl Thread { impl Thread {
/// Returns reference to the Lua state that this thread is associated with. /// Returns reference to the Lua state that this thread is associated with.
#[doc(hidden)]
#[inline(always)] #[inline(always)]
pub fn state(&self) -> *mut ffi::lua_State { pub fn state(&self) -> *mut ffi::lua_State {
self.1 self.1
@@ -157,6 +271,14 @@ impl Thread {
unsafe { unsafe {
let _sg = StackGuard::new(state); let _sg = StackGuard::new(state);
// Exec thread resume callback
if lua.thread_event_triggers().on_resume
&& let Some(cb) = lua.thread_event_callback()
&& XRc::strong_count(&cb) <= 2
{
cb(lua.lua(), ThreadEvent::Resume(self.clone()))?;
}
let nargs = args.push_into_stack_multi(&lua)?; let nargs = args.push_into_stack_multi(&lua)?;
if nargs > 0 { if nargs > 0 {
check_stack(thread_state, nargs)?; check_stack(thread_state, nargs)?;
@@ -165,7 +287,17 @@ impl Thread {
} }
let _thread_sg = StackGuard::with_top(thread_state, 0); let _thread_sg = StackGuard::with_top(thread_state, 0);
let (_, nresults) = self.resume_inner(&lua, pushed_nargs)?; let (status, nresults) = self.resume_inner(&lua, pushed_nargs)?;
// Exec thread yield callback
if lua.thread_event_triggers().on_yield
&& status.is_yielded()
&& let Some(cb) = lua.thread_event_callback()
&& XRc::strong_count(&cb) <= 2
{
cb(lua.lua(), ThreadEvent::Yield(self.clone()))?;
}
check_stack(state, nresults + 1)?; check_stack(state, nresults + 1)?;
ffi::lua_xmove(thread_state, state, nresults); ffi::lua_xmove(thread_state, state, nresults);
@@ -193,12 +325,30 @@ impl Thread {
unsafe { unsafe {
let _sg = StackGuard::new(state); let _sg = StackGuard::new(state);
// Exec thread resume callback
if lua.thread_event_triggers().on_resume
&& let Some(cb) = lua.thread_event_callback()
&& XRc::strong_count(&cb) <= 2
{
cb(lua.lua(), ThreadEvent::Resume(self.clone()))?;
}
check_stack(state, 1)?; check_stack(state, 1)?;
error.push_into_stack(&lua)?; error.push_into_stack(&lua)?;
ffi::lua_xmove(state, thread_state, 1); ffi::lua_xmove(state, thread_state, 1);
let _thread_sg = StackGuard::with_top(thread_state, 0); let _thread_sg = StackGuard::with_top(thread_state, 0);
let (_, nresults) = self.resume_inner(&lua, ffi::LUA_RESUMEERROR)?; let (status, nresults) = self.resume_inner(&lua, ffi::LUA_RESUMEERROR)?;
// Exec thread yield callback
if lua.thread_event_triggers().on_yield
&& status.is_yielded()
&& let Some(cb) = lua.thread_event_callback()
&& XRc::strong_count(&cb) <= 2
{
cb(lua.lua(), ThreadEvent::Yield(self.clone()))?;
}
check_stack(state, nresults + 1)?; check_stack(state, nresults + 1)?;
ffi::lua_xmove(thread_state, state, nresults); ffi::lua_xmove(thread_state, state, nresults);
@@ -259,6 +409,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. /// 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. /// This function is similar or [`Lua::set_hook`] except that it sets for the thread.
@@ -295,7 +470,7 @@ impl Thread {
/// Resets a thread /// Resets a thread
/// ///
/// In [Lua 5.4]: cleans its call stack and closes all pending to-be-closed variables. /// 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 closing methods.
/// ///
/// In Luau: resets to the initial state of a newly created Lua thread. /// In Luau: resets to the initial state of a newly created Lua thread.
@@ -449,6 +624,8 @@ impl Thread {
/// Please note that Luau links environment table with chunk when loading it into Lua state. /// 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. /// Therefore you need to load chunks into a thread to link with the thread environment.
/// ///
/// [`Lua::sandbox`]: crate::Lua::sandbox
///
/// # Examples /// # Examples
/// ///
/// ``` /// ```
@@ -502,12 +679,6 @@ impl fmt::Debug for Thread {
} }
} }
impl PartialEq for Thread {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl LuaType for Thread { impl LuaType for Thread {
const TYPE_ID: c_int = ffi::LUA_TTHREAD; const TYPE_ID: c_int = ffi::LUA_TTHREAD;
} }
@@ -565,9 +736,25 @@ impl<R: FromLuaMulti> Stream for AsyncThread<R> {
let _thread_sg = StackGuard::with_top(thread_state, 0); let _thread_sg = StackGuard::with_top(thread_state, 0);
let _wg = WakerGuard::new(&lua, cx.waker()); let _wg = WakerGuard::new(&lua, cx.waker());
// Exec thread resume callback
if lua.thread_event_triggers().on_resume
&& let Some(cb) = lua.thread_event_callback()
&& XRc::strong_count(&cb) <= 2
{
cb(lua.lua(), ThreadEvent::Resume(self.thread.clone()))?;
}
let (status, nresults) = (self.thread).resume_inner(&lua, nargs)?; let (status, nresults) = (self.thread).resume_inner(&lua, nargs)?;
if status.is_yielded() { if status.is_yielded() {
// Exec thread yield callback
if lua.thread_event_triggers().on_yield
&& let Some(cb) = lua.thread_event_callback()
&& XRc::strong_count(&cb) <= 2
{
cb(lua.lua(), ThreadEvent::Yield(self.thread.clone()))?;
}
if nresults == 1 && is_poll_pending(thread_state) { if nresults == 1 && is_poll_pending(thread_state) {
return Poll::Pending; return Poll::Pending;
} }
@@ -601,9 +788,25 @@ impl<R: FromLuaMulti> Future for AsyncThread<R> {
let _thread_sg = StackGuard::with_top(thread_state, 0); let _thread_sg = StackGuard::with_top(thread_state, 0);
let _wg = WakerGuard::new(&lua, cx.waker()); let _wg = WakerGuard::new(&lua, cx.waker());
// Exec thread resume callback
if lua.thread_event_triggers().on_resume
&& let Some(cb) = lua.thread_event_callback()
&& XRc::strong_count(&cb) <= 2
{
cb(lua.lua(), ThreadEvent::Resume(self.thread.clone()))?;
}
let (status, nresults) = self.thread.resume_inner(&lua, nargs)?; let (status, nresults) = self.thread.resume_inner(&lua, nargs)?;
if status.is_yielded() { if status.is_yielded() {
// Exec thread yield callback
if lua.thread_event_triggers().on_yield
&& let Some(cb) = lua.thread_event_callback()
&& XRc::strong_count(&cb) <= 2
{
cb(lua.lua(), ThreadEvent::Yield(self.thread.clone()))?;
}
if !(nresults == 1 && is_poll_pending(thread_state)) { if !(nresults == 1 && is_poll_pending(thread_state)) {
// Ignore values returned via yield() // Ignore values returned via yield()
cx.waker().wake_by_ref(); cx.waker().wake_by_ref();
+6 -93
View File
@@ -1,3 +1,8 @@
//! 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::os::raw::c_int;
use std::sync::Arc; use std::sync::Arc;
@@ -5,12 +10,11 @@ use crate::error::{Error, Result};
use crate::multi::MultiValue; use crate::multi::MultiValue;
use crate::private::Sealed; use crate::private::Sealed;
use crate::state::{Lua, RawLua, WeakLua}; use crate::state::{Lua, RawLua, WeakLua};
use crate::types::MaybeSend;
use crate::util::{check_stack, parse_lookup_path, short_type_name}; use crate::util::{check_stack, parse_lookup_path, short_type_name};
use crate::value::Value; use crate::value::Value;
#[cfg(feature = "async")] #[cfg(feature = "async")]
use {crate::function::AsyncCallFuture, std::future::Future}; use crate::function::AsyncCallFuture;
/// Trait for types convertible to [`Value`]. /// Trait for types convertible to [`Value`].
pub trait IntoLua: Sized { pub trait IntoLua: Sized {
@@ -245,97 +249,6 @@ pub trait ObjectLike: Sealed {
fn weak_lua(&self) -> &WeakLua; 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 { pub(crate) trait ShortTypeName {
#[inline(always)] #[inline(always)]
fn type_name() -> String { fn type_name() -> String {
+16 -10
View File
@@ -96,17 +96,11 @@ pub(crate) type InterruptCallback = XRc<dyn Fn(&Lua) -> Result<VmState> + Send>;
#[cfg(all(not(feature = "send"), feature = "luau"))] #[cfg(all(not(feature = "send"), feature = "luau"))]
pub(crate) type InterruptCallback = XRc<dyn Fn(&Lua) -> Result<VmState>>; pub(crate) type InterruptCallback = XRc<dyn Fn(&Lua) -> Result<VmState>>;
#[cfg(all(feature = "send", feature = "luau"))] #[cfg(feature = "send")]
pub(crate) type ThreadCreationCallback = XRc<dyn Fn(&Lua, crate::Thread) -> Result<()> + Send>; pub(crate) type ThreadEventCallback = XRc<dyn Fn(&Lua, crate::thread::ThreadEvent) -> Result<()> + Send>;
#[cfg(all(not(feature = "send"), feature = "luau"))] #[cfg(not(feature = "send"))]
pub(crate) type ThreadCreationCallback = XRc<dyn Fn(&Lua, crate::Thread) -> Result<()>>; pub(crate) type ThreadEventCallback = XRc<dyn Fn(&Lua, crate::thread::ThreadEvent) -> Result<()>>;
#[cfg(all(feature = "send", feature = "luau"))]
pub(crate) type ThreadCollectionCallback = XRc<dyn Fn(crate::LightUserData) + Send>;
#[cfg(all(not(feature = "send"), feature = "luau"))]
pub(crate) type ThreadCollectionCallback = XRc<dyn Fn(crate::LightUserData)>;
#[cfg(feature = "send")] #[cfg(feature = "send")]
#[cfg(any(feature = "lua55", feature = "lua54"))] #[cfg(any(feature = "lua55", feature = "lua54"))]
@@ -128,6 +122,18 @@ pub trait MaybeSend {}
#[cfg(not(feature = "send"))] #[cfg(not(feature = "send"))]
impl<T> MaybeSend for T {} 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) struct DestructedUserdata;
pub(crate) trait LuaType { pub(crate) trait LuaType {
+62 -11
View File
@@ -1,16 +1,21 @@
//! Lua userdata handling.
//!
//! This module provides types for creating and working with Lua userdata from Rust.
use std::any::TypeId; use std::any::TypeId;
use std::ffi::CStr; use std::ffi::CStr;
use std::fmt; use std::fmt;
use std::hash::Hash; use std::hash::Hash;
use std::os::raw::{c_char, c_void}; use std::os::raw::{c_char, c_void};
use crate::Either;
use crate::error::{Error, Result}; use crate::error::{Error, Result};
use crate::function::Function; use crate::function::Function;
use crate::state::Lua; use crate::state::Lua;
use crate::string::LuaString; use crate::string::LuaString;
use crate::table::{Table, TablePairs}; use crate::table::{Table, TablePairs};
use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti}; use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti};
use crate::types::{MaybeSend, ValueRef}; use crate::types::{MaybeSend, MaybeSync, ValueRef};
use crate::util::{StackGuard, check_stack, get_userdata, push_string, short_type_name, take_userdata}; use crate::util::{StackGuard, check_stack, get_userdata, push_string, short_type_name, take_userdata};
use crate::value::Value; use crate::value::Value;
@@ -25,7 +30,7 @@ use {
// Re-export for convenience // Re-export for convenience
pub(crate) use cell::UserDataStorage; pub(crate) use cell::UserDataStorage;
pub use r#ref::{UserDataRef, UserDataRefMut}; pub use r#ref::{UserDataOwned, UserDataRef, UserDataRefMut};
pub use registry::UserDataRegistry; pub use registry::UserDataRegistry;
pub(crate) use registry::{RawUserDataRegistry, UserDataProxy}; pub(crate) use registry::{RawUserDataRegistry, UserDataProxy};
pub(crate) use util::{ pub(crate) use util::{
@@ -123,6 +128,11 @@ pub enum MetaMethod {
/// ///
/// This is not an operator, but will be called by methods such as `tostring` and `print`. /// This is not an operator, but will be called by methods such as `tostring` and `print`.
ToString, 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. /// The `__pairs` metamethod.
/// ///
/// This is not an operator, but it will be called by the built-in `pairs` function. /// This is not an operator, but it will be called by the built-in `pairs` function.
@@ -232,6 +242,7 @@ impl MetaMethod {
MetaMethod::NewIndex => "__newindex", MetaMethod::NewIndex => "__newindex",
MetaMethod::Call => "__call", MetaMethod::Call => "__call",
MetaMethod::ToString => "__tostring", MetaMethod::ToString => "__tostring",
MetaMethod::ToDebugString => "__todebugstring",
#[cfg(any( #[cfg(any(
feature = "lua55", feature = "lua55",
@@ -318,7 +329,6 @@ pub trait UserDataMethods<T> {
/// ///
/// The method can be called only once per userdata instance, subsequent calls will result in a /// The method can be called only once per userdata instance, subsequent calls will result in a
/// [`Error::UserDataDestructed`] error. /// [`Error::UserDataDestructed`] error.
#[doc(hidden)]
fn add_method_once<M, A, R>(&mut self, name: impl Into<String>, method: M) fn add_method_once<M, A, R>(&mut self, name: impl Into<String>, method: M)
where where
T: 'static, T: 'static,
@@ -373,7 +383,6 @@ pub trait UserDataMethods<T> {
/// [`Error::UserDataDestructed`] error. /// [`Error::UserDataDestructed`] error.
#[cfg(feature = "async")] #[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(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<String>, method: M) fn add_async_method_once<M, A, MR, R>(&mut self, name: impl Into<String>, method: M)
where where
T: 'static, T: 'static,
@@ -705,7 +714,7 @@ pub trait UserData: Sized {
/// ///
/// [`is`]: crate::AnyUserData::is /// [`is`]: crate::AnyUserData::is
/// [`borrow`]: crate::AnyUserData::borrow /// [`borrow`]: crate::AnyUserData::borrow
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, PartialEq)]
pub struct AnyUserData(pub(crate) ValueRef); pub struct AnyUserData(pub(crate) ValueRef);
impl AnyUserData { impl AnyUserData {
@@ -1020,8 +1029,8 @@ impl AnyUserData {
/// Returns a type name of this userdata (from a metatable field). /// Returns a type name of this userdata (from a metatable field).
/// ///
/// If no type name is set, returns `None`. /// If no type name is set, returns `userdata`.
pub fn type_name(&self) -> Result<Option<String>> { pub fn type_name(&self) -> Result<LuaString> {
let lua = self.0.lua.lock(); let lua = self.0.lua.lock();
let state = lua.state(); let state = lua.state();
unsafe { unsafe {
@@ -1038,8 +1047,8 @@ impl AnyUserData {
ffi::luaL_getmetafield(state, -1, MetaMethod::Type.as_cstr().as_ptr()) ffi::luaL_getmetafield(state, -1, MetaMethod::Type.as_cstr().as_ptr())
}; };
match name_type { match name_type {
ffi::LUA_TSTRING => Ok(Some(LuaString(lua.pop_ref()).to_str()?.to_owned())), ffi::LUA_TSTRING => Ok(LuaString(lua.pop_ref())),
_ => Ok(None), _ => lua.create_string(b"userdata"),
} }
} }
} }
@@ -1075,6 +1084,48 @@ impl AnyUserData {
}; };
is_serializable().unwrap_or(false) 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. /// Handle to a [`AnyUserData`] metatable.
@@ -1171,7 +1222,7 @@ impl AnyUserData {
/// Wraps any Rust type, returning an opaque type that implements [`IntoLua`] trait. /// Wraps any Rust type, returning an opaque type that implements [`IntoLua`] trait.
/// ///
/// This function uses [`Lua::create_any_userdata`] under the hood. /// This function uses [`Lua::create_any_userdata`] under the hood.
pub fn wrap<T: MaybeSend + 'static>(data: T) -> impl IntoLua { pub fn wrap<T: MaybeSend + MaybeSync + 'static>(data: T) -> impl IntoLua {
WrappedUserdata(move |lua| lua.create_any_userdata(data)) WrappedUserdata(move |lua| lua.create_any_userdata(data))
} }
@@ -1181,7 +1232,7 @@ impl AnyUserData {
/// This function uses [`Lua::create_ser_any_userdata`] under the hood. /// This function uses [`Lua::create_ser_any_userdata`] under the hood.
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(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)) WrappedUserdata(move |lua| lua.create_ser_any_userdata(data))
} }
} }
+29 -61
View File
@@ -1,4 +1,4 @@
use std::cell::{RefCell, UnsafeCell}; use std::cell::RefCell;
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde::ser::{Serialize, Serializer}; use serde::ser::{Serialize, Serializer};
@@ -6,14 +6,14 @@ use serde::ser::{Serialize, Serializer};
use crate::error::{Error, Result}; use crate::error::{Error, Result};
use crate::types::XRc; use crate::types::XRc;
use super::lock::{RawLock, UserDataLock}; use super::lock::{RawLock, RwLock, UserDataLock};
use super::r#ref::{UserDataRef, UserDataRefMut}; use super::r#ref::{UserDataRef, UserDataRefMut};
#[cfg(all(feature = "serde", not(feature = "send")))] #[cfg(all(feature = "serde", not(feature = "send")))]
type DynSerialize = dyn erased_serde::Serialize; type DynSerialize = dyn erased_serde::Serialize;
#[cfg(all(feature = "serde", feature = "send"))] #[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> { pub(crate) enum UserDataStorage<T> {
Owned(UserDataVariant<T>), Owned(UserDataVariant<T>),
@@ -23,9 +23,9 @@ pub(crate) enum UserDataStorage<T> {
// A enum for storing userdata values. // A enum for storing userdata values.
// It's stored inside a Lua VM and protected by the outer `ReentrantMutex`. // It's stored inside a Lua VM and protected by the outer `ReentrantMutex`.
pub(crate) enum UserDataVariant<T> { pub(crate) enum UserDataVariant<T> {
Default(XRc<UserDataCell<T>>), Default(XRc<RwLock<T>>),
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
Serializable(XRc<UserDataCell<Box<DynSerialize>>>, bool), // bool is `is_sync` Serializable(XRc<RwLock<Box<DynSerialize>>>),
} }
impl<T> Clone for UserDataVariant<T> { impl<T> Clone for UserDataVariant<T> {
@@ -34,7 +34,7 @@ impl<T> Clone for UserDataVariant<T> {
match self { match self {
Self::Default(inner) => Self::Default(XRc::clone(inner)), Self::Default(inner) => Self::Default(XRc::clone(inner)),
#[cfg(feature = "serde")] #[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> { impl<T> UserDataVariant<T> {
#[inline(always)] #[inline(always)]
pub(super) fn try_borrow_scoped<R>(&self, f: impl FnOnce(&T) -> R) -> Result<R> { 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), // Shared (read) lock is always correct for in-place borrows:
// Lua mutex is already locked. // - this method is called internally while the Lua mutex is held, ensuring exclusive Lua-level
// If non-`Sync` userdata is already borrowed by another thread (via `UserDataRef`), it will be // access per call frame
// exclusively locked. // - 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)?; let _guard = (self.raw_lock().try_lock_shared_guarded()).map_err(|_| Error::UserDataBorrowError)?;
Ok(f(unsafe { &*self.as_ptr() })) Ok(f(unsafe { &*self.as_ptr() }))
} }
@@ -78,10 +80,12 @@ impl<T> UserDataVariant<T> {
return Err(Error::UserDataBorrowMutError); return Err(Error::UserDataBorrowMutError);
} }
Ok(match self { 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")] #[cfg(feature = "serde")]
Self::Serializable(inner, _) => unsafe { Self::Serializable(inner) => unsafe {
let raw = Box::into_raw(XRc::into_inner(inner).unwrap().value.into_inner()); // 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) *Box::from_raw(raw as *mut T)
}, },
}) })
@@ -92,25 +96,25 @@ impl<T> UserDataVariant<T> {
match self { match self {
Self::Default(inner) => XRc::strong_count(inner), Self::Default(inner) => XRc::strong_count(inner),
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
Self::Serializable(inner, _) => XRc::strong_count(inner), Self::Serializable(inner) => XRc::strong_count(inner),
} }
} }
#[inline(always)] #[inline(always)]
pub(super) fn raw_lock(&self) -> &RawLock { pub(super) fn raw_lock(&self) -> &RawLock {
match self { match self {
Self::Default(inner) => &inner.raw_lock, Self::Default(inner) => unsafe { inner.raw() },
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
Self::Serializable(inner, _) => &inner.raw_lock, Self::Serializable(inner) => unsafe { inner.raw() },
} }
} }
#[inline(always)] #[inline(always)]
pub(super) fn as_ptr(&self) -> *mut T { pub(super) fn as_ptr(&self) -> *mut T {
match self { match self {
Self::Default(inner) => inner.value.get(), Self::Default(inner) => inner.data_ptr(),
#[cfg(feature = "serde")] #[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<()> { impl Serialize for UserDataStorage<()> {
fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> { fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
match self { match self {
Self::Owned(variant @ UserDataVariant::Serializable(inner, is_sync)) => unsafe { Self::Owned(variant @ UserDataVariant::Serializable(inner)) => unsafe {
#[cfg(feature = "send")] let _guard = (variant.raw_lock().try_lock_shared_guarded())
if *is_sync { .map_err(|_| serde::ser::Error::custom(Error::UserDataBorrowError))?;
let _guard = (variant.raw_lock().try_lock_shared_guarded()) (*inner.data_ptr()).serialize(serializer)
.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)
}
}, },
_ => Err(serde::ser::Error::custom("cannot serialize <userdata>")), _ => 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> { pub(crate) enum ScopedUserDataVariant<T> {
Ref(*const T), Ref(*const T),
RefMut(RefCell<*mut T>), RefMut(RefCell<*mut T>),
@@ -184,7 +153,7 @@ impl<T> Drop for ScopedUserDataVariant<T> {
impl<T: 'static> UserDataStorage<T> { impl<T: 'static> UserDataStorage<T> {
#[inline(always)] #[inline(always)]
pub(crate) fn new(data: T) -> Self { 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)] #[inline(always)]
@@ -201,11 +170,10 @@ impl<T: 'static> UserDataStorage<T> {
#[inline(always)] #[inline(always)]
pub(crate) fn new_ser(data: T) -> Self pub(crate) fn new_ser(data: T) -> Self
where where
T: Serialize + crate::types::MaybeSend, T: Serialize + crate::types::MaybeSend + crate::types::MaybeSync,
{ {
let data = Box::new(data) as Box<DynSerialize>; let data = Box::new(data) as Box<DynSerialize>;
let is_sync = super::util::is_sync::<T>(); let variant = UserDataVariant::Serializable(XRc::new(RwLock::new(data)));
let variant = UserDataVariant::Serializable(XRc::new(UserDataCell::new(data)), is_sync);
Self::Owned(variant) Self::Owned(variant)
} }
+44 -19
View File
@@ -1,6 +1,4 @@
pub(crate) trait UserDataLock { pub(crate) trait UserDataLock {
const INIT: Self;
fn is_locked(&self) -> bool; fn is_locked(&self) -> bool;
fn try_lock_shared(&self) -> bool; fn try_lock_shared(&self) -> bool;
fn try_lock_exclusive(&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(feature = "send"))]
#[cfg(not(tarpaulin_include))] #[cfg(not(tarpaulin_include))]
mod lock_impl { mod lock_impl {
use std::cell::Cell; use std::cell::{Cell, UnsafeCell};
// Positive values represent the number of read references. // Positive values represent the number of read references.
// Negative values represent the number of write references (only one allowed). // Negative values represent the number of write references (only one allowed).
@@ -62,9 +60,6 @@ mod lock_impl {
const UNUSED: isize = 0; const UNUSED: isize = 0;
impl super::UserDataLock for RawLock { impl super::UserDataLock for RawLock {
#[allow(clippy::declare_interior_mutable_const)]
const INIT: Self = Cell::new(UNUSED);
#[inline(always)] #[inline(always)]
fn is_locked(&self) -> bool { fn is_locked(&self) -> bool {
self.get() != UNUSED self.get() != UNUSED
@@ -72,7 +67,7 @@ mod lock_impl {
#[inline(always)] #[inline(always)]
fn try_lock_shared(&self) -> bool { 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 { if flag <= UNUSED {
return false; return false;
} }
@@ -104,41 +99,71 @@ mod lock_impl {
self.set(flag + 1); 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")] #[cfg(feature = "send")]
mod lock_impl { mod lock_impl {
use parking_lot::lock_api::RawRwLock; pub(crate) use parking_lot::{RawRwLock as RawLock, RwLock};
pub(crate) type RawLock = parking_lot::RawRwLock;
impl super::UserDataLock for RawLock { impl super::UserDataLock for RawLock {
#[allow(clippy::declare_interior_mutable_const)]
const INIT: Self = <Self as parking_lot::lock_api::RawRwLock>::INIT;
#[inline(always)] #[inline(always)]
fn is_locked(&self) -> bool { fn is_locked(&self) -> bool {
RawRwLock::is_locked(self) parking_lot::lock_api::RawRwLock::is_locked(self)
} }
#[inline(always)] #[inline(always)]
fn try_lock_shared(&self) -> bool { fn try_lock_shared(&self) -> bool {
RawRwLock::try_lock_shared(self) parking_lot::lock_api::RawRwLock::try_lock_shared(self)
} }
#[inline(always)] #[inline(always)]
fn try_lock_exclusive(&self) -> bool { fn try_lock_exclusive(&self) -> bool {
RawRwLock::try_lock_exclusive(self) parking_lot::lock_api::RawRwLock::try_lock_exclusive(self)
} }
#[inline(always)] #[inline(always)]
unsafe fn unlock_shared(&self) { unsafe fn unlock_shared(&self) {
RawRwLock::unlock_shared(self) parking_lot::lock_api::RawRwLock::unlock_shared(self)
} }
#[inline(always)] #[inline(always)]
unsafe fn unlock_exclusive(&self) { unsafe fn unlock_exclusive(&self) {
RawRwLock::unlock_exclusive(self) parking_lot::lock_api::RawRwLock::unlock_exclusive(self)
} }
} }
} }
+69 -7
View File
@@ -7,12 +7,11 @@ use crate::error::{Error, Result};
use crate::state::{Lua, RawLua}; use crate::state::{Lua, RawLua};
use crate::traits::FromLua; use crate::traits::FromLua;
use crate::userdata::AnyUserData; use crate::userdata::AnyUserData;
use crate::util::get_userdata; use crate::util::{check_stack, get_userdata, take_userdata};
use crate::value::Value; use crate::value::Value;
use super::cell::{UserDataStorage, UserDataVariant}; use super::cell::{UserDataStorage, UserDataVariant};
use super::lock::{LockGuard, RawLock, UserDataLock}; use super::lock::{LockGuard, RawLock, UserDataLock};
use super::util::is_sync;
#[cfg(feature = "userdata-wrappers")] #[cfg(feature = "userdata-wrappers")]
use { use {
@@ -63,11 +62,10 @@ impl<T> TryFrom<UserDataVariant<T>> for UserDataRef<T> {
#[inline] #[inline]
fn try_from(variant: UserDataVariant<T>) -> Result<Self> { fn try_from(variant: UserDataVariant<T>) -> Result<Self> {
let guard = if cfg!(not(feature = "send")) || is_sync::<T>() { // Shared (read) lock is always correct:
variant.raw_lock().try_lock_shared_guarded() // - with `send` feature, `T: Sync` is guaranteed by the `MaybeSync` bound on userdata creation
} else { // - without `send` feature, single-threaded access makes shared lock safe for any `T`
variant.raw_lock().try_lock_exclusive_guarded() let guard = variant.raw_lock().try_lock_shared_guarded();
};
let guard = guard.map_err(|_| Error::UserDataBorrowError)?; let guard = guard.map_err(|_| Error::UserDataBorrowError)?;
let guard = unsafe { mem::transmute::<LockGuard<_>, LockGuard<'static, _>>(guard) }; let guard = unsafe { mem::transmute::<LockGuard<_>, LockGuard<'static, _>>(guard) };
Ok(UserDataRef::from_parts(UserDataRefInner::Default(variant), guard)) Ok(UserDataRef::from_parts(UserDataRefInner::Default(variant), guard))
@@ -442,6 +440,66 @@ 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] #[inline]
fn try_value_to_userdata<T>(value: Value) -> Result<AnyUserData> { fn try_value_to_userdata<T>(value: Value) -> Result<AnyUserData> {
match value { match value {
@@ -466,6 +524,10 @@ mod assertions {
static_assertions::assert_impl_all!(UserDataRefMut<()>: Sync, Send); static_assertions::assert_impl_all!(UserDataRefMut<()>: Sync, Send);
#[cfg(feature = "send")] #[cfg(feature = "send")]
static_assertions::assert_not_impl_all!(UserDataRefMut<std::rc::Rc<()>>: Send, Sync); 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"))] #[cfg(not(feature = "send"))]
static_assertions::assert_not_impl_all!(UserDataRef<()>: Send, Sync); static_assertions::assert_not_impl_all!(UserDataRef<()>: Send, Sync);
+6
View File
@@ -654,6 +654,12 @@ macro_rules! lua_userdata_impl {
// A special proxy object for UserData // A special proxy object for UserData
pub(crate) struct UserDataProxy<T>(pub(crate) PhantomData<T>); 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>); lua_userdata_impl!(UserDataProxy<T>);
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
-31
View File
@@ -1,6 +1,4 @@
use std::any::TypeId; use std::any::TypeId;
use std::cell::Cell;
use std::marker::PhantomData;
use std::os::raw::c_int; use std::os::raw::c_int;
use std::ptr; use std::ptr;
@@ -11,35 +9,6 @@ use crate::error::{Error, Result};
use crate::types::CallbackPtr; use crate::types::CallbackPtr;
use crate::util::{get_userdata, rawget_field, rawset_field, take_userdata}; 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 // Userdata type hints, used to match types of wrapped userdata
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub(crate) struct TypeIdHints { pub(crate) struct TypeIdHints {
+1 -1
View File
@@ -197,7 +197,7 @@ where
F: FnOnce(*mut ffi::lua_State) -> R, F: FnOnce(*mut ffi::lua_State) -> R,
R: Copy, R: Copy,
{ {
struct Params<F, R: Copy> { struct Params<F, R> {
function: Option<F>, function: Option<F>,
result: MaybeUninit<R>, result: MaybeUninit<R>,
nresults: c_int, nresults: c_int,
+6 -19
View File
@@ -128,18 +128,13 @@ impl Value {
#[inline] #[inline]
pub fn to_pointer(&self) -> *const c_void { pub fn to_pointer(&self) -> *const c_void {
match self { match self {
Value::String(LuaString(vref)) => {
// In Lua < 5.4 (excluding Luau), string pointers are NULL
// Use alternative approach
let lua = vref.lua.lock();
unsafe { ffi::lua_tostring(lua.ref_thread(), vref.index) as *const c_void }
}
Value::LightUserData(ud) => ud.0, Value::LightUserData(ud) => ud.0,
Value::Table(Table(vref)) Value::Table(Table(vref))
| Value::Function(Function(vref)) | Value::Function(Function(vref))
| Value::Thread(Thread(vref, ..)) | Value::Thread(Thread(vref, ..))
| Value::UserData(AnyUserData(vref)) | Value::UserData(AnyUserData(vref))
| Value::Other(vref) => vref.to_pointer(), | Value::Other(vref) => vref.to_pointer(),
Value::String(s) => s.to_pointer(),
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
Value::Buffer(crate::Buffer(vref)) => vref.to_pointer(), Value::Buffer(crate::Buffer(vref)) => vref.to_pointer(),
_ => ptr::null(), _ => ptr::null(),
@@ -151,7 +146,7 @@ impl Value {
/// This might invoke the `__tostring` metamethod for non-primitive types (eg. tables, /// This might invoke the `__tostring` metamethod for non-primitive types (eg. tables,
/// functions). /// functions).
pub fn to_string(&self) -> Result<String> { pub fn to_string(&self) -> Result<String> {
unsafe fn invoke_to_string(vref: &ValueRef) -> Result<String> { unsafe fn invoke_tostring(vref: &ValueRef) -> Result<String> {
let lua = vref.lua.lock(); let lua = vref.lua.lock();
let state = lua.state(); let state = lua.state();
let _guard = StackGuard::new(state); let _guard = StackGuard::new(state);
@@ -178,9 +173,9 @@ impl Value {
| Value::Function(Function(vref)) | Value::Function(Function(vref))
| Value::Thread(Thread(vref, ..)) | Value::Thread(Thread(vref, ..))
| Value::UserData(AnyUserData(vref)) | Value::UserData(AnyUserData(vref))
| Value::Other(vref) => unsafe { invoke_to_string(vref) }, | Value::Other(vref) => unsafe { invoke_tostring(vref) },
#[cfg(feature = "luau")] #[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()), Value::Error(err) => Ok(err.to_string()),
} }
} }
@@ -361,7 +356,7 @@ impl Value {
note = "This method does not follow Rust naming convention. Use `as_string().and_then(|s| s.to_str().ok())` instead." note = "This method does not follow Rust naming convention. Use `as_string().and_then(|s| s.to_str().ok())` instead."
)] )]
#[inline] #[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()) self.as_string().and_then(|s| s.to_str().ok())
} }
@@ -561,15 +556,7 @@ impl Value {
t @ Value::Table(_) => write!(fmt, "table: {:?}", t.to_pointer()), t @ Value::Table(_) => write!(fmt, "table: {:?}", t.to_pointer()),
f @ Value::Function(_) => write!(fmt, "function: {:?}", f.to_pointer()), f @ Value::Function(_) => write!(fmt, "function: {:?}", f.to_pointer()),
t @ Value::Thread(_) => write!(fmt, "thread: {:?}", t.to_pointer()), t @ Value::Thread(_) => write!(fmt, "thread: {:?}", t.to_pointer()),
u @ Value::UserData(ud) => { Value::UserData(ud) => ud.fmt_pretty(fmt),
// 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}")
}
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
buf @ Value::Buffer(_) => write!(fmt, "buffer: {:?}", buf.to_pointer()), buf @ Value::Buffer(_) => write!(fmt, "buffer: {:?}", buf.to_pointer()),
Value::Error(e) if recursive => write!(fmt, "{e:?}"), Value::Error(e) if recursive => write!(fmt, "{e:?}"),
+3 -3
View File
@@ -7,7 +7,7 @@ use futures_util::stream::TryStreamExt;
use tokio::sync::Mutex; use tokio::sync::Mutex;
use mlua::{ 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, UserDataMethods, UserDataRef, Value,
}; };
@@ -41,7 +41,7 @@ async fn test_async_function_wrap() -> Result<()> {
let f = Function::wrap_async(|s: String| async move { let f = Function::wrap_async(|s: String| async move {
tokio::task::yield_now().await; tokio::task::yield_now().await;
Ok(s) Ok::<_, Error>(s)
}); });
lua.globals().set("f", f)?; lua.globals().set("f", f)?;
let res: String = lua.load(r#"f("hello")"#).eval_async().await?; let res: String = lua.load(r#"f("hello")"#).eval_async().await?;
@@ -714,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)>((10, 11))?, (21, 110));
assert_eq!(thread.resume::<(i32, i32)>((11, 12))?, (23, 132)); assert_eq!(thread.resume::<(i32, i32)>((11, 12))?, (23, 132));
assert_eq!(thread.resume::<(i32, i32)>((12, 13))?, (0, 0)); assert_eq!(thread.resume::<(i32, i32)>((12, 13))?, (0, 0));
assert_eq!(thread.status(), ThreadStatus::Finished); assert!(thread.is_finished());
Ok(()) Ok(())
} }
+17 -1
View File
@@ -1,3 +1,4 @@
#[cfg(not(target_os = "wasi"))]
use std::{fs, io}; use std::{fs, io};
use mlua::{Chunk, ChunkMode, Lua, Result}; use mlua::{Chunk, ChunkMode, Lua, Result};
@@ -85,7 +86,7 @@ fn test_chunk_macro() -> Result<()> {
data.raw_set("num", 1)?; data.raw_set("num", 1)?;
let ud = mlua::AnyUserData::wrap("hello"); 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)?; lua.globals().set("g", 123)?;
@@ -109,6 +110,21 @@ fn test_chunk_macro() -> Result<()> {
assert_eq!(lua.globals().get::<i32>("s")?, 321); assert_eq!(lua.globals().get::<i32>("s")?, 321);
// Check line numbers in error reporting
match lua
.load(mlua::chunk! {
local x = 1
-- comment
error("boom")
})
.exec()
{
Err(mlua::Error::RuntimeError(ref msg)) => {
assert!(msg.contains(":3:"), "expected line 3, got: {msg}");
}
other => panic!("expected RuntimeError, got {other:?}"),
}
Ok(()) Ok(())
} }
+23
View File
@@ -21,4 +21,27 @@ fn test_compilation() {
t.compile_fail("tests/compile/non_send.rs"); t.compile_fail("tests/compile/non_send.rs");
#[cfg(not(feature = "send"))] #[cfg(not(feature = "send"))]
t.pass("tests/compile/non_send.rs"); t.pass("tests/compile/non_send.rs");
#[cfg(feature = "macros")]
{
t.compile_fail("tests/compile/chunk_dollar_non_ident.rs");
t.compile_fail("tests/compile/userdata_getter_and_meta.rs");
t.compile_fail("tests/compile/userdata_getter_and_setter.rs");
t.compile_fail("tests/compile/userdata_getter_mut_self.rs");
t.compile_fail("tests/compile/userdata_getter_extra_arg.rs");
t.compile_fail("tests/compile/userdata_setter_ref_self.rs");
t.compile_fail("tests/compile/userdata_mut_slice_arg.rs");
t.compile_fail("tests/compile/userdata_setter_no_value.rs");
t.compile_fail("tests/compile/userdata_static_with_self.rs");
t.compile_fail("tests/compile/userdata_meta_owned_self.rs");
t.compile_fail("tests/compile/userdata_const_getter.rs");
t.compile_fail("tests/compile/userdata_field_with_args.rs");
}
#[cfg(all(feature = "macros", feature = "async"))]
{
t.compile_fail("tests/compile/userdata_getter_async.rs");
t.compile_fail("tests/compile/userdata_setter_async.rs");
t.compile_fail("tests/compile/userdata_field_async.rs");
}
} }
+12 -8
View File
@@ -1,14 +1,18 @@
error[E0596]: cannot borrow `s` as mutable, as it is a captured variable in a `Fn` closure error[E0596]: cannot borrow `s` as mutable, as it is a captured variable in a `Fn` closure
--> tests/compile/async_any_userdata_method.rs:9:49 --> tests/compile/async_any_userdata_method.rs:9:49
| |
8 | let mut s = &s; 8 | let mut s = &s;
| ----- `s` declared here, outside the closure | ----- `s` declared here, outside the closure
9 | reg.add_async_method("t", |_, this, ()| async { 9 | reg.add_async_method("t", |_, this, ()| async {
| ------------- ^^^^^ cannot borrow as mutable | - ------------- ^^^^^ cannot borrow as mutable
| | | | |
| in this closure | _____________| in this closure
10 | s = &*this; | |
| - mutable borrow occurs due to use of `s` in closure 10 | | s = &*this;
| | - mutable borrow occurs due to use of `s` in closure
11 | | Ok(())
12 | | });
| |__________- expects `Fn` instead of `FnMut`
error[E0373]: async block may outlive the current function, but it borrows `this`, which is owned by the current function error[E0373]: async block may outlive the current function, but it borrows `this`, which is owned by the current function
--> tests/compile/async_any_userdata_method.rs:9:49 --> tests/compile/async_any_userdata_method.rs:9:49
+4
View File
@@ -0,0 +1,4 @@
use mlua::chunk;
fn main() {
let _ = chunk! { $42 };
}
@@ -0,0 +1,5 @@
error: `$` must be followed by an identifier
--> tests/compile/chunk_dollar_non_ident.rs:3:22
|
3 | let _ = chunk! { $42 };
| ^
+37 -49
View File
@@ -1,32 +1,28 @@
error[E0277]: the type `UnsafeCell<mlua::state::raw::RawLua>` may contain interior mutability and a reference may not be safely transferable across a catch_unwind boundary error[E0277]: the type `UnsafeCell<*mut lua_State>` may contain interior mutability and a reference may not be safely transferable across a catch_unwind boundary
--> tests/compile/lua_norefunwindsafe.rs:7:18 --> tests/compile/lua_norefunwindsafe.rs:7:18
| |
7 | catch_unwind(|| lua.create_table().unwrap()); 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<*mut lua_State>` 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 | required by a bound introduced by this call
| |
= help: within `Lua`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<mlua::state::raw::RawLua>` = help: within `mlua::types::sync::inner::ReentrantMutex<mlua::state::RawLua>`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<*mut lua_State>`
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 `Cell<*mut lua_State>`
--> $CARGO/lock_api-$VERSION/src/remutex.rs --> $RUST/core/src/cell.rs
| |
| pub struct ReentrantMutex<R, G, T: ?Sized> { | pub struct Cell<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 `mlua::state::RawLua`
--> $RUST/alloc/src/sync.rs --> src/state/raw.rs
| |
| struct ArcInner<T: ?Sized> { | pub struct 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::raw::RawLua>>>` note: required because it appears within the type `mlua::types::sync::inner::ReentrantMutex<mlua::state::RawLua>`
--> $RUST/core/src/marker.rs --> src/types/sync.rs
| |
| pub struct PhantomData<T: PointeeSized>; | pub(crate) struct ReentrantMutex<T>(T);
| ^^^^^^^^^^^ | ^^^^^^^^^^^^^^
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 for `Rc<mlua::types::sync::inner::ReentrantMutex<mlua::state::RawLua>>` to implement `RefUnwindSafe`
--> $RUST/alloc/src/sync.rs
|
| pub struct Arc<
| ^^^
note: required because it appears within the type `Lua` note: required because it appears within the type `Lua`
--> src/state.rs --> src/state.rs
| |
@@ -44,45 +40,37 @@ note: required by a bound in `std::panic::catch_unwind`
| pub fn catch_unwind<F: FnOnce() -> R + UnwindSafe, R>(f: F) -> Result<R> { | pub fn catch_unwind<F: FnOnce() -> R + UnwindSafe, R>(f: F) -> Result<R> {
| ^^^^^^^^^^ required by this bound in `catch_unwind` | ^^^^^^^^^^ required by this bound in `catch_unwind`
error[E0277]: the type `UnsafeCell<usize>` may contain interior mutability and a reference may not be safely transferable across a catch_unwind boundary error[E0277]: the type `UnsafeCell<state::extra::ExtraData>` may contain interior mutability and a reference may not be safely transferable across a catch_unwind boundary
--> tests/compile/lua_norefunwindsafe.rs:7:18 --> tests/compile/lua_norefunwindsafe.rs:7:18
| |
7 | catch_unwind(|| lua.create_table().unwrap()); 7 | catch_unwind(|| lua.create_table().unwrap());
| ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<usize>` may contain interior mutability and a reference may not be safely transferable across a catch_unwind boundary | ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<state::extra::ExtraData>` 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 | required by a bound introduced by this call
| |
= help: within `Lua`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<usize>` = help: the trait `RefUnwindSafe` is not implemented for `UnsafeCell<state::extra::ExtraData>`
note: required because it appears within the type `Cell<usize>` = note: required for `Rc<UnsafeCell<state::extra::ExtraData>>` to implement `RefUnwindSafe`
--> $RUST/core/src/cell.rs note: required because it appears within the type `MaybeDangling<Rc<UnsafeCell<state::extra::ExtraData>>>`
--> $RUST/core/src/mem/maybe_dangling.rs
| |
| pub struct Cell<T: ?Sized> { | pub struct MaybeDangling<P: ?Sized>(P);
| ^^^^ | ^^^^^^^^^^^^^
note: required because it appears within the type `lock_api::remutex::RawReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId>` note: required because it appears within the type `ManuallyDrop<Rc<UnsafeCell<state::extra::ExtraData>>>`
--> $CARGO/lock_api-$VERSION/src/remutex.rs --> $RUST/core/src/mem/manually_drop.rs
| |
| pub struct RawReentrantMutex<R, G> { | pub struct ManuallyDrop<T: ?Sized> {
| ^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^
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 `mlua::state::RawLua`
--> $CARGO/lock_api-$VERSION/src/remutex.rs --> src/state/raw.rs
| |
| pub struct ReentrantMutex<R, G, T: ?Sized> { | pub struct 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::raw::RawLua>>` note: required because it appears within the type `mlua::types::sync::inner::ReentrantMutex<mlua::state::RawLua>`
--> $RUST/alloc/src/sync.rs --> src/types/sync.rs
| |
| struct ArcInner<T: ?Sized> { | pub(crate) struct ReentrantMutex<T>(T);
| ^^^^^^^^ | ^^^^^^^^^^^^^^
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 for `Rc<mlua::types::sync::inner::ReentrantMutex<mlua::state::RawLua>>` to implement `RefUnwindSafe`
--> $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>>`
--> $RUST/alloc/src/sync.rs
|
| pub struct Arc<
| ^^^
note: required because it appears within the type `Lua` note: required because it appears within the type `Lua`
--> src/state.rs --> src/state.rs
| |
+100 -33
View File
@@ -1,25 +1,25 @@
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<usize>` may contain interior mutability and a reference may not be safely transferable across a catch_unwind boundary
--> tests/compile/ref_nounwindsafe.rs:8:18 --> tests/compile/ref_nounwindsafe.rs:8:18
| |
8 | catch_unwind(move || table.set("a", "b").unwrap()); 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<usize>` 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 | 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>` = help: within `rc::RcInner<mlua::types::sync::inner::ReentrantMutex<mlua::state::RawLua>>`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<usize>`
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 `Cell<usize>`
--> $CARGO/lock_api-$VERSION/src/remutex.rs --> $RUST/core/src/cell.rs
| |
| pub struct ReentrantMutex<R, G, T: ?Sized> { | pub struct Cell<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 `rc::RcInner<mlua::types::sync::inner::ReentrantMutex<mlua::state::RawLua>>`
--> $RUST/alloc/src/sync.rs --> $RUST/alloc/src/rc.rs
| |
| struct ArcInner<T: ?Sized> { | struct RcInner<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 for `NonNull<rc::RcInner<mlua::types::sync::inner::ReentrantMutex<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::raw::RawLua>>` note: required because it appears within the type `std::rc::Weak<mlua::types::sync::inner::ReentrantMutex<mlua::state::RawLua>>`
--> $RUST/alloc/src/sync.rs --> $RUST/alloc/src/rc.rs
| |
| pub struct Weak< | pub struct Weak<
| ^^^^ | ^^^^
@@ -49,38 +49,105 @@ note: required by a bound in `std::panic::catch_unwind`
| pub fn catch_unwind<F: FnOnce() -> R + UnwindSafe, R>(f: F) -> Result<R> { | pub fn catch_unwind<F: FnOnce() -> R + UnwindSafe, R>(f: F) -> Result<R> {
| ^^^^^^^^^^ required by this bound in `catch_unwind` | ^^^^^^^^^^ required by this bound in `catch_unwind`
error[E0277]: the type `UnsafeCell<usize>` may contain interior mutability and a reference may not be safely transferable across a catch_unwind boundary error[E0277]: the type `UnsafeCell<*mut lua_State>` may contain interior mutability and a reference may not be safely transferable across a catch_unwind boundary
--> tests/compile/ref_nounwindsafe.rs:8:18 --> tests/compile/ref_nounwindsafe.rs:8:18
| |
8 | catch_unwind(move || table.set("a", "b").unwrap()); 8 | catch_unwind(move || table.set("a", "b").unwrap());
| ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<usize>` may contain interior mutability and a reference may not be safely transferable across a catch_unwind boundary | ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<*mut lua_State>` 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 | 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 `rc::RcInner<mlua::types::sync::inner::ReentrantMutex<mlua::state::RawLua>>`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<*mut lua_State>`
note: required because it appears within the type `Cell<usize>` note: required because it appears within the type `Cell<*mut lua_State>`
--> $RUST/core/src/cell.rs --> $RUST/core/src/cell.rs
| |
| pub struct Cell<T: ?Sized> { | pub struct Cell<T: ?Sized> {
| ^^^^ | ^^^^
note: required because it appears within the type `lock_api::remutex::RawReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId>` note: required because it appears within the type `mlua::state::RawLua`
--> $CARGO/lock_api-$VERSION/src/remutex.rs --> src/state/raw.rs
| |
| pub struct RawReentrantMutex<R, G> { | pub struct 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>` note: required because it appears within the type `mlua::types::sync::inner::ReentrantMutex<mlua::state::RawLua>`
--> $CARGO/lock_api-$VERSION/src/remutex.rs --> src/types/sync.rs
| |
| pub struct ReentrantMutex<R, G, T: ?Sized> { | pub(crate) struct ReentrantMutex<T>(T);
| ^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^
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 `rc::RcInner<mlua::types::sync::inner::ReentrantMutex<mlua::state::RawLua>>`
--> $RUST/alloc/src/sync.rs --> $RUST/alloc/src/rc.rs
| |
| struct ArcInner<T: ?Sized> { | struct RcInner<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 for `NonNull<rc::RcInner<mlua::types::sync::inner::ReentrantMutex<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::raw::RawLua>>` note: required because it appears within the type `std::rc::Weak<mlua::types::sync::inner::ReentrantMutex<mlua::state::RawLua>>`
--> $RUST/alloc/src/sync.rs --> $RUST/alloc/src/rc.rs
|
| pub struct Weak<
| ^^^^
note: required because it appears within the type `WeakLua`
--> src/state.rs
|
| pub struct WeakLua(XWeak<ReentrantMutex<RawLua>>);
| ^^^^^^^
note: required because it appears within the type `mlua::types::value_ref::ValueRef`
--> src/types/value_ref.rs
|
| pub struct ValueRef {
| ^^^^^^^^
note: required because it appears within the type `LuaTable`
--> src/table.rs
|
| pub struct Table(pub(crate) ValueRef);
| ^^^^^
note: required because it's used within this closure
--> tests/compile/ref_nounwindsafe.rs:8:18
|
8 | catch_unwind(move || table.set("a", "b").unwrap());
| ^^^^^^^
note: required by a bound in `std::panic::catch_unwind`
--> $RUST/std/src/panic.rs
|
| pub fn catch_unwind<F: FnOnce() -> R + UnwindSafe, R>(f: F) -> Result<R> {
| ^^^^^^^^^^ required by this bound in `catch_unwind`
error[E0277]: the type `UnsafeCell<state::extra::ExtraData>` 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<state::extra::ExtraData>` 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: the trait `RefUnwindSafe` is not implemented for `UnsafeCell<state::extra::ExtraData>`
= note: required for `Rc<UnsafeCell<state::extra::ExtraData>>` to implement `RefUnwindSafe`
note: required because it appears within the type `MaybeDangling<Rc<UnsafeCell<state::extra::ExtraData>>>`
--> $RUST/core/src/mem/maybe_dangling.rs
|
| pub struct MaybeDangling<P: ?Sized>(P);
| ^^^^^^^^^^^^^
note: required because it appears within the type `ManuallyDrop<Rc<UnsafeCell<state::extra::ExtraData>>>`
--> $RUST/core/src/mem/manually_drop.rs
|
| pub struct ManuallyDrop<T: ?Sized> {
| ^^^^^^^^^^^^
note: required because it appears within the type `mlua::state::RawLua`
--> src/state/raw.rs
|
| pub struct RawLua {
| ^^^^^^
note: required because it appears within the type `mlua::types::sync::inner::ReentrantMutex<mlua::state::RawLua>`
--> src/types/sync.rs
|
| pub(crate) struct ReentrantMutex<T>(T);
| ^^^^^^^^^^^^^^
note: required because it appears within the type `rc::RcInner<mlua::types::sync::inner::ReentrantMutex<mlua::state::RawLua>>`
--> $RUST/alloc/src/rc.rs
|
| struct RcInner<T: ?Sized> {
| ^^^^^^^
= note: required for `NonNull<rc::RcInner<mlua::types::sync::inner::ReentrantMutex<mlua::state::RawLua>>>` to implement `UnwindSafe`
note: required because it appears within the type `std::rc::Weak<mlua::types::sync::inner::ReentrantMutex<mlua::state::RawLua>>`
--> $RUST/alloc/src/rc.rs
| |
| pub struct Weak< | pub struct Weak<
| ^^^^ | ^^^^
+10
View File
@@ -0,0 +1,10 @@
#[derive(Default, mlua::UserData)]
struct Foo;
#[mlua::userdata_impl]
impl Foo {
#[lua(getter)]
const X: u32 = 42;
}
fn main() {}
@@ -0,0 +1,5 @@
error: const items do not support `getter` or `setter`
--> tests/compile/userdata_const_getter.rs:6:5
|
6 | #[lua(getter)]
| ^^^^^^^^^^^^^^
+14
View File
@@ -0,0 +1,14 @@
use mlua::Result;
#[derive(Clone, Debug, mlua::UserData)]
struct Foo;
#[mlua::userdata_impl]
impl Foo {
#[lua(field)]
async fn description() -> Result<String> {
Ok("foo".into())
}
}
fn main() {}
+13
View File
@@ -0,0 +1,13 @@
error: async field function is not supported
--> tests/compile/userdata_field_async.rs:9:5
|
9 | async fn description() -> Result<String> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
warning: unused import: `mlua::Result`
--> tests/compile/userdata_field_async.rs:1:5
|
1 | use mlua::Result;
| ^^^^^^^^^^^^
|
= note: `#[warn(unused_imports)]` (part of `#[warn(unused)]`) on by default
+14
View File
@@ -0,0 +1,14 @@
#[derive(Default, mlua::UserData)]
struct Foo {
x: u32,
}
#[mlua::userdata_impl]
impl Foo {
#[lua(field)]
fn as_name(name: &str) -> String {
name.to_string()
}
}
fn main() {}
@@ -0,0 +1,5 @@
error: field function must not take arguments
--> tests/compile/userdata_field_with_args.rs:9:5
|
9 | fn as_name(name: &str) -> String {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+12
View File
@@ -0,0 +1,12 @@
#[derive(Default, mlua::UserData)]
struct Foo;
#[mlua::userdata_impl]
impl Foo {
#[lua(getter, meta)]
fn bar(&self) -> mlua::Result<u32> {
Ok(42)
}
}
fn main() {}
@@ -0,0 +1,5 @@
error: `meta` can only be combined with `field`
--> tests/compile/userdata_getter_and_meta.rs:6:5
|
6 | #[lua(getter, meta)]
| ^^^^^^^^^^^^^^^^^^^^
@@ -0,0 +1,14 @@
#[derive(Default, mlua::UserData)]
struct Foo {
x: u32,
}
#[mlua::userdata_impl]
impl Foo {
#[lua(getter, setter)]
fn x(&self) -> mlua::Result<u32> {
Ok(self.x)
}
}
fn main() {}
@@ -0,0 +1,5 @@
error: at most one of `getter`, `setter`, `field` can be specified
--> tests/compile/userdata_getter_and_setter.rs:8:5
|
8 | #[lua(getter, setter)]
| ^^^^^^^^^^^^^^^^^^^^^^
+14
View File
@@ -0,0 +1,14 @@
use mlua::Result;
#[derive(Clone, Debug, mlua::UserData)]
struct Foo(u64);
#[mlua::userdata_impl]
impl Foo {
#[lua(getter)]
async fn value(&self) -> Result<u64> {
Ok(self.0)
}
}
fn main() {}
@@ -0,0 +1,13 @@
error: async field getter is not supported
--> tests/compile/userdata_getter_async.rs:9:5
|
9 | async fn value(&self) -> Result<u64> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
warning: unused import: `mlua::Result`
--> tests/compile/userdata_getter_async.rs:1:5
|
1 | use mlua::Result;
| ^^^^^^^^^^^^
|
= note: `#[warn(unused_imports)]` (part of `#[warn(unused)]`) on by default
@@ -0,0 +1,14 @@
#[derive(Default, mlua::UserData)]
struct Foo {
x: u32,
}
#[mlua::userdata_impl]
impl Foo {
#[lua(getter)]
fn x(&self, extra: u32) -> mlua::Result<u32> {
Ok(self.x + extra)
}
}
fn main() {}
@@ -0,0 +1,5 @@
error: field getter must not take additional arguments
--> tests/compile/userdata_getter_extra_arg.rs:9:5
|
9 | fn x(&self, extra: u32) -> mlua::Result<u32> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+14
View File
@@ -0,0 +1,14 @@
#[derive(Default, mlua::UserData)]
struct Foo {
x: u32,
}
#[mlua::userdata_impl]
impl Foo {
#[lua(getter)]
fn x(&mut self) -> mlua::Result<u32> {
Ok(self.x)
}
}
fn main() {}
@@ -0,0 +1,5 @@
error: field getter must take `&self`
--> tests/compile/userdata_getter_mut_self.rs:9:5
|
9 | fn x(&mut self) -> mlua::Result<u32> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+12
View File
@@ -0,0 +1,12 @@
#[derive(Default, mlua::UserData)]
struct Foo;
#[mlua::userdata_impl]
impl Foo {
#[lua(meta)]
fn __gc(self) -> mlua::Result<()> {
Ok(())
}
}
fn main() {}
@@ -0,0 +1,5 @@
error: meta methods cannot take `self`, use `&[mut] self` instead
--> tests/compile/userdata_meta_owned_self.rs:7:5
|
7 | fn __gc(self) -> mlua::Result<()> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+11
View File
@@ -0,0 +1,11 @@
#[derive(Default, mlua::UserData)]
struct Foo(Vec<u8>);
#[mlua::userdata_impl]
impl Foo {
fn first(&self, data: &mut [u8]) -> mlua::Result<u8> {
Ok(data[0])
}
}
fn main() {}
@@ -0,0 +1,5 @@
error: this reference type is not supported as a callback parameter
--> tests/compile/userdata_mut_slice_arg.rs:6:27
|
6 | fn first(&self, data: &mut [u8]) -> mlua::Result<u8> {
| ^^^^^^^^^
+15
View File
@@ -0,0 +1,15 @@
use mlua::Result;
#[derive(Clone, Debug, mlua::UserData)]
struct Foo(u64);
#[mlua::userdata_impl]
impl Foo {
#[lua(setter)]
async fn set_value(&mut self, val: u64) -> Result<()> {
self.0 = val;
Ok(())
}
}
fn main() {}
@@ -0,0 +1,13 @@
error: async field setter is not supported
--> tests/compile/userdata_setter_async.rs:9:5
|
9 | async fn set_value(&mut self, val: u64) -> Result<()> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
warning: unused import: `mlua::Result`
--> tests/compile/userdata_setter_async.rs:1:5
|
1 | use mlua::Result;
| ^^^^^^^^^^^^
|
= note: `#[warn(unused_imports)]` (part of `#[warn(unused)]`) on by default
+14
View File
@@ -0,0 +1,14 @@
#[derive(Default, mlua::UserData)]
struct Foo {
x: u32,
}
#[mlua::userdata_impl]
impl Foo {
#[lua(setter)]
fn set_x(&mut self) -> mlua::Result<()> {
Ok(())
}
}
fn main() {}
@@ -0,0 +1,5 @@
error: field setter must take exactly one value argument
--> tests/compile/userdata_setter_no_value.rs:9:5
|
9 | fn set_x(&mut self) -> mlua::Result<()> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+15
View File
@@ -0,0 +1,15 @@
#[derive(Default, mlua::UserData)]
struct Foo {
x: u32,
}
#[mlua::userdata_impl]
impl Foo {
#[lua(setter)]
fn set_x(self, val: u32) -> mlua::Result<()> {
let _ = val;
Ok(())
}
}
fn main() {}
@@ -0,0 +1,5 @@
error: field setter must take `&[mut] self`
--> tests/compile/userdata_setter_ref_self.rs:9:5
|
9 | fn set_x(self, val: u32) -> mlua::Result<()> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -0,0 +1,14 @@
#[derive(Default, mlua::UserData)]
struct Foo {
x: u32,
}
#[mlua::userdata_impl]
impl Foo {
#[lua(field)]
fn get_x(&self) -> mlua::Result<u32> {
Ok(self.x)
}
}
fn main() {}
@@ -0,0 +1,5 @@
error: field function must not take `self`
--> tests/compile/userdata_static_with_self.rs:9:5
|
9 | fn get_x(&self) -> mlua::Result<u32> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+13
View File
@@ -77,6 +77,19 @@ fn test_error_chain() -> Result<()> {
Ok(()) 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")] #[cfg(feature = "anyhow")]
#[test] #[test]
fn test_error_anyhow() -> Result<()> { fn test_error_anyhow() -> Result<()> {
+35 -3
View File
@@ -1,3 +1,6 @@
use std::fmt;
use std::result::Result as StdResult;
use mlua::{Error, Function, Lua, LuaString, Result, Table, Variadic}; use mlua::{Error, Function, Lua, LuaString, Result, Table, Variadic};
#[test] #[test]
@@ -343,7 +346,7 @@ fn test_function_deep_clone() -> Result<()> {
fn test_function_wrap() -> Result<()> { fn test_function_wrap() -> Result<()> {
let lua = Lua::new(); let lua = Lua::new();
let f = Function::wrap(|s: LuaString, 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.globals().set("f", f)?;
lua.load(r#"assert(f("hello", 2) == "hellohello")"#) lua.load(r#"assert(f("hello", 2) == "hellohello")"#)
.exec() .exec()
@@ -361,11 +364,40 @@ fn test_function_wrap() -> Result<()> {
.exec() .exec()
.unwrap(); .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 // Mutable callback
let mut i = 0; let mut i = 0;
let fmut = Function::wrap_mut(move || { let fmut = Function::wrap_mut(move || {
i += 1; i += 1;
Ok(i) Ok::<_, Error>(i)
}); });
lua.globals().set("fmut", fmut)?; lua.globals().set("fmut", fmut)?;
lua.load(r#"fmut(); fmut(); assert(fmut() == 3)"#).exec().unwrap(); lua.load(r#"fmut(); fmut(); assert(fmut() == 3)"#).exec().unwrap();
@@ -385,7 +417,7 @@ fn test_function_wrap() -> Result<()> {
// Check recursive mut callback error // Check recursive mut callback error
let fmut = Function::wrap_mut(|f: Function| match f.call::<()>(&f) { let fmut = Function::wrap_mut(|f: Function| match f.call::<()>(&f) {
Err(Error::CallbackError { cause, .. }) => match cause.as_ref() { 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:?}"),
}, },
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::atomic::{AtomicI64, Ordering};
use std::sync::{Arc, Mutex}; 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] #[test]
fn test_hook_triggers() { 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.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"))] #[cfg(any(feature = "lua51", feature = "lua52", feature = "luajit"))]
{ {
assert!( assert!(
matches!(co.resume::<()>(()), Err(Error::RuntimeError(err)) if err.contains("attempt to yield from a hook")) 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(()) Ok(())
@@ -320,7 +321,7 @@ fn test_global_hook() -> Result<()> {
thread.resume::<()>(()).unwrap(); thread.resume::<()>(()).unwrap();
lua.remove_global_hook(); lua.remove_global_hook();
thread.resume::<()>(()).unwrap(); thread.resume::<()>(()).unwrap();
assert_eq!(thread.status(), ThreadStatus::Finished); assert!(thread.is_finished());
assert_eq!(counter.load(Ordering::Relaxed), 3); assert_eq!(counter.load(Ordering::Relaxed), 3);
Ok(()) Ok(())
+22 -87
View File
@@ -1,13 +1,11 @@
#![cfg(feature = "luau")] #![cfg(feature = "luau")]
use std::cell::Cell;
use std::fmt::Debug; use std::fmt::Debug;
use std::os::raw::c_void;
use std::sync::Arc; use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicPtr, AtomicU64, Ordering}; use std::sync::atomic::{AtomicU64, Ordering};
use mlua::{ 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] #[test]
@@ -324,11 +322,11 @@ fn test_interrupts() -> Result<()> {
.into_function()?, .into_function()?,
)?; )?;
co.resume::<()>(())?; co.resume::<()>(())?;
assert_eq!(co.status(), ThreadStatus::Resumable); assert!(co.is_resumable());
let result: i32 = co.resume(())?; let result: i32 = co.resume(())?;
assert_eq!(result, 6); assert_eq!(result, 6);
assert_eq!(yield_count.load(Ordering::Relaxed), 7); 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 // Test no yielding at non-yieldable points
yield_count.store(0, Ordering::Relaxed); yield_count.store(0, Ordering::Relaxed);
@@ -359,87 +357,6 @@ fn test_fflags() {
assert!(Lua::set_fflag("UnknownFlag", true).is_err()); assert!(Lua::set_fflag("UnknownFlag", true).is_err());
} }
#[test]
fn test_thread_events() -> Result<()> {
let lua = Lua::new();
let count = Arc::new(AtomicU64::new(0));
let thread_data: Arc<(AtomicPtr<c_void>, AtomicBool)> = Arc::new(Default::default());
let (count2, thread_data2) = (count.clone(), thread_data.clone());
lua.set_thread_creation_callback(move |_, thread| {
count2.fetch_add(1, Ordering::Relaxed);
(thread_data2.0).store(thread.to_pointer() as *mut _, Ordering::Relaxed);
thread_data2.1.store(false, Ordering::Relaxed);
Ok(())
});
let (count3, thread_data3) = (count.clone(), thread_data.clone());
lua.set_thread_collection_callback(move |thread_ptr| {
count3.fetch_add(1, Ordering::Relaxed);
if thread_data3.0.load(Ordering::Relaxed) == thread_ptr.0 {
thread_data3.1.store(true, Ordering::Relaxed);
}
});
let t = lua.create_thread(lua.load("return 123").into_function()?)?;
assert_eq!(count.load(Ordering::Relaxed), 1);
let t_ptr = t.to_pointer();
assert_eq!(t_ptr, thread_data.0.load(Ordering::Relaxed));
assert!(!thread_data.1.load(Ordering::Relaxed));
// Thead will be destroyed after GC cycle
drop(t);
lua.gc_collect()?;
assert_eq!(count.load(Ordering::Relaxed), 2);
assert_eq!(t_ptr, thread_data.0.load(Ordering::Relaxed));
assert!(thread_data.1.load(Ordering::Relaxed));
// Check that recursion is not allowed
let count4 = count.clone();
lua.set_thread_creation_callback(move |lua, _value| {
count4.fetch_add(1, Ordering::Relaxed);
let _ = lua.create_thread(lua.load("return 123").into_function().unwrap())?;
Ok(())
});
let t = lua.create_thread(lua.load("return 123").into_function()?)?;
assert_eq!(count.load(Ordering::Relaxed), 3);
lua.remove_thread_callbacks();
drop(t);
lua.gc_collect()?;
assert_eq!(count.load(Ordering::Relaxed), 3);
// Test error inside callback
lua.set_thread_creation_callback(move |_, _| Err(Error::runtime("error when processing thread event")));
let result = lua.create_thread(lua.load("return 123").into_function()?);
assert!(result.is_err());
assert!(
matches!(result, Err(Error::RuntimeError(err)) if err.contains("error when processing thread event"))
);
// Test context switch when running Lua script
let count = Cell::new(0);
lua.set_thread_creation_callback(move |_, _| {
count.set(count.get() + 1);
if count.get() == 2 {
return Err(Error::runtime("thread limit exceeded"));
}
Ok(())
});
let result = lua
.load(
r#"
local co = coroutine.wrap(function() return coroutine.create(print) end)
co()
"#,
)
.exec();
assert!(result.is_err());
assert!(matches!(result, Err(Error::RuntimeError(err)) if err.contains("thread limit exceeded")));
Ok(())
}
#[test] #[test]
fn test_loadstring() -> Result<()> { fn test_loadstring() -> Result<()> {
let lua = Lua::new(); let lua = Lua::new();
@@ -535,5 +452,23 @@ fn test_heap_dump() -> Result<()> {
Ok(()) Ok(())
} }
#[test]
fn test_integer64_type() -> Result<()> {
let lua = Lua::new();
_ = Lua::set_fflag("LuauIntegerType2", 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"] #[path = "luau/require.rs"]
mod require; mod require;
+150 -5
View File
@@ -1,7 +1,8 @@
use std::io::Result as IoResult; use std::io::Result as IoResult;
use std::result::Result as StdResult; 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> { fn run_require(lua: &Lua, path: impl IntoLua) -> Result<Value> {
lua.load(r#"return require(...)"#).call(path) lua.load(r#"return require(...)"#).call(path)
@@ -65,7 +66,7 @@ fn test_require_errors() {
assert!((res.unwrap_err().to_string()).contains("@ is not a valid alias")); assert!((res.unwrap_err().to_string()).contains("@ is not a valid alias"));
// Test throwing mlua::Error // Test throwing mlua::Error
struct MyRequire(TextRequirer); struct MyRequire(FsRequirer);
impl Require for MyRequire { impl Require for MyRequire {
fn is_require_allowed(&self, chunk_name: &str) -> bool { fn is_require_allowed(&self, chunk_name: &str) -> bool {
@@ -109,9 +110,7 @@ fn test_require_errors() {
} }
} }
let require = lua let require = lua.create_require_function(MyRequire(FsRequirer::new())).unwrap();
.create_require_function(MyRequire(TextRequirer::new()))
.unwrap();
lua.globals().set("require", require).unwrap(); lua.globals().set("require", require).unwrap();
let res = lua.load(r#"return require('./a/relative/path')"#).exec(); let res = lua.load(r#"return require('./a/relative/path')"#).exec();
assert!((res.unwrap_err().to_string()).contains("test error")); assert!((res.unwrap_err().to_string()).contains("test error"));
@@ -252,6 +251,152 @@ fn test_require_with_config_luau() {
test_require_with_config_inner("with_config_luau"); test_require_with_config_inner("with_config_luau");
} }
#[test]
fn test_alias_override() {
let lua = Lua::new();
struct OverrideRequire(FsRequirer);
impl Require for OverrideRequire {
fn is_require_allowed(&self, chunk_name: &str) -> bool {
self.0.is_require_allowed(chunk_name)
}
fn reset(&mut self, chunk_name: &str) -> StdResult<(), NavigateError> {
self.0.reset(chunk_name)
}
fn jump_to_alias(&mut self, path: &str) -> StdResult<(), NavigateError> {
self.0.jump_to_alias(path)
}
fn to_alias_override(&mut self, alias: &str) -> StdResult<(), NavigateError> {
if alias == "testoverride" {
self.0.jump_to_alias("./tests/luau/require/without_config")
} else {
Err(NavigateError::NotFound)
}
}
fn to_parent(&mut self) -> StdResult<(), NavigateError> {
self.0.to_parent()
}
fn to_child(&mut self, name: &str) -> StdResult<(), NavigateError> {
self.0.to_child(name)
}
fn has_module(&self) -> bool {
self.0.has_module()
}
fn cache_key(&self) -> String {
self.0.cache_key()
}
fn has_config(&self) -> bool {
self.0.has_config()
}
fn config(&self) -> IoResult<Vec<u8>> {
self.0.config()
}
fn loader(&self, lua: &Lua) -> Result<mlua::Function> {
self.0.loader(lua)
}
}
let require_fn = lua
.create_require_function(OverrideRequire(FsRequirer::new()))
.unwrap();
lua.globals().set("require", require_fn).unwrap();
// to_alias_override intercepts before config-file search
let res = run_require(&lua, "@testoverride/dependency").unwrap();
assert_eq!("result from dependency", get_str(&res, 1));
// Different sub-path through the same alias
let res = run_require(&lua, "@testoverride/module").unwrap();
assert_eq!("required into module", get_str(&res, 2));
// Aliases not handled by the override still fail normally
let res = run_require(&lua, "@unknown_alias_xyz/anything");
assert!(res.is_err());
assert!((res.unwrap_err().to_string()).contains("@unknown_alias_xyz is not a valid alias"));
}
#[test]
fn test_alias_fallback() {
let lua = Lua::new();
struct FallbackRequire(FsRequirer);
impl Require for FallbackRequire {
fn is_require_allowed(&self, chunk_name: &str) -> bool {
self.0.is_require_allowed(chunk_name)
}
fn reset(&mut self, chunk_name: &str) -> StdResult<(), NavigateError> {
self.0.reset(chunk_name)
}
fn jump_to_alias(&mut self, path: &str) -> StdResult<(), NavigateError> {
self.0.jump_to_alias(path)
}
fn to_alias_fallback(&mut self, alias: &str) -> StdResult<(), NavigateError> {
if alias == "testfallback" {
self.0.jump_to_alias("./tests/luau/require/without_config")
} else {
Err(NavigateError::NotFound)
}
}
fn to_parent(&mut self) -> StdResult<(), NavigateError> {
self.0.to_parent()
}
fn to_child(&mut self, name: &str) -> StdResult<(), NavigateError> {
self.0.to_child(name)
}
fn has_module(&self) -> bool {
self.0.has_module()
}
fn cache_key(&self) -> String {
self.0.cache_key()
}
fn has_config(&self) -> bool {
self.0.has_config()
}
fn config(&self) -> IoResult<Vec<u8>> {
self.0.config()
}
fn loader(&self, lua: &Lua) -> Result<mlua::Function> {
self.0.loader(lua)
}
}
let require_fn = lua
.create_require_function(FallbackRequire(FsRequirer::new()))
.unwrap();
lua.globals().set("require", require_fn).unwrap();
// to_alias_fallback catches after config-file search misses
let res = run_require(&lua, "@testfallback/dependency").unwrap();
assert_eq!("result from dependency", get_str(&res, 1));
// Aliases not handled by the fallback still fail
let res = run_require(&lua, "@unknown_alias_xyz/anything");
assert!(res.is_err());
assert!((res.unwrap_err().to_string()).contains("@unknown_alias_xyz is not a valid alias"));
}
#[cfg(all(feature = "async", not(windows)))] #[cfg(all(feature = "async", not(windows)))]
#[tokio::test] #[tokio::test]
async fn test_async_require() -> Result<()> { async fn test_async_require() -> Result<()> {
+24 -4
View File
@@ -1,6 +1,10 @@
use std::sync::Arc; 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] #[test]
fn test_memory_limit() -> Result<()> { fn test_memory_limit() -> Result<()> {
@@ -74,8 +78,14 @@ fn test_gc_control() -> Result<()> {
#[cfg(any(feature = "lua55", feature = "lua54"))] #[cfg(any(feature = "lua55", feature = "lua54"))]
{ {
assert_eq!(lua.gc_gen(0, 0), GCMode::Incremental); assert!(matches!(
assert_eq!(lua.gc_inc(0, 0, 0), GCMode::Generational); lua.gc_set_mode(GcMode::Generational(GcGenParams::default())),
GcMode::Incremental(_)
));
assert!(matches!(
lua.gc_set_mode(GcMode::Incremental(GcIncParams::default())),
GcMode::Generational(_)
));
} }
#[cfg(any( #[cfg(any(
@@ -93,7 +103,17 @@ fn test_gc_control() -> Result<()> {
assert!(lua.gc_is_running()); 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<()>); struct MyUserdata(#[allow(unused)] Arc<()>);
impl UserData for MyUserdata {} impl UserData for MyUserdata {}
+4 -49
View File
@@ -1,50 +1,7 @@
#![cfg(feature = "send")] #![cfg(feature = "send")]
use std::cell::UnsafeCell; use mlua::{AnyUserData, Lua, ObjectLike, Result, UserData, UserDataMethods, UserDataRef};
use std::marker::PhantomData; use static_assertions::assert_impl_all;
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(String, 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(())
}
#[test] #[test]
fn test_userdata_multithread_access_sync() -> Result<()> { fn test_userdata_multithread_access_sync() -> Result<()> {
@@ -74,13 +31,11 @@ fn test_userdata_multithread_access_sync() -> Result<()> {
std::thread::scope(|s| { std::thread::scope(|s| {
s.spawn(|| { s.spawn(|| {
// Getting another shared reference for `Sync` type is allowed. // 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(()) Ok(())
} }
+23 -13
View File
@@ -5,27 +5,37 @@ use mlua::{Lua, LuaString, Result};
#[test] #[test]
fn test_string_compare() { fn test_string_compare() {
fn with_str<F: FnOnce(LuaString)>(s: &str, f: F) { let lua = Lua::new();
f(Lua::new().create_string(s).unwrap());
fn with_str<F: FnOnce(LuaString)>(lua: &Lua, s: &str, f: F) {
f(lua.create_string(s).unwrap());
} }
// Tests that all comparisons we want to have are usable // Tests that all comparisons we want to have are usable
with_str("teststring", |t| assert_eq!(t, "teststring")); // &str with_str(&lua, "teststring", |t| assert_eq!(t, "teststring")); // &str
with_str("teststring", |t| assert_eq!(t, b"teststring")); // &[u8] with_str(&lua, "teststring", |t| assert_eq!(t, b"teststring")); // &[u8]
with_str("teststring", |t| assert_eq!(t, b"teststring".to_vec())); // Vec<u8> with_str(&lua, "teststring", |t| assert_eq!(t, b"teststring".to_vec())); // Vec<u8>
with_str("teststring", |t| assert_eq!(t, "teststring".to_string())); // String with_str(&lua, "teststring", |t| assert_eq!(t, "teststring".to_string())); // String
with_str("teststring", |t| assert_eq!(t, t)); // mlua::String with_str(&lua, "teststring", |t| assert_eq!(t, t)); // mlua::String
with_str("teststring", |t| assert_eq!(t, Cow::from(b"teststring".as_ref()))); // Cow (borrowed) with_str(&lua, "teststring", |t| {
with_str("bla", |t| assert_eq!(t, Cow::from(b"bla".to_vec()))); // Cow (owned) assert_eq!(t, Cow::from(b"teststring".as_ref())) // Cow (borrowed)
});
with_str(&lua, "bla", |t| assert_eq!(t, Cow::from(b"bla".to_vec()))); // Cow (owned)
// Test ordering // Test ordering
with_str("a", |a| { with_str(&lua, "a", |a| {
assert!(!(a < a)); assert!(!(a < a));
assert!(!(a > a)); assert!(!(a > a));
}); });
with_str("a", |a| assert!(a < "b")); with_str(&lua, "a", |a| assert!(a < "b"));
with_str("a", |a| assert!(a < b"b")); with_str(&lua, "a", |a| assert!(a < b"b"));
with_str("a", |a| with_str("b", |b| assert!(a < b))); with_str(&lua, "a", |a| with_str(&lua, "b", |b| assert!(a < b)));
// Long strings (not interned by Lua)
let long_str = "abc".repeat(100);
with_str(&lua, &long_str, |s1| {
with_str(&lua, &long_str, |s2| assert_eq!(s1, s2))
});
} }
#[test] #[test]
+2 -2
View File
@@ -1374,7 +1374,7 @@ fn test_inspect_stack() -> Result<()> {
local function baz(a, b, c, ...) local function baz(a, b, c, ...)
return stack_info() return stack_info()
end 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()?; .exec()?;
@@ -1387,7 +1387,7 @@ fn test_inspect_stack() -> Result<()> {
local function baz(a, b, c, ...) local function baz(a, b, c, ...)
return stack_info() return stack_info()
end end
assert(baz() == 'DebugStack { num_ups: 1 }') assert(baz() == 'DebugStack { num_upvalues: 1 }')
"#, "#,
) )
.exec()?; .exec()?;
+232 -18
View File
@@ -1,6 +1,8 @@
use std::panic::catch_unwind; use std::panic::catch_unwind;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use mlua::{Error, Function, IntoLua, Lua, Result, Thread, ThreadStatus, Value}; use mlua::{Error, Function, IntoLua, Lua, Result, Thread, ThreadEvent, ThreadTriggers, Value};
#[test] #[test]
fn test_thread() -> Result<()> { fn test_thread() -> Result<()> {
@@ -21,17 +23,17 @@ fn test_thread() -> Result<()> {
.eval()?, .eval()?,
)?; )?;
assert_eq!(thread.status(), ThreadStatus::Resumable); assert!(thread.is_resumable());
assert_eq!(thread.resume::<i64>(0)?, 0); 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.resume::<i64>(1)?, 1);
assert_eq!(thread.status(), ThreadStatus::Resumable); assert!(thread.is_resumable());
assert_eq!(thread.resume::<i64>(2)?, 3); 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.resume::<i64>(3)?, 6);
assert_eq!(thread.status(), ThreadStatus::Resumable); assert!(thread.is_resumable());
assert_eq!(thread.resume::<i64>(4)?, 10); assert_eq!(thread.resume::<i64>(4)?, 10);
assert_eq!(thread.status(), ThreadStatus::Finished); assert!(thread.is_finished());
let accumulate = lua.create_thread( let accumulate = lua.create_thread(
lua.load( lua.load(
@@ -50,9 +52,9 @@ fn test_thread() -> Result<()> {
accumulate.resume::<()>(i)?; accumulate.resume::<()>(i)?;
} }
assert_eq!(accumulate.resume::<i64>(4)?, 10); assert_eq!(accumulate.resume::<i64>(4)?, 10);
assert_eq!(accumulate.status(), ThreadStatus::Resumable); assert!(accumulate.is_resumable());
assert!(accumulate.resume::<()>("error").is_err()); assert!(accumulate.resume::<()>("error").is_err());
assert_eq!(accumulate.status(), ThreadStatus::Error); assert!(accumulate.is_error());
let thread = lua let thread = lua
.load( .load(
@@ -65,7 +67,7 @@ fn test_thread() -> Result<()> {
"#, "#,
) )
.eval::<Thread>()?; .eval::<Thread>()?;
assert_eq!(thread.status(), ThreadStatus::Resumable); assert!(thread.is_resumable());
assert_eq!(thread.resume::<i64>(())?, 42); assert_eq!(thread.resume::<i64>(())?, 42);
let thread: Thread = lua let thread: Thread = lua
@@ -92,7 +94,7 @@ fn test_thread() -> Result<()> {
// Already running thread must be unresumable // Already running thread must be unresumable
let thread = lua.create_thread(lua.create_function(|lua, ()| { 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::<()>(()); let result = lua.current_thread().resume::<()>(());
assert!( assert!(
matches!(result, Err(Error::CoroutineUnresumable)), matches!(result, Err(Error::CoroutineUnresumable)),
@@ -123,12 +125,12 @@ fn test_thread_reset() -> Result<()> {
assert!(thread.reset(func.clone()).is_ok()); assert!(thread.reset(func.clone()).is_ok());
for _ in 0..2 { for _ in 0..2 {
assert_eq!(thread.status(), ThreadStatus::Resumable); assert!(thread.is_resumable());
let _ = thread.resume::<AnyUserData>(MyUserData(arc.clone()))?; let _ = thread.resume::<AnyUserData>(MyUserData(arc.clone()))?;
assert_eq!(thread.status(), ThreadStatus::Resumable); assert!(thread.is_resumable());
assert_eq!(Arc::strong_count(&arc), 2); assert_eq!(Arc::strong_count(&arc), 2);
thread.resume::<()>(())?; thread.resume::<()>(())?;
assert_eq!(thread.status(), ThreadStatus::Finished); assert!(thread.is_finished());
thread.reset(func.clone())?; thread.reset(func.clone())?;
lua.gc_collect()?; lua.gc_collect()?;
assert_eq!(Arc::strong_count(&arc), 1); assert_eq!(Arc::strong_count(&arc), 1);
@@ -138,21 +140,21 @@ fn test_thread_reset() -> Result<()> {
let func: Function = lua.load(r#"function(ud) error("test error") end"#).eval()?; let func: Function = lua.load(r#"function(ud) error("test error") end"#).eval()?;
let thread = lua.create_thread(func.clone())?; let thread = lua.create_thread(func.clone())?;
let _ = thread.resume::<AnyUserData>(MyUserData(arc.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); assert_eq!(Arc::strong_count(&arc), 2);
#[cfg(any(feature = "lua55", feature = "lua54"))] #[cfg(any(feature = "lua55", feature = "lua54"))]
{ {
assert!(thread.reset(func.clone()).is_err()); assert!(thread.reset(func.clone()).is_err());
// Reset behavior has changed in Lua v5.4.4 // Reset behavior has changed in Lua v5.4.4
// It's became possible to force reset thread by popping error object // 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!(thread.reset(func.clone()).is_ok());
assert_eq!(thread.status(), ThreadStatus::Resumable); assert!(thread.is_resumable());
} }
#[cfg(any(feature = "lua55", feature = "lua54", feature = "luau"))] #[cfg(any(feature = "lua55", feature = "lua54", feature = "luau"))]
{ {
assert!(thread.reset(func.clone()).is_ok()); assert!(thread.reset(func.clone()).is_ok());
assert_eq!(thread.status(), ThreadStatus::Resumable); assert!(thread.is_resumable());
} }
// Try reset running thread // Try reset running thread
@@ -275,3 +277,215 @@ fn test_thread_resume_bad_arg() -> Result<()> {
Ok(()) Ok(())
} }
#[test]
fn test_thread_event_create() -> Result<()> {
let lua = Lua::new();
let created = Arc::new(AtomicBool::new(false));
let created2 = created.clone();
lua.set_thread_event_callback(ThreadTriggers::ON_CREATE, move |_lua, event| {
assert!(matches!(event, ThreadEvent::Create(_)));
created2.store(true, Ordering::Relaxed);
Ok(())
});
let _thread = lua.create_thread(lua.create_function(|_, ()| Ok(()))?)?;
assert!(created.load(Ordering::Relaxed));
Ok(())
}
#[test]
fn test_thread_event_create_recursive() -> Result<()> {
let lua = Lua::new();
let count = Arc::new(AtomicU32::new(0));
let count2 = count.clone();
lua.set_thread_event_callback(ThreadTriggers::ON_CREATE, move |lua, event| {
assert!(matches!(event, ThreadEvent::Create(_)));
count2.fetch_add(1, Ordering::Relaxed);
// Creating a thread inside the callback
let _ = lua.create_thread(lua.load("return 321").into_function().unwrap())?;
Ok(())
});
let _t = lua.create_thread(lua.load("return 123").into_function()?)?;
assert_eq!(count.load(Ordering::Relaxed), 1);
Ok(())
}
#[test]
fn test_thread_event_create_error() -> Result<()> {
let lua = Lua::new();
lua.set_thread_event_callback(ThreadTriggers::ON_CREATE, move |_, _| Err(Error::runtime("blah")));
let result = lua.create_thread(lua.load("return 123").into_function()?);
assert!(result.is_err());
assert!(matches!(result, Err(Error::RuntimeError(err)) if err.contains("blah")));
Ok(())
}
#[test]
fn test_thread_event_resume() -> Result<()> {
let lua = Lua::new();
let count = Arc::new(AtomicBool::new(false));
let count2 = count.clone();
lua.set_thread_event_callback(ThreadTriggers::ON_RESUME, move |_lua, event| {
assert!(matches!(event, ThreadEvent::Resume(_)));
count2.store(true, Ordering::Relaxed);
Ok(())
});
let thread = lua.create_thread(lua.load("return 42").into_function()?)?;
thread.resume::<()>(())?;
assert!(count.load(Ordering::Relaxed));
Ok(())
}
#[test]
fn test_thread_event_resume_error() -> Result<()> {
let lua = Lua::new();
lua.set_thread_event_callback(ThreadTriggers::ON_RESUME, move |_lua, _event| {
Err(Error::runtime("abort resume"))
});
let thread = lua.create_thread(lua.load("return 42").into_function()?)?;
let err = thread.resume::<()>(()).unwrap_err();
assert!(matches!(err, Error::RuntimeError(msg) if msg == "abort resume"));
assert!(thread.is_resumable());
Ok(())
}
#[test]
fn test_thread_event_yield() -> Result<()> {
let lua = Lua::new();
let count = Arc::new(AtomicBool::new(false));
let count2 = count.clone();
lua.set_thread_event_callback(ThreadTriggers::ON_YIELD, move |_lua, event| {
assert!(matches!(event, ThreadEvent::Yield(_)));
count2.store(true, Ordering::Relaxed);
Ok(())
});
let thread = lua.create_thread(lua.load("coroutine.yield(1) return 2").into_function()?)?;
let val = thread.resume::<i32>(())?;
assert_eq!(val, 1);
assert!(count.load(Ordering::Relaxed));
// Reset flag and resume to completion
count.store(false, Ordering::Relaxed);
let val = thread.resume::<i32>(())?;
assert_eq!(val, 2);
// Yield hook should not fire on the final return
assert!(!count.load(Ordering::Relaxed));
assert!(thread.is_finished());
Ok(())
}
#[test]
fn test_thread_event_yield_error() -> Result<()> {
let lua = Lua::new();
lua.set_thread_event_callback(ThreadTriggers::ON_YIELD, move |_lua, _event| {
Err(Error::runtime("yield error"))
});
let thread = lua.create_thread(lua.load("coroutine.yield(1)").into_function()?)?;
let err = thread.resume::<()>(()).unwrap_err();
assert!(matches!(err, Error::RuntimeError(msg) if msg == "yield error"));
Ok(())
}
#[test]
fn test_thread_event_swap() -> Result<()> {
let lua = Lua::new();
let count = Arc::new(AtomicU32::new(0));
let count2 = count.clone();
lua.set_thread_event_callback(ThreadTriggers::ON_RESUME, move |_lua, _event| {
count2.fetch_add(1, Ordering::Relaxed);
Ok(())
});
let thread = lua.create_thread(lua.load("coroutine.yield(1) return 2").into_function()?)?;
thread.resume::<i32>(())?;
assert_eq!(count.load(Ordering::Relaxed), 1);
// Replace callback with a new one
let count3 = Arc::new(AtomicU32::new(0));
let count4 = count3.clone();
lua.set_thread_event_callback(ThreadTriggers::new().on_resume(), move |_lua, _event| {
count4.fetch_add(10, Ordering::Relaxed);
Ok(())
});
thread.resume::<i32>(())?;
assert_eq!(count.load(Ordering::Relaxed), 1);
assert_eq!(count3.load(Ordering::Relaxed), 10);
// Remove callback
lua.remove_thread_event_callback();
thread.reset(lua.load("return 0").into_function()?)?;
thread.resume::<()>(())?;
assert_eq!(count3.load(Ordering::Relaxed), 10); // unchanged
Ok(())
}
#[cfg(feature = "luau")]
#[test]
fn test_thread_event_luau_resume_error() -> Result<()> {
let lua = Lua::new();
let fired = Arc::new(AtomicBool::new(false));
let fired2 = fired.clone();
lua.set_thread_event_callback(ThreadTriggers::ON_RESUME, move |_lua, event| {
assert!(matches!(event, ThreadEvent::Resume(_)));
fired2.store(true, Ordering::Relaxed);
Ok(())
});
let thread = lua.create_thread(lua.load("return 42").into_function()?)?;
let _ = thread.resume_error::<()>("test error");
assert!(fired.load(Ordering::Relaxed));
Ok(())
}
#[cfg(feature = "luau")]
#[test]
fn test_thread_event_create_from_lua() -> Result<()> {
let lua = Lua::new();
let count = std::cell::Cell::new(0);
lua.set_thread_event_callback(ThreadTriggers::ON_CREATE, move |_, _| {
count.set(count.get() + 1);
if count.get() == 2 {
return Err(Error::runtime("thread limit exceeded"));
}
Ok(())
});
let result = lua
.load(
r#"
local co = coroutine.wrap(function() return coroutine.create(print) end)
co()
"#,
)
.exec();
assert!(result.is_err());
assert!(matches!(result, Err(Error::RuntimeError(err)) if err.contains("thread limit exceeded")));
Ok(())
}

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