Compare commits

..

31 Commits

Author SHA1 Message Date
Alex Orlenko 0711c614c7 v0.12.0-rc.2 2026-06-06 14:28:31 +01:00
Alex Orlenko 743325f7d6 clippy 2026-06-06 14:28:31 +01:00
Alex Orlenko 0b365e92a9 Support to_alias_override/to_alias_fallback in Require trait (Luau) 2026-06-06 14:11:59 +01:00
Alex Orlenko 15fb63b2a2 Support thread create/resume/yield callbacks for all Lua/Luau versions
Introduce new `Lua::set_thread_event_callback` function to register callback for
thread events.
Thread collection callback is removed as unlikely very usefil and has too many limitations.
2026-06-01 00:25:12 +01:00
Alex Orlenko 0849d05c83 mlua_derive: Update docs 2026-05-31 13:45:48 +01:00
Alex Orlenko 38c05b850e mlua_derive: Improve error reporting for #[lua] attributes 2026-05-31 13:32:33 +01:00
Alex Orlenko 8d1841f8cf Fix tests (Luau 0.723 integer type) 2026-05-31 13:22:50 +01:00
Alex Orlenko e9271d2e32 mlua_derive: Use correct Span in metamethod name validation error 2026-05-31 13:17:40 +01:00
Alex Orlenko e263220fb3 Add documentation for #derive(UserData) 2026-05-31 13:05:03 +01:00
Alex Orlenko e6d16815d7 mlua_derive: Improve context-aware validation for #[lua] attr 2026-05-31 12:56:15 +01:00
Alex Orlenko ae88e8acf8 mlua_derive: Detect (and reject) all generic type parameters in UserData derive 2026-05-31 12:31:35 +01:00
Alex Orlenko fcab60bac4 mlua_derive: Fix #[lua(meta, field)] case 2026-05-31 12:28:06 +01:00
Alex Orlenko 208a70f407 Move inlined #[lua_module] proc macro doc to docs/lua_module.md 2026-05-31 11:17:51 +01:00
Alex Orlenko ca360f9019 Move inlined chunk! macro doc to docs/chunk.md 2026-05-31 11:16:40 +01:00
Alex Orlenko a7c5a24a7b mlua_derive: Deny #[lua = "..."] syntax 2026-05-30 22:23:03 +01:00
Alex Orlenko b7c98ad9bb mlua_derive: Switch from #[userdata] to [derive(UserData)] 2026-05-30 13:11:26 +01:00
Alex Orlenko 1f3dafa564 mlua_derive: Reject static field functions with args 2026-05-30 12:46:31 +01:00
Alex Orlenko 1d4a756436 mlua_derive: Support async userdata methods in macro 2026-05-29 00:56:33 +01:00
Alex Orlenko 6e7d6c78ed mlua_derive: Group functionality in modules
Create new `chunk`, `userdata`, `module` modules.
2026-05-26 23:23:09 +01:00
Alex Orlenko 023e4c61d8 mlua_derive: Fix compilation / remove "if let" guards 2026-05-26 23:23:09 +01:00
Alex Orlenko 92bd06d3c1 mlua_derive: Refactor Capture implementation for chunk! 2026-05-26 23:23:09 +01:00
Alex Orlenko d8544bf038 mlua_derive: Optimize Captures::add 2026-05-26 23:23:09 +01:00
Alex Orlenko 7114c03489 mlua_derive: Remove unneeded Span::line hack for chunk! since we use Rust 1.95+ 2026-05-26 23:23:09 +01:00
Alex Orlenko f4cacc524e mlua_derive: Bump MSRV to 1.88 2026-05-26 23:23:09 +01:00
Alex Orlenko cc7f7ce7b7 mlua_derive: show better error message when $ is not followed by ident 2026-05-26 23:23:09 +01:00
Alex Orlenko 72de602ec3 Fix modules compilation after switching to 2024 edition in mlua_derive 2026-05-26 23:23:09 +01:00
Alex Orlenko 1573dd1242 Add #[mlua::userdata] and #[mlua::userdata_impl] macros 2026-05-26 23:23:09 +01:00
Alex Orlenko 39d3201848 Prevent XRc overflow when dropping RawLua with foreign Lua state
Calling `lua_close` triggers GC collection of the `ExtraData` that
cascades to `RawLua::drop` where extra is already decremented to 0,
causing a subtraction overflow.
2026-05-26 23:18:24 +01:00
莯凛 4aa6214b45 feat: implement Not for StdLib (#699) 2026-04-30 22:56:50 +01:00
Alex Orlenko 72824a468a Remove custom PartialEq for LuaString and use derived one
Lua can compare strings using `lua_rawequal` and it's more efficient
than always compare bytes.
Under the hood Lua compare pointers for interned strings and content for long ones.
Close #694
2026-04-22 22:30:45 +01:00
Alex Orlenko c54b90623c Fix String::to_pointer return NULL in Lua <5.4
Lua 5.4+ can return string pointer when calling `lua_topointer`.
In earlier Lua versions this API always returns NULL.
Let's unify this behavior.
2026-04-22 22:23:25 +01:00
68 changed files with 3248 additions and 723 deletions
+9
View File
@@ -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
View File
@@ -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" }
+149
View File
@@ -0,0 +1,149 @@
Implements the [`UserData`] trait for a Rust type.
This derive macro generates an implementation of [`UserData`] that exposes
struct fields to Lua and integrates with `#[mlua::userdata_impl]` for
registering methods.
Named fields are exposed as readable and writable fields in Lua by default.
Use `#[lua(...)]` on individual fields or methods to control how they are
registered.
```rust,ignore
use mlua::{Lua, Result, UserData};
#[derive(UserData)]
struct Rectangle {
length: u32,
width: u32,
}
#[mlua::userdata_impl]
impl Rectangle {
#[lua(infallible)]
fn new(length: u32, width: u32) -> Self {
Self { length, width }
}
#[lua(getter, name = "area", infallible)]
fn calculate_area(&self) -> u32 {
self.length * self.width
}
fn diagonal(&self) -> Result<f64> {
Ok(((self.length.pow(2) + self.width.pow(2)) as f64).sqrt())
}
}
```
# Struct field attributes
Each named field can be annotated with `#[lua(...)]`:
| Attribute | Description |
| -------------- | ----------------------------------------------------- |
| `get` | Expose a getter. The field becomes readable from Lua. |
| `set` | Expose a setter. The field becomes writable from Lua. |
| `skip` | Do not expose this field. |
| `name = "..."` | Override the Lua-facing name for the field. |
If neither `get` nor `set` is specified, both are enabled.
Fields exposed as readable (via `get` or by default) must implement `Clone`.
The generated getter clones the field value when accessed from Lua.
# Methods registration
Use `#[mlua::userdata_impl]` on an `impl` block to register methods,
metamethods, and constants. All public items in the block are registered
automatically.
## Method detection
The receiver type determines how a method is registered:
| Receiver | Registration |
| ----------- | ----------------- |
| `&self` | `add_method` |
| `&mut self` | `add_method_mut` |
| `self` | `add_method_once` |
| None | `add_function` |
A first parameter of type `&Lua` (or `&mlua::Lua`) is treated as the
Lua state reference and passed automatically.
## Method and constant attributes
Each item in the impl block can be annotated with `#[lua(...)]`:
| Attribute | Applies to | Description |
| -------------- | ------------------ | -------------------------------------------------------------------------------------- |
| `skip` | Methods, constants | Exclude this item from registration. |
| `name = "..."` | Methods, constants | Override the Lua-facing name. |
| `infallible` | Methods | Wrap the return value in `Ok(...)`. |
| `getter` | Methods | Register as a field getter. Must take `&self` and no Lua-facing arguments. |
| `setter` | Methods | Register as a field setter. Must take `&[mut] self` and one value argument. |
| `field` | Methods, constants | Register as a static field. Methods must take no receiver and no Lua-facing arguments. |
| `meta` | Methods, constants | Register as a metamethod. May be combined with `field` for meta static fields. |
At most one of `getter`, `setter`, `field` may be specified on a method.
## Constants
Constants in an `#[mlua::userdata_impl]` block are registered as static
fields:
```rust,ignore
#[mlua::userdata_impl]
impl MyType {
const VERSION: &str = "1.0";
const COUNT: u32 = 42;
}
```
Use `#[lua(meta)]` on a constant to register it as a meta static field.
## Metamethods
Annotate a method with `#[lua(meta)]` to register it as a Lua metamethod.
The metamethod name is inferred from the function name when it starts with
`__`. Use `name = "..."` to specify the name explicitly.
```rust,ignore
#[mlua::userdata_impl]
impl MyType {
#[lua(meta, infallible)]
fn __add(&self, other: &Self) -> Self { ... }
#[lua(meta, name = "__call", infallible)]
fn construct(lua: &Lua, value: u32) -> Self { ... }
}
```
## Reference parameters
Reference parameters in method signatures are automatically mapped to
the appropriate callback wrapper types:
| Parameter type | Callback type |
| -------------- | ------------------- |
| `&str` | `BorrowedStr` |
| `&[u8]` | `BorrowedBytes` |
| `&T` | `UserDataRef<T>` |
| `&mut T` | `UserDataRefMut<T>` |
## Async methods
Async methods are supported and registered via the corresponding async
variants (`add_async_method`, `add_async_method_mut`, etc.).
# Limitations
Generics are not supported. Wrap a generic type in a concrete newtype
instead.
Union types cannot derive `UserData`.
Enum types are accepted but generate no field registrations. All method
registration must be done via `#[mlua::userdata_impl]`.
[`UserData`]: crate::UserData
+52
View File
@@ -0,0 +1,52 @@
Create a type that implements [`AsChunk`] and can capture Rust variables.
This macro allows to write Lua code directly in Rust code.
Rust variables can be referenced from Lua using `$` prefix, as shown in the example below.
User's Rust types needs to implement [`UserData`] or [`IntoLua`] traits.
Captured variables are **moved** into the chunk.
```rust
use mlua::{Lua, Result, chunk};
fn main() -> Result<()> {
let lua = Lua::new();
let name = "Rustacean";
lua.load(chunk! {
print("hello, " .. $name)
}).exec()
}
```
## Syntax issues
Since the Rust tokenizer will tokenize Lua code, this imposes some restrictions.
The main thing to remember is:
- Use double quoted strings (`""`) instead of single quoted strings (`''`).
(Single quoted strings only work if they contain a single character, since in Rust,
`'a'` is a character literal).
- Using Lua comments `--` is not desirable in **stable** Rust and can have bad side effects.
This is because procedural macros have Line/Column information available only in
**nightly** Rust. Instead, Lua chunks represented as a big single line of code in stable Rust.
As workaround, Rust comments `//` can be used.
Other minor limitations:
- Certain escape codes in string literals don't work. (Specifically: `\a`, `\b`, `\f`, `\v`,
`\123` (octal escape codes), `\u`, and `\U`).
These are accepted: : `\\`, `\n`, `\t`, `\r`, `\xAB` (hex escape codes), and `\0`.
- The `//` (floor division) operator is unusable, as its start a comment.
Everything else should work.
[`AsChunk`]: crate::chunk::AsChunk
[`UserData`]: crate::UserData
[`IntoLua`]: crate::IntoLua
+41
View File
@@ -0,0 +1,41 @@
Registers Lua module entrypoint.
You can register multiple entrypoints as required.
```rust,ignore
use mlua::{Lua, Result, Table};
#[mlua::lua_module]
fn my_module(lua: &Lua) -> Result<Table> {
let exports = lua.create_table()?;
exports.set("hello", "world")?;
Ok(exports)
}
```
Internally in the code above the compiler defines C function `luaopen_my_module`.
You can also pass options to the attribute:
* name - name of the module, defaults to the name of the function
```rust,ignore
#[mlua::lua_module(name = "alt_module")]
fn my_module(lua: &Lua) -> Result<Table> {
...
}
```
* skip_memory_check - skip memory allocation checks for some operations.
In module mode, mlua runs in an unknown environment and cannot tell whether there are any memory
limits or not. As a result, some operations that require memory allocation run in protected
mode. Setting this attribute will improve performance of such operations with risk of having
uncaught exceptions and memory leaks.
```rust,ignore
#[mlua::lua_module(skip_memory_check)]
fn my_module(lua: &Lua) -> Result<Table> {
...
}
```
+28 -27
View File
@@ -1,45 +1,46 @@
use mlua::{Lua, MetaMethod, Result, UserData, chunk};
use mlua::{Lua, Result, UserData, chunk};
#[derive(Default)]
#[derive(Default, UserData)]
struct Rectangle {
length: u32,
width: u32,
}
impl UserData for Rectangle {
fn add_fields<F: mlua::UserDataFields<Self>>(fields: &mut F) {
fields.add_field_method_get("length", |_, this| Ok(this.length));
fields.add_field_method_set("length", |_, this, val| {
this.length = val;
Ok(())
});
fields.add_field_method_get("width", |_, this| Ok(this.width));
fields.add_field_method_set("width", |_, this, val| {
this.width = val;
Ok(())
});
#[mlua::userdata_impl]
impl Rectangle {
const NAME: &str = "Rectangle";
#[lua(infallible)]
fn new(length: u32, width: u32) -> Self {
Self { length, width }
}
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
methods.add_method("area", |_, this, ()| Ok(this.length * this.width));
methods.add_method("diagonal", |_, this, ()| {
Ok((this.length.pow(2) as f64 + this.width.pow(2) as f64).sqrt())
});
#[lua(getter, name = "area", infallible)]
fn calculate_area(&self) -> u32 {
self.length * self.width
}
// Constructor
methods.add_meta_function(MetaMethod::Call, |_, ()| Ok(Rectangle::default()));
fn diagonal(&self) -> Result<f64> {
Ok((self.length.pow(2) as f64 + self.width.pow(2) as f64).sqrt())
}
// Constructor via `__call` metamethod
#[lua(meta, infallible)]
fn __call(length: u32, width: u32) -> Self {
Rectangle::new(length, width)
}
}
fn main() -> Result<()> {
let lua = Lua::new();
let rectangle = Rectangle::default();
lua.globals().set("Rectangle", lua.create_proxy::<Rectangle>()?)?;
lua.load(chunk! {
local rect = $rectangle()
rect.width = 10
rect.length = 5
assert(rect:area() == 50)
assert(rect:diagonal() - 11.1803 < 0.0001)
local rect = Rectangle(10, 5)
rect.width = rect.width + 5
rect.length = rect.length + 5
assert(rect.NAME == "Rectangle")
assert(rect.area == 150)
assert(math.floor(rect:diagonal()) == 18)
})
.exec()
}
+4 -5
View File
@@ -1,8 +1,9 @@
[package]
name = "mlua_derive"
version = "0.11.0"
version = "0.12.0-rc.1"
authors = ["Aleksandr Orlenko <zxteam@pm.me>"]
edition = "2021"
rust-version = "1.88"
edition = "2024"
description = "Procedural macros for the mlua crate."
repository = "https://github.com/mlua-rs/mlua"
keywords = ["lua", "mlua"]
@@ -12,7 +13,7 @@ license = "MIT"
proc-macro = true
[features]
macros = ["proc-macro-error2", "itertools", "regex", "once_cell"]
macros = ["proc-macro-error2", "itertools"]
[dependencies]
quote = "1.0"
@@ -20,5 +21,3 @@ proc-macro2 = { version = "1.0", features = ["span-locations"] }
proc-macro-error2 = { version = "2.0.1", optional = true }
syn = { version = "2.0", features = ["full"] }
itertools = { version = "0.14", optional = true }
regex = { version = "1.4", optional = true }
once_cell = { version = "1.0", optional = true }
-105
View File
@@ -1,105 +0,0 @@
use proc_macro::{TokenStream, TokenTree};
use crate::token::{Pos, Token, Tokens};
#[derive(Debug, Clone)]
pub(crate) struct Capture {
key: Token,
rust: TokenTree,
}
impl Capture {
fn new(key: Token, rust: TokenTree) -> Self {
Self { key, rust }
}
/// Token string inside `chunk!`
pub(crate) fn key(&self) -> &Token {
&self.key
}
/// As rust variable, e.g. `x`
pub(crate) fn as_rust(&self) -> &TokenTree {
&self.rust
}
}
#[derive(Debug)]
pub(crate) struct Captures(Vec<Capture>);
impl Captures {
pub(crate) fn new() -> Self {
Self(Vec::new())
}
pub(crate) fn add(&mut self, token: &Token) -> Capture {
let tt = token.tree();
let key = token.clone();
match self.0.iter().find(|arg| arg.key() == &key) {
Some(arg) => arg.clone(),
None => {
let arg = Capture::new(key, tt.clone());
self.0.push(arg.clone());
arg
}
}
}
pub(crate) fn captures(&self) -> &[Capture] {
&self.0
}
}
#[derive(Debug)]
pub(crate) struct Chunk {
source: String,
caps: Captures,
}
impl Chunk {
pub(crate) fn new(tokens: TokenStream) -> Self {
let tokens = Tokens::retokenize(tokens);
let mut source = String::new();
let mut caps = Captures::new();
let mut pos: Option<Pos> = None;
for t in tokens {
if t.is_cap() {
caps.add(&t);
}
let (line, col) = (t.start().line, t.start().column);
let (prev_line, prev_col) = pos
.take()
.map(|lc| (lc.line, lc.column))
.unwrap_or_else(|| (line, col));
#[allow(clippy::comparison_chain)]
if line > prev_line {
source.push('\n');
} else if line == prev_line {
for _ in 0..col.saturating_sub(prev_col) {
source.push(' ');
}
}
source.push_str(&t.to_string());
pos = Some(t.end());
}
Self {
source: source.trim_end().to_string(),
caps,
}
}
pub(crate) fn source(&self) -> &str {
&self.source
}
pub(crate) fn captures(&self) -> &[Capture] {
self.caps.captures()
}
}
+160
View File
@@ -0,0 +1,160 @@
use std::ops::Deref;
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::{ToTokens, quote};
use self::token::{Pos, Token, Tokens};
mod token;
#[derive(Debug, Clone)]
pub(crate) struct Capture(Token);
impl Deref for Capture {
type Target = Token;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Capture {
fn new(token: &Token) -> Self {
Self(token.clone())
}
pub(crate) fn name(&self) -> String {
self.0.to_string()
}
}
impl ToTokens for Capture {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let ts: TokenStream = self.0.tree().clone().into();
tokens.extend(TokenStream2::from(ts));
}
}
#[derive(Debug)]
pub(crate) struct Captures(Vec<Capture>);
impl Captures {
pub(crate) fn new() -> Self {
Self(Vec::new())
}
pub(crate) fn add(&mut self, token: &Token) {
if self.0.iter().any(|arg| &**arg == token) {
return;
}
self.0.push(Capture::new(token));
}
pub(crate) fn captures(&self) -> &[Capture] {
&self.0
}
}
#[derive(Debug)]
pub(crate) struct Chunk {
source: String,
caps: Captures,
}
impl Chunk {
pub(crate) fn new(tokens: TokenStream) -> Self {
let tokens = Tokens::retokenize(tokens);
let mut source = String::new();
let mut caps = Captures::new();
let mut prev_end: Option<Pos> = None;
for t in tokens {
if t.is_cap() {
caps.add(&t);
}
let (line, col) = (t.start().line, t.start().column);
if let Some(prev) = prev_end {
if line > prev.line {
source.push('\n');
source.push_str(&" ".repeat(col.saturating_sub(1)));
} else if line == prev.line {
source.push_str(&" ".repeat(col.saturating_sub(prev.column)));
}
} else {
source.push_str(&" ".repeat(col.saturating_sub(1)));
}
source.push_str(&t.to_string());
prev_end = Some(t.end());
}
Self {
source: source.trim_end().to_string(),
caps,
}
}
pub(crate) fn captures(&self) -> &[Capture] {
self.caps.captures()
}
pub(crate) fn expand(&self) -> TokenStream2 {
let source = &self.source;
let caps_len = self.captures().len();
let caps = self.captures().iter().map(|cap| {
let cap_name = cap.name();
quote! { env.raw_set(#cap_name, #cap)?; }
});
quote! {{
use mlua::{AsChunk, ChunkMode, Lua, Result, Table};
use ::std::borrow::Cow;
use ::std::cell::Cell;
use ::std::io::Result as IoResult;
struct InnerChunk<F: FnOnce(&Lua) -> Result<Table>>(Cell<Option<F>>);
impl<F> AsChunk for InnerChunk<F>
where
F: FnOnce(&Lua) -> Result<Table>,
{
fn environment(&self, lua: &Lua) -> Result<Option<Table>> {
if #caps_len > 0 {
if let Some(make_env) = self.0.take() {
return make_env(lua).map(Some);
}
}
Ok(None)
}
fn mode(&self) -> Option<ChunkMode> {
Some(ChunkMode::Text)
}
fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>> {
Ok(Cow::Borrowed((#source).as_bytes()))
}
}
let make_env = move |lua: &Lua| -> Result<Table> {
let globals = lua.globals();
let env = lua.create_table()?;
let meta = lua.create_table()?;
meta.raw_set("__index", &globals)?;
meta.raw_set("__newindex", &globals)?;
// Add captured variables
#(#caps)*
env.set_metatable(Some(meta))?;
Ok(env)
};
InnerChunk(Cell::new(Some(make_env)))
}}
}
}
@@ -3,10 +3,8 @@ use std::fmt::{self, Display, Formatter};
use std::vec::IntoIter;
use itertools::Itertools;
use once_cell::sync::Lazy;
use proc_macro::{Delimiter, Span, TokenStream, TokenTree};
use proc_macro2::Span as Span2;
use regex::Regex;
#[derive(Clone, Copy, Debug)]
pub(crate) struct Pos {
@@ -39,46 +37,16 @@ fn span_pos(span: &Span) -> (Pos, Pos) {
let start = span2.start();
let end = span2.end();
// In stable, line/column information is not provided
// and set to 0 (line is 1-indexed)
// Rust 1.88 stabilized Span APIs, so this branch must be unreachable
if start.line == 0 || end.line == 0 {
return fallback_span_pos(span);
proc_macro_error2::abort_call_site!(
"cannot retrieve span location information; mlua requires nightly Rust or stable >= 1.88"
);
}
(Pos::new(start.line, start.column), Pos::new(end.line, end.column))
}
fn parse_pos(span: &Span) -> Option<(usize, usize)> {
// Workaround to somehow retrieve location information in span in stable rust :(
static RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"bytes\(([0-9]+)\.\.([0-9]+)\)").unwrap());
match RE.captures(&format!("{span:?}")) {
Some(caps) => match (caps.get(1), caps.get(2)) {
(Some(start), Some(end)) => Some((
match start.as_str().parse() {
Ok(v) => v,
_ => return None,
},
match end.as_str().parse() {
Ok(v) => v,
_ => return None,
},
)),
_ => None,
},
None => None,
}
}
fn fallback_span_pos(span: &Span) -> (Pos, Pos) {
let (start, end) = match parse_pos(span) {
Some(v) => v,
None => proc_macro_error2::abort_call_site!("Cannot retrieve span information; please use nightly"),
};
(Pos::new(1, start), Pos::new(1, end))
}
/// Attribute of token.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum TokenAttr {
@@ -108,8 +76,9 @@ impl Eq for Token {}
impl Token {
fn new(tree: TokenTree) -> Self {
let (start, end) = span_pos(&tree.span());
let source = tree.span().source_text().unwrap_or_else(|| tree.to_string());
Self {
source: tree.to_string(),
source,
start,
end,
tree,
@@ -168,14 +137,17 @@ impl Tokens {
Tokens(
tt.into_iter()
.flat_map(Tokens::from)
.peekable()
.batching(|iter| {
// Find variable tokens
// Find variable tokens: `$` + `ident` => `$ident`
let t = iter.next()?;
if t.is("$") {
// `$` + `ident` => `$ident`
let t = iter.next().expect("$ must trail an identifier");
Some(t.attr(TokenAttr::Cap))
if let Some(next) = iter.next()
&& matches!(next.tree, TokenTree::Ident(_))
{
Some(next.attr(TokenAttr::Cap))
} else {
proc_macro_error2::abort!(t.tree.span(), "`$` must be followed by an identifier");
}
} else {
Some(t)
}
+13 -13
View File
@@ -1,6 +1,6 @@
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};
use syn::{DeriveInput, parse_macro_input};
pub fn from_lua(input: TokenStream) -> TokenStream {
let DeriveInput { ident, generics, .. } = parse_macro_input!(input as DeriveInput);
@@ -13,19 +13,19 @@ pub fn from_lua(input: TokenStream) -> TokenStream {
};
quote! {
impl #impl_generics ::mlua::FromLua for #ident #ty_generics #where_clause {
#[inline]
fn from_lua(value: ::mlua::Value, _: &::mlua::Lua) -> ::mlua::Result<Self> {
match value {
::mlua::Value::UserData(ud) => Ok(ud.borrow::<Self>()?.clone()),
_ => Err(::mlua::Error::FromLuaConversionError {
from: value.type_name(),
to: #ident_str.to_string(),
message: None,
}),
}
impl #impl_generics ::mlua::FromLua for #ident #ty_generics #where_clause {
#[inline]
fn from_lua(value: ::mlua::Value, _: &::mlua::Lua) -> ::mlua::Result<Self> {
match value {
::mlua::Value::UserData(ud) => Ok(ud.borrow::<Self>()?.clone()),
_ => Err(::mlua::Error::FromLuaConversionError {
from: value.type_name(),
to: #ident_str.to_string(),
message: None,
}),
}
}
}
}
}
.into()
}
+27 -131
View File
@@ -1,148 +1,30 @@
use proc_macro::TokenStream;
use proc_macro2::{Ident, Span};
use quote::quote;
use syn::meta::ParseNestedMeta;
use syn::{parse_macro_input, ItemFn, LitStr, Result};
mod module;
#[cfg(feature = "macros")]
use {
crate::chunk::Chunk, proc_macro::TokenTree, proc_macro2::TokenStream as TokenStream2,
proc_macro_error2::proc_macro_error,
};
use {crate::chunk::Chunk, proc_macro_error2::proc_macro_error};
#[derive(Default)]
struct ModuleAttributes {
name: Option<Ident>,
skip_memory_check: bool,
}
impl ModuleAttributes {
fn parse(&mut self, meta: ParseNestedMeta) -> Result<()> {
if meta.path.is_ident("name") {
match meta.value() {
Ok(value) => {
self.name = Some(value.parse::<LitStr>()?.parse()?);
}
Err(_) => {
return Err(meta.error("`name` attribute must have a value"));
}
}
} else if meta.path.is_ident("skip_memory_check") {
if meta.value().is_ok() {
return Err(meta.error("`skip_memory_check` attribute have no values"));
}
self.skip_memory_check = true;
} else {
return Err(meta.error("unsupported module attribute"));
#[cfg(feature = "macros")]
macro_rules! try_compile {
($expr:expr) => {
match $expr {
Ok(val) => val,
Err(err) => return err.to_compile_error().into(),
}
Ok(())
}
};
}
#[proc_macro_attribute]
pub fn lua_module(attr: TokenStream, item: TokenStream) -> TokenStream {
let mut args = ModuleAttributes::default();
if !attr.is_empty() {
let args_parser = syn::meta::parser(|meta| args.parse(meta));
parse_macro_input!(attr with args_parser);
}
let func = parse_macro_input!(item as ItemFn);
let func_name = &func.sig.ident;
let module_name = args.name.unwrap_or_else(|| func_name.clone());
let ext_entrypoint_name = Ident::new(&format!("luaopen_{module_name}"), Span::call_site());
let skip_memory_check = if args.skip_memory_check {
quote! { lua.skip_memory_check(true); }
} else {
quote! {}
};
let wrapped = quote! {
mlua::require_module_feature!();
#func
#[no_mangle]
unsafe extern "C-unwind" fn #ext_entrypoint_name(state: *mut mlua::lua_State) -> ::std::os::raw::c_int {
mlua::Lua::entrypoint1(state, move |lua| {
#skip_memory_check
#func_name(lua)
})
}
};
wrapped.into()
}
#[cfg(feature = "macros")]
fn to_ident(tt: &TokenTree) -> TokenStream2 {
let s: TokenStream = tt.clone().into();
s.into()
module::lua_module(attr, item)
}
#[cfg(feature = "macros")]
#[proc_macro]
#[proc_macro_error]
pub fn chunk(input: TokenStream) -> TokenStream {
let chunk = Chunk::new(input);
let source = chunk.source();
let caps_len = chunk.captures().len();
let caps = chunk.captures().iter().map(|cap| {
let cap_name = cap.as_rust().to_string();
let cap = to_ident(cap.as_rust());
quote! { env.raw_set(#cap_name, #cap)?; }
});
let wrapped_code = quote! {{
use mlua::{AsChunk, ChunkMode, Lua, Result, Table};
use ::std::borrow::Cow;
use ::std::cell::Cell;
use ::std::io::Result as IoResult;
struct InnerChunk<F: FnOnce(&Lua) -> Result<Table>>(Cell<Option<F>>);
impl<F> AsChunk for InnerChunk<F>
where
F: FnOnce(&Lua) -> Result<Table>,
{
fn environment(&self, lua: &Lua) -> Result<Option<Table>> {
if #caps_len > 0 {
if let Some(make_env) = self.0.take() {
return make_env(lua).map(Some);
}
}
Ok(None)
}
fn mode(&self) -> Option<ChunkMode> {
Some(ChunkMode::Text)
}
fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>> {
Ok(Cow::Borrowed((#source).as_bytes()))
}
}
let make_env = move |lua: &Lua| -> Result<Table> {
let globals = lua.globals();
let env = lua.create_table()?;
let meta = lua.create_table()?;
meta.raw_set("__index", &globals)?;
meta.raw_set("__newindex", &globals)?;
// Add captured variables
#(#caps)*
env.set_metatable(Some(meta))?;
Ok(env)
};
InnerChunk(Cell::new(Some(make_env)))
}};
wrapped_code.into()
Chunk::new(input).expand().into()
}
#[cfg(feature = "macros")]
@@ -151,9 +33,23 @@ pub fn from_lua(input: TokenStream) -> TokenStream {
from_lua::from_lua(input)
}
/// Derive macro for implementing `UserData` for a Rust type.
#[cfg(feature = "macros")]
#[proc_macro_derive(UserData, attributes(lua))]
pub fn userdata(item: TokenStream) -> TokenStream {
userdata::userdata_type(item)
}
/// Attribute macro for exposing impl block methods to Lua userdata.
#[cfg(feature = "macros")]
#[proc_macro_attribute]
pub fn userdata_impl(attr: TokenStream, item: TokenStream) -> TokenStream {
userdata::userdata_impl::userdata_impl(attr, item)
}
#[cfg(feature = "macros")]
mod chunk;
#[cfg(feature = "macros")]
mod from_lua;
#[cfg(feature = "macros")]
mod token;
mod userdata;
+68
View File
@@ -0,0 +1,68 @@
use proc_macro::TokenStream;
use proc_macro2::{Ident, Span};
use quote::quote;
use syn::meta::ParseNestedMeta;
use syn::{ItemFn, LitStr, Result, parse_macro_input};
#[derive(Default)]
struct ModuleAttributes {
name: Option<Ident>,
skip_memory_check: bool,
}
impl ModuleAttributes {
fn parse(&mut self, meta: ParseNestedMeta) -> Result<()> {
if meta.path.is_ident("name") {
match meta.value() {
Ok(value) => {
self.name = Some(value.parse::<LitStr>()?.parse()?);
}
Err(_) => {
return Err(meta.error("`name` attribute must have a value"));
}
}
} else if meta.path.is_ident("skip_memory_check") {
if meta.value().is_ok() {
return Err(meta.error("`skip_memory_check` attribute have no values"));
}
self.skip_memory_check = true;
} else {
return Err(meta.error("unsupported module attribute"));
}
Ok(())
}
}
pub fn lua_module(attr: TokenStream, item: TokenStream) -> TokenStream {
let mut args = ModuleAttributes::default();
if !attr.is_empty() {
let args_parser = syn::meta::parser(|meta| args.parse(meta));
parse_macro_input!(attr with args_parser);
}
let func = parse_macro_input!(item as ItemFn);
let func_name = &func.sig.ident;
let module_name = args.name.unwrap_or_else(|| func_name.clone());
let ext_entrypoint_name = Ident::new(&format!("luaopen_{module_name}"), Span::call_site());
let skip_memory_check = if args.skip_memory_check {
quote! { lua.skip_memory_check(true); }
} else {
quote! {}
};
let wrapped = quote! {
mlua::require_module_feature!();
#func
#[unsafe(no_mangle)]
unsafe extern "C-unwind" fn #ext_entrypoint_name(state: *mut mlua::lua_State) -> ::std::os::raw::c_int {
mlua::Lua::entrypoint1(state, move |lua| {
#skip_memory_check
#func_name(lua)
})
}
};
wrapped.into()
}
+93
View File
@@ -0,0 +1,93 @@
use proc_macro2::Span;
use syn::meta::ParseNestedMeta;
use syn::{Ident, LitStr, Result};
/// Parsed `#[lua(...)]` attribute.
///
/// Some flags are context-dependent:
/// - Struct fields: `get`, `set`, `name`, `skip`
/// - Impl methods: `getter`, `setter`, `field`, `meta`, `infallible`, `name`, `skip`
#[derive(Default)]
pub(crate) struct LuaAttr {
pub(crate) span: Option<Span>,
pub(crate) name: Option<String>,
pub(crate) infallible: bool,
pub(crate) skip: bool,
// Struct field context flags
pub(crate) get: bool,
pub(crate) set: bool,
// Impl method context flags
pub(crate) getter: bool,
pub(crate) setter: bool,
pub(crate) field: bool,
pub(crate) meta: bool,
}
impl LuaAttr {
pub(crate) fn parse_inner(&mut self, meta: ParseNestedMeta) -> Result<()> {
match &meta.path {
path if path.is_ident("skip") => {
if meta.value().is_ok() {
return Err(meta.error("`skip` does not take a value"));
}
self.skip = true;
}
path if path.is_ident("infallible") => {
if meta.value().is_ok() {
return Err(meta.error("`infallible` does not take a value"));
}
self.infallible = true;
}
path if path.is_ident("get") => self.get = true,
path if path.is_ident("set") => self.set = true,
path if path.is_ident("getter") => self.getter = true,
path if path.is_ident("setter") => self.setter = true,
path if path.is_ident("field") => self.field = true,
path if path.is_ident("meta") => self.meta = true,
path if path.is_ident("name") => {
let value = meta.value()?;
let lit: LitStr = value.parse()?;
self.name = Some(lit.value());
}
_ => {
return Err(meta.error(
"unsupported lua attribute, expected: ".to_string()
+ "`skip`, `infallible`, `get`, `set`, `getter`, `setter`, `field`, `meta`, `name`",
));
}
}
Ok(())
}
/// Returns the effective Lua name.
pub(crate) fn name(&self, ident: &Ident) -> String {
self.name.clone().unwrap_or_else(|| ident.to_string())
}
/// Returns the span to use for error reporting.
pub(crate) fn span(&self) -> Span {
self.span.unwrap_or_else(Span::call_site)
}
/// Returns the effective Lua metamethod name.
///
/// If `name` is set via attribute, use it. Otherwise, if the function name
/// starts with `__`, use that. Returns an error if neither is available.
pub(crate) fn effective_meta_name(&self, fn_ident: &Ident) -> Result<String> {
if let Some(ref name) = self.name {
return Ok(name.clone());
}
let fn_name = fn_ident.to_string();
if fn_name.starts_with("__") {
return Ok(fn_name);
}
Err(syn::Error::new(
fn_ident.span(),
format!(
"could not infer metamethod name from `{fn_name}`, either add `name = \"...\"` to `#[lua(meta, ...)]` or prefix the function with `__`"
),
))
}
}
+165
View File
@@ -0,0 +1,165 @@
mod attr;
pub(crate) mod userdata_impl;
use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::spanned::Spanned;
use syn::{Attribute, Data, DeriveInput, Error, Fields, FieldsNamed, Meta, parse_macro_input};
use self::attr::LuaAttr;
/// Wrap registration tokens with any `#[cfg]`/`#[cfg_attr]` attributes from the original item.
pub(crate) fn with_cfg(tokens: proc_macro2::TokenStream, attrs: &[Attribute]) -> proc_macro2::TokenStream {
let cfgs: Vec<_> = (attrs.iter())
.filter(|attr| attr.path().is_ident("cfg") || attr.path().is_ident("cfg_attr"))
.collect();
if cfgs.is_empty() {
return tokens;
}
quote! {
#(#cfgs)*
#tokens
}
}
/// Parse all `#[lua(...)]` attributes on a field, merging them into one `LuaAttr`.
fn parse_field_lua_attr(attrs: &[Attribute]) -> syn::Result<LuaAttr> {
let mut lua_attr = LuaAttr::default();
for attr in attrs {
if !attr.path().is_ident("lua") {
continue;
}
match &attr.meta {
Meta::List(_) => {
lua_attr.span = Some(attr.span());
attr.parse_nested_meta(|meta| lua_attr.parse_inner(meta))?;
validate_field_lua_attr(&lua_attr)?;
}
Meta::Path(_) => {}
Meta::NameValue(_) => {
return Err(syn::Error::new_spanned(
attr,
"`#[lua = \"...\"]` is not supported: use `#[lua(attr = \"...\")]`",
));
}
}
}
Ok(lua_attr)
}
fn validate_field_lua_attr(attr: &LuaAttr) -> syn::Result<()> {
for (set, name) in [
(attr.getter, "getter"),
(attr.setter, "setter"),
(attr.field, "field"),
(attr.meta, "meta"),
(attr.infallible, "infallible"),
] {
if set {
return Err(syn::Error::new(
attr.span(),
format!("`{name}` is not valid for struct fields"),
));
}
}
Ok(())
}
pub fn userdata_type(item: TokenStream) -> TokenStream {
let input = parse_macro_input!(item as DeriveInput);
let type_name = &input.ident;
let named_fields: Option<&FieldsNamed> = match &input.data {
Data::Struct(data) => match &data.fields {
Fields::Named(fields) => Some(fields),
Fields::Unnamed(_) | Fields::Unit => None,
},
Data::Enum(_) => None,
Data::Union(_) => {
return Error::new_spanned(&input, "`#[derive(UserData)]` cannot be applied to unions")
.to_compile_error()
.into();
}
};
// Check for generic parameters (not supported)
let has_generics = !input.generics.params.is_empty();
if has_generics {
return Error::new_spanned(
&input.generics,
"`#[derive(UserData)]` does not support generic type parameters. Wrap the generic type in a concrete newtype instead."
)
.to_compile_error()
.into();
}
let mut field_registrations = Vec::new();
if let Some(fields) = &named_fields {
for field in &fields.named {
let field_name = field.ident.as_ref().unwrap();
let lua_attr = try_compile!(parse_field_lua_attr(&field.attrs));
if lua_attr.skip {
continue;
}
let lua_name = lua_attr.name.unwrap_or_else(|| field_name.to_string());
// Assume get/set by default (unless explicitly specified)
let (has_get, has_set) = if lua_attr.get || lua_attr.set {
(lua_attr.get, lua_attr.set)
} else {
(true, true)
};
if has_get {
let tokens = quote! {
registry.add_field_method_get(#lua_name, |_lua, this| Ok(this.#field_name.clone()));
};
field_registrations.push(with_cfg(tokens, &field.attrs));
}
if has_set {
let tokens = quote! {
registry.add_field_method_set(#lua_name, |_lua, this, val| {
this.#field_name = val;
Ok(())
});
};
field_registrations.push(with_cfg(tokens, &field.attrs));
}
}
}
let registration_type_name = format_ident!("__MluaUserDataRegistration_{type_name}");
let register_fields_fn_name = format_ident!("__mlua_register_{type_name}_fields");
let output = quote! {
#[doc(hidden)]
#[allow(non_camel_case_types)]
struct #registration_type_name {
register: fn(&mut ::mlua::userdata::UserDataRegistry<#type_name>),
}
::mlua::__inventory::collect!(#registration_type_name);
#[allow(non_snake_case)]
fn #register_fields_fn_name(registry: &mut ::mlua::userdata::UserDataRegistry<#type_name>) {
use ::mlua::userdata::UserDataFields as _;
#(#field_registrations)*
}
::mlua::__inventory::submit! {
#registration_type_name { register: #register_fields_fn_name }
}
impl ::mlua::userdata::UserData for #type_name {
fn register(registry: &mut ::mlua::userdata::UserDataRegistry<Self>) {
for item in ::mlua::__inventory::iter::<#registration_type_name> {
(item.register)(registry);
}
}
}
};
output.into()
}
+730
View File
@@ -0,0 +1,730 @@
use std::sync::atomic::{AtomicUsize, Ordering};
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
use syn::spanned::Spanned;
use syn::{
Attribute, FnArg, Ident, ImplItem, ItemImpl, Meta, Signature, Type, parse_macro_input, parse_quote,
};
use super::attr::LuaAttr;
use super::with_cfg;
/// `&T` reference types that mlua provides as wrapper types via `FromLua`.
static BORROW_WRAPPERS: &[(&str, &str)] = &[
("str", "::mlua::string::BorrowedStr"),
("[u8]", "::mlua::string::BorrowedBytes"),
];
enum SelfKind {
Ref(RefKind),
Owned,
None,
}
enum RefKind {
Ref,
Mut,
}
struct ArgInfo {
ident: Ident,
userdata_ref: Option<RefKind>,
callback_type: Type,
}
struct MethodInfo {
self_kind: SelfKind,
has_lua: bool,
args: Vec<ArgInfo>,
}
/// Extract the inner type from a reference type.
fn ref_inner_type(ty: &Type) -> Type {
match ty {
Type::Reference(ref_ty) => (*ref_ty.elem).clone(),
_ => ty.clone(),
}
}
/// Check if the type is `&Lua` or `&mlua::Lua`.
fn is_lua_ref(ty: &Type) -> bool {
let Type::Reference(ref_ty) = ty else { return false };
match &*ref_ty.elem {
Type::Path(p) if p.path.segments.len() == 1 => p.path.segments[0].ident == "Lua",
Type::Path(p) if p.path.segments.len() == 2 => {
p.path.segments[0].ident == "mlua" && p.path.segments[1].ident == "Lua"
}
_ => false,
}
}
/// Classify a `&[mut] T` parameter, returning the callback wrapper type.
///
/// Known borrow types come from the mapping table `BORROW_WRAPPERS`.
/// Everything else gets `UserDataRef[Mut]<T>`.
fn classify_ref_type(ty: &Type) -> Option<Type> {
let Type::Reference(ref_ty) = ty else { return None };
// Check known borrow wrappers:
// - For `&T` check the path name
// - For `&[T]` unpack the slice and format the element as `[T]` for lookup
if ref_ty.mutability.is_none() {
let lookup_name: Option<String> = match &*ref_ty.elem {
Type::Path(path) => path.path.segments.last().map(|seg| seg.ident.to_string()),
Type::Slice(slice) => {
if let Type::Path(path) = &*slice.elem {
path.path.segments.last().map(|seg| format!("[{}]", seg.ident))
} else {
None
}
}
_ => None,
};
if let Some(ref name) = lookup_name {
for &(inner, wrapper) in BORROW_WRAPPERS {
if name == inner {
let wrapper = syn::parse_str(wrapper).expect("invalid wrapper type");
return Some(wrapper);
}
}
}
}
// Mutable references to slices are not supported.
if matches!(&*ref_ty.elem, Type::Slice(_)) && ref_ty.mutability.is_some() {
return None;
}
let inner = ref_inner_type(ty);
if ref_ty.mutability.is_none() {
Some(parse_quote! { ::mlua::userdata::UserDataRef<#inner> })
} else {
Some(parse_quote! { ::mlua::userdata::UserDataRefMut<#inner> })
}
}
/// Analyze method signature.
///
/// Determine `self` kind and collect the callback arguments.
/// Auto-detects `&Lua` as the first non-self parameter.
fn analyze_self_and_args(sig: &Signature) -> syn::Result<MethodInfo> {
let mut self_kind = SelfKind::None;
let mut has_lua = false;
let mut args = Vec::new();
let mut check_first_typed = true;
for param in &sig.inputs {
match param {
FnArg::Receiver(recv) if recv.reference.is_some() && recv.mutability.is_some() => {
self_kind = SelfKind::Ref(RefKind::Mut);
}
FnArg::Receiver(recv) if recv.reference.is_some() => {
self_kind = SelfKind::Ref(RefKind::Ref);
}
FnArg::Receiver(_) => {
self_kind = SelfKind::Owned;
}
FnArg::Typed(typed) => {
if check_first_typed && is_lua_ref(&typed.ty) {
has_lua = true;
check_first_typed = false;
continue;
}
check_first_typed = false;
if let syn::Pat::Ident(pat_ident) = &*typed.pat {
let arg_type = &*typed.ty;
let ref_kind = match arg_type {
Type::Reference(r) if r.mutability.is_some() => Some(RefKind::Mut),
Type::Reference(_) => Some(RefKind::Ref),
_ => None,
};
let callback_type = match &ref_kind {
Some(_) => match classify_ref_type(arg_type) {
Some(ty) => ty,
None => {
return Err(syn::Error::new_spanned(
arg_type,
"this reference type is not supported as a callback parameter",
));
}
},
None => arg_type.clone(),
};
args.push(ArgInfo {
ident: pat_ident.ident.clone(),
userdata_ref: ref_kind,
callback_type,
});
}
}
}
}
Ok(MethodInfo {
self_kind,
has_lua,
args,
})
}
fn strip_item_attrs(attrs: &[Attribute]) -> Vec<Attribute> {
(attrs.iter())
.filter(|attr| !attr.path().is_ident("lua"))
.cloned()
.collect()
}
fn parse_lua_attr(attrs: &[Attribute]) -> syn::Result<LuaAttr> {
let mut lua_attr = LuaAttr::default();
for attr in attrs {
if !attr.path().is_ident("lua") {
continue;
}
match &attr.meta {
Meta::List(_) => {
lua_attr.span = Some(attr.span());
attr.parse_nested_meta(|meta| lua_attr.parse_inner(meta))?;
validate_lua_attr(&lua_attr)?;
}
Meta::Path(_) => {}
Meta::NameValue(_) => {
return Err(syn::Error::new_spanned(
attr,
"`#[lua = \"...\"]` is not supported: use `#[lua(attr = \"...\")]`",
));
}
}
}
Ok(lua_attr)
}
fn validate_lua_attr(attr: &LuaAttr) -> syn::Result<()> {
for (set, name) in [(attr.get, "get"), (attr.set, "set")] {
if set {
return Err(syn::Error::new(
attr.span(),
format!("`{name}` is not valid for methods"),
));
}
}
Ok(())
}
pub fn userdata_impl(attr: TokenStream, item: TokenStream) -> TokenStream {
if !attr.is_empty() {
return syn::Error::new_spanned(
proc_macro2::TokenStream::from(attr),
"`#[userdata_impl]` does not accept arguments",
)
.to_compile_error()
.into();
}
let mut input = parse_macro_input!(item as ItemImpl);
let type_path = match &*input.self_ty {
Type::Path(type_path) => &type_path.path,
_ => {
return syn::Error::new_spanned(&input.self_ty, "`#[userdata_impl]` requires a simple path type")
.to_compile_error()
.into();
}
};
let type_name = (type_path.segments)
.last()
.map(|seg| seg.ident.clone())
.ok_or_else(|| syn::Error::new_spanned(&input.self_ty, "cannot determine type name"));
let type_name = try_compile!(type_name);
static COUNTER: AtomicUsize = AtomicUsize::new(0);
let unique_suffix = COUNTER.fetch_add(1, Ordering::Relaxed);
let register_fn_name = format_ident!("__mlua_register_{type_name}_{unique_suffix}");
let registration_type_name = format_ident!("__MluaUserDataRegistration_{type_name}");
let mut registration_calls = Vec::new();
for item in &input.items {
match item {
ImplItem::Const(const_item) => {
let lua_attr = try_compile!(parse_lua_attr(&const_item.attrs));
if lua_attr.skip {
continue;
}
if lua_attr.getter || lua_attr.setter {
return syn::Error::new(
lua_attr.span(),
"const items do not support `getter` or `setter`",
)
.to_compile_error()
.into();
}
let const_name = &const_item.ident;
let lua_name = lua_attr.name(const_name);
if lua_attr.meta {
let tokens = quote! {
registry.add_meta_field(#lua_name, #type_path::#const_name);
};
registration_calls.push(with_cfg(tokens, &const_item.attrs));
} else {
let tokens = quote! {
registry.add_field(#lua_name, #type_path::#const_name);
};
registration_calls.push(with_cfg(tokens, &const_item.attrs));
}
}
ImplItem::Fn(method) => {
let lua_attr = try_compile!(parse_lua_attr(&method.attrs));
if lua_attr.skip {
continue;
}
// Validate mutually exclusive role flags.
// `getter`, `setter`, `field` are exclusive.
// `meta` on its own means a metamethod.
// `meta` combined with `field` means a meta static field.
// `meta` with `getter` or `setter` is invalid.
let primary = [lua_attr.getter, lua_attr.setter, lua_attr.field];
let primary_count = primary.iter().filter(|&&x| x).count();
if primary_count > 1 {
return syn::Error::new(
lua_attr.span(),
"at most one of `getter`, `setter`, `field` can be specified",
)
.to_compile_error()
.into();
}
if lua_attr.meta && primary_count == 1 && !lua_attr.field {
return syn::Error::new(lua_attr.span(), "`meta` can only be combined with `field`")
.to_compile_error()
.into();
}
let fn_name = &method.sig.ident;
let info = try_compile!(analyze_self_and_args(&method.sig));
let is_async = method.sig.asyncness.is_some();
if lua_attr.getter {
if is_async {
return syn::Error::new_spanned(&method.sig, "async field getter is not supported")
.to_compile_error()
.into();
}
if !matches!(info.self_kind, SelfKind::Ref(RefKind::Ref)) {
return syn::Error::new_spanned(&method.sig, "field getter must take `&self`")
.to_compile_error()
.into();
}
if !info.args.is_empty() {
return syn::Error::new_spanned(
&method.sig,
"field getter must not take additional arguments",
)
.to_compile_error()
.into();
}
let tokens = gen_field_getter(type_path, fn_name, &lua_attr, &info);
registration_calls.push(with_cfg(tokens, &method.attrs));
continue;
}
if lua_attr.setter {
if is_async {
return syn::Error::new_spanned(&method.sig, "async field setter is not supported")
.to_compile_error()
.into();
}
if !matches!(info.self_kind, SelfKind::Ref(_)) {
return syn::Error::new_spanned(&method.sig, "field setter must take `&[mut] self`")
.to_compile_error()
.into();
}
if info.args.len() != 1 {
return syn::Error::new_spanned(
&method.sig,
"field setter must take exactly one value argument",
)
.to_compile_error()
.into();
}
let tokens = gen_field_setter(type_path, fn_name, &lua_attr, &info);
registration_calls.push(with_cfg(tokens, &method.attrs));
continue;
}
if lua_attr.field {
if is_async {
return syn::Error::new_spanned(&method.sig, "async field function is not supported")
.to_compile_error()
.into();
}
if !matches!(info.self_kind, SelfKind::None) {
return syn::Error::new_spanned(&method.sig, "field function must not take `self`")
.to_compile_error()
.into();
}
if !info.args.is_empty() {
return syn::Error::new_spanned(
&method.sig,
"field function must not take arguments",
)
.to_compile_error()
.into();
}
let lua_name = lua_attr.name(fn_name);
if lua_attr.meta {
let tokens = quote! {
registry.add_meta_field(#lua_name, #type_path::#fn_name());
};
registration_calls.push(with_cfg(tokens, &method.attrs));
} else {
let tokens = quote! {
registry.add_field(#lua_name, #type_path::#fn_name());
};
registration_calls.push(with_cfg(tokens, &method.attrs));
}
continue;
}
if lua_attr.meta {
if matches!(info.self_kind, SelfKind::Owned) {
return syn::Error::new_spanned(
&method.sig,
"meta methods cannot take `self`, use `&[mut] self` instead",
)
.to_compile_error()
.into();
}
if is_async {
let tokens = gen_async_meta(type_path, fn_name, &lua_attr, &info);
registration_calls.push(with_cfg(tokens, &method.attrs));
} else {
let tokens = gen_meta(type_path, fn_name, &lua_attr, &info);
registration_calls.push(with_cfg(tokens, &method.attrs));
}
continue;
}
if is_async {
let tokens = gen_async_regular_method(type_path, fn_name, &lua_attr, &info);
registration_calls.push(with_cfg(tokens, &method.attrs));
} else {
let tokens = gen_regular_method(type_path, fn_name, &lua_attr, &info);
registration_calls.push(with_cfg(tokens, &method.attrs));
}
}
_ => {}
}
}
for item in &mut input.items {
match item {
ImplItem::Const(c) => c.attrs = strip_item_attrs(&c.attrs),
ImplItem::Fn(m) => m.attrs = strip_item_attrs(&m.attrs),
_ => {}
}
}
input.attrs = strip_item_attrs(&input.attrs);
let output = quote! {
#[allow(non_snake_case)]
fn #register_fn_name(registry: &mut ::mlua::userdata::UserDataRegistry<#type_path>) {
use ::mlua::userdata::{UserDataFields as _, UserDataMethods as _};
#(#registration_calls)*
}
::mlua::__inventory::submit! {
#registration_type_name { register: #register_fn_name }
}
#input
};
output.into()
}
/// Generate the closure argument destructuring pattern.
fn gen_closure_destructure(info: &MethodInfo) -> TokenStream2 {
if info.args.is_empty() {
return quote! { () };
}
let idents: Vec<_> = (info.args)
.iter()
.map(|a| {
let ident = &a.ident;
if matches!(a.userdata_ref, Some(RefKind::Mut)) {
quote! { mut #ident }
} else {
quote! { #ident }
}
})
.collect();
let types: Vec<_> = info.args.iter().map(|a| &a.callback_type).collect();
quote! { (#(#idents),*): (#(#types),*) }
}
/// Generate call arguments for invoking the original method.
fn gen_call_args(info: &MethodInfo) -> TokenStream2 {
let mut call_args: Vec<TokenStream2> = Vec::new();
match info.self_kind {
SelfKind::None => {}
_ => call_args.push(quote! { this }),
}
if info.has_lua {
call_args.push(quote! { lua });
}
for arg in &info.args {
let ident = &arg.ident;
match arg.userdata_ref {
Some(RefKind::Ref) => call_args.push(quote! { &*#ident }),
Some(RefKind::Mut) => call_args.push(quote! { &mut *#ident }),
None => call_args.push(quote! { #ident }),
}
}
quote! { #(#call_args),* }
}
/// Generate call arguments for invoking the original async method.
fn gen_async_call_args(info: &MethodInfo) -> TokenStream2 {
let mut call_args: Vec<TokenStream2> = Vec::new();
match info.self_kind {
SelfKind::None => {}
SelfKind::Ref(RefKind::Ref) => call_args.push(quote! { &this }),
SelfKind::Ref(RefKind::Mut) => call_args.push(quote! { &mut this }),
SelfKind::Owned => call_args.push(quote! { this }),
}
if info.has_lua {
call_args.push(quote! { lua });
}
for arg in &info.args {
let ident = &arg.ident;
match arg.userdata_ref {
Some(RefKind::Ref) => call_args.push(quote! { &*#ident }),
Some(RefKind::Mut) => call_args.push(quote! { &mut *#ident }),
None => call_args.push(quote! { #ident }),
}
}
quote! { #(#call_args),* }
}
/// Generate the closure params for the registration callback.
fn gen_closure_params(info: &MethodInfo) -> TokenStream2 {
let destructure = gen_closure_destructure(info);
match info.self_kind {
SelfKind::None => quote! { |lua, #destructure| },
_ => quote! { |lua, this, #destructure| },
}
}
/// Generate the closure params for an async registration callback.
fn gen_async_closure_params(info: &MethodInfo) -> TokenStream2 {
let destructure = gen_closure_destructure(info);
match info.self_kind {
SelfKind::None => quote! { |lua, #destructure| },
SelfKind::Ref(RefKind::Mut) => quote! { |lua, mut this, #destructure| },
_ => quote! { |lua, this, #destructure| },
}
}
fn gen_field_getter(
type_path: &syn::Path,
fn_name: &Ident,
lua_attr: &LuaAttr,
info: &MethodInfo,
) -> TokenStream2 {
let lua_name = lua_attr.name(fn_name);
let call_args = gen_call_args(info);
if lua_attr.infallible {
return quote! {
registry.add_field_method_get(#lua_name, |lua, this| {
let _ = lua; // silence unused variable warning
Ok(#type_path::#fn_name(#call_args))
});
};
}
quote! {
registry.add_field_method_get(#lua_name, |lua, this| {
let _ = lua; // silence unused variable warning
#type_path::#fn_name(#call_args)
});
}
}
fn gen_field_setter(
type_path: &syn::Path,
fn_name: &Ident,
lua_attr: &LuaAttr,
info: &MethodInfo,
) -> TokenStream2 {
let lua_name = lua_attr.name(fn_name);
let call_args = gen_call_args(info);
if lua_attr.infallible {
let val_ident = info.args.first().map(|a| &a.ident);
return quote! {
registry.add_field_method_set(#lua_name, |lua, this, #val_ident| {
let _ = lua; // silence unused variable warning
Ok(#type_path::#fn_name(#call_args))
});
};
}
let val_ident = info.args.first().map(|a| &a.ident);
quote! {
registry.add_field_method_set(#lua_name, |lua, this, #val_ident| {
let _ = lua; // silence unused variable warning
#type_path::#fn_name(#call_args)
});
}
}
fn gen_meta(type_path: &syn::Path, fn_name: &Ident, lua_attr: &LuaAttr, info: &MethodInfo) -> TokenStream2 {
let meta_name = match lua_attr.effective_meta_name(fn_name) {
Ok(name) => name,
Err(err) => return err.to_compile_error(),
};
let closure_params = if matches!(info.self_kind, SelfKind::None) {
// Lua always passes `self` to the stack arg, just ignore it.
if info.args.is_empty() {
quote! { |lua, _this: ::mlua::AnyUserData| }
} else {
let idents: Vec<_> = info.args.iter().map(|a| &a.ident).collect();
let types: Vec<_> = info.args.iter().map(|a| &a.callback_type).collect();
quote! { |lua, (_this, #(#idents),*): (::mlua::AnyUserData, #(#types),*) | }
}
} else {
gen_closure_params(info)
};
let call_args = gen_call_args(info);
let fn_path = quote! { #type_path::#fn_name };
let body = if lua_attr.infallible {
quote! { Ok(#fn_path(#call_args)) }
} else {
quote! { #fn_path(#call_args) }
};
match info.self_kind {
SelfKind::None => quote! {
registry.add_meta_function(#meta_name, #closure_params { #body });
},
SelfKind::Ref(RefKind::Mut) => quote! {
registry.add_meta_method_mut(#meta_name, #closure_params { #body });
},
_ => quote! {
registry.add_meta_method(#meta_name, #closure_params { #body });
},
}
}
fn gen_regular_method(
type_path: &syn::Path,
fn_name: &Ident,
lua_attr: &LuaAttr,
info: &MethodInfo,
) -> TokenStream2 {
let fn_path = quote! { #type_path::#fn_name };
let closure_params = gen_closure_params(info);
let call_args = gen_call_args(info);
let lua_name = lua_attr.name(fn_name);
let body = if lua_attr.infallible {
quote! { Ok(#fn_path(#call_args)) }
} else {
quote! { #fn_path(#call_args) }
};
match info.self_kind {
SelfKind::Ref(RefKind::Ref) => quote! {
registry.add_method(#lua_name, #closure_params { #body });
},
SelfKind::Ref(RefKind::Mut) => quote! {
registry.add_method_mut(#lua_name, #closure_params { #body });
},
SelfKind::Owned => quote! {
registry.add_method_once(#lua_name, #closure_params { #body });
},
SelfKind::None => quote! {
registry.add_function(#lua_name, #closure_params { #body });
},
}
}
fn gen_async_regular_method(
type_path: &syn::Path,
fn_name: &Ident,
lua_attr: &LuaAttr,
info: &MethodInfo,
) -> TokenStream2 {
let fn_path = quote! { #type_path::#fn_name };
let closure_params = gen_async_closure_params(info);
let call_args = gen_async_call_args(info);
let lua_name = lua_attr.name(fn_name);
let body = if lua_attr.infallible {
quote! { async move { Ok(#fn_path(#call_args).await) } }
} else {
quote! { async move { #fn_path(#call_args).await } }
};
match info.self_kind {
SelfKind::Ref(RefKind::Ref) => quote! {
registry.add_async_method(#lua_name, #closure_params #body);
},
SelfKind::Ref(RefKind::Mut) => quote! {
registry.add_async_method_mut(#lua_name, #closure_params #body);
},
SelfKind::Owned => quote! {
registry.add_async_method_once(#lua_name, #closure_params #body);
},
SelfKind::None => quote! {
registry.add_async_function(#lua_name, #closure_params #body);
},
}
}
fn gen_async_meta(
type_path: &syn::Path,
fn_name: &Ident,
lua_attr: &LuaAttr,
info: &MethodInfo,
) -> TokenStream2 {
let meta_name = match lua_attr.effective_meta_name(fn_name) {
Ok(name) => name,
Err(err) => return err.to_compile_error(),
};
let closure_params = if matches!(info.self_kind, SelfKind::None) {
if info.args.is_empty() {
quote! { |lua, _this: ::mlua::AnyUserData| }
} else {
let idents: Vec<_> = info.args.iter().map(|a| &a.ident).collect();
let types: Vec<_> = info.args.iter().map(|a| &a.callback_type).collect();
quote! { |lua, (_this, #(#idents),*): (::mlua::AnyUserData, #(#types),*) | }
}
} else {
gen_async_closure_params(info)
};
let call_args = gen_async_call_args(info);
let fn_path = quote! { #type_path::#fn_name };
let body = if lua_attr.infallible {
quote! { async move { Ok(#fn_path(#call_args).await) } }
} else {
quote! { async move { #fn_path(#call_args).await } }
};
match info.self_kind {
SelfKind::None => quote! {
registry.add_async_meta_function(#meta_name, #closure_params #body);
},
SelfKind::Ref(RefKind::Mut) => quote! {
registry.add_async_meta_method_mut(#meta_name, #closure_params #body);
},
_ => quote! {
registry.add_async_meta_method(#meta_name, #closure_params #body);
},
}
}
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -12,7 +12,8 @@ use rustc_hash::FxHashMap;
use crate::error::Result;
use crate::state::RawLua;
use crate::stdlib::StdLib;
use crate::types::{AppData, ReentrantMutex, XRc};
use crate::thread::ThreadTriggers;
use crate::types::{AppData, ReentrantMutex, ThreadEventCallback, XRc};
use crate::userdata::RawUserDataRegistry;
use crate::util::{TypeKey, WrappedFailure, get_internal_metatable, push_internal_userdata};
@@ -81,10 +82,8 @@ pub(crate) struct ExtraData {
pub(super) warn_callback: Option<crate::types::WarnCallback>,
#[cfg(feature = "luau")]
pub(super) interrupt_callback: Option<crate::types::InterruptCallback>,
#[cfg(feature = "luau")]
pub(super) thread_creation_callback: Option<crate::types::ThreadCreationCallback>,
#[cfg(feature = "luau")]
pub(super) thread_collection_callback: Option<crate::types::ThreadCollectionCallback>,
pub(super) thread_triggers: ThreadTriggers,
pub(super) thread_event_callback: Option<ThreadEventCallback>,
#[cfg(feature = "luau")]
pub(crate) running_gc: bool,
@@ -186,10 +185,8 @@ impl ExtraData {
warn_callback: None,
#[cfg(feature = "luau")]
interrupt_callback: None,
#[cfg(feature = "luau")]
thread_creation_callback: None,
#[cfg(feature = "luau")]
thread_collection_callback: None,
thread_triggers: ThreadTriggers::default(),
thread_event_callback: None,
#[cfg(feature = "luau")]
sandboxed: false,
#[cfg(feature = "luau")]
+32 -6
View File
@@ -1,7 +1,7 @@
use std::any::TypeId;
use std::cell::{Cell, UnsafeCell};
use std::ffi::CStr;
use std::mem;
use std::mem::{self, ManuallyDrop};
use std::os::raw::{c_char, c_int, c_void};
use std::panic::resume_unwind;
use std::ptr::{self, NonNull};
@@ -15,11 +15,11 @@ use crate::state::util::callback_error_ext;
use crate::stdlib::StdLib;
use crate::string::LuaString;
use crate::table::Table;
use crate::thread::Thread;
use crate::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
View File
@@ -1,4 +1,4 @@
use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign};
use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Not};
/// Flags describing the set of lua standard libraries to load.
#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -96,17 +96,11 @@ pub(crate) type InterruptCallback = XRc<dyn Fn(&Lua) -> Result<VmState> + Send>;
#[cfg(all(not(feature = "send"), feature = "luau"))]
pub(crate) type InterruptCallback = XRc<dyn Fn(&Lua) -> Result<VmState>>;
#[cfg(all(feature = "send", feature = "luau"))]
pub(crate) type ThreadCreationCallback = XRc<dyn Fn(&Lua, crate::Thread) -> Result<()> + Send>;
#[cfg(feature = "send")]
pub(crate) type ThreadEventCallback = XRc<dyn Fn(&Lua, crate::thread::ThreadEvent) -> Result<()> + Send>;
#[cfg(all(not(feature = "send"), feature = "luau"))]
pub(crate) type ThreadCreationCallback = XRc<dyn Fn(&Lua, crate::Thread) -> Result<()>>;
#[cfg(all(feature = "send", feature = "luau"))]
pub(crate) type ThreadCollectionCallback = XRc<dyn Fn(crate::LightUserData) + Send>;
#[cfg(all(not(feature = "send"), feature = "luau"))]
pub(crate) type ThreadCollectionCallback = XRc<dyn Fn(crate::LightUserData)>;
#[cfg(not(feature = "send"))]
pub(crate) type ThreadEventCallback = XRc<dyn Fn(&Lua, crate::thread::ThreadEvent) -> Result<()>>;
#[cfg(feature = "send")]
#[cfg(any(feature = "lua55", feature = "lua54"))]
+1 -6
View File
@@ -128,18 +128,13 @@ impl Value {
#[inline]
pub fn to_pointer(&self) -> *const c_void {
match self {
Value::String(LuaString(vref)) => {
// In Lua < 5.4 (excluding Luau), string pointers are NULL
// Use alternative approach
let lua = vref.lua.lock();
unsafe { ffi::lua_tostring(lua.ref_thread(), vref.index) as *const c_void }
}
Value::LightUserData(ud) => ud.0,
Value::Table(Table(vref))
| Value::Function(Function(vref))
| Value::Thread(Thread(vref, ..))
| Value::UserData(AnyUserData(vref))
| Value::Other(vref) => vref.to_pointer(),
Value::String(s) => s.to_pointer(),
#[cfg(feature = "luau")]
Value::Buffer(crate::Buffer(vref)) => vref.to_pointer(),
_ => ptr::null(),
+15
View File
@@ -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(())
}
+23
View File
@@ -21,4 +21,27 @@ fn test_compilation() {
t.compile_fail("tests/compile/non_send.rs");
#[cfg(not(feature = "send"))]
t.pass("tests/compile/non_send.rs");
#[cfg(feature = "macros")]
{
t.compile_fail("tests/compile/chunk_dollar_non_ident.rs");
t.compile_fail("tests/compile/userdata_getter_and_meta.rs");
t.compile_fail("tests/compile/userdata_getter_and_setter.rs");
t.compile_fail("tests/compile/userdata_getter_mut_self.rs");
t.compile_fail("tests/compile/userdata_getter_extra_arg.rs");
t.compile_fail("tests/compile/userdata_setter_ref_self.rs");
t.compile_fail("tests/compile/userdata_mut_slice_arg.rs");
t.compile_fail("tests/compile/userdata_setter_no_value.rs");
t.compile_fail("tests/compile/userdata_static_with_self.rs");
t.compile_fail("tests/compile/userdata_meta_owned_self.rs");
t.compile_fail("tests/compile/userdata_const_getter.rs");
t.compile_fail("tests/compile/userdata_field_with_args.rs");
}
#[cfg(all(feature = "macros", feature = "async"))]
{
t.compile_fail("tests/compile/userdata_getter_async.rs");
t.compile_fail("tests/compile/userdata_setter_async.rs");
t.compile_fail("tests/compile/userdata_field_async.rs");
}
}
+12 -8
View File
@@ -1,14 +1,18 @@
error[E0596]: cannot borrow `s` as mutable, as it is a captured variable in a `Fn` closure
--> tests/compile/async_any_userdata_method.rs:9:49
|
8 | let mut s = &s;
| ----- `s` declared here, outside the closure
9 | reg.add_async_method("t", |_, this, ()| async {
| ------------- ^^^^^ cannot borrow as mutable
| |
| in this closure
10 | s = &*this;
| - mutable borrow occurs due to use of `s` in closure
8 | let mut s = &s;
| ----- `s` declared here, outside the closure
9 | reg.add_async_method("t", |_, this, ()| async {
| - ------------- ^^^^^ cannot borrow as mutable
| | |
| _____________| in this closure
| |
10 | | s = &*this;
| | - mutable borrow occurs due to use of `s` in closure
11 | | Ok(())
12 | | });
| |__________- expects `Fn` instead of `FnMut`
error[E0373]: async block may outlive the current function, but it borrows `this`, which is owned by the current function
--> tests/compile/async_any_userdata_method.rs:9:49
+4
View File
@@ -0,0 +1,4 @@
use mlua::chunk;
fn main() {
let _ = chunk! { $42 };
}
@@ -0,0 +1,5 @@
error: `$` must be followed by an identifier
--> tests/compile/chunk_dollar_non_ident.rs:3:22
|
3 | let _ = chunk! { $42 };
| ^
+37 -49
View File
@@ -1,32 +1,28 @@
error[E0277]: the type `UnsafeCell<mlua::state::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
|
+100 -33
View File
@@ -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<
| ^^^^
+10
View File
@@ -0,0 +1,10 @@
#[derive(Default, mlua::UserData)]
struct Foo;
#[mlua::userdata_impl]
impl Foo {
#[lua(getter)]
const X: u32 = 42;
}
fn main() {}
@@ -0,0 +1,5 @@
error: const items do not support `getter` or `setter`
--> tests/compile/userdata_const_getter.rs:6:5
|
6 | #[lua(getter)]
| ^^^^^^^^^^^^^^
+14
View File
@@ -0,0 +1,14 @@
use mlua::Result;
#[derive(Clone, Debug, mlua::UserData)]
struct Foo;
#[mlua::userdata_impl]
impl Foo {
#[lua(field)]
async fn description() -> Result<String> {
Ok("foo".into())
}
}
fn main() {}
+13
View File
@@ -0,0 +1,13 @@
error: async field function is not supported
--> tests/compile/userdata_field_async.rs:9:5
|
9 | async fn description() -> Result<String> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
warning: unused import: `mlua::Result`
--> tests/compile/userdata_field_async.rs:1:5
|
1 | use mlua::Result;
| ^^^^^^^^^^^^
|
= note: `#[warn(unused_imports)]` (part of `#[warn(unused)]`) on by default
+14
View File
@@ -0,0 +1,14 @@
#[derive(Default, mlua::UserData)]
struct Foo {
x: u32,
}
#[mlua::userdata_impl]
impl Foo {
#[lua(field)]
fn as_name(name: &str) -> String {
name.to_string()
}
}
fn main() {}
@@ -0,0 +1,5 @@
error: field function must not take arguments
--> tests/compile/userdata_field_with_args.rs:9:5
|
9 | fn as_name(name: &str) -> String {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+12
View File
@@ -0,0 +1,12 @@
#[derive(Default, mlua::UserData)]
struct Foo;
#[mlua::userdata_impl]
impl Foo {
#[lua(getter, meta)]
fn bar(&self) -> mlua::Result<u32> {
Ok(42)
}
}
fn main() {}
@@ -0,0 +1,5 @@
error: `meta` can only be combined with `field`
--> tests/compile/userdata_getter_and_meta.rs:6:5
|
6 | #[lua(getter, meta)]
| ^^^^^^^^^^^^^^^^^^^^
@@ -0,0 +1,14 @@
#[derive(Default, mlua::UserData)]
struct Foo {
x: u32,
}
#[mlua::userdata_impl]
impl Foo {
#[lua(getter, setter)]
fn x(&self) -> mlua::Result<u32> {
Ok(self.x)
}
}
fn main() {}
@@ -0,0 +1,5 @@
error: at most one of `getter`, `setter`, `field` can be specified
--> tests/compile/userdata_getter_and_setter.rs:8:5
|
8 | #[lua(getter, setter)]
| ^^^^^^^^^^^^^^^^^^^^^^
+14
View File
@@ -0,0 +1,14 @@
use mlua::Result;
#[derive(Clone, Debug, mlua::UserData)]
struct Foo(u64);
#[mlua::userdata_impl]
impl Foo {
#[lua(getter)]
async fn value(&self) -> Result<u64> {
Ok(self.0)
}
}
fn main() {}
@@ -0,0 +1,13 @@
error: async field getter is not supported
--> tests/compile/userdata_getter_async.rs:9:5
|
9 | async fn value(&self) -> Result<u64> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
warning: unused import: `mlua::Result`
--> tests/compile/userdata_getter_async.rs:1:5
|
1 | use mlua::Result;
| ^^^^^^^^^^^^
|
= note: `#[warn(unused_imports)]` (part of `#[warn(unused)]`) on by default
@@ -0,0 +1,14 @@
#[derive(Default, mlua::UserData)]
struct Foo {
x: u32,
}
#[mlua::userdata_impl]
impl Foo {
#[lua(getter)]
fn x(&self, extra: u32) -> mlua::Result<u32> {
Ok(self.x + extra)
}
}
fn main() {}
@@ -0,0 +1,5 @@
error: field getter must not take additional arguments
--> tests/compile/userdata_getter_extra_arg.rs:9:5
|
9 | fn x(&self, extra: u32) -> mlua::Result<u32> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+14
View File
@@ -0,0 +1,14 @@
#[derive(Default, mlua::UserData)]
struct Foo {
x: u32,
}
#[mlua::userdata_impl]
impl Foo {
#[lua(getter)]
fn x(&mut self) -> mlua::Result<u32> {
Ok(self.x)
}
}
fn main() {}
@@ -0,0 +1,5 @@
error: field getter must take `&self`
--> tests/compile/userdata_getter_mut_self.rs:9:5
|
9 | fn x(&mut self) -> mlua::Result<u32> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+12
View File
@@ -0,0 +1,12 @@
#[derive(Default, mlua::UserData)]
struct Foo;
#[mlua::userdata_impl]
impl Foo {
#[lua(meta)]
fn __gc(self) -> mlua::Result<()> {
Ok(())
}
}
fn main() {}
@@ -0,0 +1,5 @@
error: meta methods cannot take `self`, use `&[mut] self` instead
--> tests/compile/userdata_meta_owned_self.rs:7:5
|
7 | fn __gc(self) -> mlua::Result<()> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+11
View File
@@ -0,0 +1,11 @@
#[derive(Default, mlua::UserData)]
struct Foo(Vec<u8>);
#[mlua::userdata_impl]
impl Foo {
fn first(&self, data: &mut [u8]) -> mlua::Result<u8> {
Ok(data[0])
}
}
fn main() {}
@@ -0,0 +1,5 @@
error: this reference type is not supported as a callback parameter
--> tests/compile/userdata_mut_slice_arg.rs:6:27
|
6 | fn first(&self, data: &mut [u8]) -> mlua::Result<u8> {
| ^^^^^^^^^
+15
View File
@@ -0,0 +1,15 @@
use mlua::Result;
#[derive(Clone, Debug, mlua::UserData)]
struct Foo(u64);
#[mlua::userdata_impl]
impl Foo {
#[lua(setter)]
async fn set_value(&mut self, val: u64) -> Result<()> {
self.0 = val;
Ok(())
}
}
fn main() {}
@@ -0,0 +1,13 @@
error: async field setter is not supported
--> tests/compile/userdata_setter_async.rs:9:5
|
9 | async fn set_value(&mut self, val: u64) -> Result<()> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
warning: unused import: `mlua::Result`
--> tests/compile/userdata_setter_async.rs:1:5
|
1 | use mlua::Result;
| ^^^^^^^^^^^^
|
= note: `#[warn(unused_imports)]` (part of `#[warn(unused)]`) on by default
+14
View File
@@ -0,0 +1,14 @@
#[derive(Default, mlua::UserData)]
struct Foo {
x: u32,
}
#[mlua::userdata_impl]
impl Foo {
#[lua(setter)]
fn set_x(&mut self) -> mlua::Result<()> {
Ok(())
}
}
fn main() {}
@@ -0,0 +1,5 @@
error: field setter must take exactly one value argument
--> tests/compile/userdata_setter_no_value.rs:9:5
|
9 | fn set_x(&mut self) -> mlua::Result<()> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+15
View File
@@ -0,0 +1,15 @@
#[derive(Default, mlua::UserData)]
struct Foo {
x: u32,
}
#[mlua::userdata_impl]
impl Foo {
#[lua(setter)]
fn set_x(self, val: u32) -> mlua::Result<()> {
let _ = val;
Ok(())
}
}
fn main() {}
@@ -0,0 +1,5 @@
error: field setter must take `&[mut] self`
--> tests/compile/userdata_setter_ref_self.rs:9:5
|
9 | fn set_x(self, val: u32) -> mlua::Result<()> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -0,0 +1,14 @@
#[derive(Default, mlua::UserData)]
struct Foo {
x: u32,
}
#[mlua::userdata_impl]
impl Foo {
#[lua(field)]
fn get_x(&self) -> mlua::Result<u32> {
Ok(self.x)
}
}
fn main() {}
@@ -0,0 +1,5 @@
error: field function must not take `self`
--> tests/compile/userdata_static_with_self.rs:9:5
|
9 | fn get_x(&self) -> mlua::Result<u32> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+2 -85
View File
@@ -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)?;
+146
View File
@@ -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
View File
@@ -5,27 +5,37 @@ use mlua::{Lua, LuaString, Result};
#[test]
fn test_string_compare() {
fn with_str<F: FnOnce(LuaString)>(s: &str, f: F) {
f(Lua::new().create_string(s).unwrap());
let lua = Lua::new();
fn with_str<F: FnOnce(LuaString)>(lua: &Lua, s: &str, f: F) {
f(lua.create_string(s).unwrap());
}
// Tests that all comparisons we want to have are usable
with_str("teststring", |t| assert_eq!(t, "teststring")); // &str
with_str("teststring", |t| assert_eq!(t, b"teststring")); // &[u8]
with_str("teststring", |t| assert_eq!(t, b"teststring".to_vec())); // Vec<u8>
with_str("teststring", |t| assert_eq!(t, "teststring".to_string())); // String
with_str("teststring", |t| assert_eq!(t, t)); // mlua::String
with_str("teststring", |t| assert_eq!(t, Cow::from(b"teststring".as_ref()))); // Cow (borrowed)
with_str("bla", |t| assert_eq!(t, Cow::from(b"bla".to_vec()))); // Cow (owned)
with_str(&lua, "teststring", |t| assert_eq!(t, "teststring")); // &str
with_str(&lua, "teststring", |t| assert_eq!(t, b"teststring")); // &[u8]
with_str(&lua, "teststring", |t| assert_eq!(t, b"teststring".to_vec())); // Vec<u8>
with_str(&lua, "teststring", |t| assert_eq!(t, "teststring".to_string())); // String
with_str(&lua, "teststring", |t| assert_eq!(t, t)); // mlua::String
with_str(&lua, "teststring", |t| {
assert_eq!(t, Cow::from(b"teststring".as_ref())) // Cow (borrowed)
});
with_str(&lua, "bla", |t| assert_eq!(t, Cow::from(b"bla".to_vec()))); // Cow (owned)
// Test ordering
with_str("a", |a| {
with_str(&lua, "a", |a| {
assert!(!(a < a));
assert!(!(a > a));
});
with_str("a", |a| assert!(a < "b"));
with_str("a", |a| assert!(a < b"b"));
with_str("a", |a| with_str("b", |b| assert!(a < b)));
with_str(&lua, "a", |a| assert!(a < "b"));
with_str(&lua, "a", |a| assert!(a < b"b"));
with_str(&lua, "a", |a| with_str(&lua, "b", |b| assert!(a < b)));
// Long strings (not interned by Lua)
let long_str = "abc".repeat(100);
with_str(&lua, &long_str, |s1| {
with_str(&lua, &long_str, |s2| assert_eq!(s1, s2))
});
}
#[test]
+215 -1
View File
@@ -1,6 +1,8 @@
use std::panic::catch_unwind;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use mlua::{Error, Function, IntoLua, Lua, Result, Thread, 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(())
}
+495
View File
@@ -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
View File
@@ -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());