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:
push:
branches: [dev]
branches: [main]
workflow_dispatch:
# 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)
- Added Lua 5.5 support (`lua55` feature flag)
+8 -7
View File
@@ -1,6 +1,6 @@
[package]
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>"]
rust-version = "1.88"
edition = "2024"
@@ -42,7 +42,7 @@ async = ["dep:futures-util"]
send = ["error-send"]
error-send = []
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"]
userdata-wrappers = ["parking_lot/send_guard"]
@@ -50,7 +50,7 @@ userdata-wrappers = ["parking_lot/send_guard"]
serialize = ["serde"]
[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 }
either = "1.0"
num-traits = { version = "0.2.14" }
@@ -61,9 +61,10 @@ erased-serde = { version = "0.4", optional = true }
serde-value = { version = "0.7", optional = true }
parking_lot = { version = "0.12", features = ["arc_lock"] }
anyhow = { version = "1.0", optional = true }
inventory = { version = "0.3", optional = true }
libc = "0.2"
ffi = { package = "mlua-sys", version = "0.10.0", path = "mlua-sys" }
ffi = { package = "mlua-sys", version = "0.11.0-rc.1", path = "mlua-sys" }
[dev-dependencies]
trybuild = "1.0"
@@ -77,10 +78,10 @@ static_assertions = "1.0"
hyper = { version = "1.2", features = ["full"] }
hyper-util = { version = "0.1.3", features = ["full"] }
http-body-util = "0.1.1"
reqwest = { version = "0.12", features = ["json"] }
reqwest = { version = "0.13", features = ["json"] }
tempfile = "3"
criterion = { version = "0.7", features = ["async_tokio"] }
rustyline = "17.0"
criterion = { version = "0.8", features = ["async_tokio"] }
rustyline = "18.0"
tokio = { version = "1.0", features = ["full"] }
[lints.rust]
+3 -1
View File
@@ -17,6 +17,8 @@
[Benchmarks]: https://github.com/khvzak/script-bench-rs
[FAQ]: FAQ.md
## The main branch is the development version of `mlua`. Please see the [v0.11](https://github.com/mlua-rs/mlua/tree/v0.11) branch for the stable versions of `mlua`.
`mlua` is a set of bindings to the [Lua](https://www.lua.org) programming language for Rust with a goal of providing a
_safe_ (as much as possible), high level, easy to use, practical and flexible API.
@@ -125,7 +127,7 @@ my_project $ LUA_LIB=$HOME/tmp/lua-5.2.4/src LUA_LIB_NAME=lua LUA_LINK=static ca
Just enable the `vendored` feature and cargo will automatically build and link the specified Lua/LuaJIT version. This is the easiest way to get started with `mlua`.
### Standalone mode
In standalone mode, `mlua` allows adding scripting support to your application with a gently configured Lua runtime to ensure safety and soundness.
In standalone mode, `mlua` allows adding scripting support to your application with a properly configured Lua runtime to ensure safety and soundness.
Add to `Cargo.toml`:
+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
The v0.10 version of mlua has goal to improve the user experience while keeping the same performance and safety guarantees.
The v0.10 version of mlua has a goal to improve the user experience while keeping the same performance and safety guarantees.
This document highlights the most notable features. For a full list of changes, see the [CHANGELOG].
[CHANGELOG]: https://github.com/mlua-rs/mlua/blob/main/CHANGELOG.md
@@ -40,7 +40,7 @@ assert_eq!(lua.globals().get::<i32>("i")?, 20);
Under the hood, to synchronize access to the Lua state, mlua uses [`ReentrantMutex`] which can be recursively locked by a single thread. Only one thread can execute Lua code at a time, but it's possible to share Lua values between threads.
This has some performance penalties (about 10-20%) compared to the lock free mode. This flag is disabled by default and does not supported in module mode.
This has some performance penalties (about 10-20%) compared to the lock free mode. This flag is disabled by default and is not supported in module mode.
[`ReentrantMutex`]: https://docs.rs/parking_lot/latest/parking_lot/type.ReentrantMutex.html
@@ -144,7 +144,7 @@ The following `Scope` methods were changed:
Instead, scope has comprehensive support for borrowed userdata: `create_any_userdata_ref`, `create_any_userdata_ref_mut`, `create_userdata_ref`, `create_userdata_ref_mut`.
`UserDataRef` and `UserDataRefMut` are no longer acceptable for scoped userdata access as they require owned underlying data.
In mlua v0.9 this can cause read-after-free bug in some edge cases.
In mlua v0.9 this could cause a read-after-free bug in some edge cases.
To temporarily borrow underlying data, the `AnyUserData::borrow_scoped` and `AnyUserData::borrow_mut_scoped` methods were introduced:
+3 -3
View File
@@ -152,9 +152,9 @@ It will automatically trigger JIT compilation for new Lua chunks. To disable it,
#### 1. Better error reporting
When calling a Rust function from Lua and passing wrong arguments, previous mlua versions reported a error message without any context or reference to the particular argument.
When calling a Rust function from Lua and passing wrong arguments, previous mlua versions reported an error message without any context or reference to the particular argument.
In v0.9 it reports a error message with the argument index and expected type:
In v0.9 it reports an error message with the argument index and expected type:
```rust
let func = lua.create_function(|_, _a: i32| Ok(()))?;
@@ -327,7 +327,7 @@ Under the hood a new function `luaopen_alt_module` will be created for the Lua m
- `skip_memory_check` - skip memory allocation checks for some operations.
In module mode, mlua runs in unknown environment and cannot say are there any memory limits or not. As result, some operations that require memory allocation runs in
In module mode, mlua runs in an unknown environment and cannot tell whether there are any memory limits or not. As a result, some operations that require memory allocation run in
protected mode. Setting this attribute will improve performance of such operations with risk of having uncaught exceptions and memory leaks.
#### Improved Windows target
+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 {
length: u32,
width: u32,
}
impl UserData for Rectangle {
fn add_fields<F: mlua::UserDataFields<Self>>(fields: &mut F) {
fields.add_field_method_get("length", |_, this| Ok(this.length));
fields.add_field_method_set("length", |_, this, val| {
this.length = val;
Ok(())
});
fields.add_field_method_get("width", |_, this| Ok(this.width));
fields.add_field_method_set("width", |_, this, val| {
this.width = val;
Ok(())
});
#[mlua::userdata_impl]
impl Rectangle {
const NAME: &str = "Rectangle";
#[lua(infallible)]
fn new(length: u32, width: u32) -> Self {
Self { length, width }
}
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
methods.add_method("area", |_, this, ()| Ok(this.length * this.width));
methods.add_method("diagonal", |_, this, ()| {
Ok((this.length.pow(2) as f64 + this.width.pow(2) as f64).sqrt())
});
#[lua(getter, name = "area", infallible)]
fn calculate_area(&self) -> u32 {
self.length * self.width
}
// Constructor
methods.add_meta_function(MetaMethod::Call, |_, ()| Ok(Rectangle::default()));
fn diagonal(&self) -> Result<f64> {
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<()> {
let lua = Lua::new();
let rectangle = Rectangle::default();
lua.globals().set("Rectangle", lua.create_proxy::<Rectangle>()?)?;
lua.load(chunk! {
local rect = $rectangle()
rect.width = 10
rect.length = 5
assert(rect:area() == 50)
assert(rect:diagonal() - 11.1803 < 0.0001)
local rect = Rectangle(10, 5)
rect.width = rect.width + 5
rect.length = rect.length + 5
assert(rect.NAME == "Rectangle")
assert(rect.area == 150)
assert(math.floor(rect:diagonal()) == 18)
})
.exec()
}
+4 -4
View File
@@ -1,6 +1,6 @@
[package]
name = "mlua-sys"
version = "0.10.0"
version = "0.11.0-rc.1"
authors = ["Aleksandr Orlenko <zxteam@pm.me>"]
rust-version = "1.88"
edition = "2024"
@@ -41,9 +41,9 @@ libc = "0.2"
cc = "1.0"
cfg-if = "1.0"
pkg-config = "0.3.17"
lua-src = { version = ">= 550.0.0, < 550.1.0", optional = true }
luajit-src = { version = ">= 210.6.0, < 210.7.0", optional = true }
luau0-src = { version = "0.18.0", optional = true }
lua-src = { version = ">= 550.1.0, < 550.2.0", optional = true }
luajit-src = { version = ">= 210.7.0, < 210.8.0", optional = true }
luau0-src = { version = "0.20.0", optional = true }
[lints.rust]
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(raw_dylib)'] }
+2
View File
@@ -38,8 +38,10 @@ unsafe extern "C-unwind" {
#[link_name = "luaL_checkinteger"]
pub fn luaL_checkinteger_(L: *mut lua_State, narg: c_int) -> c_int;
pub fn luaL_checkinteger64(L: *mut lua_State, narg: c_int) -> i64;
#[link_name = "luaL_optinteger"]
pub fn luaL_optinteger_(L: *mut lua_State, narg: c_int, def: c_int) -> c_int;
pub fn luaL_optinteger64(L: *mut lua_State, narg: c_int, def: i64) -> i64;
pub fn luaL_checkunsigned(L: *mut lua_State, narg: c_int) -> lua_Unsigned;
pub fn luaL_optunsigned(L: *mut lua_State, narg: c_int, def: lua_Unsigned) -> lua_Unsigned;
+31 -9
View File
@@ -65,14 +65,15 @@ pub const LUA_TBOOLEAN: c_int = 1;
pub const LUA_TLIGHTUSERDATA: c_int = 2;
pub const LUA_TNUMBER: c_int = 3;
pub const LUA_TVECTOR: c_int = 4;
pub const LUA_TINTEGER: c_int = 4;
pub const LUA_TVECTOR: c_int = 5;
pub const LUA_TSTRING: c_int = 5;
pub const LUA_TTABLE: c_int = 6;
pub const LUA_TFUNCTION: c_int = 7;
pub const LUA_TUSERDATA: c_int = 8;
pub const LUA_TTHREAD: c_int = 9;
pub const LUA_TBUFFER: c_int = 10;
pub const LUA_TSTRING: c_int = 6;
pub const LUA_TTABLE: c_int = 7;
pub const LUA_TFUNCTION: c_int = 8;
pub const LUA_TUSERDATA: c_int = 9;
pub const LUA_TTHREAD: c_int = 10;
pub const LUA_TBUFFER: c_int = 11;
/// Guaranteed number of Lua stack slots available to a C function.
pub const LUA_MINSTACK: c_int = 20;
@@ -153,6 +154,7 @@ unsafe extern "C-unwind" {
pub fn lua_tounsignedx(L: *mut lua_State, idx: c_int, isnum: *mut c_int) -> lua_Unsigned;
pub fn lua_tovector(L: *mut lua_State, idx: c_int) -> *const c_float;
pub fn lua_toboolean(L: *mut lua_State, idx: c_int) -> c_int;
pub fn lua_tointeger64(L: *mut lua_State, idx: c_int, isinteger: *mut c_int) -> i64;
pub fn lua_tolstring(L: *mut lua_State, idx: c_int, len: *mut usize) -> *const c_char;
pub fn lua_tostringatom(L: *mut lua_State, idx: c_int, atom: *mut c_int) -> *const c_char;
pub fn lua_tolstringatom(
@@ -182,6 +184,7 @@ unsafe extern "C-unwind" {
pub fn lua_pushnumber(L: *mut lua_State, n: lua_Number);
#[link_name = "lua_pushinteger"]
pub fn lua_pushinteger_(L: *mut lua_State, n: c_int);
pub fn lua_pushinteger64(L: *mut lua_State, n: i64);
pub fn lua_pushunsigned(L: *mut lua_State, n: lua_Unsigned);
#[cfg(not(feature = "luau-vector4"))]
pub fn lua_pushvector(L: *mut lua_State, x: c_float, y: c_float, z: c_float);
@@ -412,6 +415,11 @@ pub unsafe fn lua_isboolean(L: *mut lua_State, n: c_int) -> c_int {
(lua_type(L, n) == LUA_TBOOLEAN) as c_int
}
#[inline(always)]
pub unsafe fn lua_isinteger64(L: *mut lua_State, n: c_int) -> c_int {
(lua_type(L, n) == LUA_TINTEGER) as c_int
}
#[inline(always)]
pub unsafe fn lua_isvector(L: *mut lua_State, n: c_int) -> c_int {
(lua_type(L, n) == LUA_TVECTOR) as c_int
@@ -501,6 +509,12 @@ pub type lua_Coverage = unsafe extern "C-unwind" fn(
size: usize,
);
pub type lua_CounterFunction =
unsafe extern "C-unwind" fn(context: *mut c_void, function: *const c_char, linedefined: c_int);
pub type lua_CounterValue =
unsafe extern "C-unwind" fn(context: *mut c_void, kind: c_int, line: c_int, hits: u64);
unsafe extern "C-unwind" {
pub fn lua_stackdepth(L: *mut lua_State) -> c_int;
pub fn lua_getinfo(L: *mut lua_State, level: c_int, what: *const c_char, ar: *mut lua_Debug) -> c_int;
@@ -515,6 +529,14 @@ unsafe extern "C-unwind" {
pub fn lua_getcoverage(L: *mut lua_State, funcindex: c_int, context: *mut c_void, callback: lua_Coverage);
pub fn lua_getcounters(
L: *mut lua_State,
funcindex: c_int,
context: *mut c_void,
functionvisit: lua_CounterFunction,
countervisit: lua_CounterValue,
);
pub fn lua_debugtrace(L: *mut lua_State) -> *const c_char;
}
@@ -551,8 +573,8 @@ pub struct lua_Callbacks {
/// gets called when L is created (LP == parent) or destroyed (LP == NULL)
pub userthread: Option<unsafe extern "C-unwind" fn(LP: *mut lua_State, L: *mut lua_State)>,
/// gets called when a string is created; returned atom can be retrieved via tostringatom
pub useratom: Option<unsafe extern "C-unwind" fn(s: *const c_char, l: usize) -> i16>,
/// gets called when a string is created to assign an atom id
pub useratom: Option<unsafe extern "C-unwind" fn(L: *mut lua_State, s: *const c_char, l: usize) -> i16>,
/// gets called when BREAK instruction is encountered
pub debugbreak: Option<unsafe extern "C-unwind" fn(L: *mut lua_State, ar: *mut lua_Debug)>,
+1
View File
@@ -80,6 +80,7 @@ unsafe extern "C" {
pub fn luau_set_compile_constant_nil(cons: *mut lua_CompileConstant);
pub fn luau_set_compile_constant_boolean(cons: *mut lua_CompileConstant, b: c_int);
pub fn luau_set_compile_constant_number(cons: *mut lua_CompileConstant, n: f64);
pub fn luau_set_compile_constant_integer64(cons: *mut lua_CompileConstant, l: i64);
pub fn luau_set_compile_constant_vector(cons: *mut lua_CompileConstant, x: f32, y: f32, z: f32, w: f32);
pub fn luau_set_compile_constant_string(cons: *mut lua_CompileConstant, s: *const c_char, l: usize);
}
+2
View File
@@ -14,6 +14,7 @@ pub const LUA_UTF8LIBNAME: *const c_char = cstr!("utf8");
pub const LUA_MATHLIBNAME: *const c_char = cstr!("math");
pub const LUA_DBLIBNAME: *const c_char = cstr!("debug");
pub const LUA_VECLIBNAME: *const c_char = cstr!("vector");
pub const LUA_INTLIBNAME: *const c_char = cstr!("integer");
unsafe extern "C-unwind" {
pub fn luaopen_base(L: *mut lua_State) -> c_int;
@@ -27,6 +28,7 @@ unsafe extern "C-unwind" {
pub fn luaopen_math(L: *mut lua_State) -> c_int;
pub fn luaopen_debug(L: *mut lua_State) -> c_int;
pub fn luaopen_vector(L: *mut lua_State) -> c_int;
pub fn luaopen_integer(L: *mut lua_State) -> c_int;
// open all builtin libraries
pub fn luaL_openlibs(L: *mut lua_State);
+4 -5
View File
@@ -1,8 +1,9 @@
[package]
name = "mlua_derive"
version = "0.11.0"
version = "0.12.0-rc.1"
authors = ["Aleksandr Orlenko <zxteam@pm.me>"]
edition = "2021"
rust-version = "1.88"
edition = "2024"
description = "Procedural macros for the mlua crate."
repository = "https://github.com/mlua-rs/mlua"
keywords = ["lua", "mlua"]
@@ -12,7 +13,7 @@ license = "MIT"
proc-macro = true
[features]
macros = ["proc-macro-error2", "itertools", "regex", "once_cell"]
macros = ["proc-macro-error2", "itertools"]
[dependencies]
quote = "1.0"
@@ -20,5 +21,3 @@ proc-macro2 = { version = "1.0", features = ["span-locations"] }
proc-macro-error2 = { version = "2.0.1", optional = true }
syn = { version = "2.0", features = ["full"] }
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 itertools::Itertools;
use once_cell::sync::Lazy;
use proc_macro::{Delimiter, Span, TokenStream, TokenTree};
use proc_macro2::Span as Span2;
use regex::Regex;
#[derive(Clone, Copy, Debug)]
pub(crate) struct Pos {
@@ -39,46 +37,16 @@ fn span_pos(span: &Span) -> (Pos, Pos) {
let start = span2.start();
let end = span2.end();
// In stable, line/column information is not provided
// and set to 0 (line is 1-indexed)
// Rust 1.88 stabilized Span APIs, so this branch must be unreachable
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))
}
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.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum TokenAttr {
@@ -108,8 +76,9 @@ impl Eq for Token {}
impl Token {
fn new(tree: TokenTree) -> Self {
let (start, end) = span_pos(&tree.span());
let source = tree.span().source_text().unwrap_or_else(|| tree.to_string());
Self {
source: tree.to_string(),
source,
start,
end,
tree,
@@ -168,14 +137,17 @@ impl Tokens {
Tokens(
tt.into_iter()
.flat_map(Tokens::from)
.peekable()
.batching(|iter| {
// Find variable tokens
// Find variable tokens: `$` + `ident` => `$ident`
let t = iter.next()?;
if t.is("$") {
// `$` + `ident` => `$ident`
let t = iter.next().expect("$ must trail an identifier");
Some(t.attr(TokenAttr::Cap))
if let Some(next) = iter.next()
&& matches!(next.tree, TokenTree::Ident(_))
{
Some(next.attr(TokenAttr::Cap))
} else {
proc_macro_error2::abort!(t.tree.span(), "`$` must be followed by an identifier");
}
} else {
Some(t)
}
+13 -13
View File
@@ -1,6 +1,6 @@
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};
use syn::{DeriveInput, parse_macro_input};
pub fn from_lua(input: TokenStream) -> TokenStream {
let DeriveInput { ident, generics, .. } = parse_macro_input!(input as DeriveInput);
@@ -13,19 +13,19 @@ pub fn from_lua(input: TokenStream) -> TokenStream {
};
quote! {
impl #impl_generics ::mlua::FromLua for #ident #ty_generics #where_clause {
#[inline]
fn from_lua(value: ::mlua::Value, _: &::mlua::Lua) -> ::mlua::Result<Self> {
match value {
::mlua::Value::UserData(ud) => Ok(ud.borrow::<Self>()?.clone()),
_ => Err(::mlua::Error::FromLuaConversionError {
from: value.type_name(),
to: #ident_str.to_string(),
message: None,
}),
}
impl #impl_generics ::mlua::FromLua for #ident #ty_generics #where_clause {
#[inline]
fn from_lua(value: ::mlua::Value, _: &::mlua::Lua) -> ::mlua::Result<Self> {
match value {
::mlua::Value::UserData(ud) => Ok(ud.borrow::<Self>()?.clone()),
_ => Err(::mlua::Error::FromLuaConversionError {
from: value.type_name(),
to: #ident_str.to_string(),
message: None,
}),
}
}
}
}
}
.into()
}
+27 -131
View File
@@ -1,148 +1,30 @@
use proc_macro::TokenStream;
use proc_macro2::{Ident, Span};
use quote::quote;
use syn::meta::ParseNestedMeta;
use syn::{parse_macro_input, ItemFn, LitStr, Result};
mod module;
#[cfg(feature = "macros")]
use {
crate::chunk::Chunk, proc_macro::TokenTree, proc_macro2::TokenStream as TokenStream2,
proc_macro_error2::proc_macro_error,
};
use {crate::chunk::Chunk, proc_macro_error2::proc_macro_error};
#[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"));
#[cfg(feature = "macros")]
macro_rules! try_compile {
($expr:expr) => {
match $expr {
Ok(val) => val,
Err(err) => return err.to_compile_error().into(),
}
Ok(())
}
};
}
#[proc_macro_attribute]
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
#[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()
module::lua_module(attr, item)
}
#[cfg(feature = "macros")]
#[proc_macro]
#[proc_macro_error]
pub fn chunk(input: TokenStream) -> TokenStream {
let chunk = Chunk::new(input);
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()
Chunk::new(input).expand().into()
}
#[cfg(feature = "macros")]
@@ -151,9 +33,23 @@ pub fn from_lua(input: TokenStream) -> TokenStream {
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")]
mod chunk;
#[cfg(feature = "macros")]
mod from_lua;
#[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
/// buffer.
///
/// Buffer operations are infallible, none of the read/write functions will return a Err.
/// Buffer operations are infallible, none of the read/write functions will return an Err.
pub fn cursor(self) -> impl io::Read + io::Write + io::Seek {
BufferCursor(self, 0)
}
+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::collections::HashMap;
use std::ffi::CString;
@@ -153,6 +160,7 @@ pub enum ChunkMode {
/// Represents a constant value that can be used by Luau compiler.
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
#[non_exhaustive]
#[derive(Clone, Debug)]
pub enum CompileConstant {
Nil,
@@ -779,7 +787,6 @@ impl Chunk<'_> {
///
/// The resulted `IntoLua` implementation will convert the chunk into a Lua function without
/// executing it.
#[doc(hidden)]
#[track_caller]
pub fn wrap(chunk: impl AsChunk) -> impl IntoLua {
WrappedChunk {
+36 -35
View File
@@ -4,7 +4,7 @@ use std::ffi::{CStr, CString, OsStr, OsString};
use std::hash::{BuildHasher, Hash};
use std::os::raw::c_int;
use std::path::{Path, PathBuf};
use std::{mem, slice, str};
use std::{slice, str};
use bstr::{BStr, BString, ByteVec};
use num_traits::cast;
@@ -16,7 +16,7 @@ use crate::string::{BorrowedBytes, BorrowedStr, LuaString};
use crate::table::Table;
use crate::thread::Thread;
use crate::traits::{FromLua, IntoLua, ShortTypeName as _};
use crate::types::{Either, LightUserData, MaybeSend, RegistryKey};
use crate::types::{Either, LightUserData, MaybeSend, MaybeSync, RegistryKey};
use crate::userdata::{AnyUserData, UserData};
use crate::value::{Nil, Value};
@@ -86,91 +86,79 @@ impl FromLua for LuaString {
}
}
impl IntoLua for BorrowedStr<'_> {
impl IntoLua for BorrowedStr {
#[inline]
fn into_lua(self, _: &Lua) -> Result<Value> {
Ok(Value::String(self.borrow.into_owned()))
Ok(Value::String(LuaString(self.vref)))
}
#[inline]
unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> {
lua.push_ref(&self.borrow.0);
lua.push_ref(&self.vref);
Ok(())
}
}
impl IntoLua for &BorrowedStr<'_> {
impl IntoLua for &BorrowedStr {
#[inline]
fn into_lua(self, _: &Lua) -> Result<Value> {
Ok(Value::String(self.borrow.clone().into_owned()))
Ok(Value::String(LuaString(self.vref.clone())))
}
#[inline]
unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> {
lua.push_ref(&self.borrow.0);
lua.push_ref(&self.vref);
Ok(())
}
}
impl FromLua for BorrowedStr<'_> {
impl FromLua for BorrowedStr {
fn from_lua(value: Value, lua: &Lua) -> Result<Self> {
let s = LuaString::from_lua(value, lua)?;
let BorrowedStr { buf, _lua, .. } = BorrowedStr::try_from(&s)?;
let buf = unsafe { mem::transmute::<&str, &'static str>(buf) };
let borrow = Cow::Owned(s);
Ok(Self { buf, borrow, _lua })
BorrowedStr::try_from(&s)
}
unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result<Self> {
let s = LuaString::from_stack(idx, lua)?;
let BorrowedStr { buf, _lua, .. } = BorrowedStr::try_from(&s)?;
let buf = unsafe { mem::transmute::<&str, &'static str>(buf) };
let borrow = Cow::Owned(s);
Ok(Self { buf, borrow, _lua })
BorrowedStr::try_from(&s)
}
}
impl IntoLua for BorrowedBytes<'_> {
impl IntoLua for BorrowedBytes {
#[inline]
fn into_lua(self, _: &Lua) -> Result<Value> {
Ok(Value::String(self.borrow.into_owned()))
Ok(Value::String(LuaString(self.vref)))
}
#[inline]
unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> {
lua.push_ref(&self.borrow.0);
lua.push_ref(&self.vref);
Ok(())
}
}
impl IntoLua for &BorrowedBytes<'_> {
impl IntoLua for &BorrowedBytes {
#[inline]
fn into_lua(self, _: &Lua) -> Result<Value> {
Ok(Value::String(self.borrow.clone().into_owned()))
Ok(Value::String(LuaString(self.vref.clone())))
}
#[inline]
unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> {
lua.push_ref(&self.borrow.0);
lua.push_ref(&self.vref);
Ok(())
}
}
impl FromLua for BorrowedBytes<'_> {
impl FromLua for BorrowedBytes {
fn from_lua(value: Value, lua: &Lua) -> Result<Self> {
let s = LuaString::from_lua(value, lua)?;
let BorrowedBytes { buf, _lua, .. } = BorrowedBytes::from(&s);
let buf = unsafe { mem::transmute::<&[u8], &'static [u8]>(buf) };
let borrow = Cow::Owned(s);
Ok(Self { buf, borrow, _lua })
Ok(BorrowedBytes::from(&s))
}
unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result<Self> {
let s = LuaString::from_stack(idx, lua)?;
let BorrowedBytes { buf, _lua, .. } = BorrowedBytes::from(&s);
let buf = unsafe { mem::transmute::<&[u8], &'static [u8]>(buf) };
let borrow = Cow::Owned(s);
Ok(Self { buf, borrow, _lua })
Ok(BorrowedBytes::from(&s))
}
}
@@ -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]
fn into_lua(self, lua: &Lua) -> Result<Value> {
Ok(Value::UserData(lua.create_userdata(self)?))
@@ -535,7 +523,10 @@ impl IntoLua for &str {
impl IntoLua for Cow<'_, str> {
#[inline]
fn into_lua(self, lua: &Lua) -> Result<Value> {
Ok(Value::String(lua.create_string(self.as_bytes())?))
match self {
Cow::Borrowed(s) => s.into_lua(lua),
Cow::Owned(s) => s.into_lua(lua),
}
}
}
@@ -597,7 +588,10 @@ impl IntoLua for &CStr {
impl IntoLua for Cow<'_, CStr> {
#[inline]
fn into_lua(self, lua: &Lua) -> Result<Value> {
Ok(Value::String(lua.create_string(self.to_bytes())?))
match self {
Cow::Borrowed(s) => s.into_lua(lua),
Cow::Owned(s) => s.into_lua(lua),
}
}
}
@@ -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
Self::from_lua(lua.stack_value(idx, Some(type_id)), lua.lua())
}
+2 -2
View File
@@ -1,8 +1,8 @@
//! Lua debugging interface.
//!
//! This module provides access to the Lua debug interface, allowing inspection of the call stack,
//! and function information. The main types are [`Debug`] for accessing debug information and
//! [`HookTriggers`] for configuring debug hooks.
//! and function information. The main types are [`struct@Debug`] for accessing debug information
//! and [`HookTriggers`] for configuring debug hooks.
use std::borrow::Cow;
use std::os::raw::c_int;
+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::fmt;
use std::io::Error as IoError;
@@ -285,7 +290,7 @@ impl fmt::Display for Error {
// Try to find local traceback within the full traceback
if let Some(pos) = full_traceback.find(traceback) {
write!(fmt, "{}", &full_traceback[..pos])?;
writeln!(fmt, ">{}", &full_traceback[pos..].trim_end())?;
writeln!(fmt, ">{}", full_traceback[pos..].trim_end())?;
} else {
writeln!(fmt, "{}", full_traceback.trim_end())?;
}
@@ -340,7 +345,11 @@ impl Error {
/// Wraps an external error object.
#[inline]
pub fn external<T: Into<Box<DynStdError>>>(err: T) -> Self {
Error::ExternalError(err.into().into())
let boxed = err.into();
match boxed.downcast::<Self>() {
Ok(err) => *err,
Err(boxed) => Error::ExternalError(boxed.into()),
}
}
/// Attempts to downcast the external error object to a concrete type by reference.
@@ -550,10 +559,8 @@ impl<'a> Iterator for Chain<'a> {
#[cfg(test)]
mod assertions {
use super::*;
#[cfg(not(feature = "error-send"))]
static_assertions::assert_not_impl_any!(Error: Send, Sync);
static_assertions::assert_not_impl_any!(super::Error: Send, Sync);
#[cfg(feature = "send")]
static_assertions::assert_impl_all!(Error: Send, Sync);
static_assertions::assert_impl_all!(super::Error: Send, Sync);
}
+107 -19
View File
@@ -3,12 +3,6 @@
//! This module provides types for working with Lua functions from Rust, including
//! 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
//!
//! Use [`Function::call`] to invoke a Lua function synchronously:
@@ -81,12 +75,13 @@
use std::cell::RefCell;
use std::os::raw::{c_int, c_void};
use std::result::Result as StdResult;
use std::{mem, ptr, slice};
use crate::error::{Error, Result};
use crate::error::{Error, ExternalError, ExternalResult, Result};
use crate::state::Lua;
use crate::table::Table;
use crate::traits::{FromLuaMulti, IntoLua, IntoLuaMulti, LuaNativeFn, LuaNativeFnMut};
use crate::traits::{FromLuaMulti, IntoLua, IntoLuaMulti};
use crate::types::{Callback, LuaType, MaybeSend, ValueRef};
use crate::util::{
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")]
use {
crate::thread::AsyncThread,
crate::traits::LuaNativeAsyncFn,
crate::types::AsyncCallback,
std::future::{self, Future},
std::pin::{Pin, pin},
@@ -246,7 +240,7 @@ impl Function {
/// # }
/// ```
///
/// [`AsyncThread`]: crate::AsyncThread
/// [`AsyncThread`]: crate::thread::AsyncThread
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub fn call_async<R>(&self, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
@@ -636,30 +630,32 @@ impl Function {
/// Wraps a Rust function or closure, returning an opaque type that implements [`IntoLua`]
/// trait.
#[inline]
pub fn wrap<F, A, R>(func: F) -> impl IntoLua
pub fn wrap<F, A, R, E>(func: F) -> impl IntoLua
where
F: LuaNativeFn<A, Output = Result<R>> + MaybeSend + 'static,
F: LuaNativeFn<A, Output = StdResult<R, E>> + MaybeSend + 'static,
A: FromLuaMulti,
R: IntoLuaMulti,
E: ExternalError,
{
WrappedFunction(Box::new(move |lua, nargs| unsafe {
let args = A::from_stack_args(nargs, 1, None, lua)?;
func.call(args)?.push_into_stack_multi(lua)
func.call(args).into_lua_err()?.push_into_stack_multi(lua)
}))
}
/// Wraps a Rust mutable closure, returning an opaque type that implements [`IntoLua`] trait.
pub fn wrap_mut<F, A, R>(func: F) -> impl IntoLua
pub fn wrap_mut<F, A, R, E>(func: F) -> impl IntoLua
where
F: LuaNativeFnMut<A, Output = Result<R>> + MaybeSend + 'static,
F: LuaNativeFnMut<A, Output = StdResult<R, E>> + MaybeSend + 'static,
A: FromLuaMulti,
R: IntoLuaMulti,
E: ExternalError,
{
let func = RefCell::new(func);
WrappedFunction(Box::new(move |lua, nargs| unsafe {
let mut func = func.try_borrow_mut().map_err(|_| Error::RecursiveMutCallback)?;
let args = A::from_stack_args(nargs, 1, None, lua)?;
func.call(args)?.push_into_stack_multi(lua)
func.call(args).into_lua_err()?.push_into_stack_multi(lua)
}))
}
@@ -672,6 +668,7 @@ impl Function {
pub fn wrap_raw<F, A>(func: F) -> impl IntoLua
where
F: LuaNativeFn<A> + MaybeSend + 'static,
F::Output: IntoLuaMulti,
A: FromLuaMulti,
{
WrappedFunction(Box::new(move |lua, nargs| unsafe {
@@ -688,6 +685,7 @@ impl Function {
pub fn wrap_raw_mut<F, A>(func: F) -> impl IntoLua
where
F: LuaNativeFnMut<A> + MaybeSend + 'static,
F::Output: IntoLuaMulti,
A: FromLuaMulti,
{
let func = RefCell::new(func);
@@ -702,11 +700,12 @@ impl Function {
/// trait.
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub fn wrap_async<F, A, R>(func: F) -> impl IntoLua
pub fn wrap_async<F, A, R, E>(func: F) -> impl IntoLua
where
F: LuaNativeAsyncFn<A, Output = Result<R>> + MaybeSend + 'static,
F: LuaNativeAsyncFn<A, Output = StdResult<R, E>> + MaybeSend + 'static,
A: FromLuaMulti,
R: IntoLuaMulti,
E: ExternalError,
{
WrappedAsyncFunction(Box::new(move |rawlua, nargs| unsafe {
let args = match A::from_stack_args(nargs, 1, None, rawlua) {
@@ -715,7 +714,7 @@ impl Function {
};
let lua = rawlua.lua();
let fut = func.call(args);
Box::pin(async move { fut.await?.push_into_stack_multi(lua.raw_lua()) })
Box::pin(async move { fut.await.into_lua_err()?.push_into_stack_multi(lua.raw_lua()) })
}))
}
@@ -729,6 +728,7 @@ impl Function {
pub fn wrap_raw_async<F, A>(func: F) -> impl IntoLua
where
F: LuaNativeAsyncFn<A> + MaybeSend + 'static,
F::Output: IntoLuaMulti,
A: FromLuaMulti,
{
WrappedAsyncFunction(Box::new(move |rawlua, nargs| unsafe {
@@ -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)]
mod assertions {
use super::*;
+67 -124
View File
@@ -61,6 +61,7 @@
//! [`Future`]: std::future::Future
//! [`serde::Serialize`]: https://docs.serde.rs/serde/ser/trait.Serialize.html
//! [`serde::Deserialize`]: https://docs.serde.rs/serde/de/trait.Deserialize.html
//! [`AsyncThread`]: crate::thread::AsyncThread
// Deny warnings inside doc tests / examples. When this isn't present, rustdoc doesn't show *any*
// warnings at all.
@@ -72,77 +73,96 @@
mod macros;
mod buffer;
mod chunk;
mod conversion;
mod error;
#[cfg(any(feature = "luau", doc))]
mod luau;
mod memory;
mod multi;
mod scope;
mod state;
mod stdlib;
mod string;
mod thread;
mod traits;
mod types;
mod userdata;
mod util;
mod value;
mod vector;
pub mod chunk;
pub mod debug;
pub mod error;
pub mod function;
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub mod luau;
pub mod prelude;
pub mod state;
pub mod string;
pub mod table;
pub mod thread;
pub mod userdata;
pub use bstr::BString;
pub use ffi::{self, lua_CFunction, lua_State};
#[cfg(feature = "macros")]
#[doc(hidden)]
pub use inventory as __inventory;
pub use crate::chunk::{AsChunk, Chunk, ChunkMode};
pub use crate::error::{Error, ErrorContext, ExternalError, ExternalResult, Result};
#[doc(inline)]
pub use crate::error::{Error, Result};
#[doc(inline)]
pub use crate::function::Function;
pub use crate::multi::{MultiValue, Variadic};
pub use crate::scope::Scope;
pub use crate::state::{GCMode, Lua, LuaOptions, WeakLua};
#[doc(inline)]
pub use crate::state::{Lua, LuaOptions, WeakLua};
pub use crate::stdlib::StdLib;
pub use crate::string::{BorrowedBytes, BorrowedStr, LuaString, LuaString as String};
#[doc(inline)]
pub use crate::string::{BorrowedBytes, BorrowedStr, LuaString};
#[doc(inline)]
pub use crate::table::Table;
pub use crate::thread::{Thread, ThreadStatus};
pub use crate::traits::{
FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, LuaNativeFn, LuaNativeFnMut, ObjectLike,
};
#[doc(inline)]
pub use crate::thread::Thread;
#[doc(inline)]
pub use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, ObjectLike};
pub use crate::types::{
AppDataRef, AppDataRefMut, Either, Integer, LightUserData, MaybeSend, Number, RegistryKey, VmState,
};
pub use crate::userdata::{
AnyUserData, MetaMethod, UserData, UserDataFields, UserDataMetatable, UserDataMethods, UserDataRef,
UserDataRefMut, UserDataRegistry,
AppDataRef, AppDataRefMut, Either, Integer, LightUserData, MaybeSend, MaybeSync, Number, RegistryKey,
VmState,
};
#[doc(inline)]
pub use crate::userdata::AnyUserData;
pub use crate::value::{Nil, Value};
// Re-export some types to keep backward compatibility and avoid breaking changes in the public API.
#[doc(hidden)]
pub use crate::chunk::{AsChunk, Chunk, ChunkMode};
#[cfg(feature = "luau")]
#[doc(hidden)]
pub use crate::chunk::{CompileConstant, Compiler};
#[doc(hidden)]
pub use crate::error::{ErrorContext, ExternalError, ExternalResult};
#[doc(hidden)]
pub use crate::string::LuaString as String;
#[doc(hidden)]
pub use crate::table::{TablePairs, TableSequence};
#[doc(hidden)]
pub use crate::thread::{ThreadEvent, ThreadStatus, ThreadTriggers};
#[doc(hidden)]
pub use crate::userdata::{
MetaMethod, UserData, UserDataFields, UserDataMetatable, UserDataMethods, UserDataOwned, UserDataRef,
UserDataRefMut, UserDataRegistry,
};
#[cfg(not(feature = "luau"))]
#[doc(inline)]
pub use crate::debug::HookTriggers;
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub use crate::{
buffer::Buffer,
chunk::{CompileConstant, Compiler},
luau::{HeapDump, NavigateError, Require, TextRequirer},
vector::Vector,
};
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub use crate::{thread::AsyncThread, traits::LuaNativeAsyncFn};
pub use crate::{buffer::Buffer, vector::Vector};
#[cfg(feature = "serde")]
#[doc(hidden)]
pub use crate::serde::{DeserializeOptions, SerializeOptions};
#[cfg(feature = "serde")]
#[doc(inline)]
pub use crate::{
serde::{LuaSerdeExt, de::Options as DeserializeOptions, ser::Options as SerializeOptions},
value::SerializableValue,
};
pub use crate::{serde::LuaSerdeExt, value::SerializableValue};
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
@@ -153,54 +173,7 @@ pub mod serde;
#[macro_use]
extern crate mlua_derive;
/// 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.
///
/// ```
/// 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.
#[doc = include_str!("../docs/chunk.md")]
#[cfg(feature = "macros")]
#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
pub use mlua_derive::chunk;
@@ -213,47 +186,17 @@ pub use mlua_derive::chunk;
#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
pub use mlua_derive::FromLua;
/// Registers Lua module entrypoint.
///
/// You can register multiple entrypoints as required.
///
/// ```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
///
/// ```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> {
/// ...
/// }
/// ```
#[doc = include_str!("../docs/UserData.md")]
#[cfg(feature = "macros")]
#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
pub use mlua_derive::UserData;
#[doc(hidden)]
#[cfg(feature = "macros")]
#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
pub use mlua_derive::userdata_impl;
#[doc = include_str!("../docs/lua_module.md")]
#[cfg(all(feature = "mlua_derive", any(feature = "module", doc)))]
#[cfg_attr(docsrs, doc(cfg(feature = "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::os::raw::c_int;
use std::ptr;
@@ -10,7 +17,7 @@ use crate::traits::{FromLuaMulti, IntoLua};
use crate::types::MaybeSend;
pub use heap_dump::HeapDump;
pub use require::{NavigateError, Require, TextRequirer};
pub use require::{FsRequirer, NavigateError, Require};
// Since Luau has some missing standard functions, we re-implement them here
@@ -86,7 +93,7 @@ impl Lua {
}
// Enable default `require` implementation
let require = self.create_require_function(require::TextRequirer::new())?;
let require = self.create_require_function(FsRequirer::new())?;
self.globals().raw_set("require", require)?;
Ok(())
+45 -4
View File
@@ -12,8 +12,7 @@ use crate::state::{Lua, callback_error_ext};
use crate::table::Table;
use crate::types::MaybeSend;
// TODO: Rename to FsRequirer
pub use fs::TextRequirer;
pub use fs::FsRequirer;
/// An error that can occur during navigation in the Luau `require-by-string` system.
#[derive(Debug, Clone)]
@@ -67,6 +66,24 @@ pub trait Require {
/// configuration file.
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
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(
state: *mut ffi::lua_State,
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).reset = reset;
(*config).jump_to_alias = jump_to_alias;
(*config).to_alias_override = None;
(*config).to_alias_fallback = None;
(*config).to_alias_override = Some(to_alias_override);
(*config).to_alias_fallback = Some(to_alias_fallback);
(*config).to_parent = to_parent;
(*config).to_child = to_child;
(*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.
#[derive(Default, Debug)]
pub struct TextRequirer {
pub struct FsRequirer {
/// An absolute path to the current Luau module (not mapped to a physical file)
abs_path: PathBuf,
/// A relative path to the current Luau module (not mapped to a physical file)
@@ -22,7 +22,7 @@ pub struct TextRequirer {
resolved_path: Option<PathBuf>,
}
impl TextRequirer {
impl FsRequirer {
/// The prefix used for chunk names in the require system.
/// Only chunk names starting with this prefix are allowed to be used in `require`.
const CHUNK_PREFIX: &str = "@";
@@ -36,7 +36,7 @@ impl TextRequirer {
/// The filename for the Luau configuration file.
const LUAU_CONFIG_FILENAME: &str = ".config.luau";
/// Creates a new `TextRequirer` instance.
/// Creates a new `FsRequirer` instance.
pub fn new() -> Self {
Self::default()
}
@@ -114,7 +114,7 @@ impl TextRequirer {
}
}
impl Require for TextRequirer {
impl Require for FsRequirer {
fn is_require_allowed(&self, chunk_name: &str) -> bool {
chunk_name.starts_with(Self::CHUNK_PREFIX)
}
@@ -231,7 +231,7 @@ impl Require for TextRequirer {
mod tests {
use std::path::Path;
use super::TextRequirer;
use super::FsRequirer;
#[test]
fn test_path_normalize() {
@@ -267,7 +267,7 @@ mod tests {
// '..' disappears if path is absolute and component is non-erasable
("/../", "/"),
] {
let path = TextRequirer::normalize_path(input.as_ref());
let path = FsRequirer::normalize_path(input.as_ref());
assert_eq!(
&path,
expected.as_ref() as &Path,
+24 -14
View File
@@ -3,34 +3,44 @@
#[doc(no_inline)]
pub use crate::{
AnyUserData as LuaAnyUserData, BorrowedBytes as LuaBorrowedBytes, BorrowedStr as LuaBorrowedStr,
Chunk as LuaChunk, Either as LuaEither, Error as LuaError, ErrorContext as LuaErrorContext,
ExternalError as LuaExternalError, ExternalResult as LuaExternalResult, FromLua, FromLuaMulti,
Function as LuaFunction, GCMode as LuaGCMode, Integer as LuaInteger, IntoLua, IntoLuaMulti,
LightUserData as LuaLightUserData, Lua, LuaNativeFn, LuaNativeFnMut, LuaOptions, LuaString,
MetaMethod as LuaMetaMethod, MultiValue as LuaMultiValue, Nil as LuaNil, Number as LuaNumber,
Either as LuaEither, Error as LuaError, FromLua, FromLuaMulti, Function as LuaFunction,
Integer as LuaInteger, IntoLua, IntoLuaMulti, LightUserData as LuaLightUserData, Lua, LuaOptions,
LuaString, MetaMethod as LuaMetaMethod, MultiValue as LuaMultiValue, Nil as LuaNil, Number as LuaNumber,
ObjectLike as LuaObjectLike, RegistryKey as LuaRegistryKey, Result as LuaResult, StdLib as LuaStdLib,
Table as LuaTable, Thread as LuaThread, ThreadStatus as LuaThreadStatus, UserData as LuaUserData,
UserDataFields as LuaUserDataFields, UserDataMetatable as LuaUserDataMetatable,
UserDataMethods as LuaUserDataMethods, UserDataRef as LuaUserDataRef,
UserDataRefMut as LuaUserDataRefMut, UserDataRegistry as LuaUserDataRegistry, Value as LuaValue,
Variadic as LuaVariadic, VmState as LuaVmState, WeakLua, function::FunctionInfo as LuaFunctionInfo,
table::TablePairs as LuaTablePairs, table::TableSequence as LuaTableSequence,
Table as LuaTable, Thread as LuaThread, UserData as LuaUserData, UserDataFields as LuaUserDataFields,
UserDataMetatable as LuaUserDataMetatable, UserDataMethods as LuaUserDataMethods,
UserDataOwned as LuaUserDataOwned, UserDataRef as LuaUserDataRef, UserDataRefMut as LuaUserDataRefMut,
UserDataRegistry as LuaUserDataRegistry, Value as LuaValue, Variadic as LuaVariadic,
VmState as LuaVmState, WeakLua, chunk::AsChunk as AsLuaChunk, chunk::Chunk as LuaChunk,
chunk::ChunkMode as LuaChunkMode, error::ErrorContext as LuaErrorContext,
error::ExternalError as LuaExternalError, error::ExternalResult as LuaExternalResult,
function::FunctionInfo as LuaFunctionInfo, function::LuaNativeFn, function::LuaNativeFnMut,
state::GcIncParams as LuaGcIncParams, state::GcMode as LuaGcMode, table::TablePairs as LuaTablePairs,
table::TableSequence as LuaTableSequence, thread::ThreadStatus as LuaThreadStatus,
};
#[cfg(not(feature = "luau"))]
#[doc(no_inline)]
pub use crate::HookTriggers as LuaHookTriggers;
#[cfg(any(feature = "lua54", feature = "lua55"))]
#[doc(no_inline)]
pub use crate::state::GcGenParams as LuaGcGenParams;
#[cfg(feature = "luau")]
#[doc(no_inline)]
pub use crate::{
CompileConstant as LuaCompileConstant, NavigateError as LuaNavigateError, Require as LuaRequire,
TextRequirer as LuaTextRequirer, Vector as LuaVector,
Vector as LuaVector,
chunk::{CompileConstant as LuaCompileConstant, Compiler as LuaCompiler},
luau::{
FsRequirer as LuaFsRequirer, HeapDump as LuaHeapDump, NavigateError as LuaNavigateError,
Require as LuaRequire,
},
};
#[cfg(feature = "async")]
#[doc(no_inline)]
pub use crate::{AsyncThread as LuaAsyncThread, LuaNativeAsyncFn};
pub use crate::{function::LuaNativeAsyncFn, thread::AsyncThread as LuaAsyncThread};
#[cfg(feature = "serde")]
#[doc(no_inline)]
+4 -6
View File
@@ -37,8 +37,8 @@ pub trait LuaSerdeExt: Sealed {
fn null(&self) -> Value;
/// A metatable attachable to a Lua table to systematically encode it as Array (instead of Map).
/// As result, encoded Array will contain only sequence part of the table, with the same length
/// as the `#` operator on that table.
/// As a result, encoded Array will contain only sequence part of the table, with the same
/// length as the `#` operator on that table.
///
/// # Example
///
@@ -242,7 +242,5 @@ static ARRAY_METATABLE_REGISTRY_KEY: u8 = 0;
pub mod de;
pub mod ser;
#[doc(inline)]
pub use de::Deserializer;
#[doc(inline)]
pub use ser::Serializer;
pub use de::{Deserializer, Options as DeserializeOptions};
pub use ser::{Options as SerializeOptions, Serializer};
+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::cell::{BorrowError, BorrowMutError, RefCell};
use std::marker::PhantomData;
@@ -17,11 +22,11 @@ use crate::scope::Scope;
use crate::stdlib::StdLib;
use crate::string::LuaString;
use crate::table::Table;
use crate::thread::Thread;
use crate::thread::{Thread, ThreadEvent, ThreadTriggers};
use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti};
use crate::types::{
AppDataRef, AppDataRefMut, ArcReentrantMutexGuard, Integer, LuaType, MaybeSend, Number, ReentrantMutex,
ReentrantMutexGuard, RegistryKey, VmState, XRc, XWeak,
AppDataRef, AppDataRefMut, ArcReentrantMutexGuard, Integer, LuaType, MaybeSend, MaybeSync, Number,
ReentrantMutex, ReentrantMutexGuard, RegistryKey, VmState, XRc, XWeak,
};
use crate::userdata::{AnyUserData, UserData, UserDataProxy, UserDataRegistry, UserDataStorage};
use crate::util::{StackGuard, assert_stack, check_stack, protect_lua_closure, push_string, rawset_field};
@@ -44,6 +49,7 @@ use {
use serde::Serialize;
pub(crate) use extra::ExtraData;
#[doc(hidden)]
pub use raw::RawLua;
pub(crate) use util::callback_error_ext;
@@ -62,20 +68,126 @@ pub struct WeakLua(XWeak<ReentrantMutex<RawLua>>);
pub(crate) struct LuaGuard(ArcReentrantMutexGuard<RawLua>);
/// Mode of the Lua garbage collector (GC).
///
/// In Lua 5.4 GC can work in two modes: incremental and generational.
/// Previous Lua versions support only incremental GC.
/// Tuning parameters for the incremental GC collector.
///
/// More information can be found in the Lua [documentation].
///
/// [documentation]: https://www.lua.org/manual/5.4/manual.html#2.5
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum GCMode {
Incremental,
/// [documentation]: https://www.lua.org/manual/5.5/manual.html#2.5.1
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Default)]
pub struct GcIncParams {
/// Pause between successive GC cycles, expressed as a percentage of live memory.
#[cfg(not(feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
pub pause: Option<c_int>,
/// Target heap size as a percentage of live data, controlling how aggressively
/// the GC reclaims memory (`LUA_GCSETGOAL`).
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub goal: Option<c_int>,
/// GC work performed per unit of memory allocated.
pub step_multiplier: Option<c_int>,
/// Granularity of each GC step (see Lua reference for details).
#[cfg(any(feature = "lua55", feature = "lua54", feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua55", feature = "lua54", feature = "luau"))))]
pub step_size: Option<c_int>,
}
impl GcIncParams {
/// Sets the `pause` parameter.
#[cfg(not(feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
pub fn pause(mut self, v: c_int) -> Self {
self.pause = Some(v);
self
}
/// Sets the `goal` parameter.
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn goal(mut self, v: c_int) -> Self {
self.goal = Some(v);
self
}
/// Sets the `step_multiplier` parameter.
pub fn step_multiplier(mut self, v: c_int) -> Self {
self.step_multiplier = Some(v);
self
}
/// Sets the `step_size` parameter.
#[cfg(any(feature = "lua55", feature = "lua54", feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua55", feature = "lua54", feature = "luau"))))]
pub fn step_size(mut self, v: c_int) -> Self {
self.step_size = Some(v);
self
}
}
/// Tuning parameters for the generational GC collector (Lua 5.4+).
///
/// More information can be found in the Lua [documentation].
///
/// [documentation]: https://www.lua.org/manual/5.5/manual.html#2.5.2
#[cfg(any(feature = "lua55", feature = "lua54"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua55", feature = "lua54"))))]
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Default)]
pub struct GcGenParams {
/// Frequency of minor (young-generation) collection steps.
pub minor_multiplier: Option<c_int>,
/// Threshold controlling how large the young generation can grow before triggering
/// a shift from minor to major collection.
pub minor_to_major: Option<c_int>,
/// Threshold controlling how much the major collection must shrink the heap before
/// switching back to minor (young-generation) collection.
#[cfg(feature = "lua55")]
#[cfg_attr(docsrs, doc(cfg(feature = "lua55")))]
pub major_to_minor: Option<c_int>,
}
#[cfg(any(feature = "lua55", feature = "lua54"))]
impl GcGenParams {
/// Sets the `minor_multiplier` parameter.
pub fn minor_multiplier(mut self, v: c_int) -> Self {
self.minor_multiplier = Some(v);
self
}
/// Sets the `minor_to_major` threshold.
pub fn minor_to_major(mut self, v: c_int) -> Self {
self.minor_to_major = Some(v);
self
}
/// Sets the `major_to_minor` parameter.
#[cfg(feature = "lua55")]
#[cfg_attr(docsrs, doc(cfg(feature = "lua55")))]
pub fn major_to_minor(mut self, v: c_int) -> Self {
self.major_to_minor = Some(v);
self
}
}
/// Lua garbage collector (GC) operating mode.
///
/// Use [`Lua::gc_set_mode`] to switch the collector mode and/or tune its parameters.
#[non_exhaustive]
#[derive(Clone, Debug)]
pub enum GcMode {
/// Incremental mark-and-sweep
Incremental(GcIncParams),
/// Generational
#[cfg(any(feature = "lua55", feature = "lua54"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua55", feature = "lua54"))))]
Generational,
Generational(GcGenParams),
}
/// Controls Lua interpreter behavior such as Rust panics handling.
@@ -337,30 +449,27 @@ impl Lua {
R::from_stack_multi(nresults, &lua)
}
/// Runs callback with the inner RawLua value. It can be used to manually push and get values on
/// the stack.
/// Calls provided function passing a reference to the [`RawLua`] handle.
///
/// This function is safe because all unsafe actions with RawLua can only be done with unsafe
/// Provided [`RawLua`] handle can be used to manually pushing/popping values to/from the stack.
///
/// # Example
/// ```
/// # use mlua::{Lua, Result, FromLua, IntoLua};
/// # use mlua::{Lua, Result, FromLua, IntoLua, IntoLuaMulti};
/// # fn main() -> Result<()> {
/// let lua = Lua::new();
/// let n: i32 = {
/// let num = 11i32;
/// lua.exec_raw_lua(|lua| {
/// unsafe {
/// <i32 as IntoLua>::push_into_stack(num, lua)?;
/// let nums = (3, 4, 5);
/// lua.exec_raw_lua(|rawlua| unsafe {
/// nums.push_into_stack_multi(rawlua)?;
/// let mut sum = 0;
/// for _ in 0..3 {
/// sum += rawlua.pop::<i32>()?;
/// }
///
/// let n = unsafe {
/// <i32 as FromLua>::from_stack(-1, lua)?
/// };
/// Result::Ok(n)
/// Result::Ok(sum)
/// })
/// }?;
/// assert_eq!(n, 11);
/// assert_eq!(n, 12);
/// # Ok(())
/// # }
/// ```
@@ -437,31 +546,6 @@ impl Lua {
Ok(())
}
#[doc(hidden)]
#[deprecated(since = "0.11.0", note = "Use `register_module` instead")]
#[cfg(not(feature = "luau"))]
#[cfg(not(tarpaulin_include))]
pub fn load_from_function<T: FromLua>(&self, modname: &str, func: Function) -> Result<T> {
let loaded = unsafe {
self.exec_raw::<Table>((), |state| {
ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_LOADED_TABLE);
})?
};
let value = match loaded.raw_get(modname)? {
Value::Nil => {
let result = match func.call(modname)? {
Value::Nil => Value::Boolean(true),
res => res,
};
loaded.raw_set(modname, &result)?;
result
}
res => res,
};
T::from_lua(value, self)
}
/// Unloads module `modname`.
///
/// This method does not support unloading binary Lua modules since they are internally cached
@@ -758,92 +842,87 @@ impl Lua {
}
}
/// Sets a thread creation callback that will be called when a thread is created.
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn set_thread_creation_callback<F>(&self, callback: F)
/// Sets a callback invoked when thread lifecycle events occur.
///
/// `triggers` controls which events trigger the callback, see [`ThreadTriggers`] for more
/// 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
F: Fn(&Lua, Thread) -> Result<()> + MaybeSend + 'static,
F: Fn(&Lua, ThreadEvent) -> Result<()> + MaybeSend + 'static,
{
let lua = self.lock();
unsafe {
(*lua.extra.get()).thread_creation_callback = Some(XRc::new(callback));
(*ffi::lua_callbacks(lua.main_state())).userthread = Some(Self::userthread_proc);
(*lua.extra.get()).thread_triggers = triggers;
(*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
/// non-panicking. If the callback panics, the program will be aborted.
#[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,
{
/// This function has no effect if a callback was not previously set.
pub fn remove_thread_event_callback(&self) {
let lua = self.lock();
let extra = lua.extra.get();
unsafe {
(*lua.extra.get()).thread_collection_callback = Some(XRc::new(callback));
(*ffi::lua_callbacks(lua.main_state())).userthread = Some(Self::userthread_proc);
(*extra).thread_triggers = ThreadTriggers::new();
(*extra).thread_event_callback = None;
#[cfg(feature = "luau")]
{
(*ffi::lua_callbacks(lua.main_state())).userthread = None;
}
}
}
#[cfg(feature = "luau")]
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);
if !parent.is_null() {
// Thread is created
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;
if !(*extra).thread_triggers.on_create {
return;
}
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.
@@ -909,8 +988,8 @@ impl Lua {
/// Gets information about the interpreter runtime stack at the given level.
///
/// This function calls callback `f`, passing the [`Debug`] structure that can be used to get
/// information about the function executing at a given level.
/// This function calls callback `f`, passing the [`struct@Debug`] structure that can be used to
/// get information about the function executing at a given level.
/// Level `0` is the current running function, whereas level `n+1` is the function that has
/// called level `n` (except for tail calls, which do not count in the stack).
pub fn inspect_stack<R>(&self, level: usize, f: impl FnOnce(&Debug) -> R) -> Option<R> {
@@ -998,19 +1077,19 @@ impl Lua {
unsafe { ffi::lua_gc(lua.main_state(), ffi::LUA_GCISRUNNING, 0) != 0 }
}
/// Stop the Lua GC from running
/// Stops the Lua GC from running.
pub fn gc_stop(&self) {
let lua = self.lock();
unsafe { ffi::lua_gc(lua.main_state(), ffi::LUA_GCSTOP, 0) };
}
/// Restarts the Lua GC if it is not running
/// Restarts the Lua GC if it is not running.
pub fn gc_restart(&self) {
let lua = self.lock();
unsafe { ffi::lua_gc(lua.main_state(), ffi::LUA_GCRESTART, 0) };
}
/// Perform a full garbage-collection cycle.
/// Performs a full garbage-collection cycle.
///
/// It may be necessary to call this function twice to collect all currently unreachable
/// objects. Once to finish the current gc cycle, and once to start and finish the next cycle.
@@ -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> {
self.gc_step_kbytes(0)
}
/// Steps the garbage collector as though memory had been allocated.
///
/// if `kbytes` is 0, then this is the same as calling `gc_step`. Returns true if this step has
/// finished a collection cycle.
pub fn gc_step_kbytes(&self, kbytes: c_int) -> Result<bool> {
let lua = self.lock();
let state = lua.main_state();
unsafe {
check_stack(state, 3)?;
protect_lua!(state, 0, 0, |state| {
ffi::lua_gc(state, ffi::LUA_GCSTEP, kbytes) != 0
ffi::lua_gc(state, ffi::LUA_GCSTEP, 0) != 0
})
}
}
/// Sets the `pause` value of the collector.
/// Switches the GC to the given mode with the provided parameters.
///
/// Returns the previous value of `pause`. More information can be found in the Lua
/// [documentation].
/// Returns the previous [`GcMode`]. The returned value's parameter fields are always
/// `None` because Lua's C API does not provide a way to read back current parameter values
/// without changing them.
///
/// For Luau this parameter sets GC goal
/// # Examples
///
/// [documentation]: https://www.lua.org/manual/5.4/manual.html#2.5
pub fn gc_set_pause(&self, pause: c_int) -> c_int {
/// Switch to generational mode (Lua 5.4+):
/// ```ignore
/// let prev = lua.gc_set_mode(GcMode::Generational(GcGenParams::default()));
/// ```
///
/// Switch to incremental mode with custom parameters:
/// ```ignore
/// lua.gc_set_mode(GcMode::Incremental(
/// GcIncParams::default().pause(200).step_multiplier(100)
/// ));
/// ```
pub fn gc_set_mode(&self, mode: GcMode) -> GcMode {
let lua = self.lock();
let state = lua.main_state();
unsafe {
match mode {
#[cfg(feature = "lua55")]
return ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPPAUSE, pause);
#[cfg(not(any(feature = "lua55", feature = "luau")))]
return ffi::lua_gc(state, ffi::LUA_GCSETPAUSE, pause);
GcMode::Incremental(params) => unsafe {
if let Some(v) = params.pause {
ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPPAUSE, v);
}
if let Some(v) = params.step_multiplier {
ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPSTEPMUL, v);
}
if let Some(v) = params.step_size {
ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPSTEPSIZE, v);
}
match ffi::lua_gc(state, ffi::LUA_GCINC) {
ffi::LUA_GCINC => GcMode::Incremental(GcIncParams::default()),
ffi::LUA_GCGEN => GcMode::Generational(GcGenParams::default()),
_ => unreachable!(),
}
},
#[cfg(feature = "lua54")]
GcMode::Incremental(params) => unsafe {
let pause = params.pause.unwrap_or(0);
let step_mul = params.step_multiplier.unwrap_or(0);
let step_size = params.step_size.unwrap_or(0);
match ffi::lua_gc(state, ffi::LUA_GCINC, pause, step_mul, step_size) {
ffi::LUA_GCINC => GcMode::Incremental(GcIncParams::default()),
ffi::LUA_GCGEN => GcMode::Generational(GcGenParams::default()),
_ => unreachable!(),
}
},
#[cfg(any(feature = "lua53", feature = "lua52", feature = "lua51", feature = "luajit"))]
GcMode::Incremental(params) => unsafe {
if let Some(v) = params.pause {
ffi::lua_gc(state, ffi::LUA_GCSETPAUSE, v);
}
if let Some(v) = params.step_multiplier {
ffi::lua_gc(state, ffi::LUA_GCSETSTEPMUL, v);
}
GcMode::Incremental(GcIncParams::default())
},
#[cfg(feature = "luau")]
return ffi::lua_gc(state, ffi::LUA_GCSETGOAL, pause);
}
}
GcMode::Incremental(params) => unsafe {
if let Some(v) = params.goal {
ffi::lua_gc(state, ffi::LUA_GCSETGOAL, v);
}
if let Some(v) = params.step_multiplier {
ffi::lua_gc(state, ffi::LUA_GCSETSTEPMUL, v);
}
if let Some(v) = params.step_size {
ffi::lua_gc(state, ffi::LUA_GCSETSTEPSIZE, v);
}
GcMode::Incremental(GcIncParams::default())
},
/// Sets the `step multiplier` value of the collector.
///
/// Returns the previous value of the `step multiplier`. More information can be found in the
/// Lua [documentation].
///
/// [documentation]: https://www.lua.org/manual/5.4/manual.html#2.5
pub fn gc_set_step_multiplier(&self, step_multiplier: c_int) -> c_int {
let lua = self.lock();
unsafe {
#[cfg(feature = "lua55")]
return ffi::lua_gc(
lua.main_state(),
ffi::LUA_GCPARAM,
ffi::LUA_GCPSTEPMUL,
step_multiplier,
);
#[cfg(not(feature = "lua55"))]
return ffi::lua_gc(lua.main_state(), ffi::LUA_GCSETSTEPMUL, step_multiplier);
}
}
/// Changes the collector to incremental mode with the given parameters.
///
/// Returns the previous mode (always `GCMode::Incremental` in Lua < 5.4).
/// More information can be found in the Lua [documentation].
///
/// [documentation]: https://www.lua.org/manual/5.4/manual.html#2.5.1
pub fn gc_inc(&self, pause: c_int, step_multiplier: c_int, step_size: c_int) -> GCMode {
let lua = self.lock();
let state = lua.main_state();
#[cfg(any(
feature = "lua53",
feature = "lua52",
feature = "lua51",
feature = "luajit",
feature = "luau"
))]
unsafe {
if pause > 0 {
#[cfg(not(feature = "luau"))]
ffi::lua_gc(state, ffi::LUA_GCSETPAUSE, pause);
#[cfg(feature = "luau")]
ffi::lua_gc(state, ffi::LUA_GCSETGOAL, pause);
}
if step_multiplier > 0 {
ffi::lua_gc(state, ffi::LUA_GCSETSTEPMUL, step_multiplier);
}
#[cfg(feature = "luau")]
if step_size > 0 {
ffi::lua_gc(state, ffi::LUA_GCSETSTEPSIZE, step_size);
}
#[cfg(not(feature = "luau"))]
let _ = step_size; // Ignored
GCMode::Incremental
}
#[cfg(feature = "lua55")]
let prev_mode = unsafe {
ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPPAUSE, pause);
ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPSTEPMUL, step_multiplier);
ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPSTEPSIZE, step_size);
ffi::lua_gc(state, ffi::LUA_GCINC)
};
#[cfg(feature = "lua54")]
let prev_mode = unsafe { ffi::lua_gc(state, ffi::LUA_GCINC, pause, step_multiplier, step_size) };
#[cfg(any(feature = "lua55", feature = "lua54"))]
match prev_mode {
ffi::LUA_GCINC => GCMode::Incremental,
ffi::LUA_GCGEN => GCMode::Generational,
_ => unreachable!(),
}
}
/// Changes the collector to generational mode with the given parameters.
///
/// Returns the previous mode. More information about the generational GC
/// can be found in the Lua 5.4 [documentation][lua_doc].
///
/// [lua_doc]: https://www.lua.org/manual/5.4/manual.html#2.5.2
#[cfg(any(feature = "lua55", feature = "lua54"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua55", feature = "lua54"))))]
pub fn gc_gen(&self, minor_multiplier: c_int, major_multiplier: c_int) -> GCMode {
let lua = self.lock();
let state = lua.main_state();
#[cfg(feature = "lua55")]
let prev_mode = unsafe {
ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPMINORMUL, minor_multiplier);
ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPMINORMAJOR, major_multiplier);
// TODO: LUA_GCPMAJORMINOR
ffi::lua_gc(state, ffi::LUA_GCGEN)
};
#[cfg(not(feature = "lua55"))]
let prev_mode = unsafe { ffi::lua_gc(state, ffi::LUA_GCGEN, minor_multiplier, major_multiplier) };
match prev_mode {
ffi::LUA_GCGEN => GCMode::Generational,
ffi::LUA_GCINC => GCMode::Incremental,
_ => unreachable!(),
GcMode::Generational(params) => unsafe {
if let Some(v) = params.minor_multiplier {
ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPMINORMUL, v);
}
if let Some(v) = params.minor_to_major {
ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPMINORMAJOR, v);
}
if let Some(v) = params.major_to_minor {
ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPMAJORMINOR, v);
}
match ffi::lua_gc(state, ffi::LUA_GCGEN) {
ffi::LUA_GCGEN => GcMode::Generational(GcGenParams::default()),
ffi::LUA_GCINC => GcMode::Incremental(GcIncParams::default()),
_ => unreachable!(),
}
},
#[cfg(feature = "lua54")]
GcMode::Generational(params) => unsafe {
let minor = params.minor_multiplier.unwrap_or(0);
let minor_to_major = params.minor_to_major.unwrap_or(0);
match ffi::lua_gc(state, ffi::LUA_GCGEN, minor, minor_to_major) {
ffi::LUA_GCGEN => GcMode::Generational(GcGenParams::default()),
ffi::LUA_GCINC => GcMode::Incremental(GcIncParams::default()),
_ => unreachable!(),
}
},
}
}
@@ -1456,7 +1510,7 @@ impl Lua {
/// }
/// ```
///
/// [`AsyncThread`]: crate::AsyncThread
/// [`AsyncThread`]: crate::thread::AsyncThread
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub fn create_async_function<F, A, FR, R>(&self, func: F) -> Result<Function>
@@ -1492,7 +1546,7 @@ impl Lua {
#[inline]
pub fn create_userdata<T>(&self, data: T) -> Result<AnyUserData>
where
T: UserData + MaybeSend + 'static,
T: UserData + MaybeSend + MaybeSync + 'static,
{
unsafe { self.lock().make_userdata(UserDataStorage::new(data)) }
}
@@ -1503,7 +1557,7 @@ impl Lua {
#[inline]
pub fn create_ser_userdata<T>(&self, data: T) -> Result<AnyUserData>
where
T: UserData + Serialize + MaybeSend + 'static,
T: UserData + Serialize + MaybeSend + MaybeSync + 'static,
{
unsafe { self.lock().make_userdata(UserDataStorage::new_ser(data)) }
}
@@ -1518,7 +1572,7 @@ impl Lua {
#[inline]
pub fn create_any_userdata<T>(&self, data: T) -> Result<AnyUserData>
where
T: MaybeSend + 'static,
T: MaybeSend + MaybeSync + 'static,
{
unsafe { self.lock().make_any_userdata(UserDataStorage::new(data)) }
}
@@ -1531,7 +1585,7 @@ impl Lua {
#[inline]
pub fn create_ser_any_userdata<T>(&self, data: T) -> Result<AnyUserData>
where
T: Serialize + MaybeSend + 'static,
T: Serialize + MaybeSend + MaybeSync + 'static,
{
unsafe { (self.lock()).make_any_userdata(UserDataStorage::new_ser(data)) }
}
+6 -9
View File
@@ -12,7 +12,8 @@ use rustc_hash::FxHashMap;
use crate::error::Result;
use crate::state::RawLua;
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::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>,
#[cfg(feature = "luau")]
pub(super) interrupt_callback: Option<crate::types::InterruptCallback>,
#[cfg(feature = "luau")]
pub(super) thread_creation_callback: Option<crate::types::ThreadCreationCallback>,
#[cfg(feature = "luau")]
pub(super) thread_collection_callback: Option<crate::types::ThreadCollectionCallback>,
pub(super) thread_triggers: ThreadTriggers,
pub(super) thread_event_callback: Option<ThreadEventCallback>,
#[cfg(feature = "luau")]
pub(crate) running_gc: bool,
@@ -186,10 +185,8 @@ impl ExtraData {
warn_callback: None,
#[cfg(feature = "luau")]
interrupt_callback: None,
#[cfg(feature = "luau")]
thread_creation_callback: None,
#[cfg(feature = "luau")]
thread_collection_callback: None,
thread_triggers: ThreadTriggers::default(),
thread_event_callback: None,
#[cfg(feature = "luau")]
sandboxed: false,
#[cfg(feature = "luau")]
+70 -18
View File
@@ -1,7 +1,7 @@
use std::any::TypeId;
use std::cell::{Cell, UnsafeCell};
use std::ffi::CStr;
use std::mem;
use std::mem::{self, ManuallyDrop};
use std::os::raw::{c_char, c_int, c_void};
use std::panic::resume_unwind;
use std::ptr::{self, NonNull};
@@ -15,11 +15,11 @@ use crate::state::util::callback_error_ext;
use crate::stdlib::StdLib;
use crate::string::LuaString;
use crate::table::Table;
use crate::thread::Thread;
use crate::traits::IntoLua;
use crate::thread::{Thread, ThreadTriggers};
use crate::traits::{FromLua, IntoLua};
use crate::types::{
AppDataRef, AppDataRefMut, Callback, CallbackUpvalue, DestructedUserdata, Integer, LightUserData,
LuaType, MaybeSend, ReentrantMutex, RegistryKey, ValueRef, XRc,
LuaType, MaybeSend, ReentrantMutex, RegistryKey, ThreadEventCallback, ValueRef, XRc,
};
use crate::userdata::{
AnyUserData, MetaMethod, RawUserDataRegistry, UserData, UserDataRegistry, UserDataStorage,
@@ -50,13 +50,13 @@ use {
std::task::{Context, Poll, Waker},
};
/// An inner Lua struct which holds a raw Lua state.
/// An internal Lua struct which holds a raw Lua state.
#[doc(hidden)]
pub struct RawLua {
// The state is dynamic and depends on context
pub(super) state: Cell<*mut 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,
}
@@ -82,6 +82,9 @@ impl Drop for RawLua {
if !mem_state.is_null() {
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),
// Make sure that we don't store current state as main state (if it's not available)
main_state: get_main_state(state).and_then(NonNull::new),
extra: XRc::clone(&extra),
extra: ManuallyDrop::new(XRc::clone(&extra)),
owned,
}));
(*extra.get()).set_lua(&rawlua);
@@ -640,7 +643,7 @@ impl RawLua {
let protect = !self.unlikely_memory_error();
#[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 {
ffi::lua_newthread(state)
@@ -653,6 +656,19 @@ impl RawLua {
self.set_thread_hook(thread_state, HookKind::Global)?;
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);
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.
pub(crate) unsafe fn push_primitive_type<T: LuaType>(&self) -> bool {
match T::TYPE_ID {
@@ -731,14 +757,27 @@ impl RawLua {
/// Pushes a value that implements `IntoLua` onto the Lua stack.
///
/// Uses up to 2 stack spaces to push a single value, does not call `checkstack`.
#[allow(clippy::missing_safety_doc)]
#[inline(always)]
pub unsafe fn push(&self, value: impl IntoLua) -> Result<()> {
value.push_into_stack(self)
}
/// Pops a value that implements [`FromLua`] from the top of the Lua stack.
///
/// Uses up to 1 stack space, does not call `checkstack`.
#[allow(clippy::missing_safety_doc)]
#[inline(always)]
pub unsafe fn pop<R: FromLua>(&self) -> Result<R> {
let v = R::from_stack(-1, self)?;
ffi::lua_pop(self.state(), 1);
Ok(v)
}
/// Pushes a `Value` (by reference) onto the Lua stack.
///
/// Uses 2 stack spaces, does not call `checkstack`.
/// Uses up to 2 stack spaces, does not call `checkstack`.
#[allow(clippy::missing_safety_doc)]
pub unsafe fn push_value(&self, value: &Value) -> Result<()> {
let state = self.state();
match value {
@@ -773,6 +812,7 @@ impl RawLua {
/// Pops a value from the Lua stack.
///
/// Uses up to 1 stack spaces, does not call `checkstack`.
#[allow(clippy::missing_safety_doc)]
#[inline]
pub unsafe fn pop_value(&self) -> Value {
let value = self.stack_value(-1, None);
@@ -803,15 +843,22 @@ impl RawLua {
#[cfg(any(feature = "lua52", feature = "lua51", feature = "luajit", feature = "luau"))]
ffi::LUA_TNUMBER => {
use crate::types::Number;
let n = ffi::lua_tonumber(state, idx);
match num_traits::cast(n) {
Some(i) if n.to_bits() == (i as Number).to_bits() => Value::Integer(i),
Some(i) if n.to_bits() == (i as crate::types::Number).to_bits() => Value::Integer(i),
_ => Value::Number(n),
}
}
#[cfg(feature = "luau")]
ffi::LUA_TINTEGER => {
let i = ffi::lua_tointeger64(state, idx, ptr::null_mut());
match num_traits::cast(i) {
Some(i) => Value::Integer(i),
_ => Value::Number(i as crate::types::Number),
}
}
#[cfg(feature = "luau")]
ffi::LUA_TVECTOR => {
let v = ffi::lua_tovector(state, idx);
@@ -949,7 +996,7 @@ impl RawLua {
// Check if userdata/metatable is already registered
let type_id = TypeId::of::<T>();
if let Some(&table_id) = (*self.extra.get()).registered_userdata_t.get(&type_id) {
return Ok(table_id as Integer);
return Ok(table_id);
}
// Create a new metatable from `UserData` definition
@@ -968,7 +1015,7 @@ impl RawLua {
// Check if userdata/metatable is already registered
let type_id = TypeId::of::<T>();
if let Some(&table_id) = (*self.extra.get()).registered_userdata_t.get(&type_id) {
return Ok(table_id as Integer);
return Ok(table_id);
}
// Check if metatable creation is pending or create an empty metatable otherwise
@@ -983,7 +1030,7 @@ impl RawLua {
unsafe fn make_userdata_with_metatable<T>(
&self,
data: UserDataStorage<T>,
get_metatable_id: impl FnOnce() -> Result<Integer>,
get_metatable_id: impl FnOnce() -> Result<c_int>,
) -> Result<AnyUserData> {
let state = self.state();
let _sg = StackGuard::new(state);
@@ -993,7 +1040,7 @@ impl RawLua {
let mt_id = get_metatable_id()?;
let protect = !self.unlikely_memory_error();
push_userdata(state, data, protect)?;
ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, mt_id);
ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, mt_id as _);
ffi::lua_setmetatable(state, -2);
// Set empty environment for Lua 5.1
@@ -1011,7 +1058,7 @@ impl RawLua {
Ok(AnyUserData(self.pop_ref()))
}
pub(crate) unsafe fn create_userdata_metatable(&self, registry: RawUserDataRegistry) -> Result<Integer> {
pub(crate) unsafe fn create_userdata_metatable(&self, registry: RawUserDataRegistry) -> Result<c_int> {
let state = self.state();
let type_id = registry.type_id;
@@ -1027,7 +1074,7 @@ impl RawLua {
}
self.register_userdata_metatable(mt_ptr, type_id);
Ok(id as Integer)
Ok(id)
}
pub(crate) unsafe fn push_userdata_metatable(&self, mut registry: RawUserDataRegistry) -> Result<()> {
@@ -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)?;
}
#[cfg(feature = "luau")]
if libs.contains(StdLib::INTEGER) {
requiref(state, ffi::LUA_INTLIBNAME, ffi::luaopen_integer, 1)?;
}
if libs.contains(StdLib::MATH) {
requiref(state, ffi::LUA_MATHLIBNAME, ffi::luaopen_math, 1)?;
}
+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.
#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
@@ -73,10 +73,15 @@ impl StdLib {
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub const VECTOR: StdLib = StdLib(1 << 10);
/// [`integer`](https://luau.org/library#integer-library) library
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub const INTEGER: StdLib = StdLib(1 << 11);
/// [`jit`](http://luajit.org/ext_jit.html) library
#[cfg(any(feature = "luajit", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luajit")))]
pub const JIT: StdLib = StdLib(1 << 11);
pub const JIT: StdLib = StdLib(1 << 12);
/// (**unsafe**) [`ffi`](http://luajit.org/ext_ffi.html) library
#[cfg(any(feature = "luajit", doc))]
@@ -139,3 +144,10 @@ impl BitXorAssign for StdLib {
*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::ops::Deref;
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::state::Lua;
@@ -19,12 +23,15 @@ use {
/// Handle to an internal Lua string.
///
/// Unlike Rust strings, Lua strings may not be valid UTF-8.
#[derive(Clone)]
#[derive(Clone, PartialEq)]
pub struct LuaString(pub(crate) ValueRef);
impl LuaString {
/// Get a [`BorrowedStr`] if the Lua string is valid UTF-8.
///
/// The returned `BorrowedStr` holds a strong reference to the Lua state to guarantee the
/// validity of the underlying data.
///
/// # Examples
///
/// ```
@@ -42,7 +49,7 @@ impl LuaString {
/// # }
/// ```
#[inline]
pub fn to_str(&self) -> Result<BorrowedStr<'_>> {
pub fn to_str(&self) -> Result<BorrowedStr> {
BorrowedStr::try_from(self)
}
@@ -85,8 +92,9 @@ impl LuaString {
/// Get the bytes that make up this string.
///
/// The returned slice will not contain the terminating null byte, but will contain any null
/// bytes embedded into the Lua string.
/// The returned `BorrowedStr` holds a strong reference to the Lua state to guarantee the
/// validity of the underlying data. The data will not contain the terminating null byte, but
/// will contain any null bytes embedded into the Lua string.
///
/// # Examples
///
@@ -101,16 +109,16 @@ impl LuaString {
/// # }
/// ```
#[inline]
pub fn as_bytes(&self) -> BorrowedBytes<'_> {
pub fn as_bytes(&self) -> BorrowedBytes {
BorrowedBytes::from(self)
}
/// Get the bytes that make up this string, including the trailing null byte.
pub fn as_bytes_with_nul(&self) -> BorrowedBytes<'_> {
let BorrowedBytes { buf, borrow, _lua } = BorrowedBytes::from(self);
pub fn as_bytes_with_nul(&self) -> BorrowedBytes {
let BorrowedBytes { buf, vref, _lua } = BorrowedBytes::from(self);
// Include the trailing null byte (it's always present but excluded by default)
let buf = unsafe { slice::from_raw_parts((*buf).as_ptr(), (*buf).len() + 1) };
BorrowedBytes { buf, borrow, _lua }
BorrowedBytes { buf, vref, _lua }
}
// Does not return the terminating null byte
@@ -141,7 +149,10 @@ impl LuaString {
/// Typically this function is used only for hashing and debug information.
#[inline]
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<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.
pub struct BorrowedStr<'a> {
pub struct BorrowedStr {
// `buf` points to a readonly memory managed by Lua
pub(crate) buf: &'a str,
pub(crate) borrow: Cow<'a, LuaString>,
pub(crate) buf: &'static str,
pub(crate) vref: ValueRef,
pub(crate) _lua: Lua,
}
impl Deref for BorrowedStr<'_> {
impl Deref for BorrowedStr {
type Target = str;
#[inline(always)]
@@ -249,33 +254,33 @@ impl Deref for BorrowedStr<'_> {
}
}
impl Borrow<str> for BorrowedStr<'_> {
impl Borrow<str> for BorrowedStr {
#[inline(always)]
fn borrow(&self) -> &str {
self.buf
}
}
impl AsRef<str> for BorrowedStr<'_> {
impl AsRef<str> for BorrowedStr {
#[inline(always)]
fn as_ref(&self) -> &str {
self.buf
}
}
impl fmt::Display for BorrowedStr<'_> {
impl fmt::Display for BorrowedStr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.buf.fmt(f)
}
}
impl fmt::Debug for BorrowedStr<'_> {
impl fmt::Debug for BorrowedStr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.buf.fmt(f)
}
}
impl<T> PartialEq<T> for BorrowedStr<'_>
impl<T> PartialEq<T> for BorrowedStr
where
T: AsRef<str>,
{
@@ -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
T: AsRef<str>,
{
@@ -295,33 +300,33 @@ where
}
}
impl Ord for BorrowedStr<'_> {
impl Ord for BorrowedStr {
fn cmp(&self, other: &Self) -> cmp::Ordering {
self.buf.cmp(other.buf)
}
}
impl<'a> TryFrom<&'a LuaString> for BorrowedStr<'a> {
impl TryFrom<&LuaString> for BorrowedStr {
type Error = Error;
#[inline]
fn try_from(value: &'a LuaString) -> Result<Self> {
let BorrowedBytes { buf, borrow, _lua } = BorrowedBytes::from(value);
fn try_from(value: &LuaString) -> Result<Self> {
let BorrowedBytes { buf, vref, _lua } = BorrowedBytes::from(value);
let buf =
str::from_utf8(buf).map_err(|e| Error::from_lua_conversion("string", "&str", e.to_string()))?;
Ok(Self { buf, borrow, _lua })
Ok(Self { buf, vref, _lua })
}
}
/// A borrowed byte slice (`&[u8]`) that holds a strong reference to the Lua state.
pub struct BorrowedBytes<'a> {
pub struct BorrowedBytes {
// `buf` points to a readonly memory managed by Lua
pub(crate) buf: &'a [u8],
pub(crate) borrow: Cow<'a, LuaString>,
pub(crate) buf: &'static [u8],
pub(crate) vref: ValueRef,
pub(crate) _lua: Lua,
}
impl Deref for BorrowedBytes<'_> {
impl Deref for BorrowedBytes {
type Target = [u8];
#[inline(always)]
@@ -330,27 +335,27 @@ impl Deref for BorrowedBytes<'_> {
}
}
impl Borrow<[u8]> for BorrowedBytes<'_> {
impl Borrow<[u8]> for BorrowedBytes {
#[inline(always)]
fn borrow(&self) -> &[u8] {
self.buf
}
}
impl AsRef<[u8]> for BorrowedBytes<'_> {
impl AsRef<[u8]> for BorrowedBytes {
#[inline(always)]
fn as_ref(&self) -> &[u8] {
self.buf
}
}
impl fmt::Debug for BorrowedBytes<'_> {
impl fmt::Debug for BorrowedBytes {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.buf.fmt(f)
}
}
impl<T> PartialEq<T> for BorrowedBytes<'_>
impl<T> PartialEq<T> for BorrowedBytes
where
T: AsRef<[u8]>,
{
@@ -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
T: AsRef<[u8]>,
{
@@ -370,13 +375,13 @@ where
}
}
impl Ord for BorrowedBytes<'_> {
impl Ord for BorrowedBytes {
fn cmp(&self, other: &Self) -> cmp::Ordering {
self.buf.cmp(other.buf)
}
}
impl<'a> IntoIterator for &'a BorrowedBytes<'_> {
impl<'a> IntoIterator for &'a BorrowedBytes {
type Item = &'a u8;
type IntoIter = slice::Iter<'a, u8>;
@@ -385,12 +390,14 @@ impl<'a> IntoIterator for &'a BorrowedBytes<'_> {
}
}
impl<'a> From<&'a LuaString> for BorrowedBytes<'a> {
impl From<&LuaString> for BorrowedBytes {
#[inline]
fn from(value: &'a LuaString) -> Self {
fn from(value: &LuaString) -> Self {
let (buf, _lua) = unsafe { value.to_slice() };
let borrow = Cow::Borrowed(value);
Self { buf, borrow, _lua }
let vref = value.0.clone();
// SAFETY: The `buf` is valid for the lifetime of the Lua state and occupied slot index
let buf = unsafe { mem::transmute::<&[u8], &'static [u8]>(buf) };
Self { buf, vref, _lua }
}
}
+2 -11
View File
@@ -3,12 +3,6 @@
//! 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.
//!
//! # 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
//!
//! Tables support key-value access similar to Rust's `HashMap`:
@@ -784,10 +778,8 @@ impl Table {
ffi::lua_pushnil(state);
while ffi::lua_next(state, -2) != 0 {
let k = K::from_stack(-2, &lua)?;
let v = V::from_stack(-1, &lua)?;
let v = lua.pop::<V>()?;
f(k, v)?;
// Keep key for next iteration
ffi::lua_pop(state, 1);
}
}
Ok(())
@@ -860,8 +852,7 @@ impl Table {
if len.is_none() && t == ffi::LUA_TNIL {
break;
}
f(V::from_stack(-1, &lua)?)?;
ffi::lua_pop(state, 1);
f(lua.pop::<V>()?)?;
}
}
Ok(())
+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::os::raw::{c_int, c_void};
@@ -5,7 +42,7 @@ use crate::error::{Error, Result};
use crate::function::Function;
use crate::state::RawLua;
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};
#[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).
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum ThreadStatus {
@@ -61,7 +177,6 @@ impl ThreadStatusInner {
matches!(self, ThreadStatusInner::New(_) | ThreadStatusInner::Yielded(_))
}
#[cfg(feature = "async")]
#[inline(always)]
fn is_yielded(self) -> bool {
matches!(self, ThreadStatusInner::Yielded(_))
@@ -69,7 +184,7 @@ impl ThreadStatusInner {
}
/// Handle to an internal Lua thread (coroutine).
#[derive(Clone)]
#[derive(Clone, PartialEq)]
pub struct Thread(pub(crate) ValueRef, pub(crate) *mut ffi::lua_State);
#[cfg(feature = "send")]
@@ -92,7 +207,6 @@ pub struct AsyncThread<R> {
impl Thread {
/// Returns reference to the Lua state that this thread is associated with.
#[doc(hidden)]
#[inline(always)]
pub fn state(&self) -> *mut ffi::lua_State {
self.1
@@ -157,6 +271,14 @@ impl Thread {
unsafe {
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)?;
if nargs > 0 {
check_stack(thread_state, nargs)?;
@@ -165,7 +287,17 @@ impl Thread {
}
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)?;
ffi::lua_xmove(thread_state, state, nresults);
@@ -193,12 +325,30 @@ impl Thread {
unsafe {
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)?;
error.push_into_stack(&lua)?;
ffi::lua_xmove(state, thread_state, 1);
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)?;
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.
///
/// This function is similar or [`Lua::set_hook`] except that it sets for the thread.
@@ -295,7 +470,7 @@ impl Thread {
/// Resets a thread
///
/// In [Lua 5.4]: cleans its call stack and closes all pending to-be-closed variables.
/// Returns a error in case of either the original error that stopped the thread or errors
/// Returns an error in case of either the original error that stopped the thread or errors
/// in closing methods.
///
/// In Luau: resets to the initial state of a newly created Lua thread.
@@ -449,6 +624,8 @@ impl Thread {
/// Please note that Luau links environment table with chunk when loading it into Lua state.
/// Therefore you need to load chunks into a thread to link with the thread environment.
///
/// [`Lua::sandbox`]: crate::Lua::sandbox
///
/// # Examples
///
/// ```
@@ -502,12 +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 {
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 _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)?;
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) {
return Poll::Pending;
}
@@ -601,9 +788,25 @@ impl<R: FromLuaMulti> Future for AsyncThread<R> {
let _thread_sg = StackGuard::with_top(thread_state, 0);
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)?;
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)) {
// Ignore values returned via yield()
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::sync::Arc;
@@ -5,12 +10,11 @@ use crate::error::{Error, Result};
use crate::multi::MultiValue;
use crate::private::Sealed;
use crate::state::{Lua, RawLua, WeakLua};
use crate::types::MaybeSend;
use crate::util::{check_stack, parse_lookup_path, short_type_name};
use crate::value::Value;
#[cfg(feature = "async")]
use {crate::function::AsyncCallFuture, std::future::Future};
use crate::function::AsyncCallFuture;
/// Trait for types convertible to [`Value`].
pub trait IntoLua: Sized {
@@ -245,97 +249,6 @@ pub trait ObjectLike: Sealed {
fn weak_lua(&self) -> &WeakLua;
}
/// A trait for types that can be used as Lua functions.
pub trait LuaNativeFn<A: FromLuaMulti> {
type Output: IntoLuaMulti;
fn call(&self, args: A) -> Self::Output;
}
/// A trait for types with mutable state that can be used as Lua functions.
pub trait LuaNativeFnMut<A: FromLuaMulti> {
type Output: IntoLuaMulti;
fn call(&mut self, args: A) -> Self::Output;
}
/// A trait for types that returns a future and can be used as Lua functions.
#[cfg(feature = "async")]
pub trait LuaNativeAsyncFn<A: FromLuaMulti> {
type Output: IntoLuaMulti;
fn call(&self, args: A) -> impl Future<Output = Self::Output> + MaybeSend + 'static;
}
macro_rules! impl_lua_native_fn {
($($A:ident),*) => {
impl<FN, $($A,)* R> LuaNativeFn<($($A,)*)> for FN
where
FN: Fn($($A,)*) -> R + MaybeSend + 'static,
($($A,)*): FromLuaMulti,
R: IntoLuaMulti,
{
type Output = R;
#[allow(non_snake_case)]
fn call(&self, args: ($($A,)*)) -> Self::Output {
let ($($A,)*) = args;
self($($A,)*)
}
}
impl<FN, $($A,)* R> LuaNativeFnMut<($($A,)*)> for FN
where
FN: FnMut($($A,)*) -> R + MaybeSend + 'static,
($($A,)*): FromLuaMulti,
R: IntoLuaMulti,
{
type Output = R;
#[allow(non_snake_case)]
fn call(&mut self, args: ($($A,)*)) -> Self::Output {
let ($($A,)*) = args;
self($($A,)*)
}
}
#[cfg(feature = "async")]
impl<FN, $($A,)* Fut, R> LuaNativeAsyncFn<($($A,)*)> for FN
where
FN: Fn($($A,)*) -> Fut + MaybeSend + 'static,
($($A,)*): FromLuaMulti,
Fut: Future<Output = R> + MaybeSend + 'static,
R: IntoLuaMulti,
{
type Output = R;
#[allow(non_snake_case)]
fn call(&self, args: ($($A,)*)) -> impl Future<Output = Self::Output> + MaybeSend + 'static {
let ($($A,)*) = args;
self($($A,)*)
}
}
};
}
impl_lua_native_fn!();
impl_lua_native_fn!(A);
impl_lua_native_fn!(A, B);
impl_lua_native_fn!(A, B, C);
impl_lua_native_fn!(A, B, C, D);
impl_lua_native_fn!(A, B, C, D, E);
impl_lua_native_fn!(A, B, C, D, E, F);
impl_lua_native_fn!(A, B, C, D, E, F, G);
impl_lua_native_fn!(A, B, C, D, E, F, G, H);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J, K);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J, K, L);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J, K, L, M);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J, K, L, M, N);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P);
pub(crate) trait ShortTypeName {
#[inline(always)]
fn type_name() -> 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"))]
pub(crate) type InterruptCallback = XRc<dyn Fn(&Lua) -> Result<VmState>>;
#[cfg(all(feature = "send", feature = "luau"))]
pub(crate) type ThreadCreationCallback = XRc<dyn Fn(&Lua, crate::Thread) -> Result<()> + Send>;
#[cfg(feature = "send")]
pub(crate) type ThreadEventCallback = XRc<dyn Fn(&Lua, crate::thread::ThreadEvent) -> Result<()> + Send>;
#[cfg(all(not(feature = "send"), feature = "luau"))]
pub(crate) type ThreadCreationCallback = XRc<dyn Fn(&Lua, crate::Thread) -> 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(not(feature = "send"))]
pub(crate) type ThreadEventCallback = XRc<dyn Fn(&Lua, crate::thread::ThreadEvent) -> Result<()>>;
#[cfg(feature = "send")]
#[cfg(any(feature = "lua55", feature = "lua54"))]
@@ -128,6 +122,18 @@ pub trait MaybeSend {}
#[cfg(not(feature = "send"))]
impl<T> MaybeSend for T {}
/// A trait that adds `Sync` requirement if `send` feature is enabled.
#[cfg(feature = "send")]
pub trait MaybeSync: Sync {}
#[cfg(feature = "send")]
impl<T: Sync> MaybeSync for T {}
/// A trait that adds `Sync` requirement if `send` feature is enabled.
#[cfg(not(feature = "send"))]
pub trait MaybeSync {}
#[cfg(not(feature = "send"))]
impl<T> MaybeSync for T {}
pub(crate) struct DestructedUserdata;
pub(crate) trait LuaType {
+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::ffi::CStr;
use std::fmt;
use std::hash::Hash;
use std::os::raw::{c_char, c_void};
use crate::Either;
use crate::error::{Error, Result};
use crate::function::Function;
use crate::state::Lua;
use crate::string::LuaString;
use crate::table::{Table, TablePairs};
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::value::Value;
@@ -25,7 +30,7 @@ use {
// Re-export for convenience
pub(crate) use cell::UserDataStorage;
pub use r#ref::{UserDataRef, UserDataRefMut};
pub use r#ref::{UserDataOwned, UserDataRef, UserDataRefMut};
pub use registry::UserDataRegistry;
pub(crate) use registry::{RawUserDataRegistry, UserDataProxy};
pub(crate) use util::{
@@ -123,6 +128,11 @@ pub enum MetaMethod {
///
/// This is not an operator, but will be called by methods such as `tostring` and `print`.
ToString,
/// The `__todebugstring` metamethod for debug purposes.
///
/// This is an mlua-specific metamethod that can be used to provide debug representation for
/// userdata.
ToDebugString,
/// The `__pairs` metamethod.
///
/// This is not an operator, but it will be called by the built-in `pairs` function.
@@ -232,6 +242,7 @@ impl MetaMethod {
MetaMethod::NewIndex => "__newindex",
MetaMethod::Call => "__call",
MetaMethod::ToString => "__tostring",
MetaMethod::ToDebugString => "__todebugstring",
#[cfg(any(
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
/// [`Error::UserDataDestructed`] error.
#[doc(hidden)]
fn add_method_once<M, A, R>(&mut self, name: impl Into<String>, method: M)
where
T: 'static,
@@ -373,7 +383,6 @@ pub trait UserDataMethods<T> {
/// [`Error::UserDataDestructed`] error.
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
#[doc(hidden)]
fn add_async_method_once<M, A, MR, R>(&mut self, name: impl Into<String>, method: M)
where
T: 'static,
@@ -705,7 +714,7 @@ pub trait UserData: Sized {
///
/// [`is`]: crate::AnyUserData::is
/// [`borrow`]: crate::AnyUserData::borrow
#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, PartialEq)]
pub struct AnyUserData(pub(crate) ValueRef);
impl AnyUserData {
@@ -1020,8 +1029,8 @@ impl AnyUserData {
/// Returns a type name of this userdata (from a metatable field).
///
/// If no type name is set, returns `None`.
pub fn type_name(&self) -> Result<Option<String>> {
/// If no type name is set, returns `userdata`.
pub fn type_name(&self) -> Result<LuaString> {
let lua = self.0.lua.lock();
let state = lua.state();
unsafe {
@@ -1038,8 +1047,8 @@ impl AnyUserData {
ffi::luaL_getmetafield(state, -1, MetaMethod::Type.as_cstr().as_ptr())
};
match name_type {
ffi::LUA_TSTRING => Ok(Some(LuaString(lua.pop_ref()).to_str()?.to_owned())),
_ => Ok(None),
ffi::LUA_TSTRING => Ok(LuaString(lua.pop_ref())),
_ => lua.create_string(b"userdata"),
}
}
}
@@ -1075,6 +1084,48 @@ impl AnyUserData {
};
is_serializable().unwrap_or(false)
}
unsafe fn invoke_tostring_dbg(&self) -> Result<Option<String>> {
let lua = self.0.lua.lock();
let state = lua.state();
let _guard = StackGuard::new(state);
check_stack(state, 3)?;
lua.push_ref(&self.0);
protect_lua!(state, 1, 1, fn(state) {
// Try `__todebugstring` metamethod first, then `__tostring`
#[allow(clippy::collapsible_if)]
if ffi::luaL_callmeta(state, -1, cstr!("__todebugstring")) == 0 {
if ffi::luaL_callmeta(state, -1, cstr!("__tostring")) == 0 {
ffi::lua_pushnil(state);
}
}
})?;
Ok(lua.pop_value().as_string().map(|s| s.to_string_lossy()))
}
pub(crate) fn fmt_pretty(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
// Try converting to a (debug) string first, with fallback to `__name/__type`
match unsafe { self.invoke_tostring_dbg() } {
Ok(Some(s)) => write!(fmt, "{s}"),
_ => {
let name = self.type_name().ok();
let name = (name.as_ref())
.map(|s| Either::Left(s.display()))
.unwrap_or(Either::Right("userdata"));
write!(fmt, "{name}: {:?}", self.to_pointer())
}
}
}
}
impl fmt::Debug for AnyUserData {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
if fmt.alternate() {
return self.fmt_pretty(fmt);
}
fmt.debug_tuple("AnyUserData").field(&self.0).finish()
}
}
/// Handle to a [`AnyUserData`] metatable.
@@ -1171,7 +1222,7 @@ impl AnyUserData {
/// Wraps any Rust type, returning an opaque type that implements [`IntoLua`] trait.
///
/// This function uses [`Lua::create_any_userdata`] under the hood.
pub fn wrap<T: MaybeSend + 'static>(data: T) -> impl IntoLua {
pub fn wrap<T: MaybeSend + MaybeSync + 'static>(data: T) -> impl IntoLua {
WrappedUserdata(move |lua| lua.create_any_userdata(data))
}
@@ -1181,7 +1232,7 @@ impl AnyUserData {
/// This function uses [`Lua::create_ser_any_userdata`] under the hood.
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
pub fn wrap_ser<T: Serialize + MaybeSend + 'static>(data: T) -> impl IntoLua {
pub fn wrap_ser<T: Serialize + MaybeSend + MaybeSync + 'static>(data: T) -> impl IntoLua {
WrappedUserdata(move |lua| lua.create_ser_any_userdata(data))
}
}
+29 -61
View File
@@ -1,4 +1,4 @@
use std::cell::{RefCell, UnsafeCell};
use std::cell::RefCell;
#[cfg(feature = "serde")]
use serde::ser::{Serialize, Serializer};
@@ -6,14 +6,14 @@ use serde::ser::{Serialize, Serializer};
use crate::error::{Error, Result};
use crate::types::XRc;
use super::lock::{RawLock, UserDataLock};
use super::lock::{RawLock, RwLock, UserDataLock};
use super::r#ref::{UserDataRef, UserDataRefMut};
#[cfg(all(feature = "serde", not(feature = "send")))]
type DynSerialize = dyn erased_serde::Serialize;
#[cfg(all(feature = "serde", feature = "send"))]
type DynSerialize = dyn erased_serde::Serialize + Send;
type DynSerialize = dyn erased_serde::Serialize + Send + Sync;
pub(crate) enum UserDataStorage<T> {
Owned(UserDataVariant<T>),
@@ -23,9 +23,9 @@ pub(crate) enum UserDataStorage<T> {
// A enum for storing userdata values.
// It's stored inside a Lua VM and protected by the outer `ReentrantMutex`.
pub(crate) enum UserDataVariant<T> {
Default(XRc<UserDataCell<T>>),
Default(XRc<RwLock<T>>),
#[cfg(feature = "serde")]
Serializable(XRc<UserDataCell<Box<DynSerialize>>>, bool), // bool is `is_sync`
Serializable(XRc<RwLock<Box<DynSerialize>>>),
}
impl<T> Clone for UserDataVariant<T> {
@@ -34,7 +34,7 @@ impl<T> Clone for UserDataVariant<T> {
match self {
Self::Default(inner) => Self::Default(XRc::clone(inner)),
#[cfg(feature = "serde")]
Self::Serializable(inner, is_sync) => Self::Serializable(XRc::clone(inner), *is_sync),
Self::Serializable(inner) => Self::Serializable(XRc::clone(inner)),
}
}
}
@@ -42,10 +42,12 @@ impl<T> Clone for UserDataVariant<T> {
impl<T> UserDataVariant<T> {
#[inline(always)]
pub(super) fn try_borrow_scoped<R>(&self, f: impl FnOnce(&T) -> R) -> Result<R> {
// We don't need to check for `T: Sync` because when this method is used (internally),
// Lua mutex is already locked.
// If non-`Sync` userdata is already borrowed by another thread (via `UserDataRef`), it will be
// exclusively locked.
// Shared (read) lock is always correct for in-place borrows:
// - this method is called internally while the Lua mutex is held, ensuring exclusive Lua-level
// access per call frame
// - with `send` feature, all owned userdata satisfies `T: Sync`, so simultaneous shared references
// from multiple threads are sound
// - without `send` feature, single-threaded execution makes shared lock safe for any `T`
let _guard = (self.raw_lock().try_lock_shared_guarded()).map_err(|_| Error::UserDataBorrowError)?;
Ok(f(unsafe { &*self.as_ptr() }))
}
@@ -78,10 +80,12 @@ impl<T> UserDataVariant<T> {
return Err(Error::UserDataBorrowMutError);
}
Ok(match self {
Self::Default(inner) => XRc::into_inner(inner).unwrap().value.into_inner(),
Self::Default(inner) => XRc::into_inner(inner).unwrap().into_inner(),
#[cfg(feature = "serde")]
Self::Serializable(inner, _) => unsafe {
let raw = Box::into_raw(XRc::into_inner(inner).unwrap().value.into_inner());
Self::Serializable(inner) => unsafe {
// The serde variant erases `T` to `Box<DynSerialize>`, so we
// must cast the raw pointer back to recover the concrete type.
let raw = Box::into_raw(XRc::into_inner(inner).unwrap().into_inner());
*Box::from_raw(raw as *mut T)
},
})
@@ -92,25 +96,25 @@ impl<T> UserDataVariant<T> {
match self {
Self::Default(inner) => XRc::strong_count(inner),
#[cfg(feature = "serde")]
Self::Serializable(inner, _) => XRc::strong_count(inner),
Self::Serializable(inner) => XRc::strong_count(inner),
}
}
#[inline(always)]
pub(super) fn raw_lock(&self) -> &RawLock {
match self {
Self::Default(inner) => &inner.raw_lock,
Self::Default(inner) => unsafe { inner.raw() },
#[cfg(feature = "serde")]
Self::Serializable(inner, _) => &inner.raw_lock,
Self::Serializable(inner) => unsafe { inner.raw() },
}
}
#[inline(always)]
pub(super) fn as_ptr(&self) -> *mut T {
match self {
Self::Default(inner) => inner.value.get(),
Self::Default(inner) => inner.data_ptr(),
#[cfg(feature = "serde")]
Self::Serializable(inner, _) => unsafe { &mut **(inner.value.get() as *mut Box<T>) },
Self::Serializable(inner) => unsafe { (&mut **inner.data_ptr()) as *mut DynSerialize as *mut T },
}
}
}
@@ -119,51 +123,16 @@ impl<T> UserDataVariant<T> {
impl Serialize for UserDataStorage<()> {
fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
match self {
Self::Owned(variant @ UserDataVariant::Serializable(inner, is_sync)) => unsafe {
#[cfg(feature = "send")]
if *is_sync {
let _guard = (variant.raw_lock().try_lock_shared_guarded())
.map_err(|_| serde::ser::Error::custom(Error::UserDataBorrowError))?;
(*inner.value.get()).serialize(serializer)
} else {
let _guard = (variant.raw_lock().try_lock_exclusive_guarded())
.map_err(|_| serde::ser::Error::custom(Error::UserDataBorrowError))?;
(*inner.value.get()).serialize(serializer)
}
#[cfg(not(feature = "send"))]
{
let _ = is_sync;
let _guard = (variant.raw_lock().try_lock_shared_guarded())
.map_err(|_| serde::ser::Error::custom(Error::UserDataBorrowError))?;
(*inner.value.get()).serialize(serializer)
}
Self::Owned(variant @ UserDataVariant::Serializable(inner)) => unsafe {
let _guard = (variant.raw_lock().try_lock_shared_guarded())
.map_err(|_| serde::ser::Error::custom(Error::UserDataBorrowError))?;
(*inner.data_ptr()).serialize(serializer)
},
_ => Err(serde::ser::Error::custom("cannot serialize <userdata>")),
}
}
}
/// A type that provides interior mutability for a userdata value (thread-safe).
pub(crate) struct UserDataCell<T> {
raw_lock: RawLock,
value: UnsafeCell<T>,
}
#[cfg(feature = "send")]
unsafe impl<T: Send> Send for UserDataCell<T> {}
#[cfg(feature = "send")]
unsafe impl<T: Send> Sync for UserDataCell<T> {}
impl<T> UserDataCell<T> {
#[inline(always)]
fn new(value: T) -> Self {
UserDataCell {
raw_lock: RawLock::INIT,
value: UnsafeCell::new(value),
}
}
}
pub(crate) enum ScopedUserDataVariant<T> {
Ref(*const T),
RefMut(RefCell<*mut T>),
@@ -184,7 +153,7 @@ impl<T> Drop for ScopedUserDataVariant<T> {
impl<T: 'static> UserDataStorage<T> {
#[inline(always)]
pub(crate) fn new(data: T) -> Self {
Self::Owned(UserDataVariant::Default(XRc::new(UserDataCell::new(data))))
Self::Owned(UserDataVariant::Default(XRc::new(RwLock::new(data))))
}
#[inline(always)]
@@ -201,11 +170,10 @@ impl<T: 'static> UserDataStorage<T> {
#[inline(always)]
pub(crate) fn new_ser(data: T) -> Self
where
T: Serialize + crate::types::MaybeSend,
T: Serialize + crate::types::MaybeSend + crate::types::MaybeSync,
{
let data = Box::new(data) as Box<DynSerialize>;
let is_sync = super::util::is_sync::<T>();
let variant = UserDataVariant::Serializable(XRc::new(UserDataCell::new(data)), is_sync);
let variant = UserDataVariant::Serializable(XRc::new(RwLock::new(data)));
Self::Owned(variant)
}
+44 -19
View File
@@ -1,6 +1,4 @@
pub(crate) trait UserDataLock {
const INIT: Self;
fn is_locked(&self) -> bool;
fn try_lock_shared(&self) -> bool;
fn try_lock_exclusive(&self) -> bool;
@@ -48,12 +46,12 @@ impl<L: UserDataLock + ?Sized> Drop for LockGuard<'_, L> {
}
}
pub(crate) use lock_impl::RawLock;
pub(crate) use lock_impl::{RawLock, RwLock};
#[cfg(not(feature = "send"))]
#[cfg(not(tarpaulin_include))]
mod lock_impl {
use std::cell::Cell;
use std::cell::{Cell, UnsafeCell};
// Positive values represent the number of read references.
// Negative values represent the number of write references (only one allowed).
@@ -62,9 +60,6 @@ mod lock_impl {
const UNUSED: isize = 0;
impl super::UserDataLock for RawLock {
#[allow(clippy::declare_interior_mutable_const)]
const INIT: Self = Cell::new(UNUSED);
#[inline(always)]
fn is_locked(&self) -> bool {
self.get() != UNUSED
@@ -72,7 +67,7 @@ mod lock_impl {
#[inline(always)]
fn try_lock_shared(&self) -> bool {
let flag = self.get().wrapping_add(1);
let flag = self.get().checked_add(1).expect("userdata lock count overflow");
if flag <= UNUSED {
return false;
}
@@ -104,41 +99,71 @@ mod lock_impl {
self.set(flag + 1);
}
}
/// A cheap single-threaded read-write lock pairing a `parking_lot::RwLock` type.
pub(crate) struct RwLock<T> {
lock: RawLock,
data: UnsafeCell<T>,
}
impl<T> RwLock<T> {
/// Creates a new `RwLock` containing the given value.
#[inline(always)]
pub(crate) fn new(value: T) -> Self {
RwLock {
lock: RawLock::new(UNUSED),
data: UnsafeCell::new(value),
}
}
/// Returns a reference to the underlying raw lock.
#[inline(always)]
pub(crate) unsafe fn raw(&self) -> &RawLock {
&self.lock
}
/// Returns a raw pointer to the underlying data.
#[inline(always)]
pub(crate) fn data_ptr(&self) -> *mut T {
self.data.get()
}
/// Consumes this `RwLock`, returning the underlying data.
#[inline(always)]
pub(crate) fn into_inner(self) -> T {
self.data.into_inner()
}
}
}
#[cfg(feature = "send")]
mod lock_impl {
use parking_lot::lock_api::RawRwLock;
pub(crate) type RawLock = parking_lot::RawRwLock;
pub(crate) use parking_lot::{RawRwLock as RawLock, RwLock};
impl super::UserDataLock for RawLock {
#[allow(clippy::declare_interior_mutable_const)]
const INIT: Self = <Self as parking_lot::lock_api::RawRwLock>::INIT;
#[inline(always)]
fn is_locked(&self) -> bool {
RawRwLock::is_locked(self)
parking_lot::lock_api::RawRwLock::is_locked(self)
}
#[inline(always)]
fn try_lock_shared(&self) -> bool {
RawRwLock::try_lock_shared(self)
parking_lot::lock_api::RawRwLock::try_lock_shared(self)
}
#[inline(always)]
fn try_lock_exclusive(&self) -> bool {
RawRwLock::try_lock_exclusive(self)
parking_lot::lock_api::RawRwLock::try_lock_exclusive(self)
}
#[inline(always)]
unsafe fn unlock_shared(&self) {
RawRwLock::unlock_shared(self)
parking_lot::lock_api::RawRwLock::unlock_shared(self)
}
#[inline(always)]
unsafe fn unlock_exclusive(&self) {
RawRwLock::unlock_exclusive(self)
parking_lot::lock_api::RawRwLock::unlock_exclusive(self)
}
}
}
+69 -7
View File
@@ -7,12 +7,11 @@ use crate::error::{Error, Result};
use crate::state::{Lua, RawLua};
use crate::traits::FromLua;
use crate::userdata::AnyUserData;
use crate::util::get_userdata;
use crate::util::{check_stack, get_userdata, take_userdata};
use crate::value::Value;
use super::cell::{UserDataStorage, UserDataVariant};
use super::lock::{LockGuard, RawLock, UserDataLock};
use super::util::is_sync;
#[cfg(feature = "userdata-wrappers")]
use {
@@ -63,11 +62,10 @@ impl<T> TryFrom<UserDataVariant<T>> for UserDataRef<T> {
#[inline]
fn try_from(variant: UserDataVariant<T>) -> Result<Self> {
let guard = if cfg!(not(feature = "send")) || is_sync::<T>() {
variant.raw_lock().try_lock_shared_guarded()
} else {
variant.raw_lock().try_lock_exclusive_guarded()
};
// Shared (read) lock is always correct:
// - with `send` feature, `T: Sync` is guaranteed by the `MaybeSync` bound on userdata creation
// - without `send` feature, single-threaded access makes shared lock safe for any `T`
let guard = variant.raw_lock().try_lock_shared_guarded();
let guard = guard.map_err(|_| Error::UserDataBorrowError)?;
let guard = unsafe { mem::transmute::<LockGuard<_>, LockGuard<'static, _>>(guard) };
Ok(UserDataRef::from_parts(UserDataRefInner::Default(variant), guard))
@@ -442,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]
fn try_value_to_userdata<T>(value: Value) -> Result<AnyUserData> {
match value {
@@ -466,6 +524,10 @@ mod assertions {
static_assertions::assert_impl_all!(UserDataRefMut<()>: Sync, Send);
#[cfg(feature = "send")]
static_assertions::assert_not_impl_all!(UserDataRefMut<std::rc::Rc<()>>: Send, Sync);
#[cfg(feature = "send")]
static_assertions::assert_impl_all!(UserDataOwned<()>: Send, Sync);
#[cfg(feature = "send")]
static_assertions::assert_not_impl_all!(UserDataOwned<std::rc::Rc<()>>: Send, Sync);
#[cfg(not(feature = "send"))]
static_assertions::assert_not_impl_all!(UserDataRef<()>: Send, Sync);
+6
View File
@@ -654,6 +654,12 @@ macro_rules! lua_userdata_impl {
// A special proxy object for UserData
pub(crate) struct UserDataProxy<T>(pub(crate) PhantomData<T>);
// `UserDataProxy` holds no real `T` value, only a type marker, so it is always safe to send/share.
#[cfg(feature = "send")]
unsafe impl<T> Send for UserDataProxy<T> {}
#[cfg(feature = "send")]
unsafe impl<T> Sync for UserDataProxy<T> {}
lua_userdata_impl!(UserDataProxy<T>);
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
-31
View File
@@ -1,6 +1,4 @@
use std::any::TypeId;
use std::cell::Cell;
use std::marker::PhantomData;
use std::os::raw::c_int;
use std::ptr;
@@ -11,35 +9,6 @@ use crate::error::{Error, Result};
use crate::types::CallbackPtr;
use crate::util::{get_userdata, rawget_field, rawset_field, take_userdata};
// This is a trick to check if a type is `Sync` or not.
// It uses leaked specialization feature from stdlib.
struct IsSync<'a, T> {
is_sync: &'a Cell<bool>,
_marker: PhantomData<T>,
}
impl<T> Clone for IsSync<'_, T> {
fn clone(&self) -> Self {
self.is_sync.set(false);
IsSync {
is_sync: self.is_sync,
_marker: PhantomData,
}
}
}
impl<T: Sync> Copy for IsSync<'_, T> {}
pub(crate) fn is_sync<T>() -> bool {
let is_sync = Cell::new(true);
let _ = [IsSync::<T> {
is_sync: &is_sync,
_marker: PhantomData,
}]
.clone();
is_sync.get()
}
// Userdata type hints, used to match types of wrapped userdata
#[derive(Clone, Copy)]
pub(crate) struct TypeIdHints {
+1 -1
View File
@@ -197,7 +197,7 @@ where
F: FnOnce(*mut ffi::lua_State) -> R,
R: Copy,
{
struct Params<F, R: Copy> {
struct Params<F, R> {
function: Option<F>,
result: MaybeUninit<R>,
nresults: c_int,
+6 -19
View File
@@ -128,18 +128,13 @@ impl Value {
#[inline]
pub fn to_pointer(&self) -> *const c_void {
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::Table(Table(vref))
| Value::Function(Function(vref))
| Value::Thread(Thread(vref, ..))
| Value::UserData(AnyUserData(vref))
| Value::Other(vref) => vref.to_pointer(),
Value::String(s) => s.to_pointer(),
#[cfg(feature = "luau")]
Value::Buffer(crate::Buffer(vref)) => vref.to_pointer(),
_ => ptr::null(),
@@ -151,7 +146,7 @@ impl Value {
/// This might invoke the `__tostring` metamethod for non-primitive types (eg. tables,
/// functions).
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 state = lua.state();
let _guard = StackGuard::new(state);
@@ -178,9 +173,9 @@ impl Value {
| Value::Function(Function(vref))
| Value::Thread(Thread(vref, ..))
| Value::UserData(AnyUserData(vref))
| Value::Other(vref) => unsafe { invoke_to_string(vref) },
| Value::Other(vref) => unsafe { invoke_tostring(vref) },
#[cfg(feature = "luau")]
Value::Buffer(crate::Buffer(vref)) => unsafe { invoke_to_string(vref) },
Value::Buffer(crate::Buffer(vref)) => unsafe { invoke_tostring(vref) },
Value::Error(err) => Ok(err.to_string()),
}
}
@@ -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."
)]
#[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())
}
@@ -561,15 +556,7 @@ impl Value {
t @ Value::Table(_) => write!(fmt, "table: {:?}", t.to_pointer()),
f @ Value::Function(_) => write!(fmt, "function: {:?}", f.to_pointer()),
t @ Value::Thread(_) => write!(fmt, "thread: {:?}", t.to_pointer()),
u @ Value::UserData(ud) => {
// Try `__name/__type` first then `__tostring`
let name = ud.type_name().ok().flatten();
let s = name
.map(|name| format!("{name}: {:?}", u.to_pointer()))
.or_else(|| u.to_string().ok())
.unwrap_or_else(|| format!("userdata: {:?}", u.to_pointer()));
write!(fmt, "{s}")
}
Value::UserData(ud) => ud.fmt_pretty(fmt),
#[cfg(feature = "luau")]
buf @ Value::Buffer(_) => write!(fmt, "buffer: {:?}", buf.to_pointer()),
Value::Error(e) if recursive => write!(fmt, "{e:?}"),
+3 -3
View File
@@ -7,7 +7,7 @@ use futures_util::stream::TryStreamExt;
use tokio::sync::Mutex;
use mlua::{
Error, Function, Lua, LuaOptions, MultiValue, ObjectLike, Result, StdLib, Table, ThreadStatus, UserData,
Error, Function, Lua, LuaOptions, MultiValue, ObjectLike, Result, StdLib, Table, UserData,
UserDataMethods, UserDataRef, Value,
};
@@ -41,7 +41,7 @@ async fn test_async_function_wrap() -> Result<()> {
let f = Function::wrap_async(|s: String| async move {
tokio::task::yield_now().await;
Ok(s)
Ok::<_, Error>(s)
});
lua.globals().set("f", f)?;
let res: String = lua.load(r#"f("hello")"#).eval_async().await?;
@@ -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)>((11, 12))?, (23, 132));
assert_eq!(thread.resume::<(i32, i32)>((12, 13))?, (0, 0));
assert_eq!(thread.status(), ThreadStatus::Finished);
assert!(thread.is_finished());
Ok(())
}
+17 -1
View File
@@ -1,3 +1,4 @@
#[cfg(not(target_os = "wasi"))]
use std::{fs, io};
use mlua::{Chunk, ChunkMode, Lua, Result};
@@ -85,7 +86,7 @@ fn test_chunk_macro() -> Result<()> {
data.raw_set("num", 1)?;
let ud = mlua::AnyUserData::wrap("hello");
let f = mlua::Function::wrap(|| Ok(()));
let f = mlua::Function::wrap(|| Ok::<_, mlua::Error>(()));
lua.globals().set("g", 123)?;
@@ -109,6 +110,21 @@ fn test_chunk_macro() -> Result<()> {
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(())
}
+23
View File
@@ -21,4 +21,27 @@ fn test_compilation() {
t.compile_fail("tests/compile/non_send.rs");
#[cfg(not(feature = "send"))]
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
--> tests/compile/async_any_userdata_method.rs:9:49
|
8 | let mut s = &s;
| ----- `s` declared here, outside the closure
9 | reg.add_async_method("t", |_, this, ()| async {
| ------------- ^^^^^ cannot borrow as mutable
| |
| in this closure
10 | s = &*this;
| - mutable borrow occurs due to use of `s` in closure
8 | let mut s = &s;
| ----- `s` declared here, outside the closure
9 | reg.add_async_method("t", |_, this, ()| async {
| - ------------- ^^^^^ cannot borrow as mutable
| | |
| _____________| in this 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
--> 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
|
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
|
= help: within `Lua`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<mlua::state::raw::RawLua>`
note: required because it appears within the type `lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>`
--> $CARGO/lock_api-$VERSION/src/remutex.rs
= 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 `Cell<*mut lua_State>`
--> $RUST/core/src/cell.rs
|
| pub struct ReentrantMutex<R, G, T: ?Sized> {
| ^^^^^^^^^^^^^^
note: required because it appears within the type `alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>`
--> $RUST/alloc/src/sync.rs
| pub struct Cell<T: ?Sized> {
| ^^^^
note: required because it appears within the type `mlua::state::RawLua`
--> src/state/raw.rs
|
| struct ArcInner<T: ?Sized> {
| ^^^^^^^^
note: required because it appears within the type `PhantomData<alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>>`
--> $RUST/core/src/marker.rs
| pub struct RawLua {
| ^^^^^^
note: required because it appears within the type `mlua::types::sync::inner::ReentrantMutex<mlua::state::RawLua>`
--> src/types/sync.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<
| ^^^
| pub(crate) struct ReentrantMutex<T>(T);
| ^^^^^^^^^^^^^^
= note: required for `Rc<mlua::types::sync::inner::ReentrantMutex<mlua::state::RawLua>>` to implement `RefUnwindSafe`
note: required because it appears within the type `Lua`
--> 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> {
| ^^^^^^^^^^ 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
|
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
|
= help: within `Lua`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<usize>`
note: required because it appears within the type `Cell<usize>`
--> $RUST/core/src/cell.rs
= 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 Cell<T: ?Sized> {
| ^^^^
note: required because it appears within the type `lock_api::remutex::RawReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId>`
--> $CARGO/lock_api-$VERSION/src/remutex.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 RawReentrantMutex<R, G> {
| ^^^^^^^^^^^^^^^^^
note: required because it appears within the type `lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>`
--> $CARGO/lock_api-$VERSION/src/remutex.rs
| pub struct ManuallyDrop<T: ?Sized> {
| ^^^^^^^^^^^^
note: required because it appears within the type `mlua::state::RawLua`
--> src/state/raw.rs
|
| pub struct ReentrantMutex<R, G, T: ?Sized> {
| ^^^^^^^^^^^^^^
note: required because it appears within the type `alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>`
--> $RUST/alloc/src/sync.rs
| pub struct RawLua {
| ^^^^^^
note: required because it appears within the type `mlua::types::sync::inner::ReentrantMutex<mlua::state::RawLua>`
--> src/types/sync.rs
|
| struct ArcInner<T: ?Sized> {
| ^^^^^^^^
note: required because it appears within the type `PhantomData<alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>>`
--> $RUST/core/src/marker.rs
|
| pub struct PhantomData<T: 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<
| ^^^
| pub(crate) struct ReentrantMutex<T>(T);
| ^^^^^^^^^^^^^^
= note: required for `Rc<mlua::types::sync::inner::ReentrantMutex<mlua::state::RawLua>>` to implement `RefUnwindSafe`
note: required because it appears within the type `Lua`
--> 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
|
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
|
= help: within `alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<mlua::state::raw::RawLua>`
note: required because it appears within the type `lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>`
--> $CARGO/lock_api-$VERSION/src/remutex.rs
= 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 `Cell<usize>`
--> $RUST/core/src/cell.rs
|
| pub struct ReentrantMutex<R, G, T: ?Sized> {
| ^^^^^^^^^^^^^^
note: required because it appears within the type `alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>`
--> $RUST/alloc/src/sync.rs
| pub struct Cell<T: ?Sized> {
| ^^^^
note: required because it appears within the type `rc::RcInner<mlua::types::sync::inner::ReentrantMutex<mlua::state::RawLua>>`
--> $RUST/alloc/src/rc.rs
|
| struct ArcInner<T: ?Sized> {
| ^^^^^^^^
= note: required for `NonNull<alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>>` to implement `UnwindSafe`
note: required because it appears within the type `std::sync::Weak<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>`
--> $RUST/alloc/src/sync.rs
| struct RcInner<T: ?Sized> {
| ^^^^^^^
= note: required for `NonNull<rc::RcInner<mlua::types::sync::inner::ReentrantMutex<mlua::state::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<
| ^^^^
@@ -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> {
| ^^^^^^^^^^ 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
|
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
|
= 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>`
note: required because it appears within the type `Cell<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<*mut lua_State>`
--> $RUST/core/src/cell.rs
|
| pub struct Cell<T: ?Sized> {
| ^^^^
note: required because it appears within the type `lock_api::remutex::RawReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId>`
--> $CARGO/lock_api-$VERSION/src/remutex.rs
note: required because it appears within the type `mlua::state::RawLua`
--> src/state/raw.rs
|
| pub struct RawReentrantMutex<R, G> {
| ^^^^^^^^^^^^^^^^^
note: required because it appears within the type `lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>`
--> $CARGO/lock_api-$VERSION/src/remutex.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 struct ReentrantMutex<R, G, T: ?Sized> {
| ^^^^^^^^^^^^^^
note: required because it appears within the type `alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>`
--> $RUST/alloc/src/sync.rs
| pub(crate) struct ReentrantMutex<T>(T);
| ^^^^^^^^^^^^^^
note: required because it appears within the type `rc::RcInner<mlua::types::sync::inner::ReentrantMutex<mlua::state::RawLua>>`
--> $RUST/alloc/src/rc.rs
|
| struct ArcInner<T: ?Sized> {
| ^^^^^^^^
= note: required for `NonNull<alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>>` to implement `UnwindSafe`
note: required because it appears within the type `std::sync::Weak<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>`
--> $RUST/alloc/src/sync.rs
| struct RcInner<T: ?Sized> {
| ^^^^^^^
= note: required for `NonNull<rc::RcInner<mlua::types::sync::inner::ReentrantMutex<mlua::state::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<
| ^^^^
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<
| ^^^^
+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(())
}
#[test]
fn test_external_error() {
// `Error::external` should preserve `mlua::Error`
let runtime_err = Error::runtime("test error");
let converted = Error::external(runtime_err);
assert!(matches!(converted, Error::RuntimeError(ref msg) if msg == "test error"));
// Other errors should become `ExternalError`
let converted = Error::external(io::Error::other("other error"));
assert!(matches!(converted, Error::ExternalError(_)));
assert!(converted.downcast_ref::<io::Error>().is_some());
}
#[cfg(feature = "anyhow")]
#[test]
fn test_error_anyhow() -> Result<()> {
+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};
#[test]
@@ -343,7 +346,7 @@ fn test_function_deep_clone() -> Result<()> {
fn test_function_wrap() -> Result<()> {
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.load(r#"assert(f("hello", 2) == "hellohello")"#)
.exec()
@@ -361,11 +364,40 @@ fn test_function_wrap() -> Result<()> {
.exec()
.unwrap();
// Return external error
#[derive(Debug)]
struct MyError(String);
impl fmt::Display for MyError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "MyError: {}", self.0)
}
}
impl std::error::Error for MyError {}
let fext = Function::wrap(|s: String| -> StdResult<String, MyError> {
if s == "bad" {
return Err(MyError("bad input".into()));
}
Ok(format!("ok: {s}"))
});
lua.globals().set("fext", fext)?;
lua.load(r#"assert(fext("hello") == "ok: hello")"#)
.exec()
.unwrap();
lua.load(
r#"
local ok, err = pcall(fext, "bad")
assert(not ok and tostring(err):find("MyError: bad input"))
"#,
)
.exec()
.unwrap();
// Mutable callback
let mut i = 0;
let fmut = Function::wrap_mut(move || {
i += 1;
Ok(i)
Ok::<_, Error>(i)
});
lua.globals().set("fmut", fmut)?;
lua.load(r#"fmut(); fmut(); assert(fmut() == 3)"#).exec().unwrap();
@@ -385,7 +417,7 @@ fn test_function_wrap() -> Result<()> {
// Check recursive mut callback error
let fmut = Function::wrap_mut(|f: Function| match f.call::<()>(&f) {
Err(Error::CallbackError { cause, .. }) => match cause.as_ref() {
Error::RecursiveMutCallback { .. } => Ok(()),
Error::RecursiveMutCallback { .. } => Ok::<_, Error>(()),
other => panic!("incorrect result: {other:?}"),
},
other => panic!("incorrect result: {other:?}"),
+5 -4
View File
@@ -3,7 +3,8 @@
use std::sync::atomic::{AtomicI64, Ordering};
use std::sync::{Arc, Mutex};
use mlua::{DebugEvent, Error, HookTriggers, Lua, Result, ThreadStatus, Value, VmState};
use mlua::debug::DebugEvent;
use mlua::{Error, HookTriggers, Lua, Result, Value, VmState};
#[test]
fn test_hook_triggers() {
@@ -280,14 +281,14 @@ fn test_hook_yield() -> Result<()> {
assert!(co.resume::<()>(()).is_ok());
assert!(co.resume::<()>(()).is_ok());
assert!(co.resume::<()>(()).is_ok());
assert!(co.status() == ThreadStatus::Finished);
assert!(co.is_finished());
}
#[cfg(any(feature = "lua51", feature = "lua52", feature = "luajit"))]
{
assert!(
matches!(co.resume::<()>(()), Err(Error::RuntimeError(err)) if err.contains("attempt to yield from a hook"))
);
assert!(co.status() == ThreadStatus::Error);
assert!(co.is_error());
}
Ok(())
@@ -320,7 +321,7 @@ fn test_global_hook() -> Result<()> {
thread.resume::<()>(()).unwrap();
lua.remove_global_hook();
thread.resume::<()>(()).unwrap();
assert_eq!(thread.status(), ThreadStatus::Finished);
assert!(thread.is_finished());
assert_eq!(counter.load(Ordering::Relaxed), 3);
Ok(())
+22 -87
View File
@@ -1,13 +1,11 @@
#![cfg(feature = "luau")]
use std::cell::Cell;
use std::fmt::Debug;
use std::os::raw::c_void;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicPtr, AtomicU64, Ordering};
use std::sync::atomic::{AtomicU64, Ordering};
use mlua::{
Compiler, Error, Function, Lua, LuaOptions, Result, StdLib, Table, ThreadStatus, Value, Vector, VmState,
Compiler, Error, Function, Lua, LuaOptions, ObjectLike, Result, StdLib, Table, Value, Vector, VmState,
};
#[test]
@@ -324,11 +322,11 @@ fn test_interrupts() -> Result<()> {
.into_function()?,
)?;
co.resume::<()>(())?;
assert_eq!(co.status(), ThreadStatus::Resumable);
assert!(co.is_resumable());
let result: i32 = co.resume(())?;
assert_eq!(result, 6);
assert_eq!(yield_count.load(Ordering::Relaxed), 7);
assert_eq!(co.status(), ThreadStatus::Finished);
assert!(co.is_finished());
// Test no yielding at non-yieldable points
yield_count.store(0, Ordering::Relaxed);
@@ -359,87 +357,6 @@ fn test_fflags() {
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]
fn test_loadstring() -> Result<()> {
let lua = Lua::new();
@@ -535,5 +452,23 @@ fn test_heap_dump() -> Result<()> {
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"]
mod require;
+150 -5
View File
@@ -1,7 +1,8 @@
use std::io::Result as IoResult;
use std::result::Result as StdResult;
use mlua::{Error, FromLua, IntoLua, Lua, MultiValue, NavigateError, Require, Result, TextRequirer, Value};
use mlua::luau::{FsRequirer, NavigateError, Require};
use mlua::{Error, FromLua, IntoLua, Lua, MultiValue, Result, Value};
fn run_require(lua: &Lua, path: impl IntoLua) -> Result<Value> {
lua.load(r#"return require(...)"#).call(path)
@@ -65,7 +66,7 @@ fn test_require_errors() {
assert!((res.unwrap_err().to_string()).contains("@ is not a valid alias"));
// Test throwing mlua::Error
struct MyRequire(TextRequirer);
struct MyRequire(FsRequirer);
impl Require for MyRequire {
fn is_require_allowed(&self, chunk_name: &str) -> bool {
@@ -109,9 +110,7 @@ fn test_require_errors() {
}
}
let require = lua
.create_require_function(MyRequire(TextRequirer::new()))
.unwrap();
let require = lua.create_require_function(MyRequire(FsRequirer::new())).unwrap();
lua.globals().set("require", require).unwrap();
let res = lua.load(r#"return require('./a/relative/path')"#).exec();
assert!((res.unwrap_err().to_string()).contains("test error"));
@@ -252,6 +251,152 @@ fn test_require_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)))]
#[tokio::test]
async fn test_async_require() -> Result<()> {
+24 -4
View File
@@ -1,6 +1,10 @@
use std::sync::Arc;
use mlua::{Error, GCMode, Lua, Result, UserData};
use mlua::state::{GcIncParams, GcMode};
use mlua::{Error, Lua, Result, UserData};
#[cfg(any(feature = "lua54", feature = "lua55"))]
use mlua::state::GcGenParams;
#[test]
fn test_memory_limit() -> Result<()> {
@@ -74,8 +78,14 @@ fn test_gc_control() -> Result<()> {
#[cfg(any(feature = "lua55", feature = "lua54"))]
{
assert_eq!(lua.gc_gen(0, 0), GCMode::Incremental);
assert_eq!(lua.gc_inc(0, 0, 0), GCMode::Generational);
assert!(matches!(
lua.gc_set_mode(GcMode::Generational(GcGenParams::default())),
GcMode::Incremental(_)
));
assert!(matches!(
lua.gc_set_mode(GcMode::Incremental(GcIncParams::default())),
GcMode::Generational(_)
));
}
#[cfg(any(
@@ -93,7 +103,17 @@ fn test_gc_control() -> Result<()> {
assert!(lua.gc_is_running());
}
assert_eq!(lua.gc_inc(200, 100, 13), GCMode::Incremental);
assert!(matches!(
lua.gc_set_mode(GcMode::Incremental({
let p = GcIncParams::default().step_multiplier(100);
#[cfg(not(feature = "luau"))]
let p = p.pause(200);
#[cfg(feature = "luau")]
let p = p.goal(200);
p
})),
GcMode::Incremental(_)
));
struct MyUserdata(#[allow(unused)] Arc<()>);
impl UserData for MyUserdata {}
+4 -49
View File
@@ -1,50 +1,7 @@
#![cfg(feature = "send")]
use std::cell::UnsafeCell;
use std::marker::PhantomData;
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(())
}
use mlua::{AnyUserData, Lua, ObjectLike, Result, UserData, UserDataMethods, UserDataRef};
use static_assertions::assert_impl_all;
#[test]
fn test_userdata_multithread_access_sync() -> Result<()> {
@@ -74,13 +31,11 @@ fn test_userdata_multithread_access_sync() -> Result<()> {
std::thread::scope(|s| {
s.spawn(|| {
// Getting another shared reference for `Sync` type is allowed.
// FIXME: does not work due to https://github.com/rust-lang/rust/pull/135634
// let _ = lua.globals().get::<UserDataRef<MyUserData>>("ud").unwrap();
let _ = lua.globals().get::<UserDataRef<MyUserData>>("ud").unwrap();
});
});
// FIXME: does not work due to https://github.com/rust-lang/rust/pull/135634
// lua.load("ud:method()").exec().unwrap();
lua.load("ud:method()").exec().unwrap();
Ok(())
}
+23 -13
View File
@@ -5,27 +5,37 @@ use mlua::{Lua, LuaString, Result};
#[test]
fn test_string_compare() {
fn with_str<F: FnOnce(LuaString)>(s: &str, f: F) {
f(Lua::new().create_string(s).unwrap());
let lua = Lua::new();
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
with_str("teststring", |t| assert_eq!(t, "teststring")); // &str
with_str("teststring", |t| assert_eq!(t, b"teststring")); // &[u8]
with_str("teststring", |t| assert_eq!(t, b"teststring".to_vec())); // Vec<u8>
with_str("teststring", |t| assert_eq!(t, "teststring".to_string())); // String
with_str("teststring", |t| assert_eq!(t, t)); // mlua::String
with_str("teststring", |t| assert_eq!(t, Cow::from(b"teststring".as_ref()))); // Cow (borrowed)
with_str("bla", |t| assert_eq!(t, Cow::from(b"bla".to_vec()))); // Cow (owned)
with_str(&lua, "teststring", |t| assert_eq!(t, "teststring")); // &str
with_str(&lua, "teststring", |t| assert_eq!(t, b"teststring")); // &[u8]
with_str(&lua, "teststring", |t| assert_eq!(t, b"teststring".to_vec())); // Vec<u8>
with_str(&lua, "teststring", |t| assert_eq!(t, "teststring".to_string())); // String
with_str(&lua, "teststring", |t| assert_eq!(t, t)); // mlua::String
with_str(&lua, "teststring", |t| {
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
with_str("a", |a| {
with_str(&lua, "a", |a| {
assert!(!(a < a));
assert!(!(a > a));
});
with_str("a", |a| assert!(a < "b"));
with_str("a", |a| assert!(a < b"b"));
with_str("a", |a| with_str("b", |b| assert!(a < b)));
with_str(&lua, "a", |a| assert!(a < "b"));
with_str(&lua, "a", |a| assert!(a < b"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]
+2 -2
View File
@@ -1374,7 +1374,7 @@ fn test_inspect_stack() -> Result<()> {
local function baz(a, b, c, ...)
return stack_info()
end
assert(baz() == 'DebugStack { num_ups: 1, num_params: 3, is_vararg: true }')
assert(baz() == 'DebugStack { num_upvalues: 1, num_params: 3, is_vararg: true }')
"#,
)
.exec()?;
@@ -1387,7 +1387,7 @@ fn test_inspect_stack() -> Result<()> {
local function baz(a, b, c, ...)
return stack_info()
end
assert(baz() == 'DebugStack { num_ups: 1 }')
assert(baz() == 'DebugStack { num_upvalues: 1 }')
"#,
)
.exec()?;
+232 -18
View File
@@ -1,6 +1,8 @@
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]
fn test_thread() -> Result<()> {
@@ -21,17 +23,17 @@ fn test_thread() -> Result<()> {
.eval()?,
)?;
assert_eq!(thread.status(), ThreadStatus::Resumable);
assert!(thread.is_resumable());
assert_eq!(thread.resume::<i64>(0)?, 0);
assert_eq!(thread.status(), ThreadStatus::Resumable);
assert!(thread.is_resumable());
assert_eq!(thread.resume::<i64>(1)?, 1);
assert_eq!(thread.status(), ThreadStatus::Resumable);
assert!(thread.is_resumable());
assert_eq!(thread.resume::<i64>(2)?, 3);
assert_eq!(thread.status(), ThreadStatus::Resumable);
assert!(thread.is_resumable());
assert_eq!(thread.resume::<i64>(3)?, 6);
assert_eq!(thread.status(), ThreadStatus::Resumable);
assert!(thread.is_resumable());
assert_eq!(thread.resume::<i64>(4)?, 10);
assert_eq!(thread.status(), ThreadStatus::Finished);
assert!(thread.is_finished());
let accumulate = lua.create_thread(
lua.load(
@@ -50,9 +52,9 @@ fn test_thread() -> Result<()> {
accumulate.resume::<()>(i)?;
}
assert_eq!(accumulate.resume::<i64>(4)?, 10);
assert_eq!(accumulate.status(), ThreadStatus::Resumable);
assert!(accumulate.is_resumable());
assert!(accumulate.resume::<()>("error").is_err());
assert_eq!(accumulate.status(), ThreadStatus::Error);
assert!(accumulate.is_error());
let thread = lua
.load(
@@ -65,7 +67,7 @@ fn test_thread() -> Result<()> {
"#,
)
.eval::<Thread>()?;
assert_eq!(thread.status(), ThreadStatus::Resumable);
assert!(thread.is_resumable());
assert_eq!(thread.resume::<i64>(())?, 42);
let thread: Thread = lua
@@ -92,7 +94,7 @@ fn test_thread() -> Result<()> {
// Already running thread must be unresumable
let thread = lua.create_thread(lua.create_function(|lua, ()| {
assert_eq!(lua.current_thread().status(), ThreadStatus::Running);
assert!(lua.current_thread().is_running());
let result = lua.current_thread().resume::<()>(());
assert!(
matches!(result, Err(Error::CoroutineUnresumable)),
@@ -123,12 +125,12 @@ fn test_thread_reset() -> Result<()> {
assert!(thread.reset(func.clone()).is_ok());
for _ in 0..2 {
assert_eq!(thread.status(), ThreadStatus::Resumable);
assert!(thread.is_resumable());
let _ = thread.resume::<AnyUserData>(MyUserData(arc.clone()))?;
assert_eq!(thread.status(), ThreadStatus::Resumable);
assert!(thread.is_resumable());
assert_eq!(Arc::strong_count(&arc), 2);
thread.resume::<()>(())?;
assert_eq!(thread.status(), ThreadStatus::Finished);
assert!(thread.is_finished());
thread.reset(func.clone())?;
lua.gc_collect()?;
assert_eq!(Arc::strong_count(&arc), 1);
@@ -138,21 +140,21 @@ fn test_thread_reset() -> Result<()> {
let func: Function = lua.load(r#"function(ud) error("test error") end"#).eval()?;
let thread = lua.create_thread(func.clone())?;
let _ = thread.resume::<AnyUserData>(MyUserData(arc.clone()));
assert_eq!(thread.status(), ThreadStatus::Error);
assert!(thread.is_error());
assert_eq!(Arc::strong_count(&arc), 2);
#[cfg(any(feature = "lua55", feature = "lua54"))]
{
assert!(thread.reset(func.clone()).is_err());
// Reset behavior has changed in Lua v5.4.4
// It's became possible to force reset thread by popping error object
assert!(matches!(thread.status(), ThreadStatus::Finished));
assert!(thread.is_finished());
assert!(thread.reset(func.clone()).is_ok());
assert_eq!(thread.status(), ThreadStatus::Resumable);
assert!(thread.is_resumable());
}
#[cfg(any(feature = "lua55", feature = "lua54", feature = "luau"))]
{
assert!(thread.reset(func.clone()).is_ok());
assert_eq!(thread.status(), ThreadStatus::Resumable);
assert!(thread.is_resumable());
}
// Try reset running thread
@@ -275,3 +277,215 @@ fn test_thread_resume_bad_arg() -> Result<()> {
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