mirror of
https://github.com/mlua-rs/mlua
synced 2026-06-08 16:05:43 +00:00
Compare commits
31 Commits
v0.12.0-rc.1
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 0711c614c7 | |||
| 743325f7d6 | |||
| 0b365e92a9 | |||
| 15fb63b2a2 | |||
| 0849d05c83 | |||
| 38c05b850e | |||
| 8d1841f8cf | |||
| e9271d2e32 | |||
| e263220fb3 | |||
| e6d16815d7 | |||
| ae88e8acf8 | |||
| fcab60bac4 | |||
| 208a70f407 | |||
| ca360f9019 | |||
| a7c5a24a7b | |||
| b7c98ad9bb | |||
| 1f3dafa564 | |||
| 1d4a756436 | |||
| 6e7d6c78ed | |||
| 023e4c61d8 | |||
| 92bd06d3c1 | |||
| d8544bf038 | |||
| 7114c03489 | |||
| f4cacc524e | |||
| cc7f7ce7b7 | |||
| 72de602ec3 | |||
| 1573dd1242 | |||
| 39d3201848 | |||
| 4aa6214b45 | |||
| 72824a468a | |||
| c54b90623c |
@@ -1,3 +1,12 @@
|
||||
## 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
|
||||
|
||||
+4
-3
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "mlua"
|
||||
version = "0.12.0-rc.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,6 +61,7 @@ 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.11.0-rc.1", path = "mlua-sys" }
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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> {
|
||||
...
|
||||
}
|
||||
```
|
||||
+28
-27
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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 `__`"
|
||||
),
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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
@@ -290,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())?;
|
||||
}
|
||||
|
||||
+16
-90
@@ -100,6 +100,9 @@ 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;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use crate::error::{Error, Result};
|
||||
@@ -139,7 +142,7 @@ pub use crate::string::LuaString as String;
|
||||
#[doc(hidden)]
|
||||
pub use crate::table::{TablePairs, TableSequence};
|
||||
#[doc(hidden)]
|
||||
pub use crate::thread::ThreadStatus;
|
||||
pub use crate::thread::{ThreadEvent, ThreadStatus, ThreadTriggers};
|
||||
#[doc(hidden)]
|
||||
pub use crate::userdata::{
|
||||
MetaMethod, UserData, UserDataFields, UserDataMetatable, UserDataMethods, UserDataOwned, UserDataRef,
|
||||
@@ -170,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;
|
||||
@@ -230,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 an unknown environment and cannot tell whether there are any memory
|
||||
/// limits or not. As a result, some operations that require memory allocation run in protected
|
||||
/// mode. Setting this attribute will improve performance of such operations with risk of having
|
||||
/// uncaught exceptions and memory leaks.
|
||||
///
|
||||
/// ```ignore
|
||||
/// #[mlua::lua_module(skip_memory_check)]
|
||||
/// 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;
|
||||
|
||||
+44
-2
@@ -66,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>;
|
||||
|
||||
@@ -192,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,
|
||||
@@ -298,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;
|
||||
|
||||
+65
-70
@@ -22,7 +22,7 @@ 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, MaybeSync, Number,
|
||||
@@ -842,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.
|
||||
|
||||
+6
-9
@@ -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")]
|
||||
|
||||
+32
-6
@@ -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::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,
|
||||
@@ -56,7 +56,7 @@ 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 {
|
||||
|
||||
+8
-1
@@ -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)]
|
||||
@@ -144,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)
|
||||
}
|
||||
}
|
||||
|
||||
+5
-8
@@ -23,7 +23,7 @@ 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 {
|
||||
@@ -149,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 }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,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
|
||||
|
||||
+150
-4
@@ -42,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"))]
|
||||
@@ -63,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 {
|
||||
@@ -98,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(_))
|
||||
@@ -193,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)?;
|
||||
@@ -201,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);
|
||||
|
||||
@@ -229,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);
|
||||
|
||||
@@ -622,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;
|
||||
}
|
||||
@@ -658,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();
|
||||
|
||||
+4
-10
@@ -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"))]
|
||||
|
||||
+1
-6
@@ -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(),
|
||||
|
||||
@@ -110,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(())
|
||||
}
|
||||
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 };
|
||||
| ^
|
||||
@@ -1,32 +1,28 @@
|
||||
error[E0277]: the type `UnsafeCell<mlua::state::RawLua>` may contain interior mutability and a reference may not be safely transferable across a catch_unwind boundary
|
||||
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::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::RawLua>`
|
||||
note: required because it appears within the type `lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::RawLua>`
|
||||
--> $CARGO/lock_api-$VERSION/src/remutex.rs
|
||||
= 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::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::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::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::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::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::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::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
|
||||
|
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
error[E0277]: the type `UnsafeCell<mlua::state::RawLua>` may contain interior mutability and a reference may not be safely transferable across a catch_unwind boundary
|
||||
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::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::RawLua>>`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<mlua::state::RawLua>`
|
||||
note: required because it appears within the type `lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::RawLua>`
|
||||
--> $CARGO/lock_api-$VERSION/src/remutex.rs
|
||||
= 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::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::RawLua>>>` to implement `UnwindSafe`
|
||||
note: required because it appears within the type `std::sync::Weak<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::RawLua>>`
|
||||
--> $RUST/alloc/src/sync.rs
|
||||
| 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::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::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::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::RawLua>>>` to implement `UnwindSafe`
|
||||
note: required because it appears within the type `std::sync::Weak<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::RawLua>>`
|
||||
--> $RUST/alloc/src/sync.rs
|
||||
| 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<
|
||||
| ^^^^
|
||||
|
||||
@@ -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)]
|
||||
| ^^^^^^^^^^^^^^
|
||||
@@ -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() {}
|
||||
@@ -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
|
||||
@@ -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 {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
@@ -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)]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
@@ -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> {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
@@ -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> {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
@@ -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<()> {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
@@ -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> {
|
||||
| ^^^^^^^^^
|
||||
@@ -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
|
||||
@@ -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<()> {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
@@ -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> {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
+2
-85
@@ -1,10 +1,8 @@
|
||||
#![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, ObjectLike, Result, StdLib, Table, Value, Vector, VmState,
|
||||
@@ -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();
|
||||
@@ -539,7 +456,7 @@ fn test_heap_dump() -> Result<()> {
|
||||
fn test_integer64_type() -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
|
||||
_ = Lua::set_fflag("LuauIntegerType", true);
|
||||
_ = Lua::set_fflag("LuauIntegerType2", true);
|
||||
|
||||
let integer_lib = lua.globals().get::<Table>("integer")?;
|
||||
let n = integer_lib.call_function::<i64>("create", 42)?;
|
||||
|
||||
@@ -251,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<()> {
|
||||
|
||||
+23
-13
@@ -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]
|
||||
|
||||
+215
-1
@@ -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, Value};
|
||||
use mlua::{Error, Function, IntoLua, Lua, Result, Thread, ThreadEvent, ThreadTriggers, Value};
|
||||
|
||||
#[test]
|
||||
fn test_thread() -> Result<()> {
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -0,0 +1,495 @@
|
||||
#![cfg(feature = "macros")]
|
||||
|
||||
use mlua::{Lua, Result, UserData};
|
||||
|
||||
#[derive(Default, Clone, Debug, UserData)]
|
||||
struct Rectangle {
|
||||
length: u32,
|
||||
#[lua]
|
||||
width: u32,
|
||||
|
||||
#[lua(get, name = "version")]
|
||||
version_ro: u32,
|
||||
|
||||
#[lua(skip)]
|
||||
_internal: u64,
|
||||
}
|
||||
|
||||
#[mlua::userdata_impl]
|
||||
impl Rectangle {
|
||||
const TYPE_NAME: &str = "Rectangle";
|
||||
|
||||
#[lua(infallible)]
|
||||
fn new(length: u32, width: u32) -> Self {
|
||||
Rectangle {
|
||||
length,
|
||||
width,
|
||||
version_ro: 1,
|
||||
_internal: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn area(&self) -> Result<u32> {
|
||||
Ok(self.length * self.width)
|
||||
}
|
||||
|
||||
#[lua(getter, infallible, name = "perimeter")]
|
||||
fn calculate_perimeter(&self) -> u32 {
|
||||
2 * (self.length + self.width)
|
||||
}
|
||||
|
||||
#[lua(infallible)]
|
||||
fn diagonal(&self) -> f64 {
|
||||
(self.length.pow(2) as f64 + self.width.pow(2) as f64).sqrt()
|
||||
}
|
||||
|
||||
fn scale(&mut self, factor: u32) -> Result<()> {
|
||||
self.length *= factor;
|
||||
self.width *= factor;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[lua(setter, name = "size", infallible)]
|
||||
fn set_size(&mut self, _lua: &Lua, size: u32) {
|
||||
self.length = size;
|
||||
self.width = size;
|
||||
}
|
||||
|
||||
#[lua(field)]
|
||||
fn description() -> &'static str {
|
||||
"A rectangle shape"
|
||||
}
|
||||
|
||||
#[lua(meta)]
|
||||
fn __tostring(&self) -> Result<String> {
|
||||
Ok(format!("Rectangle({}x{})", self.length, self.width))
|
||||
}
|
||||
|
||||
#[lua(meta, name = "__call")]
|
||||
fn call() -> Result<Self> {
|
||||
Ok(Rectangle::default())
|
||||
}
|
||||
|
||||
#[lua(meta, infallible, name = "__add")]
|
||||
fn add(&self, other: &Rectangle) -> Rectangle {
|
||||
Rectangle {
|
||||
length: self.length + other.length,
|
||||
width: self.width + other.width,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[lua(meta, field, name = "__answer")]
|
||||
fn answer() -> u32 {
|
||||
42
|
||||
}
|
||||
|
||||
#[lua(skip)]
|
||||
#[allow(unused)]
|
||||
fn helper() -> u32 {
|
||||
42
|
||||
}
|
||||
|
||||
fn default_size() -> Result<(u32, u32)> {
|
||||
Ok((100, 100))
|
||||
}
|
||||
|
||||
fn get_width(&self, lua: &Lua) -> Result<u32> {
|
||||
let _ = lua.globals().len();
|
||||
Ok(self.width)
|
||||
}
|
||||
|
||||
#[lua(getter, name = "lua_version")]
|
||||
fn get_lua_version(&self, lua: &::mlua::Lua) -> Result<String> {
|
||||
// `::mlua::Lua` is used to check that the type is correctly resolved in macros
|
||||
lua.globals().get("_VERSION")
|
||||
}
|
||||
|
||||
fn into_tuple(self) -> Result<(u32, u32)> {
|
||||
Ok((self.length, self.width))
|
||||
}
|
||||
|
||||
fn greet(&self, name: &str) -> Result<String> {
|
||||
Ok(format!("Hello, {name}!"))
|
||||
}
|
||||
|
||||
fn transfer_length(&mut self, other: &mut Rectangle) -> Result<()> {
|
||||
other.length += self.length;
|
||||
self.length = 0;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[mlua::userdata_impl]
|
||||
impl Rectangle {
|
||||
#[lua(infallible)]
|
||||
fn double_length(&self) -> u32 {
|
||||
self.length * 2
|
||||
}
|
||||
}
|
||||
|
||||
fn make_lua() -> Lua {
|
||||
let lua = unsafe { Lua::unsafe_new() };
|
||||
lua.globals()
|
||||
.set("Rectangle", lua.create_proxy::<Rectangle>().unwrap())
|
||||
.unwrap();
|
||||
lua
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rectangle() {
|
||||
let lua = make_lua();
|
||||
|
||||
// Basic fields, getters, setters, methods
|
||||
lua.load(
|
||||
r#"
|
||||
rect = Rectangle.new(5, 10, 3)
|
||||
assert(rect.length == 5, "length should be 5")
|
||||
assert(rect.width == 10, "width should be 10")
|
||||
assert(rect.perimeter == 30, "perimeter should be 30")
|
||||
|
||||
-- read-only field
|
||||
assert(rect.version == 1, "version should be 1")
|
||||
local ok, err = pcall(function() rect.version = 2 end)
|
||||
assert(not ok, "version should be read-only")
|
||||
|
||||
-- skipped
|
||||
assert(rect._internal == nil, "_internal should be nil")
|
||||
assert(rect.helper == nil, "skipped method should be nil")
|
||||
|
||||
rect.length = 15
|
||||
rect.width = 20
|
||||
assert(rect.length == 15, "length should be updated to 15")
|
||||
assert(rect.width == 20, "width should be updated to 20")
|
||||
assert(rect.perimeter == 70, "perimeter should be updated to 70")
|
||||
assert(rect:area() == 300, "area should return 300")
|
||||
|
||||
rect:scale(2)
|
||||
assert(rect.length == 30, "length should be scaled to 30")
|
||||
assert(rect.width == 40, "width should be scaled to 40")
|
||||
assert(rect:diagonal() == 50.0, "diagonal should be 50.0")
|
||||
|
||||
rect.size = 7
|
||||
assert(rect.length == 7, "length should be updated to 7")
|
||||
assert(rect.width == 7, "width should be updated to 7")
|
||||
|
||||
-- static / associated items
|
||||
assert(rect.TYPE_NAME == 'Rectangle', "TYPE_NAME should be 'Rectangle'")
|
||||
assert(rect.description == 'A rectangle shape', "description should be 'A rectangle shape'")
|
||||
local w, h = rect.default_size()
|
||||
assert(w == 100, "default_size width should be 100")
|
||||
assert(h == 100, "default_size height should be 100")
|
||||
|
||||
-- meta methods
|
||||
local r1 = Rectangle.new(5, 10, 0)
|
||||
assert(tostring(r1) == 'Rectangle(5x10)', "__tostring should return 'Rectangle(5x10)'")
|
||||
local r2 = r1()
|
||||
assert(r2:area() == 0, "__call should create a default rect")
|
||||
local r3 = Rectangle.new(3, 4, 0)
|
||||
local r4 = r1 + r3
|
||||
assert(r4.length == 8, "__add length should be 5 + 3 = 8")
|
||||
assert(r4.width == 14, "__add width should be 10 + 4 = 14")
|
||||
|
||||
-- method with &mut self and &mut Rectangle param
|
||||
rect = Rectangle.new(5, 10, 3)
|
||||
other = Rectangle.new(2, 3, 0)
|
||||
rect:transfer_length(other)
|
||||
assert(rect.length == 0, "length should be 0 after transfer")
|
||||
assert(other.length == 7, "other length should be 7 after transfer")
|
||||
assert(other:double_length() == 14, "double_length should be 14")
|
||||
|
||||
-- meta field
|
||||
if _VERSION:match("Lua ") then
|
||||
local mt = debug.getmetatable(rect)
|
||||
assert(mt.__answer == 42, "__answer meta field should be 42")
|
||||
end
|
||||
|
||||
assert(rect.lua_version == _VERSION, "lua_version should match Lua's _VERSION")
|
||||
|
||||
-- Consuming method
|
||||
local w, h = rect:into_tuple()
|
||||
assert(w == 0, "into_tuple width should be 0")
|
||||
assert(h == 10, "into_tuple height should be 7")
|
||||
local ok, err = pcall(function() rect:area() end)
|
||||
assert(not ok and tostring(err):match("userdata has been destructed"), "rect should be consumed and unusable after into_tuple")
|
||||
"#,
|
||||
)
|
||||
.exec()
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, UserData)]
|
||||
enum Color {
|
||||
Red,
|
||||
Green,
|
||||
Blue,
|
||||
}
|
||||
|
||||
fn make_lua_color() -> Lua {
|
||||
let lua = Lua::new();
|
||||
lua.globals()
|
||||
.set("Color", lua.create_proxy::<Color>().unwrap())
|
||||
.unwrap();
|
||||
lua
|
||||
}
|
||||
|
||||
#[mlua::userdata_impl]
|
||||
impl Color {
|
||||
#[lua(infallible)]
|
||||
fn new(r: u8, g: u8, b: u8) -> Self {
|
||||
if r > 0 && g == 0 && b == 0 {
|
||||
Color::Red
|
||||
} else if g > 0 && r == 0 && b == 0 {
|
||||
Color::Green
|
||||
} else {
|
||||
Color::Blue
|
||||
}
|
||||
}
|
||||
|
||||
#[lua(infallible)]
|
||||
fn name(&self) -> String {
|
||||
match self {
|
||||
Color::Red => "red".into(),
|
||||
Color::Green => "green".into(),
|
||||
Color::Blue => "blue".into(),
|
||||
}
|
||||
}
|
||||
|
||||
#[lua(meta, infallible)]
|
||||
fn __tostring(&self) -> String {
|
||||
self.name()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_color() {
|
||||
let lua = make_lua_color();
|
||||
lua.load(
|
||||
r#"
|
||||
red = Color.new(255, 0, 0)
|
||||
green = Color.new(0, 255, 0)
|
||||
blue = Color.new(0, 0, 255)
|
||||
|
||||
assert(red:name() == 'red', "red name should be 'red'")
|
||||
assert(green:name() == 'green', "green name should be 'green'")
|
||||
assert(blue:name() == 'blue', "blue name should be 'blue'")
|
||||
|
||||
assert(tostring(red) == 'red', "red tostring should be 'red'")
|
||||
assert(tostring(green) == 'green', "green tostring should be 'green'")
|
||||
assert(tostring(blue) == 'blue', "blue tostring should be 'blue'")
|
||||
"#,
|
||||
)
|
||||
.exec()
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, UserData)]
|
||||
struct Point(i32, i32);
|
||||
|
||||
fn make_lua_point() -> Lua {
|
||||
let lua = Lua::new();
|
||||
lua.globals()
|
||||
.set("Point", lua.create_proxy::<Point>().unwrap())
|
||||
.unwrap();
|
||||
lua
|
||||
}
|
||||
|
||||
#[mlua::userdata_impl]
|
||||
impl Point {
|
||||
#[lua(infallible)]
|
||||
fn new(x: i32, y: i32) -> Self {
|
||||
Point(x, y)
|
||||
}
|
||||
|
||||
fn x(&self) -> Result<i32> {
|
||||
Ok(self.0)
|
||||
}
|
||||
|
||||
fn y(&self) -> Result<i32> {
|
||||
Ok(self.1)
|
||||
}
|
||||
|
||||
fn distance(&self, other: &Point) -> Result<f64> {
|
||||
let dx = (self.0 - other.0) as f64;
|
||||
let dy = (self.1 - other.1) as f64;
|
||||
Ok((dx * dx + dy * dy).sqrt())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_point() {
|
||||
let lua = make_lua_point();
|
||||
lua.load(
|
||||
r#"
|
||||
p1 = Point.new(0, 0)
|
||||
p2 = Point.new(3, 4)
|
||||
|
||||
assert(p1:x() == 0, "p1.x should be 0")
|
||||
assert(p1:y() == 0, "p1.y should be 0")
|
||||
assert(p2:x() == 3, "p2.x should be 3")
|
||||
assert(p2:y() == 4, "p2.y should be 4")
|
||||
|
||||
assert(p1:distance(p2) == 5.0, "distance should be 5.0")
|
||||
"#,
|
||||
)
|
||||
.exec()
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, UserData)]
|
||||
struct Bytes(Vec<u8>);
|
||||
|
||||
#[mlua::userdata_impl]
|
||||
impl Bytes {
|
||||
#[lua(meta, name = "__type")]
|
||||
const TYPE: &str = "MyBytes";
|
||||
|
||||
#[lua(infallible)]
|
||||
fn new(data: &[u8]) -> Self {
|
||||
Bytes(data.to_vec())
|
||||
}
|
||||
|
||||
fn first(&self) -> Result<Option<u8>> {
|
||||
Ok(self.0.first().copied())
|
||||
}
|
||||
|
||||
fn len(&self) -> Result<usize> {
|
||||
Ok(self.0.len())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_known_borrow_wrappers() -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
lua.globals()
|
||||
.set("Bytes", lua.create_proxy::<Bytes>().unwrap())
|
||||
.unwrap();
|
||||
lua.load(
|
||||
r#"
|
||||
local b = Bytes.new('abc')
|
||||
assert(b:first() == 97, "first should return 97 ('a')")
|
||||
assert(b:len() == 3, "len should return 3")
|
||||
|
||||
if _VERSION:match("Luau") then
|
||||
assert(typeof(b) == 'MyBytes', "type should be MyBytes in Luau")
|
||||
end
|
||||
"#,
|
||||
)
|
||||
.exec()
|
||||
.unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
mod async_tests {
|
||||
use mlua::{Lua, Result, UserData};
|
||||
|
||||
#[derive(Clone, Debug, UserData)]
|
||||
struct AsyncCounter(u64);
|
||||
|
||||
#[mlua::userdata_impl]
|
||||
impl AsyncCounter {
|
||||
#[lua(infallible)]
|
||||
fn new() -> Self {
|
||||
AsyncCounter(0)
|
||||
}
|
||||
|
||||
async fn get_value(&self) -> Result<u64> {
|
||||
Ok(self.0)
|
||||
}
|
||||
|
||||
async fn set_value(&mut self, value: u64) -> Result<()> {
|
||||
self.0 = value;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn take_value(self) -> Result<u64> {
|
||||
Ok(self.0)
|
||||
}
|
||||
|
||||
#[lua(infallible)]
|
||||
async fn get_value_infallible(&self) -> u64 {
|
||||
self.0
|
||||
}
|
||||
|
||||
async fn multiply(&self, factor: u64) -> Result<u64> {
|
||||
Ok(self.0 * factor)
|
||||
}
|
||||
|
||||
async fn default_value() -> Result<u64> {
|
||||
Ok(42)
|
||||
}
|
||||
|
||||
#[cfg(not(any(feature = "lua51", feature = "luau")))]
|
||||
#[lua(meta)]
|
||||
async fn __tostring(&self) -> Result<String> {
|
||||
Ok(format!("Counter({})", self.0))
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_async_methods() {
|
||||
let lua = Lua::new();
|
||||
lua.globals()
|
||||
.set("AsyncCounter", lua.create_proxy::<AsyncCounter>().unwrap())
|
||||
.unwrap();
|
||||
|
||||
lua.load(
|
||||
r#"
|
||||
local c = AsyncCounter.new()
|
||||
c:set_value(10)
|
||||
local val = c:get_value()
|
||||
assert(val == 10, "expected 10, got " .. tostring(val))
|
||||
local doubled = c:multiply(3)
|
||||
assert(doubled == 30, "expected 30, got " .. tostring(doubled))
|
||||
local inf = c:get_value_infallible()
|
||||
assert(inf == 10, "expected infallible 10, got " .. tostring(inf))
|
||||
"#,
|
||||
)
|
||||
.exec_async()
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_async_consume() {
|
||||
let lua = Lua::new();
|
||||
lua.globals()
|
||||
.set("AsyncCounter", lua.create_proxy::<AsyncCounter>().unwrap())
|
||||
.unwrap();
|
||||
|
||||
lua.load(
|
||||
r#"
|
||||
local c = AsyncCounter.new()
|
||||
c:set_value(42)
|
||||
local val = c:take_value()
|
||||
assert(val == 42)
|
||||
local ok, err = pcall(function() c:get_value() end)
|
||||
assert(not ok and tostring(err):match("userdata has been destructed"))
|
||||
"#,
|
||||
)
|
||||
.exec_async()
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[cfg(not(any(feature = "lua51", feature = "luau")))]
|
||||
#[tokio::test]
|
||||
async fn test_async_meta() {
|
||||
let lua = Lua::new();
|
||||
lua.globals()
|
||||
.set("AsyncCounter", lua.create_proxy::<AsyncCounter>().unwrap())
|
||||
.unwrap();
|
||||
|
||||
lua.load(
|
||||
r#"
|
||||
local c = AsyncCounter.new()
|
||||
c:set_value(7)
|
||||
assert(tostring(c) == "Counter(7)")
|
||||
"#,
|
||||
)
|
||||
.exec_async()
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -65,7 +65,7 @@ fn test_value_eq() -> Result<()> {
|
||||
|
||||
assert!(!table1.to_pointer().is_null());
|
||||
assert!(!ptr::eq(table1.to_pointer(), table2.to_pointer()));
|
||||
assert!(ptr::eq(string1.to_pointer(), string2.to_pointer()));
|
||||
assert!(ptr::eq(string1.to_pointer(), string2.to_pointer()) && !string1.to_pointer().is_null());
|
||||
assert!(ptr::eq(func1.to_pointer(), func2.to_pointer()));
|
||||
assert!(num1.to_pointer().is_null());
|
||||
|
||||
|
||||
Reference in New Issue
Block a user