mirror of
https://github.com/mlua-rs/mlua
synced 2026-06-08 16:05:43 +00:00
Compare commits
85 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 247208edb1 | |||
| e08768cc5e | |||
| 5b38af9746 | |||
| 54907f80c5 | |||
| ae512f2b49 | |||
| 53c159b6cb | |||
| 2beca6ebe1 | |||
| 09da7a41e5 | |||
| bad20374ad | |||
| 40b507c3ec | |||
| 537cc995f6 | |||
| 5d27cb91b2 | |||
| c70a636ca9 | |||
| 13ff0ca798 | |||
| 44f49e35d6 | |||
| e1ee4058a6 | |||
| f06d0020ea | |||
| d399559d30 | |||
| 30735d5ff1 | |||
| 75c23e5853 | |||
| 347856b806 | |||
| 774a63bece | |||
| c481c87eac | |||
| 85b280a9d6 | |||
| db7b782d3c | |||
| 5f38445558 | |||
| df0a44d405 | |||
| f0806a6d62 | |||
| 3516f4c6ca | |||
| ca73583714 | |||
| 36560435f7 | |||
| 763c2b2564 | |||
| bafdb6138c | |||
| c9d6a610e1 | |||
| bd63f63bc9 | |||
| c035c23a15 | |||
| cb153a52b2 | |||
| b1c69d3005 | |||
| 841bd332e4 | |||
| 815d1bd7c9 | |||
| 78331ceebe | |||
| f945a35cbd | |||
| 459edb6816 | |||
| 00328b0b64 | |||
| 928d94d255 | |||
| 583c35a172 | |||
| 1791c599f4 | |||
| 1e48817a64 | |||
| 8d219503dd | |||
| 95367855c1 | |||
| 49389c4aa4 | |||
| 13dc2b5352 | |||
| 7afbf74128 | |||
| 06c3bd9d69 | |||
| 1ddaea60ce | |||
| a653d08768 | |||
| 2b6b0144a1 | |||
| 4cfe0be945 | |||
| a9a4814c3c | |||
| b1f73ec29d | |||
| 61a2141151 | |||
| dea38f27a5 | |||
| 04aaa18dc8 | |||
| d8455c038a | |||
| ca22ea3be7 | |||
| cf05593d66 | |||
| d011a1f851 | |||
| 1ec4661bf9 | |||
| d3b2999d2f | |||
| 72f6536efb | |||
| 646827a6bb | |||
| 1882931cd9 | |||
| c90cac5189 | |||
| c0d839d8d2 | |||
| 80471c6dad | |||
| 4b9d1cf271 | |||
| 55c07f3b28 | |||
| ef4eabd327 | |||
| a3302afdc1 | |||
| dfb4e9a668 | |||
| 92db0f6d3a | |||
| a3697ab1db | |||
| 052740db15 | |||
| faf547c154 | |||
| 0de7cd1c7d |
@@ -1,3 +1,44 @@
|
||||
## v0.11.4 (Sep 29, 2025)
|
||||
|
||||
- Make `Value::to_serializable` public
|
||||
- Add new serde option `detect_mixed_tables` (to encode mixed array+map tables)
|
||||
- Add `ObjectLike::get_path` helper (for tables and userdata)
|
||||
|
||||
## v0.11.3 (Aug 30, 2025)
|
||||
|
||||
- Add `Lua::yield_with` to use as `coroutine.yield` functional replacement in async functions for any Lua
|
||||
- Do not try to yield at non-yielable points in Luau interrupt (#632)
|
||||
- Add `Buffer::cursor` method (Luau)
|
||||
- Add `Lua::create_buffer_with_capacity` method (Luau)
|
||||
- Make Lua reference values cheap to clone (only increments ref count)
|
||||
- Fix panic on large (>67M entries) table creation
|
||||
|
||||
## v0.11.2 (Aug 10, 2025)
|
||||
|
||||
- Faster stack push for `Variadic<T>`
|
||||
- Fix handling Windows paths with drive letter in Luau require (#623)
|
||||
- Make Luau registered aliases ascii case-insensitive (#620)
|
||||
- Fix deserializing negative zeros `-0.0` (#618)
|
||||
|
||||
## v0.11.1 (Jul 15, 2025)
|
||||
|
||||
- Fixed bug exhausting Lua auxiliary stack and leaving it without reserve (#615)
|
||||
- `Lua::push_c_function` now correctly handles OOM for Lua 5.1 and Luau
|
||||
|
||||
## v0.11.0 (Jul 14, 2025)
|
||||
|
||||
Changes since v0.11.0-beta.3
|
||||
|
||||
- Allow linking external Lua libraries in a build script (e.g. pluto) using `external` mlua-sys feature flag
|
||||
- `Lua::inspect_stack` takes a callback with `&Debug` argument, instead of returning `Debug` directly
|
||||
- Added `Debug::function` method to get function running at a given level
|
||||
- `Debug::curr_line` is deprecated in favour of `Debug::current_line` that returns `Option<usize>`
|
||||
- Added `Lua::set_globals` method to replace global environment
|
||||
- `Table::set_metatable` now returns `Result<()>` (this operation can fail in sandboxed Luau mode)
|
||||
- `impl ToString` replaced with `Into<StdString>` in `UserData` registration
|
||||
- `Value::as_str` and `Value::as_string_lossy` methods are deprecated (as they are non-idiomatic)
|
||||
- Bugfixes and improvements
|
||||
|
||||
## v0.11.0-beta.3 (Jun 23, 2025)
|
||||
|
||||
- Luau in sandboxed mode has reduced options in `collectgarbage` function (to follow the official doc)
|
||||
|
||||
+5
-5
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "mlua"
|
||||
version = "0.11.0-beta.3" # remember to update mlua_derive
|
||||
version = "0.11.4" # remember to update mlua_derive
|
||||
authors = ["Aleksandr Orlenko <zxteam@pm.me>", "kyren <catherine@kyju.org>"]
|
||||
rust-version = "1.79.0"
|
||||
edition = "2021"
|
||||
@@ -49,7 +49,7 @@ userdata-wrappers = ["parking_lot/send_guard"]
|
||||
serialize = ["serde"]
|
||||
|
||||
[dependencies]
|
||||
mlua_derive = { version = "=0.11.0-beta.2", optional = true, path = "mlua_derive" }
|
||||
mlua_derive = { version = "=0.11.0", optional = true, path = "mlua_derive" }
|
||||
bstr = { version = "1.0", features = ["std"], default-features = false }
|
||||
either = "1.0"
|
||||
num-traits = { version = "0.2.14" }
|
||||
@@ -62,7 +62,7 @@ parking_lot = { version = "0.12", features = ["arc_lock"] }
|
||||
anyhow = { version = "1.0", optional = true }
|
||||
rustversion = "1.0"
|
||||
|
||||
ffi = { package = "mlua-sys", version = "0.8.0", path = "mlua-sys" }
|
||||
ffi = { package = "mlua-sys", version = "0.8.3", path = "mlua-sys" }
|
||||
|
||||
[dev-dependencies]
|
||||
trybuild = "1.0"
|
||||
@@ -78,8 +78,8 @@ tempfile = "3"
|
||||
static_assertions = "1.0"
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
|
||||
criterion = { version = "0.6", features = ["async_tokio"] }
|
||||
rustyline = "16.0"
|
||||
criterion = { version = "0.7", features = ["async_tokio"] }
|
||||
rustyline = "17.0"
|
||||
tokio = { version = "1.0", features = ["full"] }
|
||||
|
||||
[lints.rust]
|
||||
|
||||
@@ -17,20 +17,14 @@
|
||||
[Benchmarks]: https://github.com/khvzak/script-bench-rs
|
||||
[FAQ]: FAQ.md
|
||||
|
||||
# The main branch is the development version of `mlua`. Please see the [v0.10](https://github.com/mlua-rs/mlua/tree/v0.10) branch for the stable versions of `mlua`.
|
||||
|
||||
> **Note**
|
||||
>
|
||||
> See v0.10 [release notes](https://github.com/mlua-rs/mlua/blob/main/docs/release_notes/v0.10.md).
|
||||
|
||||
`mlua` is a set of bindings to the [Lua](https://www.lua.org) programming language for Rust with a goal to provide a
|
||||
`mlua` is a set of bindings to the [Lua](https://www.lua.org) programming language for Rust with a goal of providing a
|
||||
_safe_ (as much as possible), high level, easy to use, practical and flexible API.
|
||||
|
||||
Started as an `rlua` fork, `mlua` supports Lua 5.4, 5.3, 5.2, 5.1 (including LuaJIT) and [Luau] and allows writing native Lua modules in Rust as well as using Lua in a standalone mode.
|
||||
|
||||
`mlua` is tested on Windows/macOS/Linux including module mode in [GitHub Actions] on `x86_64` platforms and cross-compilation to `aarch64` (other targets are also supported).
|
||||
|
||||
WebAssembly (WASM) is supported through `wasm32-unknown-emscripten` target for all Lua/Luau versions excluding JIT.
|
||||
WebAssembly (WASM) is supported through the `wasm32-unknown-emscripten` target for all Lua/Luau versions excluding JIT.
|
||||
|
||||
[GitHub Actions]: https://github.com/mlua-rs/mlua/actions
|
||||
[Luau]: https://luau.org
|
||||
@@ -39,7 +33,7 @@ WebAssembly (WASM) is supported through `wasm32-unknown-emscripten` target for a
|
||||
|
||||
### Feature flags
|
||||
|
||||
`mlua` uses feature flags to reduce the amount of dependencies and compiled code, and allow to choose only required set of features.
|
||||
`mlua` uses feature flags to reduce the number of dependencies and compiled code, and allow choosing only the required set of features.
|
||||
Below is a list of the available feature flags. By default `mlua` does not enable any features.
|
||||
|
||||
* `lua54`: enable Lua [5.4] support
|
||||
@@ -100,11 +94,11 @@ cargo run --example async_http_server --features=lua54,async,macros,send
|
||||
curl -v http://localhost:3000
|
||||
```
|
||||
|
||||
### Serialization (serde) support
|
||||
### Serde support
|
||||
|
||||
With the `serde` feature flag enabled, `mlua` allows you to serialize/deserialize any type that implements [`serde::Serialize`] and [`serde::Deserialize`] into/from [`mlua::Value`]. In addition, `mlua` provides the [`serde::Serialize`] trait implementation for it (including `UserData` support).
|
||||
With the `serde` feature flag enabled, `mlua` allows you to serialize/deserialize any type that implements [`serde::Serialize`] and [`serde::Deserialize`] into/from [`mlua::Value`]. In addition, `mlua` provides the [`serde::Serialize`] trait implementation for `mlua::Value` (including `UserData` support).
|
||||
|
||||
[Example](examples/serialize.rs)
|
||||
[Example](examples/serde.rs)
|
||||
|
||||
[`serde::Serialize`]: https://docs.serde.rs/serde/ser/trait.Serialize.html
|
||||
[`serde::Deserialize`]: https://docs.serde.rs/serde/de/trait.Deserialize.html
|
||||
@@ -135,7 +129,7 @@ Add to `Cargo.toml`:
|
||||
|
||||
``` toml
|
||||
[dependencies]
|
||||
mlua = { version = "0.10", features = ["lua54", "vendored"] }
|
||||
mlua = { version = "0.11", features = ["lua54", "vendored"] }
|
||||
```
|
||||
|
||||
`main.rs`
|
||||
@@ -170,7 +164,7 @@ Add to `Cargo.toml`:
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
mlua = { version = "0.10", features = ["lua54", "module"] }
|
||||
mlua = { version = "0.11", features = ["lua54", "module"] }
|
||||
```
|
||||
|
||||
`lib.rs`:
|
||||
@@ -276,7 +270,7 @@ remain usable after a user generated panic, and such panics should not break int
|
||||
leak Lua stack space. This is mostly important to safely use `mlua` types in Drop impls, as you should not be
|
||||
using panics for general error handling.
|
||||
|
||||
Below is a list of `mlua` behaviors that should be considered a bug.
|
||||
Below is a list of `mlua` behaviors that should be considered bugs.
|
||||
If you encounter them, a bug report would be very welcome:
|
||||
|
||||
+ If you can cause UB with `mlua` without typing the word "unsafe", this is a bug.
|
||||
|
||||
@@ -128,6 +128,22 @@ fn table_traversal_sequence(c: &mut Criterion) {
|
||||
});
|
||||
}
|
||||
|
||||
fn table_ref_clone(c: &mut Criterion) {
|
||||
let lua = Lua::new();
|
||||
|
||||
let t = lua.create_table().unwrap();
|
||||
|
||||
c.bench_function("table [ref clone]", |b| {
|
||||
b.iter_batched(
|
||||
|| collect_gc_twice(&lua),
|
||||
|_| {
|
||||
let _t2 = t.clone();
|
||||
},
|
||||
BatchSize::SmallInput,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
fn function_create(c: &mut Criterion) {
|
||||
let lua = Lua::new();
|
||||
|
||||
@@ -350,6 +366,42 @@ fn userdata_call_method(c: &mut Criterion) {
|
||||
});
|
||||
}
|
||||
|
||||
// A userdata method call that goes through an implicit `__index` function
|
||||
fn userdata_call_method_complex(c: &mut Criterion) {
|
||||
struct UserData(u64);
|
||||
impl LuaUserData for UserData {
|
||||
fn register(registry: &mut LuaUserDataRegistry<Self>) {
|
||||
registry.add_field_method_get("val", |_, this| Ok(this.0));
|
||||
registry.add_method_mut("inc_by", |_, this, by: u64| {
|
||||
this.0 += by;
|
||||
Ok(this.0)
|
||||
});
|
||||
|
||||
#[cfg(feature = "luau")]
|
||||
registry.enable_namecall();
|
||||
}
|
||||
}
|
||||
|
||||
let lua = Lua::new();
|
||||
let ud = lua.create_userdata(UserData(0)).unwrap();
|
||||
let inc_by = lua
|
||||
.load("function(ud, s) return ud:inc_by(s) end")
|
||||
.eval::<LuaFunction>()
|
||||
.unwrap();
|
||||
|
||||
c.bench_function("userdata [call method complex]", |b| {
|
||||
b.iter_batched(
|
||||
|| {
|
||||
collect_gc_twice(&lua);
|
||||
},
|
||||
|_| {
|
||||
inc_by.call::<()>((&ud, 1)).unwrap();
|
||||
},
|
||||
BatchSize::SmallInput,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
fn userdata_async_call_method(c: &mut Criterion) {
|
||||
struct UserData(i64);
|
||||
impl LuaUserData for UserData {
|
||||
@@ -399,6 +451,7 @@ criterion_group! {
|
||||
table_traversal_pairs,
|
||||
table_traversal_for_each,
|
||||
table_traversal_sequence,
|
||||
table_ref_clone,
|
||||
|
||||
function_create,
|
||||
function_call_sum,
|
||||
@@ -413,6 +466,7 @@ criterion_group! {
|
||||
userdata_create,
|
||||
userdata_call_index,
|
||||
userdata_call_method,
|
||||
userdata_call_method_complex,
|
||||
userdata_async_call_method,
|
||||
}
|
||||
|
||||
|
||||
+10
-8
@@ -20,14 +20,16 @@ fn main() {
|
||||
match lua.load(&line).eval::<MultiValue>() {
|
||||
Ok(values) => {
|
||||
editor.add_history_entry(line).unwrap();
|
||||
println!(
|
||||
"{}",
|
||||
values
|
||||
.iter()
|
||||
.map(|value| format!("{:#?}", value))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\t")
|
||||
);
|
||||
if values.len() > 0 {
|
||||
println!(
|
||||
"{}",
|
||||
values
|
||||
.iter()
|
||||
.map(|value| format!("{:#?}", value))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\t")
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
Err(Error::SyntaxError {
|
||||
|
||||
+3
-2
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "mlua-sys"
|
||||
version = "0.8.1"
|
||||
version = "0.8.3"
|
||||
authors = ["Aleksandr Orlenko <zxteam@pm.me>"]
|
||||
rust-version = "1.71"
|
||||
edition = "2021"
|
||||
@@ -30,6 +30,7 @@ luau = ["luau0-src"]
|
||||
luau-codegen = ["luau"]
|
||||
luau-vector4 = ["luau"]
|
||||
vendored = ["lua-src", "luajit-src"]
|
||||
external = []
|
||||
module = []
|
||||
|
||||
[dependencies]
|
||||
@@ -40,7 +41,7 @@ cfg-if = "1.0"
|
||||
pkg-config = "0.3.17"
|
||||
lua-src = { version = ">= 548.1.0, < 548.2.0", optional = true }
|
||||
luajit-src = { version = ">= 210.6.0, < 210.7.0", optional = true }
|
||||
luau0-src = { version = "0.15.0", optional = true }
|
||||
luau0-src = { version = "0.15.6", optional = true }
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(raw_dylib)'] }
|
||||
|
||||
@@ -14,22 +14,29 @@ fn main() {
|
||||
#[cfg(all(feature = "luau", feature = "module", windows))]
|
||||
compile_error!("Luau does not support `module` mode on Windows");
|
||||
|
||||
#[cfg(all(feature = "module", feature = "vendored"))]
|
||||
compile_error!("`vendored` and `module` features are mutually exclusive");
|
||||
#[cfg(any(
|
||||
all(feature = "vendored", any(feature = "external", feature = "module")),
|
||||
all(feature = "external", any(feature = "vendored", feature = "module")),
|
||||
all(feature = "module", any(feature = "vendored", feature = "external"))
|
||||
))]
|
||||
compile_error!("`vendored`, `external` and `module` features are mutually exclusive");
|
||||
|
||||
println!("cargo:rerun-if-changed=build");
|
||||
|
||||
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
|
||||
if target_os == "windows" && cfg!(feature = "module") {
|
||||
if !std::env::var("LUA_LIB_NAME").unwrap_or_default().is_empty() {
|
||||
// Don't use raw-dylib linking
|
||||
find::probe_lua();
|
||||
return;
|
||||
// Check if compilation and linking is handled by external crate
|
||||
if cfg!(not(feature = "external")) {
|
||||
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
|
||||
if target_os == "windows" && cfg!(feature = "module") {
|
||||
if !std::env::var("LUA_LIB_NAME").unwrap_or_default().is_empty() {
|
||||
// Don't use raw-dylib linking
|
||||
find::probe_lua();
|
||||
return;
|
||||
}
|
||||
|
||||
println!("cargo:rustc-cfg=raw_dylib");
|
||||
}
|
||||
|
||||
println!("cargo:rustc-cfg=raw_dylib");
|
||||
#[cfg(not(feature = "module"))]
|
||||
find::probe_lua();
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "module"))]
|
||||
find::probe_lua();
|
||||
}
|
||||
|
||||
@@ -186,7 +186,8 @@ pub unsafe fn lua_isinteger(L: *mut lua_State, idx: c_int) -> c_int {
|
||||
if lua_type(L, idx) == LUA_TNUMBER {
|
||||
let n = lua_tonumber(L, idx);
|
||||
let i = lua_tointeger(L, idx);
|
||||
if (n - i as lua_Number).abs() < lua_Number::EPSILON {
|
||||
// Lua 5.3+ returns "false" for `-0.0`
|
||||
if n.to_bits() == (i as lua_Number).to_bits() {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,8 @@ pub unsafe fn lua_isinteger(L: *mut lua_State, idx: c_int) -> c_int {
|
||||
if lua_type(L, idx) == LUA_TNUMBER {
|
||||
let n = lua_tonumber(L, idx);
|
||||
let i = lua_tointeger(L, idx);
|
||||
if (n - i as lua_Number).abs() < lua_Number::EPSILON {
|
||||
// Lua 5.3+ returns "false" for `-0.0`
|
||||
if n.to_bits() == (i as lua_Number).to_bits() {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,7 +120,8 @@ pub unsafe fn lua_isinteger(L: *mut lua_State, idx: c_int) -> c_int {
|
||||
if lua_type(L, idx) == LUA_TNUMBER {
|
||||
let n = lua_tonumber(L, idx);
|
||||
let i = lua_tointeger(L, idx);
|
||||
if (n - i as lua_Number).abs() < lua_Number::EPSILON {
|
||||
// Lua 5.3+ returns "false" for `-0.0`
|
||||
if n.to_bits() == (i as lua_Number).to_bits() {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,6 +235,7 @@ unsafe extern "C-unwind" {
|
||||
) -> c_int;
|
||||
pub fn lua_call(L: *mut lua_State, nargs: c_int, nresults: c_int);
|
||||
pub fn lua_pcall(L: *mut lua_State, nargs: c_int, nresults: c_int, errfunc: c_int) -> c_int;
|
||||
pub fn lua_cpcall(L: *mut lua_State, f: lua_CFunction, ud: *mut c_void) -> c_int;
|
||||
|
||||
//
|
||||
// Coroutine functions
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "mlua_derive"
|
||||
version = "0.11.0-beta.2"
|
||||
version = "0.11.0"
|
||||
authors = ["Aleksandr Orlenko <zxteam@pm.me>"]
|
||||
edition = "2021"
|
||||
description = "Procedural macros for the mlua crate."
|
||||
|
||||
@@ -129,13 +129,13 @@ pub fn chunk(input: TokenStream) -> TokenStream {
|
||||
let globals = lua.globals();
|
||||
let env = lua.create_table()?;
|
||||
let meta = lua.create_table()?;
|
||||
meta.raw_set("__index", globals.clone())?;
|
||||
meta.raw_set("__newindex", globals)?;
|
||||
meta.raw_set("__index", &globals)?;
|
||||
meta.raw_set("__newindex", &globals)?;
|
||||
|
||||
// Add captured variables
|
||||
#(#caps)*
|
||||
|
||||
env.set_metatable(Some(meta));
|
||||
env.set_metatable(Some(meta))?;
|
||||
Ok(env)
|
||||
};
|
||||
|
||||
|
||||
+96
-14
@@ -1,6 +1,9 @@
|
||||
use std::io;
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::ser::{Serialize, Serializer};
|
||||
|
||||
use crate::state::RawLua;
|
||||
use crate::types::ValueRef;
|
||||
|
||||
/// A Luau buffer type.
|
||||
@@ -16,12 +19,14 @@ pub struct Buffer(pub(crate) ValueRef);
|
||||
impl Buffer {
|
||||
/// Copies the buffer data into a new `Vec<u8>`.
|
||||
pub fn to_vec(&self) -> Vec<u8> {
|
||||
unsafe { self.as_slice().to_vec() }
|
||||
let lua = self.0.lua.lock();
|
||||
self.as_slice(&lua).to_vec()
|
||||
}
|
||||
|
||||
/// Returns the length of the buffer.
|
||||
pub fn len(&self) -> usize {
|
||||
unsafe { self.as_slice().len() }
|
||||
let lua = self.0.lua.lock();
|
||||
self.as_slice(&lua).len()
|
||||
}
|
||||
|
||||
/// Returns `true` if the buffer is empty.
|
||||
@@ -34,7 +39,8 @@ impl Buffer {
|
||||
/// Offset is 0-based.
|
||||
#[track_caller]
|
||||
pub fn read_bytes<const N: usize>(&self, offset: usize) -> [u8; N] {
|
||||
let data = unsafe { self.as_slice() };
|
||||
let lua = self.0.lua.lock();
|
||||
let data = self.as_slice(&lua);
|
||||
let mut bytes = [0u8; N];
|
||||
bytes.copy_from_slice(&data[offset..offset + N]);
|
||||
bytes
|
||||
@@ -45,21 +51,36 @@ impl Buffer {
|
||||
/// Offset is 0-based.
|
||||
#[track_caller]
|
||||
pub fn write_bytes(&self, offset: usize, bytes: &[u8]) {
|
||||
let data = unsafe {
|
||||
let (buf, size) = self.as_raw_parts();
|
||||
std::slice::from_raw_parts_mut(buf, size)
|
||||
};
|
||||
let lua = self.0.lua.lock();
|
||||
let data = self.as_slice_mut(&lua);
|
||||
data[offset..offset + bytes.len()].copy_from_slice(bytes);
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn as_slice(&self) -> &[u8] {
|
||||
let (buf, size) = self.as_raw_parts();
|
||||
std::slice::from_raw_parts(buf, size)
|
||||
/// Returns an adaptor implementing [`io::Read`], [`io::Write`] and [`io::Seek`] over the
|
||||
/// buffer.
|
||||
///
|
||||
/// Buffer operations are infallible, none of the read/write functions will return a Err.
|
||||
pub fn cursor(self) -> impl io::Read + io::Write + io::Seek {
|
||||
BufferCursor(self, 0)
|
||||
}
|
||||
|
||||
pub(crate) fn as_slice(&self, lua: &RawLua) -> &[u8] {
|
||||
unsafe {
|
||||
let (buf, size) = self.as_raw_parts(lua);
|
||||
std::slice::from_raw_parts(buf, size)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::mut_from_ref)]
|
||||
fn as_slice_mut(&self, lua: &RawLua) -> &mut [u8] {
|
||||
unsafe {
|
||||
let (buf, size) = self.as_raw_parts(lua);
|
||||
std::slice::from_raw_parts_mut(buf, size)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "luau")]
|
||||
unsafe fn as_raw_parts(&self) -> (*mut u8, usize) {
|
||||
let lua = self.0.lua.lock();
|
||||
unsafe fn as_raw_parts(&self, lua: &RawLua) -> (*mut u8, usize) {
|
||||
let mut size = 0usize;
|
||||
let buf = ffi::lua_tobuffer(lua.ref_thread(), self.0.index, &mut size);
|
||||
mlua_assert!(!buf.is_null(), "invalid Luau buffer");
|
||||
@@ -67,15 +88,76 @@ impl Buffer {
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "luau"))]
|
||||
unsafe fn as_raw_parts(&self) -> (*mut u8, usize) {
|
||||
unsafe fn as_raw_parts(&self, lua: &RawLua) -> (*mut u8, usize) {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
struct BufferCursor(Buffer, usize);
|
||||
|
||||
impl io::Read for BufferCursor {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
let lua = self.0 .0.lua.lock();
|
||||
let data = self.0.as_slice(&lua);
|
||||
if self.1 == data.len() {
|
||||
return Ok(0);
|
||||
}
|
||||
let len = buf.len().min(data.len() - self.1);
|
||||
buf[..len].copy_from_slice(&data[self.1..self.1 + len]);
|
||||
self.1 += len;
|
||||
Ok(len)
|
||||
}
|
||||
}
|
||||
|
||||
impl io::Write for BufferCursor {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
let lua = self.0 .0.lua.lock();
|
||||
let data = self.0.as_slice_mut(&lua);
|
||||
if self.1 == data.len() {
|
||||
return Ok(0);
|
||||
}
|
||||
let len = buf.len().min(data.len() - self.1);
|
||||
data[self.1..self.1 + len].copy_from_slice(&buf[..len]);
|
||||
self.1 += len;
|
||||
Ok(len)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl io::Seek for BufferCursor {
|
||||
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
|
||||
let lua = self.0 .0.lua.lock();
|
||||
let data = self.0.as_slice(&lua);
|
||||
let new_offset = match pos {
|
||||
io::SeekFrom::Start(offset) => offset as i64,
|
||||
io::SeekFrom::End(offset) => data.len() as i64 + offset,
|
||||
io::SeekFrom::Current(offset) => self.1 as i64 + offset,
|
||||
};
|
||||
if new_offset < 0 {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"invalid seek to a negative position",
|
||||
));
|
||||
}
|
||||
if new_offset as usize > data.len() {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"invalid seek to a position beyond the end of the buffer",
|
||||
));
|
||||
}
|
||||
self.1 = new_offset as usize;
|
||||
Ok(self.1 as u64)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl Serialize for Buffer {
|
||||
fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
|
||||
serializer.serialize_bytes(unsafe { self.as_slice() })
|
||||
let lua = self.0.lua.lock();
|
||||
serializer.serialize_bytes(self.as_slice(&lua))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+98
-45
@@ -160,18 +160,39 @@ pub enum CompileConstant {
|
||||
Boolean(bool),
|
||||
Number(crate::Number),
|
||||
Vector(crate::Vector),
|
||||
String(String),
|
||||
String(StdString),
|
||||
}
|
||||
|
||||
#[cfg(feature = "luau")]
|
||||
impl From<&'static str> for CompileConstant {
|
||||
fn from(s: &'static str) -> Self {
|
||||
CompileConstant::String(s.to_string())
|
||||
#[cfg(any(feature = "luau", doc))]
|
||||
impl From<bool> for CompileConstant {
|
||||
fn from(b: bool) -> Self {
|
||||
CompileConstant::Boolean(b)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "luau", doc))]
|
||||
type LibraryMemberConstantMap = std::sync::Arc<HashMap<(String, String), CompileConstant>>;
|
||||
impl From<crate::Number> for CompileConstant {
|
||||
fn from(n: crate::Number) -> Self {
|
||||
CompileConstant::Number(n)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "luau", doc))]
|
||||
impl From<crate::Vector> for CompileConstant {
|
||||
fn from(v: crate::Vector) -> Self {
|
||||
CompileConstant::Vector(v)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "luau", doc))]
|
||||
impl From<&str> for CompileConstant {
|
||||
fn from(s: &str) -> Self {
|
||||
CompileConstant::String(s.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "luau", doc))]
|
||||
type LibraryMemberConstantMap = HashMap<(StdString, StdString), CompileConstant>;
|
||||
|
||||
/// Luau compiler
|
||||
#[cfg(any(feature = "luau", doc))]
|
||||
@@ -182,14 +203,14 @@ pub struct Compiler {
|
||||
debug_level: u8,
|
||||
type_info_level: u8,
|
||||
coverage_level: u8,
|
||||
vector_lib: Option<String>,
|
||||
vector_ctor: Option<String>,
|
||||
vector_type: Option<String>,
|
||||
mutable_globals: Vec<String>,
|
||||
userdata_types: Vec<String>,
|
||||
libraries_with_known_members: Vec<String>,
|
||||
vector_lib: Option<StdString>,
|
||||
vector_ctor: Option<StdString>,
|
||||
vector_type: Option<StdString>,
|
||||
mutable_globals: Vec<StdString>,
|
||||
userdata_types: Vec<StdString>,
|
||||
libraries_with_known_members: Vec<StdString>,
|
||||
library_constants: Option<LibraryMemberConstantMap>,
|
||||
disabled_builtins: Vec<String>,
|
||||
disabled_builtins: Vec<StdString>,
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "luau", doc))]
|
||||
@@ -267,70 +288,102 @@ impl Compiler {
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets alternative global builtin to construct vectors, in addition to default builtin
|
||||
/// `vector.create`.
|
||||
///
|
||||
/// To set the library and method name, use the `lib.ctor` format.
|
||||
#[doc(hidden)]
|
||||
#[must_use]
|
||||
pub fn set_vector_lib(mut self, lib: impl Into<String>) -> Self {
|
||||
self.vector_lib = Some(lib.into());
|
||||
pub fn set_vector_ctor(mut self, ctor: impl Into<StdString>) -> Self {
|
||||
let ctor = ctor.into();
|
||||
let lib_ctor = ctor.split_once('.');
|
||||
self.vector_lib = lib_ctor.as_ref().map(|&(lib, _)| lib.to_owned());
|
||||
self.vector_ctor = (lib_ctor.as_ref())
|
||||
.map(|&(_, ctor)| ctor.to_owned())
|
||||
.or(Some(ctor));
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets alternative vector type name for type tables, in addition to default type `vector`.
|
||||
#[doc(hidden)]
|
||||
#[must_use]
|
||||
pub fn set_vector_ctor(mut self, ctor: impl Into<String>) -> Self {
|
||||
self.vector_ctor = Some(ctor.into());
|
||||
self
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[must_use]
|
||||
pub fn set_vector_type(mut self, r#type: impl Into<String>) -> Self {
|
||||
pub fn set_vector_type(mut self, r#type: impl Into<StdString>) -> Self {
|
||||
self.vector_type = Some(r#type.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds a mutable global.
|
||||
///
|
||||
/// It disables the import optimization for fields accessed through it.
|
||||
#[must_use]
|
||||
pub fn add_mutable_global(mut self, global: impl Into<StdString>) -> Self {
|
||||
self.mutable_globals.push(global.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets a list of globals that are mutable.
|
||||
///
|
||||
/// It disables the import optimization for fields accessed through these.
|
||||
#[must_use]
|
||||
pub fn set_mutable_globals<S: Into<String>>(mut self, globals: Vec<S>) -> Self {
|
||||
pub fn set_mutable_globals<S: Into<StdString>>(mut self, globals: impl IntoIterator<Item = S>) -> Self {
|
||||
self.mutable_globals = globals.into_iter().map(|s| s.into()).collect();
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds a userdata type to the list that will be included in the type information.
|
||||
#[must_use]
|
||||
pub fn add_userdata_type(mut self, r#type: impl Into<StdString>) -> Self {
|
||||
self.userdata_types.push(r#type.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets a list of userdata types that will be included in the type information.
|
||||
#[must_use]
|
||||
pub fn set_userdata_types<S: Into<String>>(mut self, types: Vec<S>) -> Self {
|
||||
pub fn set_userdata_types<S: Into<StdString>>(mut self, types: impl IntoIterator<Item = S>) -> Self {
|
||||
self.userdata_types = types.into_iter().map(|s| s.into()).collect();
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets constants for known library members.
|
||||
/// Adds a constant for a known library member.
|
||||
///
|
||||
/// The constants are used by the compiler to optimize the generated bytecode.
|
||||
/// Optimization level must be at least 2 for this to have any effect.
|
||||
///
|
||||
/// The first element of the tuple is the library name,the second is the member name, and the
|
||||
/// third is the constant value.
|
||||
/// The `name` is a string in the format `lib.member`, where `lib` is the library name
|
||||
/// and `member` is the member (constant) name.
|
||||
#[must_use]
|
||||
pub fn set_library_constants<L, M>(mut self, constants: Vec<(L, M, CompileConstant)>) -> Self
|
||||
where
|
||||
L: Into<String>,
|
||||
M: Into<String>,
|
||||
{
|
||||
let map = constants
|
||||
.into_iter()
|
||||
.map(|(lib, member, cons)| ((lib.into(), member.into()), cons))
|
||||
.collect::<HashMap<_, _>>();
|
||||
self.library_constants = Some(std::sync::Arc::new(map));
|
||||
self.libraries_with_known_members = (self.library_constants.clone())
|
||||
.map(|map| map.keys().map(|(lib, _)| lib.clone()).collect())
|
||||
.unwrap_or_default();
|
||||
pub fn add_library_constant(
|
||||
mut self,
|
||||
name: impl AsRef<str>,
|
||||
r#const: impl Into<CompileConstant>,
|
||||
) -> Self {
|
||||
let Some((lib, member)) = name.as_ref().split_once('.') else {
|
||||
return self;
|
||||
};
|
||||
let (lib, member) = (lib.to_owned(), member.to_owned());
|
||||
|
||||
if !self.libraries_with_known_members.contains(&lib) {
|
||||
self.libraries_with_known_members.push(lib.clone());
|
||||
}
|
||||
self.library_constants
|
||||
.get_or_insert_with(HashMap::new)
|
||||
.insert((lib, member), r#const.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds a builtin that should be disabled.
|
||||
#[must_use]
|
||||
pub fn add_disabled_builtin(mut self, builtin: impl Into<StdString>) -> Self {
|
||||
self.disabled_builtins.push(builtin.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets a list of builtins that should be disabled.
|
||||
#[must_use]
|
||||
pub fn set_disabled_builtins<S: Into<String>>(mut self, builtins: Vec<S>) -> Self {
|
||||
pub fn set_disabled_builtins<S: Into<StdString>>(
|
||||
mut self,
|
||||
builtins: impl IntoIterator<Item = S>,
|
||||
) -> Self {
|
||||
self.disabled_builtins = builtins.into_iter().map(|s| s.into()).collect();
|
||||
self
|
||||
}
|
||||
@@ -437,7 +490,7 @@ impl Compiler {
|
||||
if bytecode.first() == Some(&0) {
|
||||
// The rest of the bytecode is the error message starting with `:`
|
||||
// See https://github.com/luau-lang/luau/blob/0.640/Compiler/src/Compiler.cpp#L4336
|
||||
let message = String::from_utf8_lossy(&bytecode[2..]).to_string();
|
||||
let message = StdString::from_utf8_lossy(&bytecode[2..]).into_owned();
|
||||
return Err(Error::SyntaxError {
|
||||
incomplete_input: message.ends_with("<eof>"),
|
||||
message,
|
||||
@@ -460,7 +513,7 @@ impl Chunk<'_> {
|
||||
/// - `@` - file path (when truncation is needed, the end of the file path is kept, as this is
|
||||
/// more useful for identifying the file)
|
||||
/// - `=` - custom chunk name (when truncation is needed, the beginning of the name is kept)
|
||||
pub fn set_name(mut self, name: impl Into<String>) -> Self {
|
||||
pub fn set_name(mut self, name: impl Into<StdString>) -> Self {
|
||||
self.name = name.into();
|
||||
self
|
||||
}
|
||||
@@ -708,7 +761,7 @@ impl Chunk<'_> {
|
||||
ChunkMode::Text
|
||||
}
|
||||
|
||||
fn convert_name(name: String) -> Result<CString> {
|
||||
fn convert_name(name: StdString) -> Result<CString> {
|
||||
CString::new(name).map_err(|err| Error::runtime(format!("invalid name: {err}")))
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -645,7 +645,7 @@ impl FromLua for BString {
|
||||
match value {
|
||||
Value::String(s) => Ok((*s.as_bytes()).into()),
|
||||
#[cfg(feature = "luau")]
|
||||
Value::Buffer(buf) => unsafe { Ok(buf.as_slice().into()) },
|
||||
Value::Buffer(buf) => Ok(buf.to_vec().into()),
|
||||
_ => Ok((*lua
|
||||
.coerce_string(value)?
|
||||
.ok_or_else(|| Error::FromLuaConversionError {
|
||||
|
||||
+83
-100
@@ -1,66 +1,33 @@
|
||||
use std::borrow::Cow;
|
||||
use std::cell::UnsafeCell;
|
||||
use std::ops::Deref;
|
||||
#[cfg(not(feature = "luau"))]
|
||||
use std::ops::{BitOr, BitOrAssign};
|
||||
use std::os::raw::c_int;
|
||||
|
||||
use ffi::lua_Debug;
|
||||
use ffi::{lua_Debug, lua_State};
|
||||
|
||||
use crate::function::Function;
|
||||
use crate::state::RawLua;
|
||||
use crate::types::ReentrantMutexGuard;
|
||||
use crate::util::{linenumber_to_usize, ptr_to_lossy_str, ptr_to_str};
|
||||
use crate::util::{assert_stack, linenumber_to_usize, ptr_to_lossy_str, ptr_to_str, StackGuard};
|
||||
|
||||
/// Contains information about currently executing Lua code.
|
||||
///
|
||||
/// The `Debug` structure is provided as a parameter to the hook function set with
|
||||
/// [`Lua::set_hook`]. You may call the methods on this structure to retrieve information about the
|
||||
/// Lua code executing at the time that the hook function was called. Further information can be
|
||||
/// found in the Lua [documentation].
|
||||
/// You may call the methods on this structure to retrieve information about the Lua code executing
|
||||
/// at the specific level. Further information can be found in the Lua [documentation].
|
||||
///
|
||||
/// [documentation]: https://www.lua.org/manual/5.4/manual.html#lua_Debug
|
||||
/// [`Lua::set_hook`]: crate::Lua::set_hook
|
||||
pub struct Debug<'a> {
|
||||
lua: EitherLua<'a>,
|
||||
ar: ActivationRecord,
|
||||
#[cfg(feature = "luau")]
|
||||
state: *mut lua_State,
|
||||
lua: &'a RawLua,
|
||||
#[cfg_attr(not(feature = "luau"), allow(unused))]
|
||||
level: c_int,
|
||||
}
|
||||
|
||||
enum EitherLua<'a> {
|
||||
Owned(ReentrantMutexGuard<'a, RawLua>),
|
||||
#[cfg(not(feature = "luau"))]
|
||||
Borrowed(&'a RawLua),
|
||||
}
|
||||
|
||||
impl Deref for EitherLua<'_> {
|
||||
type Target = RawLua;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
match self {
|
||||
EitherLua::Owned(guard) => guard,
|
||||
#[cfg(not(feature = "luau"))]
|
||||
EitherLua::Borrowed(lua) => lua,
|
||||
}
|
||||
}
|
||||
ar: *mut lua_Debug,
|
||||
}
|
||||
|
||||
impl<'a> Debug<'a> {
|
||||
// We assume the lock is held when this function is called.
|
||||
#[cfg(not(feature = "luau"))]
|
||||
pub(crate) fn new(lua: &'a RawLua, ar: *mut lua_Debug) -> Self {
|
||||
pub(crate) fn new(lua: &'a RawLua, level: c_int, ar: *mut lua_Debug) -> Self {
|
||||
Debug {
|
||||
lua: EitherLua::Borrowed(lua),
|
||||
ar: ActivationRecord::Borrowed(ar),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn new_owned(guard: ReentrantMutexGuard<'a, RawLua>, _level: c_int, ar: lua_Debug) -> Self {
|
||||
Debug {
|
||||
lua: EitherLua::Owned(guard),
|
||||
ar: ActivationRecord::Owned(UnsafeCell::new(ar)),
|
||||
#[cfg(feature = "luau")]
|
||||
level: _level,
|
||||
state: lua.state(),
|
||||
lua,
|
||||
ar,
|
||||
level,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +41,7 @@ impl<'a> Debug<'a> {
|
||||
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
|
||||
pub fn event(&self) -> DebugEvent {
|
||||
unsafe {
|
||||
match (*self.ar.get()).event {
|
||||
match (*self.ar).event {
|
||||
ffi::LUA_HOOKCALL => DebugEvent::Call,
|
||||
ffi::LUA_HOOKRET => DebugEvent::Ret,
|
||||
ffi::LUA_HOOKTAILCALL => DebugEvent::TailCall,
|
||||
@@ -85,24 +52,48 @@ impl<'a> Debug<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Corresponds to the `n` what mask.
|
||||
/// Returns the function that is running at the given level.
|
||||
///
|
||||
/// Corresponds to the `f` "what" mask.
|
||||
pub fn function(&self) -> Function {
|
||||
unsafe {
|
||||
let _sg = StackGuard::new(self.state);
|
||||
assert_stack(self.state, 1);
|
||||
|
||||
#[cfg(not(feature = "luau"))]
|
||||
mlua_assert!(
|
||||
ffi::lua_getinfo(self.state, cstr!("f"), self.ar) != 0,
|
||||
"lua_getinfo failed with `f`"
|
||||
);
|
||||
#[cfg(feature = "luau")]
|
||||
mlua_assert!(
|
||||
ffi::lua_getinfo(self.state, self.level, cstr!("f"), self.ar) != 0,
|
||||
"lua_getinfo failed with `f`"
|
||||
);
|
||||
|
||||
ffi::lua_xmove(self.state, self.lua.ref_thread(), 1);
|
||||
Function(self.lua.pop_ref_thread())
|
||||
}
|
||||
}
|
||||
|
||||
/// Corresponds to the `n` "what" mask.
|
||||
pub fn names(&self) -> DebugNames<'_> {
|
||||
unsafe {
|
||||
#[cfg(not(feature = "luau"))]
|
||||
mlua_assert!(
|
||||
ffi::lua_getinfo(self.lua.state(), cstr!("n"), self.ar.get()) != 0,
|
||||
ffi::lua_getinfo(self.state, cstr!("n"), self.ar) != 0,
|
||||
"lua_getinfo failed with `n`"
|
||||
);
|
||||
#[cfg(feature = "luau")]
|
||||
mlua_assert!(
|
||||
ffi::lua_getinfo(self.lua.state(), self.level, cstr!("n"), self.ar.get()) != 0,
|
||||
ffi::lua_getinfo(self.state, self.level, cstr!("n"), self.ar) != 0,
|
||||
"lua_getinfo failed with `n`"
|
||||
);
|
||||
|
||||
DebugNames {
|
||||
name: ptr_to_lossy_str((*self.ar.get()).name),
|
||||
name: ptr_to_lossy_str((*self.ar).name),
|
||||
#[cfg(not(feature = "luau"))]
|
||||
name_what: match ptr_to_str((*self.ar.get()).namewhat) {
|
||||
name_what: match ptr_to_str((*self.ar).namewhat) {
|
||||
Some("") => None,
|
||||
val => val,
|
||||
},
|
||||
@@ -112,118 +103,110 @@ impl<'a> Debug<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Corresponds to the `S` what mask.
|
||||
/// Corresponds to the `S` "what" mask.
|
||||
pub fn source(&self) -> DebugSource<'_> {
|
||||
unsafe {
|
||||
#[cfg(not(feature = "luau"))]
|
||||
mlua_assert!(
|
||||
ffi::lua_getinfo(self.lua.state(), cstr!("S"), self.ar.get()) != 0,
|
||||
ffi::lua_getinfo(self.state, cstr!("S"), self.ar) != 0,
|
||||
"lua_getinfo failed with `S`"
|
||||
);
|
||||
#[cfg(feature = "luau")]
|
||||
mlua_assert!(
|
||||
ffi::lua_getinfo(self.lua.state(), self.level, cstr!("s"), self.ar.get()) != 0,
|
||||
ffi::lua_getinfo(self.state, self.level, cstr!("s"), self.ar) != 0,
|
||||
"lua_getinfo failed with `s`"
|
||||
);
|
||||
|
||||
DebugSource {
|
||||
source: ptr_to_lossy_str((*self.ar.get()).source),
|
||||
source: ptr_to_lossy_str((*self.ar).source),
|
||||
#[cfg(not(feature = "luau"))]
|
||||
short_src: ptr_to_lossy_str((*self.ar.get()).short_src.as_ptr()),
|
||||
short_src: ptr_to_lossy_str((*self.ar).short_src.as_ptr()),
|
||||
#[cfg(feature = "luau")]
|
||||
short_src: ptr_to_lossy_str((*self.ar.get()).short_src),
|
||||
line_defined: linenumber_to_usize((*self.ar.get()).linedefined),
|
||||
short_src: ptr_to_lossy_str((*self.ar).short_src),
|
||||
line_defined: linenumber_to_usize((*self.ar).linedefined),
|
||||
#[cfg(not(feature = "luau"))]
|
||||
last_line_defined: linenumber_to_usize((*self.ar.get()).lastlinedefined),
|
||||
last_line_defined: linenumber_to_usize((*self.ar).lastlinedefined),
|
||||
#[cfg(feature = "luau")]
|
||||
last_line_defined: None,
|
||||
what: ptr_to_str((*self.ar.get()).what).unwrap_or("main"),
|
||||
what: ptr_to_str((*self.ar).what).unwrap_or("main"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Corresponds to the `l` what mask. Returns the current line.
|
||||
#[doc(hidden)]
|
||||
#[deprecated(note = "Use `current_line` instead")]
|
||||
pub fn curr_line(&self) -> i32 {
|
||||
self.current_line().map(|n| n as i32).unwrap_or(-1)
|
||||
}
|
||||
|
||||
/// Corresponds to the `l` "what" mask. Returns the current line.
|
||||
pub fn current_line(&self) -> Option<usize> {
|
||||
unsafe {
|
||||
#[cfg(not(feature = "luau"))]
|
||||
mlua_assert!(
|
||||
ffi::lua_getinfo(self.lua.state(), cstr!("l"), self.ar.get()) != 0,
|
||||
ffi::lua_getinfo(self.state, cstr!("l"), self.ar) != 0,
|
||||
"lua_getinfo failed with `l`"
|
||||
);
|
||||
#[cfg(feature = "luau")]
|
||||
mlua_assert!(
|
||||
ffi::lua_getinfo(self.lua.state(), self.level, cstr!("l"), self.ar.get()) != 0,
|
||||
ffi::lua_getinfo(self.state, self.level, cstr!("l"), self.ar) != 0,
|
||||
"lua_getinfo failed with `l`"
|
||||
);
|
||||
|
||||
(*self.ar.get()).currentline
|
||||
linenumber_to_usize((*self.ar).currentline)
|
||||
}
|
||||
}
|
||||
|
||||
/// Corresponds to the `t` what mask. Returns true if the hook is in a function tail call, false
|
||||
/// otherwise.
|
||||
#[cfg(not(feature = "luau"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
|
||||
/// Corresponds to the `t` "what" mask. Returns true if the hook is in a function tail call,
|
||||
/// false otherwise.
|
||||
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
|
||||
#[cfg_attr(
|
||||
docsrs,
|
||||
doc(cfg(any(feature = "lua54", feature = "lua53", feature = "lua52")))
|
||||
)]
|
||||
pub fn is_tail_call(&self) -> bool {
|
||||
unsafe {
|
||||
mlua_assert!(
|
||||
ffi::lua_getinfo(self.lua.state(), cstr!("t"), self.ar.get()) != 0,
|
||||
ffi::lua_getinfo(self.state, cstr!("t"), self.ar) != 0,
|
||||
"lua_getinfo failed with `t`"
|
||||
);
|
||||
(*self.ar.get()).currentline != 0
|
||||
(*self.ar).istailcall != 0
|
||||
}
|
||||
}
|
||||
|
||||
/// Corresponds to the `u` what mask.
|
||||
/// Corresponds to the `u` "what" mask.
|
||||
pub fn stack(&self) -> DebugStack {
|
||||
unsafe {
|
||||
#[cfg(not(feature = "luau"))]
|
||||
mlua_assert!(
|
||||
ffi::lua_getinfo(self.lua.state(), cstr!("u"), self.ar.get()) != 0,
|
||||
ffi::lua_getinfo(self.state, cstr!("u"), self.ar) != 0,
|
||||
"lua_getinfo failed with `u`"
|
||||
);
|
||||
#[cfg(feature = "luau")]
|
||||
mlua_assert!(
|
||||
ffi::lua_getinfo(self.lua.state(), self.level, cstr!("au"), self.ar.get()) != 0,
|
||||
ffi::lua_getinfo(self.state, self.level, cstr!("au"), self.ar) != 0,
|
||||
"lua_getinfo failed with `au`"
|
||||
);
|
||||
|
||||
#[cfg(not(feature = "luau"))]
|
||||
let stack = DebugStack {
|
||||
num_ups: (*self.ar.get()).nups as _,
|
||||
num_ups: (*self.ar).nups as _,
|
||||
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
|
||||
num_params: (*self.ar.get()).nparams as _,
|
||||
num_params: (*self.ar).nparams as _,
|
||||
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
|
||||
is_vararg: (*self.ar.get()).isvararg != 0,
|
||||
is_vararg: (*self.ar).isvararg != 0,
|
||||
};
|
||||
#[cfg(feature = "luau")]
|
||||
let stack = DebugStack {
|
||||
num_ups: (*self.ar.get()).nupvals,
|
||||
num_params: (*self.ar.get()).nparams,
|
||||
is_vararg: (*self.ar.get()).isvararg != 0,
|
||||
num_ups: (*self.ar).nupvals,
|
||||
num_params: (*self.ar).nparams,
|
||||
is_vararg: (*self.ar).isvararg != 0,
|
||||
};
|
||||
stack
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum ActivationRecord {
|
||||
#[cfg(not(feature = "luau"))]
|
||||
Borrowed(*mut lua_Debug),
|
||||
Owned(UnsafeCell<lua_Debug>),
|
||||
}
|
||||
|
||||
impl ActivationRecord {
|
||||
#[inline]
|
||||
fn get(&self) -> *mut lua_Debug {
|
||||
match self {
|
||||
#[cfg(not(feature = "luau"))]
|
||||
ActivationRecord::Borrowed(x) => *x,
|
||||
ActivationRecord::Owned(x) => x.get(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a specific event that triggered the hook.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum DebugEvent {
|
||||
@@ -382,7 +365,7 @@ impl HookTriggers {
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "luau"))]
|
||||
impl BitOr for HookTriggers {
|
||||
impl std::ops::BitOr for HookTriggers {
|
||||
type Output = Self;
|
||||
|
||||
fn bitor(mut self, rhs: Self) -> Self::Output {
|
||||
@@ -397,7 +380,7 @@ impl BitOr for HookTriggers {
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "luau"))]
|
||||
impl BitOrAssign for HookTriggers {
|
||||
impl std::ops::BitOrAssign for HookTriggers {
|
||||
fn bitor_assign(&mut self, rhs: Self) {
|
||||
*self = *self | rhs;
|
||||
}
|
||||
+23
-16
@@ -18,7 +18,7 @@ use {
|
||||
crate::traits::LuaNativeAsyncFn,
|
||||
crate::types::AsyncCallback,
|
||||
std::future::{self, Future},
|
||||
std::pin::Pin,
|
||||
std::pin::{pin, Pin},
|
||||
std::task::{Context, Poll},
|
||||
};
|
||||
|
||||
@@ -434,7 +434,7 @@ impl Function {
|
||||
/// [`Compiler::set_coverage_level`]: crate::chunk::Compiler::set_coverage_level
|
||||
#[cfg(any(feature = "luau", doc))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
|
||||
pub fn coverage<F>(&self, mut func: F)
|
||||
pub fn coverage<F>(&self, func: F)
|
||||
where
|
||||
F: FnMut(CoverageInfo),
|
||||
{
|
||||
@@ -454,13 +454,16 @@ impl Function {
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let rust_callback = &mut *(data as *mut F);
|
||||
rust_callback(CoverageInfo {
|
||||
function,
|
||||
line_defined,
|
||||
depth,
|
||||
hits: slice::from_raw_parts(hits, size).to_vec(),
|
||||
});
|
||||
let rust_callback = &*(data as *const RefCell<F>);
|
||||
if let Ok(mut rust_callback) = rust_callback.try_borrow_mut() {
|
||||
// Call the Rust callback with CoverageInfo
|
||||
rust_callback(CoverageInfo {
|
||||
function,
|
||||
line_defined,
|
||||
depth,
|
||||
hits: slice::from_raw_parts(hits, size).to_vec(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let lua = self.0.lua.lock();
|
||||
@@ -470,7 +473,8 @@ impl Function {
|
||||
assert_stack(state, 1);
|
||||
|
||||
lua.push_ref(&self.0);
|
||||
let func_ptr = &mut func as *mut F as *mut c_void;
|
||||
let func = RefCell::new(func);
|
||||
let func_ptr = &func as *const RefCell<F> as *mut c_void;
|
||||
ffi::lua_getcoverage(state, -1, func_ptr, callback::<F>);
|
||||
}
|
||||
}
|
||||
@@ -653,18 +657,21 @@ impl LuaType for Function {
|
||||
#[must_use = "futures do nothing unless you `.await` or poll them"]
|
||||
pub struct AsyncCallFuture<R: FromLuaMulti>(Result<AsyncThread<R>>);
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
impl<R: FromLuaMulti> AsyncCallFuture<R> {
|
||||
pub(crate) fn error(err: Error) -> Self {
|
||||
AsyncCallFuture(Err(err))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
impl<R: FromLuaMulti> Future for AsyncCallFuture<R> {
|
||||
type Output = Result<R>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
// Safety: We're not moving any pinned data
|
||||
let this = unsafe { self.get_unchecked_mut() };
|
||||
let this = self.get_mut();
|
||||
match &mut this.0 {
|
||||
Ok(thread) => {
|
||||
let pinned_thread = unsafe { Pin::new_unchecked(thread) };
|
||||
pinned_thread.poll(cx)
|
||||
}
|
||||
Ok(thread) => pin!(thread).poll(cx),
|
||||
Err(err) => Poll::Ready(Err(err.clone())),
|
||||
}
|
||||
}
|
||||
|
||||
+7
-4
@@ -75,9 +75,9 @@ mod macros;
|
||||
mod buffer;
|
||||
mod chunk;
|
||||
mod conversion;
|
||||
mod debug;
|
||||
mod error;
|
||||
mod function;
|
||||
mod hook;
|
||||
#[cfg(any(feature = "luau", doc))]
|
||||
mod luau;
|
||||
mod memory;
|
||||
@@ -101,9 +101,9 @@ pub use bstr::BString;
|
||||
pub use ffi::{self, lua_CFunction, lua_State};
|
||||
|
||||
pub use crate::chunk::{AsChunk, Chunk, ChunkMode};
|
||||
pub use crate::debug::{Debug, DebugEvent, DebugNames, DebugSource, DebugStack};
|
||||
pub use crate::error::{Error, ErrorContext, ExternalError, ExternalResult, Result};
|
||||
pub use crate::function::{Function, FunctionInfo};
|
||||
pub use crate::hook::{Debug, DebugEvent, DebugNames, DebugSource, DebugStack};
|
||||
pub use crate::multi::{MultiValue, Variadic};
|
||||
pub use crate::scope::Scope;
|
||||
pub use crate::state::{GCMode, Lua, LuaOptions, WeakLua};
|
||||
@@ -124,7 +124,7 @@ pub use crate::userdata::{
|
||||
pub use crate::value::{Nil, Value};
|
||||
|
||||
#[cfg(not(feature = "luau"))]
|
||||
pub use crate::hook::HookTriggers;
|
||||
pub use crate::debug::HookTriggers;
|
||||
|
||||
#[cfg(any(feature = "luau", doc))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
|
||||
@@ -142,7 +142,10 @@ pub use crate::{thread::AsyncThread, traits::LuaNativeAsyncFn};
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
#[doc(inline)]
|
||||
pub use crate::serde::{de::Options as DeserializeOptions, ser::Options as SerializeOptions, LuaSerdeExt};
|
||||
pub use crate::{
|
||||
serde::{de::Options as DeserializeOptions, ser::Options as SerializeOptions, LuaSerdeExt},
|
||||
value::SerializableValue,
|
||||
};
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
|
||||
|
||||
+2
-1
@@ -7,6 +7,7 @@ use crate::error::Result;
|
||||
use crate::function::Function;
|
||||
use crate::state::{callback_error_ext, ExtraData, Lua};
|
||||
use crate::traits::{FromLuaMulti, IntoLua};
|
||||
use crate::types::MaybeSend;
|
||||
|
||||
pub use require::{NavigateError, Require, TextRequirer};
|
||||
|
||||
@@ -17,7 +18,7 @@ impl Lua {
|
||||
/// and load modules.
|
||||
#[cfg(any(feature = "luau", doc))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
|
||||
pub fn create_require_function<R: Require + 'static>(&self, require: R) -> Result<Function> {
|
||||
pub fn create_require_function<R: Require + MaybeSend + 'static>(&self, require: R) -> Result<Function> {
|
||||
require::create_require_function(self, require)
|
||||
}
|
||||
|
||||
|
||||
+73
-32
@@ -14,7 +14,7 @@ use crate::state::{callback_error_ext, Lua};
|
||||
use crate::table::Table;
|
||||
use crate::types::MaybeSend;
|
||||
|
||||
/// An error that can occur during navigation in the Luau `require` system.
|
||||
/// An error that can occur during navigation in the Luau `require-by-string` system.
|
||||
#[cfg(any(feature = "luau", doc))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -50,10 +50,10 @@ impl From<Error> for NavigateError {
|
||||
#[cfg(feature = "luau")]
|
||||
type WriteResult = ffi::luarequire_WriteResult;
|
||||
|
||||
/// A trait for handling modules loading and navigation in the Luau `require` system.
|
||||
/// A trait for handling modules loading and navigation in the Luau `require-by-string` system.
|
||||
#[cfg(any(feature = "luau", doc))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
|
||||
pub trait Require: MaybeSend {
|
||||
pub trait Require {
|
||||
/// Returns `true` if "require" is permitted for the given chunk name.
|
||||
fn is_require_allowed(&self, chunk_name: &str) -> bool;
|
||||
|
||||
@@ -103,23 +103,33 @@ impl fmt::Debug for dyn Require {
|
||||
}
|
||||
}
|
||||
|
||||
/// The standard implementation of Luau `require` navigation.
|
||||
#[doc(hidden)]
|
||||
/// The standard implementation of Luau `require-by-string` navigation.
|
||||
#[derive(Default, Debug)]
|
||||
pub struct TextRequirer {
|
||||
/// An absolute path to the current Luau module (not mapped to a physical file)
|
||||
abs_path: PathBuf,
|
||||
/// A relative path to the current Luau module (not mapped to a physical file)
|
||||
rel_path: PathBuf,
|
||||
module_path: PathBuf,
|
||||
/// A physical path to the current Luau module, which is a file or a directory with an
|
||||
/// `init.lua(u)` file
|
||||
resolved_path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl TextRequirer {
|
||||
/// The prefix used for chunk names in the require system.
|
||||
/// Only chunk names starting with this prefix are allowed to be used in `require`.
|
||||
const CHUNK_PREFIX: &str = "@";
|
||||
|
||||
/// The file extensions that are considered valid for Luau modules.
|
||||
const FILE_EXTENSIONS: &[&str] = &["luau", "lua"];
|
||||
|
||||
/// Creates a new `TextRequirer` instance.
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
fn normalize_chunk_name(chunk_name: &str) -> &str {
|
||||
if let Some((path, line)) = chunk_name.split_once(':') {
|
||||
if let Some((path, line)) = chunk_name.rsplit_once(':') {
|
||||
if line.parse::<u32>().is_ok() {
|
||||
return path;
|
||||
}
|
||||
@@ -156,14 +166,17 @@ impl TextRequirer {
|
||||
components.into_iter().collect()
|
||||
}
|
||||
|
||||
fn find_module(path: &Path) -> StdResult<PathBuf, NavigateError> {
|
||||
/// Resolve a Luau module path to a physical file or directory.
|
||||
///
|
||||
/// Empty directories without init files are considered valid as "intermediate" directories.
|
||||
fn resolve_module(path: &Path) -> StdResult<Option<PathBuf>, NavigateError> {
|
||||
let mut found_path = None;
|
||||
|
||||
if path.components().next_back() != Some(Component::Normal("init".as_ref())) {
|
||||
let current_ext = (path.extension().and_then(|s| s.to_str()))
|
||||
.map(|s| format!("{s}."))
|
||||
.unwrap_or_default();
|
||||
for ext in ["luau", "lua"] {
|
||||
for ext in Self::FILE_EXTENSIONS {
|
||||
let candidate = path.with_extension(format!("{current_ext}{ext}"));
|
||||
if candidate.is_file() && found_path.replace(candidate).is_some() {
|
||||
return Err(NavigateError::Ambiguous);
|
||||
@@ -171,7 +184,7 @@ impl TextRequirer {
|
||||
}
|
||||
}
|
||||
if path.is_dir() {
|
||||
for component in ["init.luau", "init.lua"] {
|
||||
for component in Self::FILE_EXTENSIONS.iter().map(|ext| format!("init.{ext}")) {
|
||||
let candidate = path.join(component);
|
||||
if candidate.is_file() && found_path.replace(candidate).is_some() {
|
||||
return Err(NavigateError::Ambiguous);
|
||||
@@ -179,21 +192,22 @@ impl TextRequirer {
|
||||
}
|
||||
|
||||
if found_path.is_none() {
|
||||
found_path = Some(PathBuf::new());
|
||||
// Directories without init files are considered valid "intermediate" path
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
|
||||
found_path.ok_or(NavigateError::NotFound)
|
||||
Ok(Some(found_path.ok_or(NavigateError::NotFound)?))
|
||||
}
|
||||
}
|
||||
|
||||
impl Require for TextRequirer {
|
||||
fn is_require_allowed(&self, chunk_name: &str) -> bool {
|
||||
chunk_name.starts_with('@')
|
||||
chunk_name.starts_with(Self::CHUNK_PREFIX)
|
||||
}
|
||||
|
||||
fn reset(&mut self, chunk_name: &str) -> StdResult<(), NavigateError> {
|
||||
if !chunk_name.starts_with('@') {
|
||||
if !chunk_name.starts_with(Self::CHUNK_PREFIX) {
|
||||
return Err(NavigateError::NotFound);
|
||||
}
|
||||
let chunk_name = Self::normalize_chunk_name(&chunk_name[1..]);
|
||||
@@ -205,24 +219,24 @@ impl Require for TextRequirer {
|
||||
let cwd = env::current_dir().map_err(|_| NavigateError::NotFound)?;
|
||||
self.abs_path = Self::normalize_path(&cwd.join(chunk_filename));
|
||||
self.rel_path = ([Component::CurDir, Component::Normal(chunk_filename)].into_iter()).collect();
|
||||
self.module_path = PathBuf::new();
|
||||
self.resolved_path = None;
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if chunk_path.is_absolute() {
|
||||
let module_path = Self::find_module(&chunk_path)?;
|
||||
let resolved_path = Self::resolve_module(&chunk_path)?;
|
||||
self.abs_path = chunk_path.clone();
|
||||
self.rel_path = chunk_path;
|
||||
self.module_path = module_path;
|
||||
self.resolved_path = resolved_path;
|
||||
} else {
|
||||
// Relative path
|
||||
let cwd = env::current_dir().map_err(|_| NavigateError::NotFound)?;
|
||||
let abs_path = Self::normalize_path(&cwd.join(&chunk_path));
|
||||
let module_path = Self::find_module(&abs_path)?;
|
||||
let resolved_path = Self::resolve_module(&abs_path)?;
|
||||
self.abs_path = abs_path;
|
||||
self.rel_path = chunk_path;
|
||||
self.module_path = module_path;
|
||||
self.resolved_path = resolved_path;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -230,11 +244,11 @@ impl Require for TextRequirer {
|
||||
|
||||
fn jump_to_alias(&mut self, path: &str) -> StdResult<(), NavigateError> {
|
||||
let path = Self::normalize_path(path.as_ref());
|
||||
let module_path = Self::find_module(&path)?;
|
||||
let resolved_path = Self::resolve_module(&path)?;
|
||||
|
||||
self.abs_path = path.clone();
|
||||
self.rel_path = path;
|
||||
self.module_path = module_path;
|
||||
self.resolved_path = resolved_path;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -242,15 +256,18 @@ impl Require for TextRequirer {
|
||||
fn to_parent(&mut self) -> StdResult<(), NavigateError> {
|
||||
let mut abs_path = self.abs_path.clone();
|
||||
if !abs_path.pop() {
|
||||
// It's important to return `NotFound` if we reached the root, as it's a "recoverable" error if we
|
||||
// cannot go beyond the root directory.
|
||||
// Luau "require-by-string` has a special logic to search for config file to resolve aliases.
|
||||
return Err(NavigateError::NotFound);
|
||||
}
|
||||
let mut rel_parent = self.rel_path.clone();
|
||||
rel_parent.pop();
|
||||
let module_path = Self::find_module(&abs_path)?;
|
||||
let resolved_path = Self::resolve_module(&abs_path)?;
|
||||
|
||||
self.abs_path = abs_path;
|
||||
self.rel_path = Self::normalize_path(&rel_parent);
|
||||
self.module_path = module_path;
|
||||
self.resolved_path = resolved_path;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -258,21 +275,23 @@ impl Require for TextRequirer {
|
||||
fn to_child(&mut self, name: &str) -> StdResult<(), NavigateError> {
|
||||
let abs_path = self.abs_path.join(name);
|
||||
let rel_path = self.rel_path.join(name);
|
||||
let module_path = Self::find_module(&abs_path)?;
|
||||
let resolved_path = Self::resolve_module(&abs_path)?;
|
||||
|
||||
self.abs_path = abs_path;
|
||||
self.rel_path = rel_path;
|
||||
self.module_path = module_path;
|
||||
self.resolved_path = resolved_path;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn has_module(&self) -> bool {
|
||||
self.module_path.is_file()
|
||||
(self.resolved_path.as_deref())
|
||||
.map(Path::is_file)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn cache_key(&self) -> String {
|
||||
self.module_path.display().to_string()
|
||||
self.resolved_path.as_deref().unwrap().display().to_string()
|
||||
}
|
||||
|
||||
fn has_config(&self) -> bool {
|
||||
@@ -285,7 +304,9 @@ impl Require for TextRequirer {
|
||||
|
||||
fn loader(&self, lua: &Lua) -> Result<Function> {
|
||||
let name = format!("@{}", self.rel_path.display());
|
||||
lua.load(&*self.module_path).set_name(name).into_function()
|
||||
lua.load(self.resolved_path.as_deref().unwrap())
|
||||
.set_name(name)
|
||||
.into_function()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -496,7 +517,10 @@ unsafe fn write_to_buffer(
|
||||
}
|
||||
|
||||
#[cfg(feature = "luau")]
|
||||
pub fn create_require_function<R: Require + 'static>(lua: &Lua, require: R) -> Result<Function> {
|
||||
pub(super) fn create_require_function<R: Require + MaybeSend + 'static>(
|
||||
lua: &Lua,
|
||||
require: R,
|
||||
) -> Result<Function> {
|
||||
unsafe extern "C-unwind" fn find_current_file(state: *mut ffi::lua_State) -> c_int {
|
||||
let mut ar: ffi::lua_Debug = mem::zeroed();
|
||||
for level in 2.. {
|
||||
@@ -543,10 +567,26 @@ pub fn create_require_function<R: Require + 'static>(lua: &Lua, require: R) -> R
|
||||
1
|
||||
}
|
||||
|
||||
let (error, r#type) = unsafe {
|
||||
lua.exec_raw::<(Function, Function)>((), move |state| {
|
||||
unsafe extern "C-unwind" fn to_lowercase(state: *mut ffi::lua_State) -> c_int {
|
||||
let s = ffi::luaL_checkstring(state, 1);
|
||||
let s = CStr::from_ptr(s);
|
||||
if !s.to_bytes().iter().any(|&c| c.is_ascii_uppercase()) {
|
||||
// If the string does not contain any uppercase ASCII letters, return it as is
|
||||
return 1;
|
||||
}
|
||||
callback_error_ext(state, ptr::null_mut(), true, |extra, _| {
|
||||
let s = (s.to_bytes().iter())
|
||||
.map(|&c| c.to_ascii_lowercase())
|
||||
.collect::<bstr::BString>();
|
||||
(*extra).raw_lua().push(s).map(|_| 1)
|
||||
})
|
||||
}
|
||||
|
||||
let (error, r#type, to_lowercase) = unsafe {
|
||||
lua.exec_raw::<(Function, Function, Function)>((), move |state| {
|
||||
ffi::lua_pushcfunctiond(state, error, cstr!("error"));
|
||||
ffi::lua_pushcfunctiond(state, r#type, cstr!("type"));
|
||||
ffi::lua_pushcfunctiond(state, to_lowercase, cstr!("to_lowercase"));
|
||||
})
|
||||
}?;
|
||||
|
||||
@@ -559,6 +599,7 @@ pub fn create_require_function<R: Require + 'static>(lua: &Lua, require: R) -> R
|
||||
env.raw_set("LOADER_CACHE", loader_cache)?;
|
||||
env.raw_set("error", error)?;
|
||||
env.raw_set("type", r#type)?;
|
||||
env.raw_set("to_lowercase", to_lowercase)?;
|
||||
|
||||
lua.load(
|
||||
r#"
|
||||
@@ -568,7 +609,7 @@ pub fn create_require_function<R: Require + 'static>(lua: &Lua, require: R) -> R
|
||||
end
|
||||
|
||||
-- Check if the module (path) is explicitly registered
|
||||
local maybe_result = REGISTERED_MODULES[path]
|
||||
local maybe_result = REGISTERED_MODULES[to_lowercase(path)]
|
||||
if maybe_result ~= nil then
|
||||
return maybe_result
|
||||
end
|
||||
|
||||
@@ -297,6 +297,15 @@ impl<T: IntoLua> IntoLuaMulti for Variadic<T> {
|
||||
fn into_lua_multi(self, lua: &Lua) -> Result<MultiValue> {
|
||||
MultiValue::from_lua_iter(lua, self)
|
||||
}
|
||||
|
||||
unsafe fn push_into_stack_multi(self, lua: &RawLua) -> Result<c_int> {
|
||||
let nresults = self.len() as i32;
|
||||
check_stack(lua.state(), nresults + 1)?;
|
||||
for value in self.0 {
|
||||
value.push_into_stack(lua)?;
|
||||
}
|
||||
Ok(nresults)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: FromLua> FromLuaMulti for Variadic<T> {
|
||||
|
||||
+4
-2
@@ -25,7 +25,8 @@ pub use crate::HookTriggers as LuaHookTriggers;
|
||||
#[doc(no_inline)]
|
||||
pub use crate::{
|
||||
CompileConstant as LuaCompileConstant, CoverageInfo as LuaCoverageInfo,
|
||||
NavigateError as LuaNavigateError, Require as LuaRequire, Vector as LuaVector,
|
||||
NavigateError as LuaNavigateError, Require as LuaRequire, TextRequirer as LuaTextRequirer,
|
||||
Vector as LuaVector,
|
||||
};
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
@@ -35,5 +36,6 @@ pub use crate::{AsyncThread as LuaAsyncThread, LuaNativeAsyncFn};
|
||||
#[cfg(feature = "serde")]
|
||||
#[doc(no_inline)]
|
||||
pub use crate::{
|
||||
DeserializeOptions as LuaDeserializeOptions, LuaSerdeExt, SerializeOptions as LuaSerializeOptions,
|
||||
DeserializeOptions as LuaDeserializeOptions, LuaSerdeExt, SerializableValue as LuaSerializableValue,
|
||||
SerializeOptions as LuaSerializeOptions,
|
||||
};
|
||||
|
||||
+45
-10
@@ -15,11 +15,12 @@ use crate::userdata::AnyUserData;
|
||||
use crate::value::Value;
|
||||
|
||||
/// A struct for deserializing Lua values into Rust values.
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Deserializer {
|
||||
value: Value,
|
||||
options: Options,
|
||||
visited: Rc<RefCell<FxHashSet<*const c_void>>>,
|
||||
len: Option<usize>, // A length hint for sequences
|
||||
}
|
||||
|
||||
/// A struct with options to change default deserializer behavior.
|
||||
@@ -54,6 +55,19 @@ pub struct Options {
|
||||
///
|
||||
/// Default: **false**
|
||||
pub encode_empty_tables_as_array: bool,
|
||||
|
||||
/// If true, enable detection of mixed tables.
|
||||
///
|
||||
/// A mixed table is a table that has both array-like and map-like entries or several borders.
|
||||
/// See [`The Length Operator`] documentation for details about borders.
|
||||
///
|
||||
/// When this option is disabled, a table with a non-zero length (with one or more borders) will
|
||||
/// be always encoded as an array.
|
||||
///
|
||||
/// Default: **false**
|
||||
///
|
||||
/// [`The Length Operator`]: https://www.lua.org/manual/5.4/manual.html#3.4.7
|
||||
pub detect_mixed_tables: bool,
|
||||
}
|
||||
|
||||
impl Default for Options {
|
||||
@@ -70,6 +84,7 @@ impl Options {
|
||||
deny_recursive_tables: true,
|
||||
sort_keys: false,
|
||||
encode_empty_tables_as_array: false,
|
||||
detect_mixed_tables: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,6 +123,15 @@ impl Options {
|
||||
self.encode_empty_tables_as_array = enabled;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets [`detect_mixed_tables`] option.
|
||||
///
|
||||
/// [`detect_mixed_tables`]: #structfield.detect_mixed_tables
|
||||
#[must_use]
|
||||
pub const fn detect_mixed_tables(mut self, enable: bool) -> Self {
|
||||
self.detect_mixed_tables = enable;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Deserializer {
|
||||
@@ -121,7 +145,7 @@ impl Deserializer {
|
||||
Deserializer {
|
||||
value,
|
||||
options,
|
||||
visited: Rc::new(RefCell::new(FxHashSet::default())),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,8 +154,14 @@ impl Deserializer {
|
||||
value,
|
||||
options,
|
||||
visited,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn with_len(mut self, len: usize) -> Self {
|
||||
self.len = Some(len);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> serde::Deserializer<'de> for Deserializer {
|
||||
@@ -155,17 +185,22 @@ impl<'de> serde::Deserializer<'de> for Deserializer {
|
||||
Ok(s) => visitor.visit_str(&s),
|
||||
Err(_) => visitor.visit_bytes(&s.as_bytes()),
|
||||
},
|
||||
Value::Table(ref t) if t.raw_len() > 0 || t.is_array() => self.deserialize_seq(visitor),
|
||||
Value::Table(ref t) if self.options.encode_empty_tables_as_array && t.is_empty() => {
|
||||
self.deserialize_seq(visitor)
|
||||
Value::Table(ref t) => {
|
||||
if let Some(len) = t.encode_as_array(self.options) {
|
||||
self.with_len(len).deserialize_seq(visitor)
|
||||
} else {
|
||||
self.deserialize_map(visitor)
|
||||
}
|
||||
}
|
||||
Value::Table(_) => self.deserialize_map(visitor),
|
||||
Value::LightUserData(ud) if ud.0.is_null() => visitor.visit_none(),
|
||||
Value::UserData(ud) if ud.is_serializable() => {
|
||||
serde_userdata(ud, |value| value.deserialize_any(visitor))
|
||||
}
|
||||
#[cfg(feature = "luau")]
|
||||
Value::Buffer(buf) => visitor.visit_bytes(unsafe { buf.as_slice() }),
|
||||
Value::Buffer(buf) => {
|
||||
let lua = buf.0.lua.lock();
|
||||
visitor.visit_bytes(buf.as_slice(&lua))
|
||||
}
|
||||
Value::Function(_)
|
||||
| Value::Thread(_)
|
||||
| Value::UserData(_)
|
||||
@@ -267,14 +302,14 @@ impl<'de> serde::Deserializer<'de> for Deserializer {
|
||||
Value::Table(t) => {
|
||||
let _guard = RecursionGuard::new(&t, &self.visited);
|
||||
|
||||
let len = t.raw_len();
|
||||
let len = self.len.unwrap_or_else(|| t.raw_len());
|
||||
let mut deserializer = SeqDeserializer {
|
||||
seq: t.sequence_values(),
|
||||
seq: t.sequence_values().with_len(len),
|
||||
options: self.options,
|
||||
visited: self.visited,
|
||||
};
|
||||
let seq = visitor.visit_seq(&mut deserializer)?;
|
||||
if deserializer.seq.count() == 0 {
|
||||
if deserializer.seq.next().is_none() {
|
||||
Ok(seq)
|
||||
} else {
|
||||
Err(de::Error::invalid_length(len, &"fewer elements in the table"))
|
||||
|
||||
+3
-3
@@ -256,7 +256,7 @@ impl<'a> ser::Serializer for Serializer<'a> {
|
||||
fn serialize_seq(self, len: Option<usize>) -> Result<Self::SerializeSeq> {
|
||||
let table = self.lua.create_table_with_capacity(len.unwrap_or(0), 0)?;
|
||||
if self.options.set_array_metatable {
|
||||
table.set_metatable(Some(self.lua.array_metatable()));
|
||||
table.set_metatable(Some(self.lua.array_metatable()))?;
|
||||
}
|
||||
Ok(SerializeSeq::new(self.lua, table, self.options))
|
||||
}
|
||||
@@ -529,8 +529,8 @@ impl ser::SerializeStruct for SerializeStruct<'_> {
|
||||
fn end(self) -> Result<Value> {
|
||||
match self.inner {
|
||||
Some(table @ Value::Table(_)) => Ok(table),
|
||||
Some(value) if self.options.detect_serde_json_arbitrary_precision => {
|
||||
let number_s = value.as_str().expect("not an arbitrary precision number");
|
||||
Some(value @ Value::String(_)) if self.options.detect_serde_json_arbitrary_precision => {
|
||||
let number_s = value.to_string()?;
|
||||
if number_s.contains(['.', 'e', 'E']) {
|
||||
if let Ok(number) = number_s.parse().map(Value::Number) {
|
||||
return Ok(number);
|
||||
|
||||
+192
-37
@@ -8,9 +8,9 @@ use std::result::Result as StdResult;
|
||||
use std::{fmt, mem, ptr};
|
||||
|
||||
use crate::chunk::{AsChunk, Chunk};
|
||||
use crate::debug::Debug;
|
||||
use crate::error::{Error, Result};
|
||||
use crate::function::Function;
|
||||
use crate::hook::Debug;
|
||||
use crate::memory::MemoryState;
|
||||
use crate::multi::MultiValue;
|
||||
use crate::scope::Scope;
|
||||
@@ -28,7 +28,7 @@ use crate::util::{assert_stack, check_stack, protect_lua_closure, push_string, r
|
||||
use crate::value::{Nil, Value};
|
||||
|
||||
#[cfg(not(feature = "luau"))]
|
||||
use crate::{hook::HookTriggers, types::HookKind};
|
||||
use crate::{debug::HookTriggers, types::HookKind};
|
||||
|
||||
#[cfg(any(feature = "luau", doc))]
|
||||
use crate::{buffer::Buffer, chunk::Compiler};
|
||||
@@ -37,6 +37,7 @@ use crate::{buffer::Buffer, chunk::Compiler};
|
||||
use {
|
||||
crate::types::LightUserData,
|
||||
std::future::{self, Future},
|
||||
std::task::Poll,
|
||||
};
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
@@ -358,6 +359,8 @@ impl Lua {
|
||||
if cfg!(feature = "luau") && !modname.starts_with('@') {
|
||||
return Err(Error::runtime("module name must begin with '@'"));
|
||||
}
|
||||
#[cfg(feature = "luau")]
|
||||
let modname = modname.to_ascii_lowercase();
|
||||
unsafe {
|
||||
self.exec_raw::<()>(value, |state| {
|
||||
ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, LOADED_MODULES_KEY);
|
||||
@@ -544,7 +547,7 @@ impl Lua {
|
||||
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
|
||||
pub fn set_global_hook<F>(&self, triggers: HookTriggers, callback: F) -> Result<()>
|
||||
where
|
||||
F: Fn(&Lua, Debug) -> Result<VmState> + MaybeSend + 'static,
|
||||
F: Fn(&Lua, &Debug) -> Result<VmState> + MaybeSend + 'static,
|
||||
{
|
||||
let lua = self.lock();
|
||||
unsafe {
|
||||
@@ -577,7 +580,7 @@ impl Lua {
|
||||
/// # fn main() -> Result<()> {
|
||||
/// let lua = Lua::new();
|
||||
/// lua.set_hook(HookTriggers::EVERY_LINE, |_lua, debug| {
|
||||
/// println!("line {}", debug.curr_line());
|
||||
/// println!("line {:?}", debug.current_line());
|
||||
/// Ok(VmState::Continue)
|
||||
/// });
|
||||
///
|
||||
@@ -594,7 +597,7 @@ impl Lua {
|
||||
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
|
||||
pub fn set_hook<F>(&self, triggers: HookTriggers, callback: F) -> Result<()>
|
||||
where
|
||||
F: Fn(&Lua, Debug) -> Result<VmState> + MaybeSend + 'static,
|
||||
F: Fn(&Lua, &Debug) -> Result<VmState> + MaybeSend + 'static,
|
||||
{
|
||||
let lua = self.lock();
|
||||
unsafe { lua.set_thread_hook(lua.state(), HookKind::Thread(triggers, XRc::new(callback))) }
|
||||
@@ -629,13 +632,13 @@ impl Lua {
|
||||
///
|
||||
/// Any Luau code is guaranteed to call this handler "eventually"
|
||||
/// (in practice this can happen at any function call or at any loop iteration).
|
||||
/// This is similar to `Lua::set_hook` but in more simplified form.
|
||||
///
|
||||
/// The provided interrupt function can error, and this error will be propagated through
|
||||
/// the Luau code that was executing at the time the interrupt was triggered.
|
||||
/// Also this can be used to implement continuous execution limits by instructing Luau VM to
|
||||
/// yield by returning [`VmState::Yield`].
|
||||
///
|
||||
/// This is similar to `Lua::set_hook` but in more simplified form.
|
||||
/// yield by returning [`VmState::Yield`]. The yield will happen only at yieldable points
|
||||
/// of execution (not across metamethod/C-call boundaries).
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
@@ -693,7 +696,10 @@ impl Lua {
|
||||
match result {
|
||||
VmState::Continue => {}
|
||||
VmState::Yield => {
|
||||
ffi::lua_yield(state, 0);
|
||||
// We can yield only at yieldable points, otherwise ignore and continue
|
||||
if ffi::lua_isyieldable(state) != 0 {
|
||||
ffi::lua_yield(state, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -869,18 +875,16 @@ impl Lua {
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets information about the interpreter runtime stack.
|
||||
/// Gets information about the interpreter runtime stack at a given level.
|
||||
///
|
||||
/// This function returns [`Debug`] structure that can be used to get information about the
|
||||
/// function executing at a given level. Level `0` is the current running function, whereas
|
||||
/// level `n+1` is the function that has called level `n` (except for tail calls, which do
|
||||
/// not count in the stack).
|
||||
///
|
||||
/// [`Debug`]: crate::hook::Debug
|
||||
pub fn inspect_stack(&self, level: usize) -> Option<Debug<'_>> {
|
||||
/// This function calls callback `f`, passing the [`Debug`] structure that can be used to get
|
||||
/// information about the function executing at a given level.
|
||||
/// Level `0` is the current running function, whereas level `n+1` is the function that has
|
||||
/// called level `n` (except for tail calls, which do not count in the stack).
|
||||
pub fn inspect_stack<R>(&self, level: usize, f: impl FnOnce(&Debug) -> R) -> Option<R> {
|
||||
let lua = self.lock();
|
||||
unsafe {
|
||||
let mut ar: ffi::lua_Debug = mem::zeroed();
|
||||
let mut ar = mem::zeroed::<ffi::lua_Debug>();
|
||||
let level = level as c_int;
|
||||
#[cfg(not(feature = "luau"))]
|
||||
if ffi::lua_getstack(lua.state(), level, &mut ar) == 0 {
|
||||
@@ -890,7 +894,8 @@ impl Lua {
|
||||
if ffi::lua_getinfo(lua.state(), level, cstr!(""), &mut ar) == 0 {
|
||||
return None;
|
||||
}
|
||||
Some(Debug::new_owned(lua, level, ar))
|
||||
|
||||
Some(f(&Debug::new(&lua, level, &mut ar)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1149,7 +1154,7 @@ impl Lua {
|
||||
}
|
||||
}
|
||||
|
||||
/// Create and return an interned Lua string.
|
||||
/// Creates and returns an interned Lua string.
|
||||
///
|
||||
/// Lua strings can be arbitrary `[u8]` data including embedded nulls, so in addition to `&str`
|
||||
/// and `&String`, you can also pass plain `&[u8]` here.
|
||||
@@ -1158,27 +1163,32 @@ impl Lua {
|
||||
unsafe { self.lock().create_string(s) }
|
||||
}
|
||||
|
||||
/// Create and return a Luau [buffer] object from a byte slice of data.
|
||||
/// Creates and returns a Luau [buffer] object from a byte slice of data.
|
||||
///
|
||||
/// [buffer]: https://luau.org/library#buffer-library
|
||||
#[cfg(any(feature = "luau", doc))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
|
||||
pub fn create_buffer(&self, buf: impl AsRef<[u8]>) -> Result<Buffer> {
|
||||
pub fn create_buffer(&self, data: impl AsRef<[u8]>) -> Result<Buffer> {
|
||||
let lua = self.lock();
|
||||
let state = lua.state();
|
||||
let data = data.as_ref();
|
||||
unsafe {
|
||||
if lua.unlikely_memory_error() {
|
||||
crate::util::push_buffer(state, buf.as_ref(), false)?;
|
||||
return Ok(Buffer(lua.pop_ref()));
|
||||
}
|
||||
|
||||
let _sg = StackGuard::new(state);
|
||||
check_stack(state, 3)?;
|
||||
crate::util::push_buffer(state, buf.as_ref(), true)?;
|
||||
Ok(Buffer(lua.pop_ref()))
|
||||
let (ptr, buffer) = lua.create_buffer_with_capacity(data.len())?;
|
||||
ptr.copy_from_nonoverlapping(data.as_ptr(), data.len());
|
||||
Ok(buffer)
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates and returns a Luau [buffer] object with the specified size.
|
||||
///
|
||||
/// Size limit is 1GB. All bytes will be initialized to zero.
|
||||
///
|
||||
/// [buffer]: https://luau.org/library#buffer-library
|
||||
#[cfg(any(feature = "luau", doc))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
|
||||
pub fn create_buffer_with_capacity(&self, size: usize) -> Result<Buffer> {
|
||||
unsafe { Ok(self.lock().create_buffer_with_capacity(size)?.1) }
|
||||
}
|
||||
|
||||
/// Creates and returns a new empty table.
|
||||
#[inline]
|
||||
pub fn create_table(&self) -> Result<Table> {
|
||||
@@ -1287,8 +1297,24 @@ impl Lua {
|
||||
/// This function is unsafe because provides a way to execute unsafe C function.
|
||||
pub unsafe fn create_c_function(&self, func: ffi::lua_CFunction) -> Result<Function> {
|
||||
let lua = self.lock();
|
||||
ffi::lua_pushcfunction(lua.ref_thread(), func);
|
||||
Ok(Function(lua.pop_ref_thread()))
|
||||
if cfg!(any(feature = "lua54", feature = "lua53", feature = "lua52")) {
|
||||
ffi::lua_pushcfunction(lua.ref_thread(), func);
|
||||
return Ok(Function(lua.pop_ref_thread()));
|
||||
}
|
||||
|
||||
// Lua <5.2 requires memory allocation to push a C function
|
||||
let state = lua.state();
|
||||
{
|
||||
let _sg = StackGuard::new(state);
|
||||
check_stack(state, 3)?;
|
||||
|
||||
if lua.unlikely_memory_error() {
|
||||
ffi::lua_pushcfunction(state, func);
|
||||
} else {
|
||||
protect_lua!(state, 0, 1, |state| ffi::lua_pushcfunction(state, func))?;
|
||||
}
|
||||
Ok(Function(lua.pop_ref()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Wraps a Rust async function or closure, creating a callable Lua function handle to it.
|
||||
@@ -1417,7 +1443,7 @@ impl Lua {
|
||||
let lua = self.lock();
|
||||
unsafe {
|
||||
// Deregister the type if it already registered
|
||||
if let Some(&table_id) = (*lua.extra.get()).registered_userdata_t.get(&type_id) {
|
||||
if let Some(table_id) = (*lua.extra.get()).registered_userdata_t.remove(&type_id) {
|
||||
ffi::luaL_unref(lua.state(), ffi::LUA_REGISTRYINDEX, table_id);
|
||||
}
|
||||
|
||||
@@ -1551,6 +1577,39 @@ impl Lua {
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the global environment.
|
||||
///
|
||||
/// This will replace the current global environment with the provided `globals` table.
|
||||
///
|
||||
/// For Lua 5.2+ the globals table is stored in the registry and shared between all threads.
|
||||
/// For Lua 5.1 and Luau the globals table is stored in each thread.
|
||||
///
|
||||
/// Please note that any existing Lua functions have cached global environment and will not
|
||||
/// see the changes made by this method.
|
||||
/// To update the environment for existing Lua functions, use [`Function::set_environment`].
|
||||
pub fn set_globals(&self, globals: Table) -> Result<()> {
|
||||
let lua = self.lock();
|
||||
let state = lua.state();
|
||||
unsafe {
|
||||
#[cfg(feature = "luau")]
|
||||
if (*lua.extra.get()).sandboxed {
|
||||
return Err(Error::runtime("cannot change globals in a sandboxed Lua state"));
|
||||
}
|
||||
|
||||
let _sg = StackGuard::new(state);
|
||||
check_stack(state, 1)?;
|
||||
|
||||
lua.push_ref(&globals.0);
|
||||
|
||||
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
|
||||
ffi::lua_rawseti(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_RIDX_GLOBALS);
|
||||
#[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))]
|
||||
ffi::lua_replace(state, ffi::LUA_GLOBALSINDEX);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns a handle to the active `Thread`.
|
||||
///
|
||||
/// For calls to `Lua` this will be the main Lua thread, for parameters given to a callback,
|
||||
@@ -2021,6 +2080,101 @@ impl Lua {
|
||||
LightUserData(&ASYNC_POLL_TERMINATE as *const u8 as *mut std::os::raw::c_void)
|
||||
}
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
#[inline(always)]
|
||||
pub(crate) fn poll_yield() -> LightUserData {
|
||||
static ASYNC_POLL_YIELD: u8 = 0;
|
||||
LightUserData(&ASYNC_POLL_YIELD as *const u8 as *mut std::os::raw::c_void)
|
||||
}
|
||||
|
||||
/// Suspends the current async function, returning the provided arguments to caller.
|
||||
///
|
||||
/// This function is similar to [`coroutine.yield`] but allow yeilding Rust functions
|
||||
/// and passing values to the caller.
|
||||
/// Please note that you cannot cross [`Thread`] boundaries (e.g. calling `yield_with` on one
|
||||
/// thread and resuming on another).
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Async iterator:
|
||||
///
|
||||
/// ```
|
||||
/// # use mlua::{Lua, Result};
|
||||
/// #
|
||||
/// async fn generator(lua: Lua, _: ()) -> Result<()> {
|
||||
/// for i in 0..10 {
|
||||
/// lua.yield_with::<()>(i).await?;
|
||||
/// }
|
||||
/// Ok(())
|
||||
/// }
|
||||
///
|
||||
/// fn main() -> Result<()> {
|
||||
/// let lua = Lua::new();
|
||||
/// lua.globals().set("generator", lua.create_async_function(generator)?)?;
|
||||
///
|
||||
/// lua.load(r#"
|
||||
/// local n = 0
|
||||
/// for i in coroutine.wrap(generator) do
|
||||
/// n = n + i
|
||||
/// end
|
||||
/// assert(n == 45)
|
||||
/// "#)
|
||||
/// .exec()
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Exchange values on yield:
|
||||
///
|
||||
/// ```
|
||||
/// # use mlua::{Lua, Result, Value};
|
||||
/// #
|
||||
/// async fn pingpong(lua: Lua, mut val: i32) -> Result<()> {
|
||||
/// loop {
|
||||
/// val = lua.yield_with::<i32>(val).await? + 1;
|
||||
/// }
|
||||
/// Ok(())
|
||||
/// }
|
||||
///
|
||||
/// # fn main() -> Result<()> {
|
||||
/// let lua = Lua::new();
|
||||
///
|
||||
/// let co = lua.create_thread(lua.create_async_function(pingpong)?)?;
|
||||
/// assert_eq!(co.resume::<i32>(1)?, 1);
|
||||
/// assert_eq!(co.resume::<i32>(2)?, 3);
|
||||
/// assert_eq!(co.resume::<i32>(3)?, 4);
|
||||
///
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// [`coroutine.yield`]: https://www.lua.org/manual/5.4/manual.html#pdf-coroutine.yield
|
||||
#[cfg(feature = "async")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
|
||||
pub async fn yield_with<R: FromLuaMulti>(&self, args: impl IntoLuaMulti) -> Result<R> {
|
||||
let mut args = Some(args.into_lua_multi(self)?);
|
||||
future::poll_fn(move |_cx| match args.take() {
|
||||
Some(args) => unsafe {
|
||||
let lua = self.lock();
|
||||
lua.push(Self::poll_yield())?; // yield marker
|
||||
if args.len() <= 1 {
|
||||
lua.push(args.front())?;
|
||||
} else {
|
||||
lua.push(lua.create_sequence_from(&args)?)?;
|
||||
}
|
||||
lua.push(args.len())?;
|
||||
Poll::Pending
|
||||
},
|
||||
None => unsafe {
|
||||
let lua = self.lock();
|
||||
let state = lua.state();
|
||||
let _sg = StackGuard::with_top(state, 0);
|
||||
let nvals = ffi::lua_gettop(state);
|
||||
Poll::Ready(R::from_stack_multi(nvals, &lua))
|
||||
},
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Returns a weak reference to the Lua instance.
|
||||
///
|
||||
/// This is useful for creating a reference to the Lua instance that does not prevent it from
|
||||
@@ -2030,7 +2184,6 @@ impl Lua {
|
||||
WeakLua(XRc::downgrade(&self.raw))
|
||||
}
|
||||
|
||||
// Luau version located in `luau/mod.rs`
|
||||
#[cfg(not(feature = "luau"))]
|
||||
fn disable_c_modules(&self) -> Result<()> {
|
||||
let package: Table = self.globals().get("package")?;
|
||||
@@ -2053,7 +2206,9 @@ impl Lua {
|
||||
|
||||
// The third and fourth searchers looks for a loader as a C library
|
||||
searchers.raw_set(3, loader)?;
|
||||
searchers.raw_remove(4)?;
|
||||
if searchers.raw_len() >= 4 {
|
||||
searchers.raw_remove(4)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+3
-3
@@ -64,7 +64,7 @@ pub(crate) struct ExtraData {
|
||||
pub(super) wrapped_failure_top: usize,
|
||||
// Pool of `Thread`s (coroutines) for async execution
|
||||
#[cfg(feature = "async")]
|
||||
pub(super) thread_pool: Vec<c_int>,
|
||||
pub(super) thread_pool: Vec<crate::types::ValueRefIndex>,
|
||||
|
||||
// Address of `WrappedFailure` metatable
|
||||
pub(super) wrapped_failure_mt_ptr: *const c_void,
|
||||
@@ -76,7 +76,7 @@ pub(crate) struct ExtraData {
|
||||
#[cfg(not(feature = "luau"))]
|
||||
pub(super) hook_callback: Option<crate::types::HookCallback>,
|
||||
#[cfg(not(feature = "luau"))]
|
||||
pub(super) hook_triggers: crate::hook::HookTriggers,
|
||||
pub(super) hook_triggers: crate::debug::HookTriggers,
|
||||
#[cfg(feature = "lua54")]
|
||||
pub(super) warn_callback: Option<crate::types::WarnCallback>,
|
||||
#[cfg(feature = "luau")]
|
||||
@@ -270,7 +270,7 @@ impl ExtraData {
|
||||
// Try to grow max stack size
|
||||
if self.ref_stack_top >= self.ref_stack_size {
|
||||
let mut inc = self.ref_stack_size; // Try to double stack size
|
||||
while inc > 0 && ffi::lua_checkstack(self.ref_thread, inc) == 0 {
|
||||
while inc > 0 && ffi::lua_checkstack(self.ref_thread, inc + REF_STACK_RESERVE) == 0 {
|
||||
inc /= 2;
|
||||
}
|
||||
if inc == 0 {
|
||||
|
||||
+67
-28
@@ -28,8 +28,8 @@ use crate::userdata::{
|
||||
use crate::util::{
|
||||
assert_stack, check_stack, get_destructed_userdata_metatable, get_internal_userdata, get_main_state,
|
||||
get_metatable_ptr, get_userdata, init_error_registry, init_internal_metatable, pop_error,
|
||||
push_internal_userdata, push_string, push_table, rawset_field, safe_pcall, safe_xpcall, short_type_name,
|
||||
StackGuard, WrappedFailure,
|
||||
push_internal_userdata, push_string, push_table, push_userdata, rawset_field, safe_pcall, safe_xpcall,
|
||||
short_type_name, StackGuard, WrappedFailure,
|
||||
};
|
||||
use crate::value::{Nil, Value};
|
||||
|
||||
@@ -38,7 +38,7 @@ use super::{Lua, LuaOptions, WeakLua};
|
||||
|
||||
#[cfg(not(feature = "luau"))]
|
||||
use crate::{
|
||||
hook::Debug,
|
||||
debug::Debug,
|
||||
types::{HookCallback, HookKind, VmState},
|
||||
};
|
||||
|
||||
@@ -296,7 +296,7 @@ impl RawLua {
|
||||
if is_safe {
|
||||
let curr_libs = (*self.extra.get()).libs;
|
||||
if (curr_libs ^ (curr_libs | libs)).contains(StdLib::PACKAGE) {
|
||||
mlua_expect!(self.lua().disable_c_modules(), "Error during disabling C modules");
|
||||
mlua_expect!(self.lua().disable_c_modules(), "Error disabling C modules");
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "luau")]
|
||||
@@ -435,8 +435,8 @@ impl RawLua {
|
||||
match (*extra).hook_callback.clone() {
|
||||
Some(hook_callback) => {
|
||||
let rawlua = (*extra).raw_lua();
|
||||
let debug = Debug::new(rawlua, ar);
|
||||
hook_callback((*extra).lua(), debug)
|
||||
let debug = Debug::new(rawlua, 0, ar);
|
||||
hook_callback((*extra).lua(), &debug)
|
||||
}
|
||||
None => {
|
||||
ffi::lua_sethook(state, None, 0, 0);
|
||||
@@ -465,9 +465,9 @@ impl RawLua {
|
||||
|
||||
let status = callback_error_ext(state, ptr::null_mut(), false, |extra, _| {
|
||||
let rawlua = (*extra).raw_lua();
|
||||
let debug = Debug::new(rawlua, ar);
|
||||
let debug = Debug::new(rawlua, 0, ar);
|
||||
let hook_callback = (*hook_callback_ptr).clone();
|
||||
hook_callback((*extra).lua(), debug)
|
||||
hook_callback((*extra).lua(), &debug)
|
||||
});
|
||||
process_status(state, (*ar).event, status)
|
||||
}
|
||||
@@ -523,6 +523,20 @@ impl RawLua {
|
||||
Ok(String(self.pop_ref()))
|
||||
}
|
||||
|
||||
#[cfg(feature = "luau")]
|
||||
pub(crate) unsafe fn create_buffer_with_capacity(&self, size: usize) -> Result<(*mut u8, crate::Buffer)> {
|
||||
let state = self.state();
|
||||
if self.unlikely_memory_error() {
|
||||
let ptr = crate::util::push_buffer(state, size, false)?;
|
||||
return Ok((ptr, crate::Buffer(self.pop_ref())));
|
||||
}
|
||||
|
||||
let _sg = StackGuard::new(state);
|
||||
check_stack(state, 3)?;
|
||||
let ptr = crate::util::push_buffer(state, size, true)?;
|
||||
Ok((ptr, crate::Buffer(self.pop_ref())))
|
||||
}
|
||||
|
||||
/// See [`Lua::create_table_with_capacity`]
|
||||
pub(crate) unsafe fn create_table_with_capacity(&self, narr: usize, nrec: usize) -> Result<Table> {
|
||||
let state = self.state();
|
||||
@@ -624,7 +638,7 @@ impl RawLua {
|
||||
#[cfg(feature = "async")]
|
||||
pub(crate) unsafe fn create_recycled_thread(&self, func: &Function) -> Result<Thread> {
|
||||
if let Some(index) = (*self.extra.get()).thread_pool.pop() {
|
||||
let thread_state = ffi::lua_tothread(self.ref_thread(), index);
|
||||
let thread_state = ffi::lua_tothread(self.ref_thread(), *index.0);
|
||||
ffi::lua_xpush(self.ref_thread(), thread_state, func.0.index);
|
||||
|
||||
#[cfg(feature = "luau")]
|
||||
@@ -645,8 +659,9 @@ impl RawLua {
|
||||
pub(crate) unsafe fn recycle_thread(&self, thread: &mut Thread) {
|
||||
let extra = &mut *self.extra.get();
|
||||
if extra.thread_pool.len() < extra.thread_pool.capacity() {
|
||||
extra.thread_pool.push(thread.0.index);
|
||||
thread.0.drop = false; // Prevent thread from being garbage collected
|
||||
if let Some(index) = thread.0.index_count.take() {
|
||||
extra.thread_pool.push(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -728,7 +743,7 @@ impl RawLua {
|
||||
|
||||
let n = ffi::lua_tonumber(state, idx);
|
||||
match num_traits::cast(n) {
|
||||
Some(i) if (n - (i as Number)).abs() < Number::EPSILON => Value::Integer(i),
|
||||
Some(i) if n.to_bits() == (i as Number).to_bits() => Value::Integer(i),
|
||||
_ => Value::Number(n),
|
||||
}
|
||||
}
|
||||
@@ -827,13 +842,6 @@ impl RawLua {
|
||||
ValueRef::new(self, index)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) unsafe fn clone_ref(&self, vref: &ValueRef) -> ValueRef {
|
||||
ffi::lua_pushvalue(self.ref_thread(), vref.index);
|
||||
let index = (*self.extra.get()).ref_stack_pop();
|
||||
ValueRef::new(self, index)
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn drop_ref(&self, vref: &ValueRef) {
|
||||
let ref_thread = self.ref_thread();
|
||||
mlua_debug_assert!(
|
||||
@@ -920,7 +928,7 @@ impl RawLua {
|
||||
// We generate metatable first to make sure it *always* available when userdata pushed
|
||||
let mt_id = get_metatable_id()?;
|
||||
let protect = !self.unlikely_memory_error();
|
||||
crate::util::push_userdata(state, data, protect)?;
|
||||
push_userdata(state, data, protect)?;
|
||||
ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, mt_id);
|
||||
ffi::lua_setmetatable(state, -2);
|
||||
|
||||
@@ -1048,6 +1056,18 @@ impl RawLua {
|
||||
field_setters_index = Some(ffi::lua_absindex(state, -1));
|
||||
}
|
||||
|
||||
// Create methods namecall table
|
||||
#[cfg_attr(not(feature = "luau"), allow(unused_mut))]
|
||||
let mut methods_map = None;
|
||||
#[cfg(feature = "luau")]
|
||||
if registry.enable_namecall {
|
||||
let map: &mut rustc_hash::FxHashMap<_, crate::types::CallbackPtr> =
|
||||
methods_map.get_or_insert_with(Default::default);
|
||||
for (k, m) in ®istry.methods {
|
||||
map.insert(k.as_bytes().to_vec(), &**m);
|
||||
}
|
||||
}
|
||||
|
||||
let mut methods_index = None;
|
||||
let methods_nrec = registry.methods.len();
|
||||
#[cfg(feature = "async")]
|
||||
@@ -1095,6 +1115,7 @@ impl RawLua {
|
||||
field_getters_index,
|
||||
field_setters_index,
|
||||
methods_index,
|
||||
methods_map,
|
||||
)?;
|
||||
|
||||
// Update stack guard to keep metatable after return
|
||||
@@ -1270,6 +1291,13 @@ impl RawLua {
|
||||
let mut ctx = Context::from_waker(rawlua.waker());
|
||||
match fut.as_mut().map(|fut| fut.as_mut().poll(&mut ctx)) {
|
||||
Some(Poll::Pending) => {
|
||||
let fut_nvals = ffi::lua_gettop(state);
|
||||
if fut_nvals >= 3 && ffi::lua_tolightuserdata(state, -3) == Lua::poll_yield().0 {
|
||||
// We have some values to yield
|
||||
ffi::lua_pushnil(state);
|
||||
ffi::lua_replace(state, -4);
|
||||
return Ok(3);
|
||||
}
|
||||
ffi::lua_pushnil(state);
|
||||
ffi::lua_pushlightuserdata(state, Lua::poll_pending().0);
|
||||
Ok(2)
|
||||
@@ -1340,6 +1368,7 @@ impl RawLua {
|
||||
local poll = get_poll(...)
|
||||
local nres, res, res2 = poll()
|
||||
while true do
|
||||
-- Poll::Ready branch, `nres` is the number of results
|
||||
if nres ~= nil then
|
||||
if nres == 0 then
|
||||
return
|
||||
@@ -1355,10 +1384,20 @@ impl RawLua {
|
||||
return unpack(res, nres)
|
||||
end
|
||||
end
|
||||
-- `res` is a "pending" value
|
||||
-- `yield` can return a signal to drop the future that we should propagate
|
||||
-- to the poller
|
||||
nres, res, res2 = poll(yield(res))
|
||||
|
||||
-- Poll::Pending branch
|
||||
if res2 == nil then
|
||||
-- `res` is a "pending" value
|
||||
-- `yield` can return a signal to drop the future that we should propagate
|
||||
-- to the poller
|
||||
nres, res, res2 = poll(yield(res))
|
||||
elseif res2 == 0 then
|
||||
nres, res, res2 = poll(yield())
|
||||
elseif res2 == 1 then
|
||||
nres, res, res2 = poll(yield(res))
|
||||
else
|
||||
nres, res, res2 = poll(yield(unpack(res, res2)))
|
||||
end
|
||||
end
|
||||
"#,
|
||||
)
|
||||
@@ -1370,14 +1409,14 @@ impl RawLua {
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
#[inline]
|
||||
pub(crate) unsafe fn waker(&self) -> &Waker {
|
||||
(*self.extra.get()).waker.as_ref()
|
||||
pub(crate) fn waker(&self) -> &Waker {
|
||||
unsafe { (*self.extra.get()).waker.as_ref() }
|
||||
}
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
#[inline]
|
||||
pub(crate) unsafe fn set_waker(&self, waker: NonNull<Waker>) -> NonNull<Waker> {
|
||||
mem::replace(&mut (*self.extra.get()).waker, waker)
|
||||
pub(crate) fn set_waker(&self, waker: NonNull<Waker>) -> NonNull<Waker> {
|
||||
unsafe { mem::replace(&mut (*self.extra.get()).waker, waker) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-4
@@ -86,7 +86,7 @@ impl String {
|
||||
|
||||
/// Get the bytes that make up this string.
|
||||
///
|
||||
/// The returned slice will not contain the terminating nul byte, but will contain any nul
|
||||
/// The returned slice will not contain the terminating null byte, but will contain any null
|
||||
/// bytes embedded into the Lua string.
|
||||
///
|
||||
/// # Examples
|
||||
@@ -106,15 +106,15 @@ impl String {
|
||||
BorrowedBytes::from(self)
|
||||
}
|
||||
|
||||
/// Get the bytes that make up this string, including the trailing nul byte.
|
||||
/// Get the bytes that make up this string, including the trailing null byte.
|
||||
pub fn as_bytes_with_nul(&self) -> BorrowedBytes<'_> {
|
||||
let BorrowedBytes { buf, borrow, _lua } = BorrowedBytes::from(self);
|
||||
// Include the trailing nul byte (it's always present but excluded by default)
|
||||
// Include the trailing null byte (it's always present but excluded by default)
|
||||
let buf = unsafe { slice::from_raw_parts((*buf).as_ptr(), (*buf).len() + 1) };
|
||||
BorrowedBytes { buf, borrow, _lua }
|
||||
}
|
||||
|
||||
// Does not return the terminating nul byte
|
||||
// Does not return the terminating null byte
|
||||
unsafe fn to_slice(&self) -> (&[u8], Lua) {
|
||||
let lua = self.0.lua.upgrade();
|
||||
let slice = {
|
||||
|
||||
+135
-58
@@ -6,14 +6,14 @@ use std::string::String as StdString;
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
use crate::function::Function;
|
||||
use crate::state::{LuaGuard, RawLua};
|
||||
use crate::state::{LuaGuard, RawLua, WeakLua};
|
||||
use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, ObjectLike};
|
||||
use crate::types::{Integer, LuaType, ValueRef};
|
||||
use crate::util::{assert_stack, check_stack, get_metatable_ptr, StackGuard};
|
||||
use crate::value::{Nil, Value};
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
use futures_util::future::{self, Either, Future};
|
||||
use crate::function::AsyncCallFuture;
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
use {
|
||||
@@ -211,7 +211,7 @@ impl Table {
|
||||
///
|
||||
/// let always_equals_mt = lua.create_table()?;
|
||||
/// always_equals_mt.set("__eq", lua.create_function(|_, (_t1, _t2): (Table, Table)| Ok(true))?)?;
|
||||
/// table2.set_metatable(Some(always_equals_mt));
|
||||
/// table2.set_metatable(Some(always_equals_mt))?;
|
||||
///
|
||||
/// assert!(table1.equals(&table1.clone())?);
|
||||
/// assert!(table1.equals(&table2)?);
|
||||
@@ -416,14 +416,7 @@ impl Table {
|
||||
|
||||
lua.push_ref(&self.0);
|
||||
|
||||
// Clear array part
|
||||
for i in 1..=ffi::lua_rawlen(state, -1) {
|
||||
ffi::lua_pushnil(state);
|
||||
ffi::lua_rawseti(state, -2, i as Integer);
|
||||
}
|
||||
|
||||
// Clear hash part
|
||||
// It must be safe as long as we don't use invalid keys
|
||||
// This is safe as long as we don't assign new keys
|
||||
ffi::lua_pushnil(state);
|
||||
while ffi::lua_next(state, -2) != 0 {
|
||||
ffi::lua_pop(state, 1); // pop value
|
||||
@@ -487,16 +480,12 @@ impl Table {
|
||||
/// [`getmetatable`]: https://www.lua.org/manual/5.4/manual.html#pdf-getmetatable
|
||||
pub fn metatable(&self) -> Option<Table> {
|
||||
let lua = self.0.lua.lock();
|
||||
let state = lua.state();
|
||||
let ref_thread = lua.ref_thread();
|
||||
unsafe {
|
||||
let _sg = StackGuard::new(state);
|
||||
assert_stack(state, 2);
|
||||
|
||||
lua.push_ref(&self.0);
|
||||
if ffi::lua_getmetatable(state, -1) == 0 {
|
||||
if ffi::lua_getmetatable(ref_thread, self.0.index) == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(Table(lua.pop_ref()))
|
||||
Some(Table(lua.pop_ref_thread()))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -505,27 +494,23 @@ impl Table {
|
||||
///
|
||||
/// If `metatable` is `None`, the metatable is removed (if no metatable is set, this does
|
||||
/// nothing).
|
||||
pub fn set_metatable(&self, metatable: Option<Table>) {
|
||||
// Workaround to throw readonly error without returning Result
|
||||
pub fn set_metatable(&self, metatable: Option<Table>) -> Result<()> {
|
||||
#[cfg(feature = "luau")]
|
||||
if self.is_readonly() {
|
||||
panic!("attempt to modify a readonly table");
|
||||
return Err(Error::runtime("attempt to modify a readonly table"));
|
||||
}
|
||||
|
||||
let lua = self.0.lua.lock();
|
||||
let state = lua.state();
|
||||
let ref_thread = lua.ref_thread();
|
||||
unsafe {
|
||||
let _sg = StackGuard::new(state);
|
||||
assert_stack(state, 2);
|
||||
|
||||
lua.push_ref(&self.0);
|
||||
if let Some(metatable) = metatable {
|
||||
lua.push_ref(&metatable.0);
|
||||
if let Some(metatable) = &metatable {
|
||||
ffi::lua_pushvalue(ref_thread, metatable.0.index);
|
||||
} else {
|
||||
ffi::lua_pushnil(state);
|
||||
ffi::lua_pushnil(ref_thread);
|
||||
}
|
||||
ffi::lua_setmetatable(state, -2);
|
||||
ffi::lua_setmetatable(ref_thread, self.0.index);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns true if the table has metatable attached.
|
||||
@@ -683,16 +668,25 @@ impl Table {
|
||||
guard: self.0.lua.lock(),
|
||||
table: self,
|
||||
index: 1,
|
||||
len: None,
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterates over the sequence part of the table, invoking the given closure on each value.
|
||||
///
|
||||
/// This methods is similar to [`Table::sequence_values`], but optimized for performance.
|
||||
#[doc(hidden)]
|
||||
pub fn for_each_value<V>(&self, mut f: impl FnMut(V) -> Result<()>) -> Result<()>
|
||||
where
|
||||
V: FromLua,
|
||||
{
|
||||
pub fn for_each_value<V: FromLua>(&self, f: impl FnMut(V) -> Result<()>) -> Result<()> {
|
||||
self.for_each_value_by_len(None, f)
|
||||
}
|
||||
|
||||
fn for_each_value_by_len<V: FromLua>(
|
||||
&self,
|
||||
len: impl Into<Option<usize>>,
|
||||
mut f: impl FnMut(V) -> Result<()>,
|
||||
) -> Result<()> {
|
||||
let len = len.into();
|
||||
let lua = self.0.lua.lock();
|
||||
let state = lua.state();
|
||||
unsafe {
|
||||
@@ -700,9 +694,14 @@ impl Table {
|
||||
check_stack(state, 4)?;
|
||||
|
||||
lua.push_ref(&self.0);
|
||||
let len = ffi::lua_rawlen(state, -1);
|
||||
for i in 1..=len {
|
||||
ffi::lua_rawgeti(state, -1, i as _);
|
||||
for i in 1.. {
|
||||
if len.map(|len| i > len).unwrap_or(false) {
|
||||
break;
|
||||
}
|
||||
let t = ffi::lua_rawgeti(state, -1, i as _);
|
||||
if len.is_none() && t == ffi::LUA_TNIL {
|
||||
break;
|
||||
}
|
||||
f(V::from_stack(-1, &lua)?)?;
|
||||
ffi::lua_pop(state, 1);
|
||||
}
|
||||
@@ -735,8 +734,9 @@ impl Table {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Checks if the table has the array metatable attached.
|
||||
#[cfg(feature = "serde")]
|
||||
pub(crate) fn is_array(&self) -> bool {
|
||||
fn has_array_metatable(&self) -> bool {
|
||||
let lua = self.0.lua.lock();
|
||||
let state = lua.state();
|
||||
unsafe {
|
||||
@@ -752,6 +752,70 @@ impl Table {
|
||||
}
|
||||
}
|
||||
|
||||
/// If the table is an array, returns the number of non-nil elements and max index.
|
||||
///
|
||||
/// Returns `None` if the table is not an array.
|
||||
///
|
||||
/// This operation has O(n) complexity.
|
||||
#[cfg(feature = "serde")]
|
||||
fn find_array_len(&self) -> Option<(usize, usize)> {
|
||||
let lua = self.0.lua.lock();
|
||||
let ref_thread = lua.ref_thread();
|
||||
unsafe {
|
||||
let _sg = StackGuard::new(ref_thread);
|
||||
|
||||
let (mut count, mut max_index) = (0, 0);
|
||||
ffi::lua_pushnil(ref_thread);
|
||||
while ffi::lua_next(ref_thread, self.0.index) != 0 {
|
||||
if ffi::lua_type(ref_thread, -2) != ffi::LUA_TNUMBER {
|
||||
return None;
|
||||
}
|
||||
|
||||
let k = ffi::lua_tonumber(ref_thread, -2);
|
||||
if k.trunc() != k || k < 1.0 {
|
||||
return None;
|
||||
}
|
||||
max_index = std::cmp::max(max_index, k as usize);
|
||||
count += 1;
|
||||
ffi::lua_pop(ref_thread, 1);
|
||||
}
|
||||
Some((count, max_index))
|
||||
}
|
||||
}
|
||||
|
||||
/// Determines if the table should be encoded as an array or a map.
|
||||
///
|
||||
/// The algorithm is the following:
|
||||
/// 1. If `detect_mixed_tables` is enabled, iterate over all keys in the table checking is they
|
||||
/// all are positive integers. If non-array key is found, return `None` (encode as map).
|
||||
/// Otherwise check the sparsity of the array. Too sparse arrays are encoded as maps.
|
||||
///
|
||||
/// 2. If `detect_mixed_tables` is disabled, check if the table has a positive length or has the
|
||||
/// array metatable. If so, encode as array. If the table is empty and
|
||||
/// `encode_empty_tables_as_array` is enabled, encode as array.
|
||||
///
|
||||
/// Returns the length of the array if it should be encoded as an array.
|
||||
#[cfg(feature = "serde")]
|
||||
pub(crate) fn encode_as_array(&self, options: crate::serde::de::Options) -> Option<usize> {
|
||||
if options.detect_mixed_tables {
|
||||
if let Some((len, max_idx)) = self.find_array_len() {
|
||||
// If the array is too sparse, serialize it as a map instead
|
||||
if len < 10 || len * 2 >= max_idx {
|
||||
return Some(max_idx);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let len = self.raw_len();
|
||||
if len > 0 || self.has_array_metatable() {
|
||||
return Some(len);
|
||||
}
|
||||
if options.encode_empty_tables_as_array && self.is_empty() {
|
||||
return Some(0);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(feature = "luau")]
|
||||
#[inline(always)]
|
||||
fn check_readonly_write(&self, lua: &RawLua) -> Result<()> {
|
||||
@@ -892,16 +956,16 @@ impl ObjectLike for Table {
|
||||
R: FromLuaMulti,
|
||||
{
|
||||
// Convert table to a function and call via pcall that respects the `__call` metamethod.
|
||||
Function(self.0.copy()).call(args)
|
||||
Function(self.0.clone()).call(args)
|
||||
}
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
#[inline]
|
||||
fn call_async<R>(&self, args: impl IntoLuaMulti) -> impl Future<Output = Result<R>>
|
||||
fn call_async<R>(&self, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
|
||||
where
|
||||
R: FromLuaMulti,
|
||||
{
|
||||
Function(self.0.copy()).call_async(args)
|
||||
Function(self.0.clone()).call_async(args)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -913,7 +977,7 @@ impl ObjectLike for Table {
|
||||
}
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
fn call_async_method<R>(&self, name: &str, args: impl IntoLuaMulti) -> impl Future<Output = Result<R>>
|
||||
fn call_async_method<R>(&self, name: &str, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
|
||||
where
|
||||
R: FromLuaMulti,
|
||||
{
|
||||
@@ -933,23 +997,33 @@ impl ObjectLike for Table {
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
#[inline]
|
||||
fn call_async_function<R>(&self, name: &str, args: impl IntoLuaMulti) -> impl Future<Output = Result<R>>
|
||||
fn call_async_function<R>(&self, name: &str, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
|
||||
where
|
||||
R: FromLuaMulti,
|
||||
{
|
||||
match self.get(name) {
|
||||
Ok(Value::Function(func)) => Either::Left(func.call_async(args)),
|
||||
Ok(Value::Function(func)) => func.call_async(args),
|
||||
Ok(val) => {
|
||||
let msg = format!("attempt to call a {} value (function '{name}')", val.type_name());
|
||||
Either::Right(future::ready(Err(Error::RuntimeError(msg))))
|
||||
AsyncCallFuture::error(Error::RuntimeError(msg))
|
||||
}
|
||||
Err(err) => Either::Right(future::ready(Err(err))),
|
||||
Err(err) => AsyncCallFuture::error(err),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn to_string(&self) -> Result<StdString> {
|
||||
Value::Table(Table(self.0.copy())).to_string()
|
||||
Value::Table(Table(self.0.clone())).to_string()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn to_value(&self) -> Value {
|
||||
Value::Table(self.clone())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn weak_lua(&self) -> &WeakLua {
|
||||
&self.0.lua
|
||||
}
|
||||
}
|
||||
|
||||
@@ -985,6 +1059,15 @@ impl<'a> SerializableTable<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> TableSequence<'_, V> {
|
||||
/// Sets the length (hint) of the sequence.
|
||||
#[cfg(feature = "serde")]
|
||||
pub(crate) fn with_len(mut self, len: usize) -> Self {
|
||||
self.len = Some(len);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl Serialize for SerializableTable<'_> {
|
||||
fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
|
||||
@@ -1006,14 +1089,10 @@ impl Serialize for SerializableTable<'_> {
|
||||
let _guard = RecursionGuard::new(self.table, visited);
|
||||
|
||||
// Array
|
||||
let len = self.table.raw_len();
|
||||
if len > 0
|
||||
|| self.table.is_array()
|
||||
|| (self.options.encode_empty_tables_as_array && self.table.is_empty())
|
||||
{
|
||||
if let Some(len) = self.table.encode_as_array(self.options) {
|
||||
let mut seq = serializer.serialize_seq(Some(len))?;
|
||||
let mut serialize_err = None;
|
||||
let res = self.table.for_each_value::<Value>(|value| {
|
||||
let res = self.table.for_each_value_by_len::<Value>(len, |value| {
|
||||
let skip = check_value_for_skip(&value, self.options, visited)
|
||||
.map_err(|err| Error::SerializeError(err.to_string()))?;
|
||||
if skip {
|
||||
@@ -1137,13 +1216,11 @@ pub struct TableSequence<'a, V> {
|
||||
guard: LuaGuard,
|
||||
table: &'a Table,
|
||||
index: Integer,
|
||||
len: Option<usize>,
|
||||
_phantom: PhantomData<V>,
|
||||
}
|
||||
|
||||
impl<V> Iterator for TableSequence<'_, V>
|
||||
where
|
||||
V: FromLua,
|
||||
{
|
||||
impl<V: FromLua> Iterator for TableSequence<'_, V> {
|
||||
type Item = Result<V>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
@@ -1157,7 +1234,7 @@ where
|
||||
|
||||
lua.push_ref(&self.table.0);
|
||||
match ffi::lua_rawgeti(state, -1, self.index) {
|
||||
ffi::LUA_TNIL => None,
|
||||
ffi::LUA_TNIL if self.index as usize > self.len.unwrap_or(0) => None,
|
||||
_ => {
|
||||
self.index += 1;
|
||||
Some(V::from_stack(-1, lua))
|
||||
|
||||
+11
-9
@@ -10,7 +10,7 @@ use crate::util::{check_stack, error_traceback_thread, pop_error, StackGuard};
|
||||
|
||||
#[cfg(not(feature = "luau"))]
|
||||
use crate::{
|
||||
hook::{Debug, HookTriggers},
|
||||
debug::{Debug, HookTriggers},
|
||||
types::HookKind,
|
||||
};
|
||||
|
||||
@@ -86,13 +86,15 @@ unsafe impl Sync for Thread {}
|
||||
#[must_use = "futures do nothing unless you `.await` or poll them"]
|
||||
pub struct AsyncThread<R> {
|
||||
thread: Thread,
|
||||
ret: PhantomData<R>,
|
||||
ret: PhantomData<fn() -> R>,
|
||||
recycle: bool,
|
||||
}
|
||||
|
||||
impl Thread {
|
||||
/// Returns reference to the Lua state that this thread is associated with.
|
||||
#[doc(hidden)]
|
||||
#[inline(always)]
|
||||
fn state(&self) -> *mut ffi::lua_State {
|
||||
pub fn state(&self) -> *mut ffi::lua_State {
|
||||
self.1
|
||||
}
|
||||
|
||||
@@ -154,7 +156,6 @@ impl Thread {
|
||||
let thread_state = self.state();
|
||||
unsafe {
|
||||
let _sg = StackGuard::new(state);
|
||||
let _thread_sg = StackGuard::with_top(thread_state, 0);
|
||||
|
||||
let nargs = args.push_into_stack_multi(&lua)?;
|
||||
if nargs > 0 {
|
||||
@@ -163,6 +164,7 @@ impl Thread {
|
||||
pushed_nargs += nargs;
|
||||
}
|
||||
|
||||
let _thread_sg = StackGuard::with_top(thread_state, 0);
|
||||
let (_, nresults) = self.resume_inner(&lua, pushed_nargs)?;
|
||||
check_stack(state, nresults + 1)?;
|
||||
ffi::lua_xmove(thread_state, state, nresults);
|
||||
@@ -190,12 +192,12 @@ impl Thread {
|
||||
let thread_state = self.state();
|
||||
unsafe {
|
||||
let _sg = StackGuard::new(state);
|
||||
let _thread_sg = StackGuard::with_top(thread_state, 0);
|
||||
|
||||
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)?;
|
||||
check_stack(state, nresults + 1)?;
|
||||
ffi::lua_xmove(thread_state, state, nresults);
|
||||
@@ -269,7 +271,7 @@ impl Thread {
|
||||
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
|
||||
pub fn set_hook<F>(&self, triggers: HookTriggers, callback: F) -> Result<()>
|
||||
where
|
||||
F: Fn(&crate::Lua, Debug) -> Result<crate::VmState> + crate::MaybeSend + 'static,
|
||||
F: Fn(&crate::Lua, &Debug) -> Result<crate::VmState> + crate::MaybeSend + 'static,
|
||||
{
|
||||
let lua = self.0.lua.lock();
|
||||
unsafe {
|
||||
@@ -602,7 +604,7 @@ impl<R: FromLuaMulti> Future for AsyncThread<R> {
|
||||
|
||||
if status.is_yielded() {
|
||||
if !(nresults == 1 && is_poll_pending(thread_state)) {
|
||||
// Ignore value returned via yield()
|
||||
// Ignore values returned via yield()
|
||||
cx.waker().wake_by_ref();
|
||||
}
|
||||
return Poll::Pending;
|
||||
@@ -633,7 +635,7 @@ struct WakerGuard<'lua, 'a> {
|
||||
impl<'lua, 'a> WakerGuard<'lua, 'a> {
|
||||
#[inline]
|
||||
pub fn new(lua: &'lua RawLua, waker: &'a Waker) -> Result<WakerGuard<'lua, 'a>> {
|
||||
let prev = unsafe { lua.set_waker(NonNull::from(waker)) };
|
||||
let prev = lua.set_waker(NonNull::from(waker));
|
||||
Ok(WakerGuard {
|
||||
lua,
|
||||
prev,
|
||||
@@ -645,7 +647,7 @@ impl<'lua, 'a> WakerGuard<'lua, 'a> {
|
||||
#[cfg(feature = "async")]
|
||||
impl Drop for WakerGuard<'_, '_> {
|
||||
fn drop(&mut self) {
|
||||
unsafe { self.lua.set_waker(self.prev) };
|
||||
self.lua.set_waker(self.prev);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+46
-6
@@ -5,13 +5,13 @@ use std::sync::Arc;
|
||||
use crate::error::{Error, Result};
|
||||
use crate::multi::MultiValue;
|
||||
use crate::private::Sealed;
|
||||
use crate::state::{Lua, RawLua};
|
||||
use crate::state::{Lua, RawLua, WeakLua};
|
||||
use crate::types::MaybeSend;
|
||||
use crate::util::{check_stack, short_type_name};
|
||||
use crate::util::{check_stack, parse_lookup_path, short_type_name};
|
||||
use crate::value::Value;
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
use std::future::Future;
|
||||
use {crate::function::AsyncCallFuture, std::future::Future};
|
||||
|
||||
/// Trait for types convertible to [`Value`].
|
||||
pub trait IntoLua: Sized {
|
||||
@@ -162,7 +162,7 @@ pub trait ObjectLike: Sealed {
|
||||
/// arguments.
|
||||
#[cfg(feature = "async")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
|
||||
fn call_async<R>(&self, args: impl IntoLuaMulti) -> impl Future<Output = Result<R>>
|
||||
fn call_async<R>(&self, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
|
||||
where
|
||||
R: FromLuaMulti;
|
||||
|
||||
@@ -178,7 +178,7 @@ pub trait ObjectLike: Sealed {
|
||||
/// This might invoke the `__index` metamethod.
|
||||
#[cfg(feature = "async")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
|
||||
fn call_async_method<R>(&self, name: &str, args: impl IntoLuaMulti) -> impl Future<Output = Result<R>>
|
||||
fn call_async_method<R>(&self, name: &str, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
|
||||
where
|
||||
R: FromLuaMulti;
|
||||
|
||||
@@ -196,14 +196,54 @@ pub trait ObjectLike: Sealed {
|
||||
/// This might invoke the `__index` metamethod.
|
||||
#[cfg(feature = "async")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
|
||||
fn call_async_function<R>(&self, name: &str, args: impl IntoLuaMulti) -> impl Future<Output = Result<R>>
|
||||
fn call_async_function<R>(&self, name: &str, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
|
||||
where
|
||||
R: FromLuaMulti;
|
||||
|
||||
/// Look up a value by a path of keys.
|
||||
///
|
||||
/// The syntax is similar to accessing nested tables in Lua, with additional support for
|
||||
/// `?` operator to perform safe navigation.
|
||||
///
|
||||
/// For example, the path `a[1].c` is equivalent to `table.a[1].c` in Lua.
|
||||
/// With `?` operator, `a[1]?.c` is equivalent to `table.a[1] and table.a[1].c or nil` in Lua.
|
||||
///
|
||||
/// Bracket notation rules:
|
||||
/// - `[123]` - integer keys
|
||||
/// - `["string key"]` or `['string key']` - string keys (must be quoted)
|
||||
/// - String keys support escape sequences: `\"`, `\'`, `\\`
|
||||
fn get_path<V: FromLua>(&self, path: &str) -> Result<V> {
|
||||
let mut current = self.to_value();
|
||||
for (key, safe_nil) in parse_lookup_path(path)? {
|
||||
current = match current {
|
||||
Value::Table(table) => table.get::<Value>(key),
|
||||
Value::UserData(ud) => ud.get::<Value>(key),
|
||||
_ => {
|
||||
let type_name = current.type_name();
|
||||
let err = format!("attempt to index a {type_name} value with key '{key}'");
|
||||
Err(Error::runtime(err))
|
||||
}
|
||||
}?;
|
||||
if safe_nil && (current == Value::Nil || current == Value::NULL) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let lua = self.weak_lua().lock();
|
||||
V::from_lua(current, lua.lua())
|
||||
}
|
||||
|
||||
/// Converts the object to a string in a human-readable format.
|
||||
///
|
||||
/// This might invoke the `__tostring` metamethod.
|
||||
fn to_string(&self) -> Result<StdString>;
|
||||
|
||||
/// Converts the object to a Lua value.
|
||||
fn to_value(&self) -> Value;
|
||||
|
||||
/// Gets a reference to the associated Lua state.
|
||||
#[doc(hidden)]
|
||||
fn weak_lua(&self) -> &WeakLua;
|
||||
}
|
||||
|
||||
/// A trait for types that can be used as Lua functions.
|
||||
|
||||
+12
-6
@@ -1,9 +1,9 @@
|
||||
use std::cell::UnsafeCell;
|
||||
use std::os::raw::{c_int, c_void};
|
||||
|
||||
use crate::error::Result;
|
||||
#[cfg(not(feature = "luau"))]
|
||||
use crate::hook::{Debug, HookTriggers};
|
||||
use crate::debug::{Debug, HookTriggers};
|
||||
use crate::error::Result;
|
||||
use crate::state::{ExtraData, Lua, RawLua};
|
||||
|
||||
// Re-export mutex wrappers
|
||||
@@ -20,6 +20,9 @@ pub use either::Either;
|
||||
pub use registry_key::RegistryKey;
|
||||
pub(crate) use value_ref::ValueRef;
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
pub(crate) use value_ref::ValueRefIndex;
|
||||
|
||||
/// Type of Lua integer numbers.
|
||||
pub type Integer = ffi::lua_Integer;
|
||||
/// Type of Lua floating point numbers.
|
||||
@@ -35,10 +38,13 @@ unsafe impl Send for LightUserData {}
|
||||
unsafe impl Sync for LightUserData {}
|
||||
|
||||
#[cfg(feature = "send")]
|
||||
pub(crate) type Callback = Box<dyn Fn(&RawLua, c_int) -> Result<c_int> + Send + 'static>;
|
||||
type CallbackFn<'a> = dyn Fn(&RawLua, c_int) -> Result<c_int> + Send + 'a;
|
||||
|
||||
#[cfg(not(feature = "send"))]
|
||||
pub(crate) type Callback = Box<dyn Fn(&RawLua, c_int) -> Result<c_int> + 'static>;
|
||||
type CallbackFn<'a> = dyn Fn(&RawLua, c_int) -> Result<c_int> + 'a;
|
||||
|
||||
pub(crate) type Callback = Box<CallbackFn<'static>>;
|
||||
pub(crate) type CallbackPtr = *const CallbackFn<'static>;
|
||||
|
||||
pub(crate) type ScopedCallback<'s> = Box<dyn Fn(&RawLua, c_int) -> Result<c_int> + 's>;
|
||||
|
||||
@@ -79,10 +85,10 @@ pub(crate) enum HookKind {
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "send", not(feature = "luau")))]
|
||||
pub(crate) type HookCallback = XRc<dyn Fn(&Lua, Debug) -> Result<VmState> + Send>;
|
||||
pub(crate) type HookCallback = XRc<dyn Fn(&Lua, &Debug) -> Result<VmState> + Send>;
|
||||
|
||||
#[cfg(all(not(feature = "send"), not(feature = "luau")))]
|
||||
pub(crate) type HookCallback = XRc<dyn Fn(&Lua, Debug) -> Result<VmState>>;
|
||||
pub(crate) type HookCallback = XRc<dyn Fn(&Lua, &Debug) -> Result<VmState>>;
|
||||
|
||||
#[cfg(all(feature = "send", feature = "luau"))]
|
||||
pub(crate) type InterruptCallback = XRc<dyn Fn(&Lua) -> Result<VmState> + Send>;
|
||||
|
||||
+28
-23
@@ -1,22 +1,39 @@
|
||||
use std::fmt;
|
||||
use std::os::raw::{c_int, c_void};
|
||||
|
||||
use super::XRc;
|
||||
use crate::state::{RawLua, WeakLua};
|
||||
|
||||
/// A reference to a Lua (complex) value stored in the Lua auxiliary thread.
|
||||
#[derive(Clone)]
|
||||
pub struct ValueRef {
|
||||
pub(crate) lua: WeakLua,
|
||||
// Keep index separate to avoid additional indirection when accessing it.
|
||||
pub(crate) index: c_int,
|
||||
pub(crate) drop: bool,
|
||||
// If `index_count` is `None`, the value does not need to be destroyed.
|
||||
pub(crate) index_count: Option<ValueRefIndex>,
|
||||
}
|
||||
|
||||
/// A reference to a Lua value index in the auxiliary thread.
|
||||
/// It's cheap to clone and can be used to track the number of references to a value.
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct ValueRefIndex(pub(crate) XRc<c_int>);
|
||||
|
||||
impl From<c_int> for ValueRefIndex {
|
||||
#[inline]
|
||||
fn from(index: c_int) -> Self {
|
||||
ValueRefIndex(XRc::new(index))
|
||||
}
|
||||
}
|
||||
|
||||
impl ValueRef {
|
||||
#[inline]
|
||||
pub(crate) fn new(lua: &RawLua, index: c_int) -> Self {
|
||||
pub(crate) fn new(lua: &RawLua, index: impl Into<ValueRefIndex>) -> Self {
|
||||
let index = index.into();
|
||||
ValueRef {
|
||||
lua: lua.weak().clone(),
|
||||
index,
|
||||
drop: true,
|
||||
index: *index.0,
|
||||
index_count: Some(index),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,16 +42,6 @@ impl ValueRef {
|
||||
let lua = self.lua.lock();
|
||||
unsafe { ffi::lua_topointer(lua.ref_thread(), self.index) }
|
||||
}
|
||||
|
||||
/// Returns a copy of the value, which is valid as long as the original value is held.
|
||||
#[inline]
|
||||
pub(crate) fn copy(&self) -> Self {
|
||||
ValueRef {
|
||||
lua: self.lua.clone(),
|
||||
index: self.index,
|
||||
drop: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for ValueRef {
|
||||
@@ -43,17 +50,15 @@ impl fmt::Debug for ValueRef {
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for ValueRef {
|
||||
fn clone(&self) -> Self {
|
||||
unsafe { self.lua.lock().clone_ref(self) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ValueRef {
|
||||
fn drop(&mut self) {
|
||||
if self.drop {
|
||||
if let Some(lua) = self.lua.try_lock() {
|
||||
unsafe { lua.drop_ref(self) };
|
||||
if let Some(ValueRefIndex(index)) = self.index_count.take() {
|
||||
// It's guaranteed that the inner value returns exactly once.
|
||||
// This means in particular that the value is not dropped.
|
||||
if XRc::into_inner(index).is_some() {
|
||||
if let Some(lua) = self.lua.try_lock() {
|
||||
unsafe { lua.drop_ref(self) };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+35
-27
@@ -240,6 +240,13 @@ impl AsRef<str> for MetaMethod {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MetaMethod> for StdString {
|
||||
#[inline]
|
||||
fn from(method: MetaMethod) -> Self {
|
||||
method.name().to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
/// Method registry for [`UserData`] implementors.
|
||||
pub trait UserDataMethods<T> {
|
||||
/// Add a regular method which accepts a `&T` as the first parameter.
|
||||
@@ -249,7 +256,7 @@ pub trait UserDataMethods<T> {
|
||||
///
|
||||
/// If `add_meta_method` is used to set the `__index` metamethod, the `__index` metamethod will
|
||||
/// be used as a fall-back if no regular method is found.
|
||||
fn add_method<M, A, R>(&mut self, name: impl ToString, method: M)
|
||||
fn add_method<M, A, R>(&mut self, name: impl Into<StdString>, method: M)
|
||||
where
|
||||
M: Fn(&Lua, &T, A) -> Result<R> + MaybeSend + 'static,
|
||||
A: FromLuaMulti,
|
||||
@@ -260,7 +267,7 @@ pub trait UserDataMethods<T> {
|
||||
/// Refer to [`add_method`] for more information about the implementation.
|
||||
///
|
||||
/// [`add_method`]: UserDataMethods::add_method
|
||||
fn add_method_mut<M, A, R>(&mut self, name: impl ToString, method: M)
|
||||
fn add_method_mut<M, A, R>(&mut self, name: impl Into<StdString>, method: M)
|
||||
where
|
||||
M: FnMut(&Lua, &mut T, A) -> Result<R> + MaybeSend + 'static,
|
||||
A: FromLuaMulti,
|
||||
@@ -273,7 +280,7 @@ pub trait UserDataMethods<T> {
|
||||
/// [`add_method`]: UserDataMethods::add_method
|
||||
#[cfg(feature = "async")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
|
||||
fn add_async_method<M, A, MR, R>(&mut self, name: impl ToString, method: M)
|
||||
fn add_async_method<M, A, MR, R>(&mut self, name: impl Into<StdString>, method: M)
|
||||
where
|
||||
T: 'static,
|
||||
M: Fn(Lua, UserDataRef<T>, A) -> MR + MaybeSend + 'static,
|
||||
@@ -288,7 +295,7 @@ pub trait UserDataMethods<T> {
|
||||
/// [`add_method`]: UserDataMethods::add_method
|
||||
#[cfg(feature = "async")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
|
||||
fn add_async_method_mut<M, A, MR, R>(&mut self, name: impl ToString, method: M)
|
||||
fn add_async_method_mut<M, A, MR, R>(&mut self, name: impl Into<StdString>, method: M)
|
||||
where
|
||||
T: 'static,
|
||||
M: Fn(Lua, UserDataRefMut<T>, A) -> MR + MaybeSend + 'static,
|
||||
@@ -301,7 +308,7 @@ pub trait UserDataMethods<T> {
|
||||
/// The first argument will be a [`AnyUserData`] of type `T` if the method is called with Lua
|
||||
/// method syntax: `my_userdata:my_method(arg1, arg2)`, or it is passed in as the first
|
||||
/// argument: `my_userdata.my_method(my_userdata, arg1, arg2)`.
|
||||
fn add_function<F, A, R>(&mut self, name: impl ToString, function: F)
|
||||
fn add_function<F, A, R>(&mut self, name: impl Into<StdString>, function: F)
|
||||
where
|
||||
F: Fn(&Lua, A) -> Result<R> + MaybeSend + 'static,
|
||||
A: FromLuaMulti,
|
||||
@@ -312,7 +319,7 @@ pub trait UserDataMethods<T> {
|
||||
/// This is a version of [`add_function`] that accepts a `FnMut` argument.
|
||||
///
|
||||
/// [`add_function`]: UserDataMethods::add_function
|
||||
fn add_function_mut<F, A, R>(&mut self, name: impl ToString, function: F)
|
||||
fn add_function_mut<F, A, R>(&mut self, name: impl Into<StdString>, function: F)
|
||||
where
|
||||
F: FnMut(&Lua, A) -> Result<R> + MaybeSend + 'static,
|
||||
A: FromLuaMulti,
|
||||
@@ -326,7 +333,7 @@ pub trait UserDataMethods<T> {
|
||||
/// [`add_function`]: UserDataMethods::add_function
|
||||
#[cfg(feature = "async")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
|
||||
fn add_async_function<F, A, FR, R>(&mut self, name: impl ToString, function: F)
|
||||
fn add_async_function<F, A, FR, R>(&mut self, name: impl Into<StdString>, function: F)
|
||||
where
|
||||
F: Fn(Lua, A) -> FR + MaybeSend + 'static,
|
||||
A: FromLuaMulti,
|
||||
@@ -341,7 +348,7 @@ pub trait UserDataMethods<T> {
|
||||
/// side has a metatable. To prevent this, use [`add_meta_function`].
|
||||
///
|
||||
/// [`add_meta_function`]: UserDataMethods::add_meta_function
|
||||
fn add_meta_method<M, A, R>(&mut self, name: impl ToString, method: M)
|
||||
fn add_meta_method<M, A, R>(&mut self, name: impl Into<StdString>, method: M)
|
||||
where
|
||||
M: Fn(&Lua, &T, A) -> Result<R> + MaybeSend + 'static,
|
||||
A: FromLuaMulti,
|
||||
@@ -355,7 +362,7 @@ pub trait UserDataMethods<T> {
|
||||
/// side has a metatable. To prevent this, use [`add_meta_function`].
|
||||
///
|
||||
/// [`add_meta_function`]: UserDataMethods::add_meta_function
|
||||
fn add_meta_method_mut<M, A, R>(&mut self, name: impl ToString, method: M)
|
||||
fn add_meta_method_mut<M, A, R>(&mut self, name: impl Into<StdString>, method: M)
|
||||
where
|
||||
M: FnMut(&Lua, &mut T, A) -> Result<R> + MaybeSend + 'static,
|
||||
A: FromLuaMulti,
|
||||
@@ -371,7 +378,7 @@ pub trait UserDataMethods<T> {
|
||||
docsrs,
|
||||
doc(cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau")))))
|
||||
)]
|
||||
fn add_async_meta_method<M, A, MR, R>(&mut self, name: impl ToString, method: M)
|
||||
fn add_async_meta_method<M, A, MR, R>(&mut self, name: impl Into<StdString>, method: M)
|
||||
where
|
||||
T: 'static,
|
||||
M: Fn(Lua, UserDataRef<T>, A) -> MR + MaybeSend + 'static,
|
||||
@@ -387,7 +394,7 @@ pub trait UserDataMethods<T> {
|
||||
/// [`add_meta_method_mut`]: UserDataMethods::add_meta_method_mut
|
||||
#[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
|
||||
fn add_async_meta_method_mut<M, A, MR, R>(&mut self, name: impl ToString, method: M)
|
||||
fn add_async_meta_method_mut<M, A, MR, R>(&mut self, name: impl Into<StdString>, method: M)
|
||||
where
|
||||
T: 'static,
|
||||
M: Fn(Lua, UserDataRefMut<T>, A) -> MR + MaybeSend + 'static,
|
||||
@@ -400,7 +407,7 @@ pub trait UserDataMethods<T> {
|
||||
/// Metamethods for binary operators can be triggered if either the left or right argument to
|
||||
/// the binary operator has a metatable, so the first argument here is not necessarily a
|
||||
/// userdata of type `T`.
|
||||
fn add_meta_function<F, A, R>(&mut self, name: impl ToString, function: F)
|
||||
fn add_meta_function<F, A, R>(&mut self, name: impl Into<StdString>, function: F)
|
||||
where
|
||||
F: Fn(&Lua, A) -> Result<R> + MaybeSend + 'static,
|
||||
A: FromLuaMulti,
|
||||
@@ -411,7 +418,7 @@ pub trait UserDataMethods<T> {
|
||||
/// This is a version of [`add_meta_function`] that accepts a `FnMut` argument.
|
||||
///
|
||||
/// [`add_meta_function`]: UserDataMethods::add_meta_function
|
||||
fn add_meta_function_mut<F, A, R>(&mut self, name: impl ToString, function: F)
|
||||
fn add_meta_function_mut<F, A, R>(&mut self, name: impl Into<StdString>, function: F)
|
||||
where
|
||||
F: FnMut(&Lua, A) -> Result<R> + MaybeSend + 'static,
|
||||
A: FromLuaMulti,
|
||||
@@ -427,7 +434,7 @@ pub trait UserDataMethods<T> {
|
||||
docsrs,
|
||||
doc(cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau")))))
|
||||
)]
|
||||
fn add_async_meta_function<F, A, FR, R>(&mut self, name: impl ToString, function: F)
|
||||
fn add_async_meta_function<F, A, FR, R>(&mut self, name: impl Into<StdString>, function: F)
|
||||
where
|
||||
F: Fn(Lua, A) -> FR + MaybeSend + 'static,
|
||||
A: FromLuaMulti,
|
||||
@@ -446,7 +453,7 @@ pub trait UserDataFields<T> {
|
||||
///
|
||||
/// If `add_meta_method` is used to set the `__index` metamethod, it will
|
||||
/// be used as a fall-back if no regular field or method are found.
|
||||
fn add_field<V>(&mut self, name: impl ToString, value: V)
|
||||
fn add_field<V>(&mut self, name: impl Into<StdString>, value: V)
|
||||
where
|
||||
V: IntoLua + 'static;
|
||||
|
||||
@@ -457,7 +464,7 @@ pub trait UserDataFields<T> {
|
||||
///
|
||||
/// If `add_meta_method` is used to set the `__index` metamethod, the `__index` metamethod will
|
||||
/// be used as a fall-back if no regular field or method are found.
|
||||
fn add_field_method_get<M, R>(&mut self, name: impl ToString, method: M)
|
||||
fn add_field_method_get<M, R>(&mut self, name: impl Into<StdString>, method: M)
|
||||
where
|
||||
M: Fn(&Lua, &T) -> Result<R> + MaybeSend + 'static,
|
||||
R: IntoLua;
|
||||
@@ -470,21 +477,21 @@ pub trait UserDataFields<T> {
|
||||
///
|
||||
/// If `add_meta_method` is used to set the `__newindex` metamethod, the `__newindex` metamethod
|
||||
/// will be used as a fall-back if no regular field is found.
|
||||
fn add_field_method_set<M, A>(&mut self, name: impl ToString, method: M)
|
||||
fn add_field_method_set<M, A>(&mut self, name: impl Into<StdString>, method: M)
|
||||
where
|
||||
M: FnMut(&Lua, &mut T, A) -> Result<()> + MaybeSend + 'static,
|
||||
A: FromLua;
|
||||
|
||||
/// Add a regular field getter as a function which accepts a generic [`AnyUserData`] of type `T`
|
||||
/// argument.
|
||||
fn add_field_function_get<F, R>(&mut self, name: impl ToString, function: F)
|
||||
fn add_field_function_get<F, R>(&mut self, name: impl Into<StdString>, function: F)
|
||||
where
|
||||
F: Fn(&Lua, AnyUserData) -> Result<R> + MaybeSend + 'static,
|
||||
R: IntoLua;
|
||||
|
||||
/// Add a regular field setter as a function which accepts a generic [`AnyUserData`] of type `T`
|
||||
/// first argument.
|
||||
fn add_field_function_set<F, A>(&mut self, name: impl ToString, function: F)
|
||||
fn add_field_function_set<F, A>(&mut self, name: impl Into<StdString>, function: F)
|
||||
where
|
||||
F: FnMut(&Lua, AnyUserData, A) -> Result<()> + MaybeSend + 'static,
|
||||
A: FromLua;
|
||||
@@ -497,7 +504,7 @@ pub trait UserDataFields<T> {
|
||||
///
|
||||
/// `mlua` will trigger an error on an attempt to define a protected metamethod,
|
||||
/// like `__gc` or `__metatable`.
|
||||
fn add_meta_field<V>(&mut self, name: impl ToString, value: V)
|
||||
fn add_meta_field<V>(&mut self, name: impl Into<StdString>, value: V)
|
||||
where
|
||||
V: IntoLua + 'static;
|
||||
|
||||
@@ -509,7 +516,7 @@ pub trait UserDataFields<T> {
|
||||
///
|
||||
/// `mlua` will trigger an error on an attempt to define a protected metamethod,
|
||||
/// like `__gc` or `__metatable`.
|
||||
fn add_meta_field_with<F, R>(&mut self, name: impl ToString, f: F)
|
||||
fn add_meta_field_with<F, R>(&mut self, name: impl Into<StdString>, f: F)
|
||||
where
|
||||
F: FnOnce(&Lua) -> Result<R> + 'static,
|
||||
R: IntoLua;
|
||||
@@ -881,16 +888,17 @@ impl AnyUserData {
|
||||
self.raw_metatable().map(UserDataMetatable)
|
||||
}
|
||||
|
||||
/// Returns a raw metatable of this [`AnyUserData`].
|
||||
fn raw_metatable(&self) -> Result<Table> {
|
||||
let lua = self.0.lua.lock();
|
||||
let state = lua.state();
|
||||
let ref_thread = lua.ref_thread();
|
||||
unsafe {
|
||||
let _sg = StackGuard::new(state);
|
||||
check_stack(state, 3)?;
|
||||
// Check that userdata is registered and not destructed
|
||||
// All registered userdata types have a non-empty metatable
|
||||
let _type_id = lua.get_userdata_ref_type_id(&self.0)?;
|
||||
|
||||
lua.push_userdata_ref(&self.0)?;
|
||||
ffi::lua_getmetatable(state, -1); // Checked that non-empty on the previous call
|
||||
Ok(Table(lua.pop_ref()))
|
||||
ffi::lua_getmetatable(ref_thread, self.0.index);
|
||||
Ok(Table(lua.pop_ref_thread()))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+23
-12
@@ -1,6 +1,7 @@
|
||||
use std::string::String as StdString;
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
use crate::state::WeakLua;
|
||||
use crate::table::Table;
|
||||
use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, ObjectLike};
|
||||
use crate::userdata::AnyUserData;
|
||||
@@ -8,21 +9,21 @@ use crate::value::Value;
|
||||
use crate::Function;
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
use futures_util::future::{self, Either, Future};
|
||||
use crate::function::AsyncCallFuture;
|
||||
|
||||
impl ObjectLike for AnyUserData {
|
||||
#[inline]
|
||||
fn get<V: FromLua>(&self, key: impl IntoLua) -> Result<V> {
|
||||
// `lua_gettable` method used under the hood can work with any Lua value
|
||||
// that has `__index` metamethod
|
||||
Table(self.0.copy()).get_protected(key)
|
||||
Table(self.0.clone()).get_protected(key)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set(&self, key: impl IntoLua, value: impl IntoLua) -> Result<()> {
|
||||
// `lua_settable` method used under the hood can work with any Lua value
|
||||
// that has `__newindex` metamethod
|
||||
Table(self.0.copy()).set_protected(key, value)
|
||||
Table(self.0.clone()).set_protected(key, value)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -30,16 +31,16 @@ impl ObjectLike for AnyUserData {
|
||||
where
|
||||
R: FromLuaMulti,
|
||||
{
|
||||
Function(self.0.copy()).call(args)
|
||||
Function(self.0.clone()).call(args)
|
||||
}
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
#[inline]
|
||||
fn call_async<R>(&self, args: impl IntoLuaMulti) -> impl Future<Output = Result<R>>
|
||||
fn call_async<R>(&self, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
|
||||
where
|
||||
R: FromLuaMulti,
|
||||
{
|
||||
Function(self.0.copy()).call_async(args)
|
||||
Function(self.0.clone()).call_async(args)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -51,7 +52,7 @@ impl ObjectLike for AnyUserData {
|
||||
}
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
fn call_async_method<R>(&self, name: &str, args: impl IntoLuaMulti) -> impl Future<Output = Result<R>>
|
||||
fn call_async_method<R>(&self, name: &str, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
|
||||
where
|
||||
R: FromLuaMulti,
|
||||
{
|
||||
@@ -72,22 +73,32 @@ impl ObjectLike for AnyUserData {
|
||||
}
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
fn call_async_function<R>(&self, name: &str, args: impl IntoLuaMulti) -> impl Future<Output = Result<R>>
|
||||
fn call_async_function<R>(&self, name: &str, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
|
||||
where
|
||||
R: FromLuaMulti,
|
||||
{
|
||||
match self.get(name) {
|
||||
Ok(Value::Function(func)) => Either::Left(func.call_async(args)),
|
||||
Ok(Value::Function(func)) => func.call_async(args),
|
||||
Ok(val) => {
|
||||
let msg = format!("attempt to call a {} value (function '{name}')", val.type_name());
|
||||
Either::Right(future::ready(Err(Error::RuntimeError(msg))))
|
||||
AsyncCallFuture::error(Error::RuntimeError(msg))
|
||||
}
|
||||
Err(err) => Either::Right(future::ready(Err(err))),
|
||||
Err(err) => AsyncCallFuture::error(err),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn to_string(&self) -> Result<StdString> {
|
||||
Value::UserData(AnyUserData(self.0.copy())).to_string()
|
||||
Value::UserData(self.clone()).to_string()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn to_value(&self) -> Value {
|
||||
Value::UserData(self.clone())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn weak_lua(&self) -> &WeakLua {
|
||||
&self.0.lua
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -63,7 +63,7 @@ impl<T> TryFrom<UserDataVariant<T>> for UserDataRef<T> {
|
||||
|
||||
#[inline]
|
||||
fn try_from(variant: UserDataVariant<T>) -> Result<Self> {
|
||||
let guard = if !cfg!(feature = "send") || is_sync::<T>() {
|
||||
let guard = if cfg!(not(feature = "send")) || is_sync::<T>() {
|
||||
variant.raw_lock().try_lock_shared_guarded()
|
||||
} else {
|
||||
variant.raw_lock().try_lock_exclusive_guarded()
|
||||
|
||||
+64
-42
@@ -56,6 +56,9 @@ pub(crate) struct RawUserDataRegistry {
|
||||
pub(crate) destructor: ffi::lua_CFunction,
|
||||
pub(crate) type_id: Option<TypeId>,
|
||||
pub(crate) type_name: StdString,
|
||||
|
||||
#[cfg(feature = "luau")]
|
||||
pub(crate) enable_namecall: bool,
|
||||
}
|
||||
|
||||
impl UserDataType {
|
||||
@@ -100,6 +103,8 @@ impl<T> UserDataRegistry<T> {
|
||||
destructor: super::util::destroy_userdata_storage::<T>,
|
||||
type_id: r#type.type_id(),
|
||||
type_name: short_type_name::<T>(),
|
||||
#[cfg(feature = "luau")]
|
||||
enable_namecall: false,
|
||||
};
|
||||
|
||||
UserDataRegistry {
|
||||
@@ -110,6 +115,23 @@ impl<T> UserDataRegistry<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Enables support for the namecall optimization in Luau.
|
||||
///
|
||||
/// This enables methods resolution optimization in Luau for complex userdata types with methods
|
||||
/// and field getters. When enabled, Luau will use a faster lookup path for method calls when a
|
||||
/// specific syntax is used (e.g. `obj:method()`.
|
||||
///
|
||||
/// This optimization does not play well with async methods, custom `__index` metamethod and
|
||||
/// field getters as functions. So, it is disabled by default.
|
||||
///
|
||||
/// Use with caution.
|
||||
#[doc(hidden)]
|
||||
#[cfg(feature = "luau")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
|
||||
pub fn enable_namecall(&mut self) {
|
||||
self.raw.enable_namecall = true;
|
||||
}
|
||||
|
||||
fn box_method<M, A, R>(&self, name: &str, method: M) -> Callback
|
||||
where
|
||||
M: Fn(&Lua, &T, A) -> Result<R> + MaybeSend + 'static,
|
||||
@@ -365,101 +387,101 @@ fn get_function_name<T>(name: &str) -> StdString {
|
||||
}
|
||||
|
||||
impl<T> UserDataFields<T> for UserDataRegistry<T> {
|
||||
fn add_field<V>(&mut self, name: impl ToString, value: V)
|
||||
fn add_field<V>(&mut self, name: impl Into<StdString>, value: V)
|
||||
where
|
||||
V: IntoLua + 'static,
|
||||
{
|
||||
let name = name.to_string();
|
||||
let name = name.into();
|
||||
self.raw.fields.push((name, value.into_lua(self.lua.lua())));
|
||||
}
|
||||
|
||||
fn add_field_method_get<M, R>(&mut self, name: impl ToString, method: M)
|
||||
fn add_field_method_get<M, R>(&mut self, name: impl Into<StdString>, method: M)
|
||||
where
|
||||
M: Fn(&Lua, &T) -> Result<R> + MaybeSend + 'static,
|
||||
R: IntoLua,
|
||||
{
|
||||
let name = name.to_string();
|
||||
let name = name.into();
|
||||
let callback = self.box_method(&name, move |lua, data, ()| method(lua, data));
|
||||
self.raw.field_getters.push((name, callback));
|
||||
}
|
||||
|
||||
fn add_field_method_set<M, A>(&mut self, name: impl ToString, method: M)
|
||||
fn add_field_method_set<M, A>(&mut self, name: impl Into<StdString>, method: M)
|
||||
where
|
||||
M: FnMut(&Lua, &mut T, A) -> Result<()> + MaybeSend + 'static,
|
||||
A: FromLua,
|
||||
{
|
||||
let name = name.to_string();
|
||||
let name = name.into();
|
||||
let callback = self.box_method_mut(&name, method);
|
||||
self.raw.field_setters.push((name, callback));
|
||||
}
|
||||
|
||||
fn add_field_function_get<F, R>(&mut self, name: impl ToString, function: F)
|
||||
fn add_field_function_get<F, R>(&mut self, name: impl Into<StdString>, function: F)
|
||||
where
|
||||
F: Fn(&Lua, AnyUserData) -> Result<R> + MaybeSend + 'static,
|
||||
R: IntoLua,
|
||||
{
|
||||
let name = name.to_string();
|
||||
let name = name.into();
|
||||
let callback = self.box_function(&name, function);
|
||||
self.raw.field_getters.push((name, callback));
|
||||
}
|
||||
|
||||
fn add_field_function_set<F, A>(&mut self, name: impl ToString, mut function: F)
|
||||
fn add_field_function_set<F, A>(&mut self, name: impl Into<StdString>, mut function: F)
|
||||
where
|
||||
F: FnMut(&Lua, AnyUserData, A) -> Result<()> + MaybeSend + 'static,
|
||||
A: FromLua,
|
||||
{
|
||||
let name = name.to_string();
|
||||
let name = name.into();
|
||||
let callback = self.box_function_mut(&name, move |lua, (data, val)| function(lua, data, val));
|
||||
self.raw.field_setters.push((name, callback));
|
||||
}
|
||||
|
||||
fn add_meta_field<V>(&mut self, name: impl ToString, value: V)
|
||||
fn add_meta_field<V>(&mut self, name: impl Into<StdString>, value: V)
|
||||
where
|
||||
V: IntoLua + 'static,
|
||||
{
|
||||
let lua = self.lua.lua();
|
||||
let name = name.to_string();
|
||||
let name = name.into();
|
||||
let field = Self::check_meta_field(lua, &name, value).and_then(|v| v.into_lua(lua));
|
||||
self.raw.meta_fields.push((name, field));
|
||||
}
|
||||
|
||||
fn add_meta_field_with<F, R>(&mut self, name: impl ToString, f: F)
|
||||
fn add_meta_field_with<F, R>(&mut self, name: impl Into<StdString>, f: F)
|
||||
where
|
||||
F: FnOnce(&Lua) -> Result<R> + 'static,
|
||||
R: IntoLua,
|
||||
{
|
||||
let lua = self.lua.lua();
|
||||
let name = name.to_string();
|
||||
let name = name.into();
|
||||
let field = f(lua).and_then(|v| Self::check_meta_field(lua, &name, v).and_then(|v| v.into_lua(lua)));
|
||||
self.raw.meta_fields.push((name, field));
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> UserDataMethods<T> for UserDataRegistry<T> {
|
||||
fn add_method<M, A, R>(&mut self, name: impl ToString, method: M)
|
||||
fn add_method<M, A, R>(&mut self, name: impl Into<StdString>, method: M)
|
||||
where
|
||||
M: Fn(&Lua, &T, A) -> Result<R> + MaybeSend + 'static,
|
||||
A: FromLuaMulti,
|
||||
R: IntoLuaMulti,
|
||||
{
|
||||
let name = name.to_string();
|
||||
let name = name.into();
|
||||
let callback = self.box_method(&name, method);
|
||||
self.raw.methods.push((name, callback));
|
||||
}
|
||||
|
||||
fn add_method_mut<M, A, R>(&mut self, name: impl ToString, method: M)
|
||||
fn add_method_mut<M, A, R>(&mut self, name: impl Into<StdString>, method: M)
|
||||
where
|
||||
M: FnMut(&Lua, &mut T, A) -> Result<R> + MaybeSend + 'static,
|
||||
A: FromLuaMulti,
|
||||
R: IntoLuaMulti,
|
||||
{
|
||||
let name = name.to_string();
|
||||
let name = name.into();
|
||||
let callback = self.box_method_mut(&name, method);
|
||||
self.raw.methods.push((name, callback));
|
||||
}
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
fn add_async_method<M, A, MR, R>(&mut self, name: impl ToString, method: M)
|
||||
fn add_async_method<M, A, MR, R>(&mut self, name: impl Into<StdString>, method: M)
|
||||
where
|
||||
T: 'static,
|
||||
M: Fn(Lua, UserDataRef<T>, A) -> MR + MaybeSend + 'static,
|
||||
@@ -467,13 +489,13 @@ impl<T> UserDataMethods<T> for UserDataRegistry<T> {
|
||||
MR: Future<Output = Result<R>> + MaybeSend + 'static,
|
||||
R: IntoLuaMulti,
|
||||
{
|
||||
let name = name.to_string();
|
||||
let name = name.into();
|
||||
let callback = self.box_async_method(&name, method);
|
||||
self.raw.async_methods.push((name, callback));
|
||||
}
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
fn add_async_method_mut<M, A, MR, R>(&mut self, name: impl ToString, method: M)
|
||||
fn add_async_method_mut<M, A, MR, R>(&mut self, name: impl Into<StdString>, method: M)
|
||||
where
|
||||
T: 'static,
|
||||
M: Fn(Lua, UserDataRefMut<T>, A) -> MR + MaybeSend + 'static,
|
||||
@@ -481,70 +503,70 @@ impl<T> UserDataMethods<T> for UserDataRegistry<T> {
|
||||
MR: Future<Output = Result<R>> + MaybeSend + 'static,
|
||||
R: IntoLuaMulti,
|
||||
{
|
||||
let name = name.to_string();
|
||||
let name = name.into();
|
||||
let callback = self.box_async_method_mut(&name, method);
|
||||
self.raw.async_methods.push((name, callback));
|
||||
}
|
||||
|
||||
fn add_function<F, A, R>(&mut self, name: impl ToString, function: F)
|
||||
fn add_function<F, A, R>(&mut self, name: impl Into<StdString>, function: F)
|
||||
where
|
||||
F: Fn(&Lua, A) -> Result<R> + MaybeSend + 'static,
|
||||
A: FromLuaMulti,
|
||||
R: IntoLuaMulti,
|
||||
{
|
||||
let name = name.to_string();
|
||||
let name = name.into();
|
||||
let callback = self.box_function(&name, function);
|
||||
self.raw.methods.push((name, callback));
|
||||
}
|
||||
|
||||
fn add_function_mut<F, A, R>(&mut self, name: impl ToString, function: F)
|
||||
fn add_function_mut<F, A, R>(&mut self, name: impl Into<StdString>, function: F)
|
||||
where
|
||||
F: FnMut(&Lua, A) -> Result<R> + MaybeSend + 'static,
|
||||
A: FromLuaMulti,
|
||||
R: IntoLuaMulti,
|
||||
{
|
||||
let name = name.to_string();
|
||||
let name = name.into();
|
||||
let callback = self.box_function_mut(&name, function);
|
||||
self.raw.methods.push((name, callback));
|
||||
}
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
fn add_async_function<F, A, FR, R>(&mut self, name: impl ToString, function: F)
|
||||
fn add_async_function<F, A, FR, R>(&mut self, name: impl Into<StdString>, function: F)
|
||||
where
|
||||
F: Fn(Lua, A) -> FR + MaybeSend + 'static,
|
||||
A: FromLuaMulti,
|
||||
FR: Future<Output = Result<R>> + MaybeSend + 'static,
|
||||
R: IntoLuaMulti,
|
||||
{
|
||||
let name = name.to_string();
|
||||
let name = name.into();
|
||||
let callback = self.box_async_function(&name, function);
|
||||
self.raw.async_methods.push((name, callback));
|
||||
}
|
||||
|
||||
fn add_meta_method<M, A, R>(&mut self, name: impl ToString, method: M)
|
||||
fn add_meta_method<M, A, R>(&mut self, name: impl Into<StdString>, method: M)
|
||||
where
|
||||
M: Fn(&Lua, &T, A) -> Result<R> + MaybeSend + 'static,
|
||||
A: FromLuaMulti,
|
||||
R: IntoLuaMulti,
|
||||
{
|
||||
let name = name.to_string();
|
||||
let name = name.into();
|
||||
let callback = self.box_method(&name, method);
|
||||
self.raw.meta_methods.push((name, callback));
|
||||
}
|
||||
|
||||
fn add_meta_method_mut<M, A, R>(&mut self, name: impl ToString, method: M)
|
||||
fn add_meta_method_mut<M, A, R>(&mut self, name: impl Into<StdString>, method: M)
|
||||
where
|
||||
M: FnMut(&Lua, &mut T, A) -> Result<R> + MaybeSend + 'static,
|
||||
A: FromLuaMulti,
|
||||
R: IntoLuaMulti,
|
||||
{
|
||||
let name = name.to_string();
|
||||
let name = name.into();
|
||||
let callback = self.box_method_mut(&name, method);
|
||||
self.raw.meta_methods.push((name, callback));
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))]
|
||||
fn add_async_meta_method<M, A, MR, R>(&mut self, name: impl ToString, method: M)
|
||||
fn add_async_meta_method<M, A, MR, R>(&mut self, name: impl Into<StdString>, method: M)
|
||||
where
|
||||
T: 'static,
|
||||
M: Fn(Lua, UserDataRef<T>, A) -> MR + MaybeSend + 'static,
|
||||
@@ -552,13 +574,13 @@ impl<T> UserDataMethods<T> for UserDataRegistry<T> {
|
||||
MR: Future<Output = Result<R>> + MaybeSend + 'static,
|
||||
R: IntoLuaMulti,
|
||||
{
|
||||
let name = name.to_string();
|
||||
let name = name.into();
|
||||
let callback = self.box_async_method(&name, method);
|
||||
self.raw.async_meta_methods.push((name, callback));
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))]
|
||||
fn add_async_meta_method_mut<M, A, MR, R>(&mut self, name: impl ToString, method: M)
|
||||
fn add_async_meta_method_mut<M, A, MR, R>(&mut self, name: impl Into<StdString>, method: M)
|
||||
where
|
||||
T: 'static,
|
||||
M: Fn(Lua, UserDataRefMut<T>, A) -> MR + MaybeSend + 'static,
|
||||
@@ -566,42 +588,42 @@ impl<T> UserDataMethods<T> for UserDataRegistry<T> {
|
||||
MR: Future<Output = Result<R>> + MaybeSend + 'static,
|
||||
R: IntoLuaMulti,
|
||||
{
|
||||
let name = name.to_string();
|
||||
let name = name.into();
|
||||
let callback = self.box_async_method_mut(&name, method);
|
||||
self.raw.async_meta_methods.push((name, callback));
|
||||
}
|
||||
|
||||
fn add_meta_function<F, A, R>(&mut self, name: impl ToString, function: F)
|
||||
fn add_meta_function<F, A, R>(&mut self, name: impl Into<StdString>, function: F)
|
||||
where
|
||||
F: Fn(&Lua, A) -> Result<R> + MaybeSend + 'static,
|
||||
A: FromLuaMulti,
|
||||
R: IntoLuaMulti,
|
||||
{
|
||||
let name = name.to_string();
|
||||
let name = name.into();
|
||||
let callback = self.box_function(&name, function);
|
||||
self.raw.meta_methods.push((name, callback));
|
||||
}
|
||||
|
||||
fn add_meta_function_mut<F, A, R>(&mut self, name: impl ToString, function: F)
|
||||
fn add_meta_function_mut<F, A, R>(&mut self, name: impl Into<StdString>, function: F)
|
||||
where
|
||||
F: FnMut(&Lua, A) -> Result<R> + MaybeSend + 'static,
|
||||
A: FromLuaMulti,
|
||||
R: IntoLuaMulti,
|
||||
{
|
||||
let name = name.to_string();
|
||||
let name = name.into();
|
||||
let callback = self.box_function_mut(&name, function);
|
||||
self.raw.meta_methods.push((name, callback));
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))]
|
||||
fn add_async_meta_function<F, A, FR, R>(&mut self, name: impl ToString, function: F)
|
||||
fn add_async_meta_function<F, A, FR, R>(&mut self, name: impl Into<StdString>, function: F)
|
||||
where
|
||||
F: Fn(Lua, A) -> FR + MaybeSend + 'static,
|
||||
A: FromLuaMulti,
|
||||
FR: Future<Output = Result<R>> + MaybeSend + 'static,
|
||||
R: IntoLuaMulti,
|
||||
{
|
||||
let name = name.to_string();
|
||||
let name = name.into();
|
||||
let callback = self.box_async_function(&name, function);
|
||||
self.raw.async_meta_methods.push((name, callback));
|
||||
}
|
||||
|
||||
@@ -4,8 +4,11 @@ use std::marker::PhantomData;
|
||||
use std::os::raw::c_int;
|
||||
use std::ptr;
|
||||
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use super::UserDataStorage;
|
||||
use crate::error::{Error, Result};
|
||||
use crate::types::CallbackPtr;
|
||||
use crate::util::{get_userdata, rawget_field, rawset_field, take_userdata};
|
||||
|
||||
// This is a trick to check if a type is `Sync` or not.
|
||||
@@ -244,6 +247,7 @@ pub(crate) unsafe fn init_userdata_metatable(
|
||||
field_getters: Option<c_int>,
|
||||
field_setters: Option<c_int>,
|
||||
methods: Option<c_int>,
|
||||
_methods_map: Option<FxHashMap<Vec<u8>, CallbackPtr>>, // Used only in Luau for `__namecall`
|
||||
) -> Result<()> {
|
||||
if field_getters.is_some() || methods.is_some() {
|
||||
// Push `__index` generator function
|
||||
@@ -267,6 +271,13 @@ pub(crate) unsafe fn init_userdata_metatable(
|
||||
}
|
||||
|
||||
rawset_field(state, metatable, "__index")?;
|
||||
|
||||
#[cfg(feature = "luau")]
|
||||
if let Some(methods_map) = _methods_map {
|
||||
// In Luau we can speedup method calls by providing a dedicated `__namecall` metamethod
|
||||
push_userdata_metatable_namecall(state, methods_map)?;
|
||||
rawset_field(state, metatable, "__namecall")?;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(field_setters) = field_setters {
|
||||
@@ -425,6 +436,36 @@ unsafe fn init_userdata_metatable_newindex(state: *mut ffi::lua_State) -> Result
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "luau")]
|
||||
unsafe fn push_userdata_metatable_namecall(
|
||||
state: *mut ffi::lua_State,
|
||||
methods_map: FxHashMap<Vec<u8>, CallbackPtr>,
|
||||
) -> Result<()> {
|
||||
unsafe extern "C-unwind" fn namecall(state: *mut ffi::lua_State) -> c_int {
|
||||
let name = ffi::lua_namecallatom(state, ptr::null_mut());
|
||||
if name.is_null() {
|
||||
ffi::luaL_error(state, cstr!("attempt to call an unknown method"));
|
||||
}
|
||||
let name_cs = std::ffi::CStr::from_ptr(name);
|
||||
let methods_map = get_userdata::<FxHashMap<Vec<u8>, CallbackPtr>>(state, ffi::lua_upvalueindex(1));
|
||||
let callback_ptr = match (*methods_map).get(name_cs.to_bytes()) {
|
||||
Some(ptr) => *ptr,
|
||||
#[rustfmt::skip]
|
||||
None => ffi::luaL_error(state, cstr!("attempt to call an unknown method '%s'"), name),
|
||||
};
|
||||
crate::state::callback_error_ext(state, ptr::null_mut(), true, |extra, nargs| {
|
||||
let rawlua = (*extra).raw_lua();
|
||||
(*callback_ptr)(rawlua, nargs)
|
||||
})
|
||||
}
|
||||
|
||||
// Automatic destructor is provided for any Luau userdata
|
||||
crate::util::push_userdata(state, methods_map, true)?;
|
||||
protect_lua!(state, 1, 1, |state| {
|
||||
ffi::lua_pushcclosured(state, namecall, cstr!("__namecall"), 1);
|
||||
})
|
||||
}
|
||||
|
||||
// This method is called by Lua GC when it's time to collect the userdata.
|
||||
//
|
||||
// This method is usually used to collect internal userdata.
|
||||
|
||||
@@ -402,6 +402,8 @@ pub(crate) unsafe fn init_error_registry(state: *mut ffi::lua_State) -> Result<(
|
||||
"__ipairs",
|
||||
#[cfg(feature = "luau")]
|
||||
"__iter",
|
||||
#[cfg(feature = "luau")]
|
||||
"__namecall",
|
||||
#[cfg(feature = "lua54")]
|
||||
"__close",
|
||||
] {
|
||||
|
||||
+9
-9
@@ -9,6 +9,7 @@ pub(crate) use error::{
|
||||
error_traceback, error_traceback_thread, init_error_registry, pop_error, protect_lua_call,
|
||||
protect_lua_closure, WrappedFailure,
|
||||
};
|
||||
pub(crate) use path::parse_path as parse_lookup_path;
|
||||
pub(crate) use short_names::short_type_name;
|
||||
pub(crate) use types::TypeKey;
|
||||
pub(crate) use userdata::{
|
||||
@@ -88,7 +89,7 @@ impl Drop for StackGuard {
|
||||
#[inline(always)]
|
||||
pub(crate) unsafe fn push_string(state: *mut ffi::lua_State, s: &[u8], protect: bool) -> Result<()> {
|
||||
// Always use protected mode if the string is too long
|
||||
if protect || s.len() > (1 << 30) {
|
||||
if protect || s.len() >= const { 1 << 30 } {
|
||||
protect_lua!(state, 0, 1, |state| {
|
||||
ffi::lua_pushlstring(state, s.as_ptr() as *const c_char, s.len());
|
||||
})
|
||||
@@ -101,15 +102,13 @@ pub(crate) unsafe fn push_string(state: *mut ffi::lua_State, s: &[u8], protect:
|
||||
// Uses 3 stack spaces (when protect), does not call checkstack.
|
||||
#[cfg(feature = "luau")]
|
||||
#[inline(always)]
|
||||
pub(crate) unsafe fn push_buffer(state: *mut ffi::lua_State, b: &[u8], protect: bool) -> Result<()> {
|
||||
let data = if protect {
|
||||
protect_lua!(state, 0, 1, |state| ffi::lua_newbuffer(state, b.len()))?
|
||||
pub(crate) unsafe fn push_buffer(state: *mut ffi::lua_State, size: usize, protect: bool) -> Result<*mut u8> {
|
||||
let data = if protect || size > const { 1024 * 1024 * 1024 } {
|
||||
protect_lua!(state, 0, 1, |state| ffi::lua_newbuffer(state, size))?
|
||||
} else {
|
||||
ffi::lua_newbuffer(state, b.len())
|
||||
ffi::lua_newbuffer(state, size)
|
||||
};
|
||||
let buf = slice::from_raw_parts_mut(data as *mut u8, b.len());
|
||||
buf.copy_from_slice(b);
|
||||
Ok(())
|
||||
Ok(data as *mut u8)
|
||||
}
|
||||
|
||||
// Uses 3 stack spaces, does not call checkstack.
|
||||
@@ -122,7 +121,7 @@ pub(crate) unsafe fn push_table(
|
||||
) -> Result<()> {
|
||||
let narr: c_int = narr.try_into().unwrap_or(c_int::MAX);
|
||||
let nrec: c_int = nrec.try_into().unwrap_or(c_int::MAX);
|
||||
if protect {
|
||||
if protect || narr >= const { 1 << 26 } || nrec >= const { 1 << 26 } {
|
||||
protect_lua!(state, 0, 1, |state| ffi::lua_createtable(state, narr, nrec))
|
||||
} else {
|
||||
ffi::lua_createtable(state, narr, nrec);
|
||||
@@ -329,6 +328,7 @@ pub(crate) fn linenumber_to_usize(n: c_int) -> Option<usize> {
|
||||
}
|
||||
|
||||
mod error;
|
||||
mod path;
|
||||
mod short_names;
|
||||
mod types;
|
||||
mod userdata;
|
||||
|
||||
@@ -0,0 +1,255 @@
|
||||
use std::borrow::Cow;
|
||||
use std::fmt;
|
||||
use std::iter::Peekable;
|
||||
use std::str::CharIndices;
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
use crate::state::Lua;
|
||||
use crate::traits::IntoLua;
|
||||
use crate::types::Integer;
|
||||
use crate::value::Value;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum PathKey<'a> {
|
||||
Str(Cow<'a, str>),
|
||||
Int(Integer),
|
||||
}
|
||||
|
||||
impl fmt::Display for PathKey<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
PathKey::Str(s) => write!(f, "{}", s),
|
||||
PathKey::Int(i) => write!(f, "{}", i),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoLua for PathKey<'_> {
|
||||
fn into_lua(self, lua: &Lua) -> Result<Value> {
|
||||
match self {
|
||||
PathKey::Str(s) => Ok(Value::String(lua.create_string(s.as_ref())?)),
|
||||
PathKey::Int(i) => Ok(Value::Integer(i)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parses a path like `a.b[3]?.c["d"]` into segments of `(key, safe_nil)`.
|
||||
pub(crate) fn parse_path<'a>(path: &'a str) -> Result<Vec<(PathKey<'a>, bool)>> {
|
||||
fn read_ident<'a>(path: &'a str, chars: &mut Peekable<CharIndices<'a>>) -> (Cow<'a, str>, bool) {
|
||||
let mut safe_nil = false;
|
||||
let start = chars.peek().map(|&(i, _)| i).unwrap_or(path.len());
|
||||
let mut end = start;
|
||||
while let Some(&(pos, c)) = chars.peek() {
|
||||
if c == '.' || c == '?' || c.is_ascii_whitespace() || c == '[' {
|
||||
if c == '?' {
|
||||
safe_nil = true;
|
||||
chars.next(); // consume '?'
|
||||
}
|
||||
break;
|
||||
}
|
||||
end = pos + c.len_utf8();
|
||||
chars.next();
|
||||
}
|
||||
(Cow::Borrowed(&path[start..end]), safe_nil)
|
||||
}
|
||||
|
||||
let mut segments = Vec::new();
|
||||
let mut chars = path.char_indices().peekable();
|
||||
while let Some(&(pos, next)) = chars.peek() {
|
||||
match next {
|
||||
'.' => {
|
||||
// Dot notation: identifier
|
||||
chars.next();
|
||||
let (key, safe_nil) = read_ident(path, &mut chars);
|
||||
if key.is_empty() {
|
||||
return Err(Error::runtime(format!("empty key in path at position {pos}")));
|
||||
}
|
||||
segments.push((PathKey::Str(key), safe_nil));
|
||||
}
|
||||
'[' => {
|
||||
// Bracket notation: either integer or quoted string
|
||||
chars.next();
|
||||
let key = match chars.peek() {
|
||||
Some(&(pos, c @ '0'..='9' | c @ '-')) => {
|
||||
// Integer key
|
||||
let negative = c == '-';
|
||||
if negative {
|
||||
chars.next(); // consume '-'
|
||||
}
|
||||
let mut num: Option<Integer> = None;
|
||||
while let Some(&(_, c @ '0'..='9')) = chars.peek() {
|
||||
let new_num = num
|
||||
.unwrap_or(0)
|
||||
.checked_mul(10)
|
||||
.and_then(|n| n.checked_add((c as u8 - b'0') as Integer))
|
||||
.ok_or_else(|| {
|
||||
Error::runtime(format!("integer overflow in path at position {pos}"))
|
||||
})?;
|
||||
num = Some(new_num);
|
||||
chars.next(); // consume digit
|
||||
}
|
||||
match num {
|
||||
Some(n) if negative => PathKey::Int(-n),
|
||||
Some(n) => PathKey::Int(n),
|
||||
None => {
|
||||
let err = format!("invalid integer in path at position {pos}");
|
||||
return Err(Error::runtime(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
Some((_, '\'' | '"')) => {
|
||||
// Quoted string
|
||||
PathKey::Str(unquote_string(path, &mut chars)?)
|
||||
}
|
||||
Some((_, ']')) => {
|
||||
return Err(Error::runtime(format!("empty key in path at position {pos}")));
|
||||
}
|
||||
Some((pos, c)) => {
|
||||
let err = format!("unexpected character '{c}' in path at position {pos}");
|
||||
return Err(Error::runtime(err));
|
||||
}
|
||||
None => {
|
||||
return Err(Error::runtime("unexpected end of path"));
|
||||
}
|
||||
};
|
||||
// Expect closing bracket
|
||||
let mut safe_nil = false;
|
||||
match chars.next() {
|
||||
Some((_, ']')) => {
|
||||
// Check for optional safe-nil operator
|
||||
if let Some(&(_, '?')) = chars.peek() {
|
||||
safe_nil = true;
|
||||
chars.next(); // consume '?'
|
||||
}
|
||||
}
|
||||
Some((pos, c)) => {
|
||||
let err = format!("expected ']' in path at position {pos}, found '{c}'");
|
||||
return Err(Error::runtime(err));
|
||||
}
|
||||
None => {
|
||||
return Err(Error::runtime("unexpected end of path"));
|
||||
}
|
||||
}
|
||||
segments.push((key, safe_nil));
|
||||
}
|
||||
c if c.is_ascii_whitespace() => {
|
||||
chars.next(); // Skip whitespace
|
||||
}
|
||||
_ if segments.is_empty() => {
|
||||
// First segment without dot/bracket notation
|
||||
let (key_cow, safe_nil) = read_ident(path, &mut chars);
|
||||
if key_cow.is_empty() {
|
||||
return Err(Error::runtime(format!("empty key in path at position {pos}")));
|
||||
}
|
||||
segments.push((PathKey::Str(key_cow), safe_nil));
|
||||
}
|
||||
c => {
|
||||
let err = format!("unexpected character '{c}' in path at position {pos}");
|
||||
return Err(Error::runtime(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(segments)
|
||||
}
|
||||
|
||||
fn unquote_string<'a>(path: &'a str, chars: &mut Peekable<CharIndices<'a>>) -> Result<Cow<'a, str>> {
|
||||
let (start_pos, first_quote) = chars.next().unwrap();
|
||||
let mut result = String::new();
|
||||
loop {
|
||||
match chars.next() {
|
||||
Some((pos, '\\')) => {
|
||||
if result.is_empty() {
|
||||
// First escape found, copy everything up to this point
|
||||
result.push_str(&path[start_pos + 1..pos]);
|
||||
}
|
||||
match chars.next() {
|
||||
Some((_, '\\')) => result.push('\\'),
|
||||
Some((_, '"')) => result.push('"'),
|
||||
Some((_, '\'')) => result.push('\''),
|
||||
Some((_, other)) => {
|
||||
result.push('\\');
|
||||
result.push(other);
|
||||
}
|
||||
None => continue, // will be handled by outer loop
|
||||
}
|
||||
}
|
||||
Some((pos, c)) if c == first_quote => {
|
||||
if !result.is_empty() {
|
||||
return Ok(Cow::Owned(result));
|
||||
}
|
||||
// No escapes, return borrowed slice
|
||||
return Ok(Cow::Borrowed(&path[start_pos + 1..pos]));
|
||||
}
|
||||
Some((_, c)) => {
|
||||
if !result.is_empty() {
|
||||
result.push(c);
|
||||
}
|
||||
// If no escapes yet, continue tracking for potential borrowed slice
|
||||
}
|
||||
None => {
|
||||
let err = format!("unexpected end of string at position {start_pos}");
|
||||
return Err(Error::runtime(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{parse_path, PathKey};
|
||||
|
||||
#[test]
|
||||
fn test_parse_path() {
|
||||
// Test valid paths
|
||||
let path = parse_path("a.b[3]?.c['d']").unwrap();
|
||||
assert_eq!(path.len(), 5);
|
||||
assert!(matches!(path[0], (PathKey::Str(ref s), false) if s == "a"));
|
||||
assert!(matches!(path[1], (PathKey::Str(ref s), false) if s == "b"));
|
||||
assert!(matches!(path[2], (PathKey::Int(3), true)));
|
||||
assert!(matches!(path[3], (PathKey::Str(ref s), false) if s == "c"));
|
||||
assert!(matches!(path[4], (PathKey::Str(ref s), false) if s == "d"));
|
||||
|
||||
// Test empty path
|
||||
let path = parse_path("").unwrap();
|
||||
assert_eq!(path.len(), 0);
|
||||
let path = parse_path(" ").unwrap();
|
||||
assert_eq!(path.len(), 0);
|
||||
|
||||
// Test invalid dot syntax
|
||||
let err = parse_path("a..b").unwrap_err().to_string();
|
||||
assert_eq!(err, "runtime error: empty key in path at position 1");
|
||||
let err = parse_path("a.b.").unwrap_err().to_string();
|
||||
assert_eq!(err, "runtime error: empty key in path at position 3");
|
||||
|
||||
// Test invalid bracket syntax
|
||||
let err = parse_path("a[unclosed").unwrap_err().to_string();
|
||||
assert_eq!(
|
||||
err,
|
||||
"runtime error: unexpected character 'u' in path at position 2"
|
||||
);
|
||||
let err = parse_path("a[]").unwrap_err().to_string();
|
||||
assert_eq!(err, "runtime error: empty key in path at position 1");
|
||||
let err = parse_path(r#"a["unclosed"#).unwrap_err().to_string();
|
||||
assert_eq!(err, "runtime error: unexpected end of string at position 2");
|
||||
let err = parse_path(r#"a["#).unwrap_err().to_string();
|
||||
assert_eq!(err, "runtime error: unexpected end of path");
|
||||
let err = parse_path(r#"a[123"#).unwrap_err().to_string();
|
||||
assert_eq!(err, "runtime error: unexpected end of path");
|
||||
let err = parse_path(r#"a['bla'123"#).unwrap_err().to_string();
|
||||
assert_eq!(
|
||||
err,
|
||||
"runtime error: expected ']' in path at position 7, found '1'"
|
||||
);
|
||||
let err = parse_path(r#"a["bla"]x"#).unwrap_err().to_string();
|
||||
assert_eq!(
|
||||
err,
|
||||
"runtime error: unexpected character 'x' in path at position 8"
|
||||
);
|
||||
|
||||
// Test bad integers
|
||||
let err = parse_path("a[99999999999999999999]").unwrap_err().to_string();
|
||||
assert_eq!(err, "runtime error: integer overflow in path at position 2");
|
||||
let err = parse_path("a[-]").unwrap_err().to_string();
|
||||
assert_eq!(err, "runtime error: invalid integer in path at position 2");
|
||||
}
|
||||
}
|
||||
+18
-8
@@ -1,6 +1,6 @@
|
||||
//! Mostly copied from [bevy_utils]
|
||||
//! Inspired by bevy's [disqualified]
|
||||
//!
|
||||
//! [bevy_utils]: https://github.com/bevyengine/bevy/blob/main/crates/bevy_utils/src/short_names.rs
|
||||
//! [disqualified]: https://github.com/bevyengine/disqualified/blob/main/src/short_name.rs
|
||||
|
||||
use std::any::type_name;
|
||||
|
||||
@@ -23,8 +23,7 @@ pub(crate) fn short_type_name<T: ?Sized>() -> String {
|
||||
while index < end_of_string {
|
||||
let rest_of_string = full_name.get(index..end_of_string).unwrap_or_default();
|
||||
|
||||
// Collapse everything up to the next special character,
|
||||
// then skip over it
|
||||
// Collapse everything up to the next special character, then skip over it
|
||||
if let Some(special_character_index) =
|
||||
rest_of_string.find(|c: char| [' ', '<', '>', '(', ')', '[', ']', ',', ';'].contains(&c))
|
||||
{
|
||||
@@ -32,11 +31,16 @@ pub(crate) fn short_type_name<T: ?Sized>() -> String {
|
||||
parsed_name += collapse_type_name(segment_to_collapse);
|
||||
// Insert the special character
|
||||
let special_character = &rest_of_string[special_character_index..=special_character_index];
|
||||
parsed_name.push_str(special_character);
|
||||
parsed_name += special_character;
|
||||
|
||||
// Remove lifetimes like <'_> or <'_, '_, ...>
|
||||
if parsed_name.ends_with("<'_>") || parsed_name.ends_with("<'_, ") {
|
||||
_ = parsed_name.split_off(parsed_name.len() - 4);
|
||||
}
|
||||
|
||||
match special_character {
|
||||
">" | ")" | "]" if rest_of_string[special_character_index + 1..].starts_with("::") => {
|
||||
parsed_name.push_str("::");
|
||||
parsed_name += "::";
|
||||
// Move the index past the "::"
|
||||
index += special_character_index + 3;
|
||||
}
|
||||
@@ -53,14 +57,18 @@ pub(crate) fn short_type_name<T: ?Sized>() -> String {
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn collapse_type_name(string: &str) -> &str {
|
||||
string.rsplit("::").next().unwrap()
|
||||
fn collapse_type_name(segment: &str) -> &str {
|
||||
segment.rsplit("::").next().unwrap()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::short_type_name;
|
||||
use std::collections::HashMap;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
struct MyData<'a, 'b>(PhantomData<&'a &'b ()>);
|
||||
struct MyDataT<'a, T>(PhantomData<&'a T>);
|
||||
|
||||
#[test]
|
||||
fn tests() {
|
||||
@@ -73,5 +81,7 @@ mod tests {
|
||||
"HashMap<String, Option<[i32; 3]>>"
|
||||
);
|
||||
assert_eq!(short_type_name::<dyn Fn(i32) -> i32>(), "dyn Fn(i32) -> i32");
|
||||
assert_eq!(short_type_name::<MyDataT<&str>>(), "MyDataT<&str>");
|
||||
assert_eq!(short_type_name::<(&MyData, [MyData])>(), "(MyData, [MyData])");
|
||||
}
|
||||
}
|
||||
|
||||
+25
-12
@@ -28,9 +28,10 @@ use {
|
||||
/// The non-primitive variants (eg. string/table/function/thread/userdata) contain handle types
|
||||
/// into the internal Lua state. It is a logic error to mix handle types between separate
|
||||
/// `Lua` instances, and doing so will result in a panic.
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Default)]
|
||||
pub enum Value {
|
||||
/// The Lua value `nil`.
|
||||
#[default]
|
||||
Nil,
|
||||
/// The Lua value `true` or `false`.
|
||||
Boolean(bool),
|
||||
@@ -356,6 +357,10 @@ impl Value {
|
||||
///
|
||||
/// If the value is a Lua [`String`], try to convert it to [`BorrowedStr`] or return `None`
|
||||
/// otherwise.
|
||||
#[deprecated(
|
||||
since = "0.11.0",
|
||||
note = "This method does not follow Rust naming convention. Use `as_string().and_then(|s| s.to_str().ok())` instead."
|
||||
)]
|
||||
#[inline]
|
||||
pub fn as_str(&self) -> Option<BorrowedStr<'_>> {
|
||||
self.as_string().and_then(|s| s.to_str().ok())
|
||||
@@ -364,6 +369,10 @@ impl Value {
|
||||
/// Cast the value to [`StdString`].
|
||||
///
|
||||
/// If the value is a Lua [`String`], converts it to [`StdString`] or returns `None` otherwise.
|
||||
#[deprecated(
|
||||
since = "0.11.0",
|
||||
note = "This method does not follow Rust naming convention. Use `as_string().map(|s| s.to_string_lossy())` instead."
|
||||
)]
|
||||
#[inline]
|
||||
pub fn as_string_lossy(&self) -> Option<StdString> {
|
||||
self.as_string().map(|s| s.to_string_lossy())
|
||||
@@ -483,7 +492,6 @@ impl Value {
|
||||
/// This allows customizing serialization behavior using serde.
|
||||
#[cfg(feature = "serde")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
|
||||
#[doc(hidden)]
|
||||
pub fn to_serializable(&self) -> SerializableValue<'_> {
|
||||
SerializableValue::new(self, Default::default(), None)
|
||||
}
|
||||
@@ -572,12 +580,6 @@ impl Value {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Value {
|
||||
fn default() -> Self {
|
||||
Self::Nil
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Value {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
if fmt.alternate() {
|
||||
@@ -676,7 +678,7 @@ impl<'a> SerializableValue<'a> {
|
||||
///
|
||||
/// Default: **true**
|
||||
#[must_use]
|
||||
pub const fn deny_unsupported_types(mut self, enabled: bool) -> Self {
|
||||
pub fn deny_unsupported_types(mut self, enabled: bool) -> Self {
|
||||
self.options.deny_unsupported_types = enabled;
|
||||
self
|
||||
}
|
||||
@@ -687,7 +689,7 @@ impl<'a> SerializableValue<'a> {
|
||||
///
|
||||
/// Default: **true**
|
||||
#[must_use]
|
||||
pub const fn deny_recursive_tables(mut self, enabled: bool) -> Self {
|
||||
pub fn deny_recursive_tables(mut self, enabled: bool) -> Self {
|
||||
self.options.deny_recursive_tables = enabled;
|
||||
self
|
||||
}
|
||||
@@ -696,7 +698,7 @@ impl<'a> SerializableValue<'a> {
|
||||
///
|
||||
/// Default: **false**
|
||||
#[must_use]
|
||||
pub const fn sort_keys(mut self, enabled: bool) -> Self {
|
||||
pub fn sort_keys(mut self, enabled: bool) -> Self {
|
||||
self.options.sort_keys = enabled;
|
||||
self
|
||||
}
|
||||
@@ -705,10 +707,21 @@ impl<'a> SerializableValue<'a> {
|
||||
///
|
||||
/// Default: **false**
|
||||
#[must_use]
|
||||
pub const fn encode_empty_tables_as_array(mut self, enabled: bool) -> Self {
|
||||
pub fn encode_empty_tables_as_array(mut self, enabled: bool) -> Self {
|
||||
self.options.encode_empty_tables_as_array = enabled;
|
||||
self
|
||||
}
|
||||
|
||||
/// If true, enable detection of mixed tables.
|
||||
///
|
||||
/// A mixed table is a table that has both array-like and map-like entries or several borders.
|
||||
///
|
||||
/// Default: **false**
|
||||
#[must_use]
|
||||
pub fn detect_mixed_tables(mut self, enabled: bool) -> Self {
|
||||
self.options.detect_mixed_tables = enabled;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
|
||||
+34
-2
@@ -8,7 +8,7 @@ use futures_util::stream::TryStreamExt;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use mlua::{
|
||||
Error, Function, Lua, LuaOptions, MultiValue, ObjectLike, Result, StdLib, Table, UserData,
|
||||
Error, Function, Lua, LuaOptions, MultiValue, ObjectLike, Result, StdLib, Table, ThreadStatus, UserData,
|
||||
UserDataMethods, UserDataRef, Value,
|
||||
};
|
||||
|
||||
@@ -386,7 +386,7 @@ async fn test_async_table_object_like() -> Result<()> {
|
||||
table.get::<i64>("val")
|
||||
})?,
|
||||
)?;
|
||||
table.set_metatable(Some(metatable));
|
||||
table.set_metatable(Some(metatable))?;
|
||||
assert_eq!(table.call_async::<i64>(()).await.unwrap(), 15);
|
||||
|
||||
match table.call_async_method::<()>("non_existent", ()).await {
|
||||
@@ -667,3 +667,35 @@ async fn test_async_hook() -> Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_async_yield_with() -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
|
||||
let func = lua.create_async_function(|lua, (mut a, mut b): (i32, i32)| async move {
|
||||
let zero = lua.yield_with::<MultiValue>(()).await?;
|
||||
assert!(zero.is_empty());
|
||||
let one = lua.yield_with::<MultiValue>(a + b).await?;
|
||||
assert_eq!(one.len(), 1);
|
||||
|
||||
for _ in 0..3 {
|
||||
(a, b) = lua.yield_with((a + b, a * b)).await?;
|
||||
}
|
||||
Ok((0, 0))
|
||||
})?;
|
||||
|
||||
let thread = lua.create_thread(func)?;
|
||||
|
||||
let zero = thread.resume::<MultiValue>((2, 3))?; // function arguments
|
||||
assert!(zero.is_empty());
|
||||
let one = thread.resume::<i32>(())?; // value of "zero" is passed here
|
||||
assert_eq!(one, 5);
|
||||
|
||||
assert_eq!(thread.resume::<(i32, i32)>(1)?, (5, 6)); // value of "one" is passed here
|
||||
assert_eq!(thread.resume::<(i32, i32)>((10, 11))?, (21, 110));
|
||||
assert_eq!(thread.resume::<(i32, i32)>((11, 12))?, (23, 132));
|
||||
assert_eq!(thread.resume::<(i32, i32)>((12, 13))?, (0, 0));
|
||||
assert_eq!(thread.status(), ThreadStatus::Finished);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+68
-2
@@ -1,5 +1,7 @@
|
||||
#![cfg(feature = "luau")]
|
||||
|
||||
use std::io::{Read, Seek, SeekFrom, Write};
|
||||
|
||||
use mlua::{Lua, Result, Value};
|
||||
|
||||
#[test]
|
||||
@@ -41,7 +43,7 @@ fn test_buffer() -> Result<()> {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "range end index 14 out of range for slice of length 13")]
|
||||
#[should_panic(expected = "out of range for slice of length 13")]
|
||||
fn test_buffer_out_of_bounds_read() {
|
||||
let lua = Lua::new();
|
||||
let buf = lua.create_buffer(b"hello, world!").unwrap();
|
||||
@@ -49,9 +51,73 @@ fn test_buffer_out_of_bounds_read() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "range end index 16 out of range for slice of length 13")]
|
||||
#[should_panic(expected = "out of range for slice of length 13")]
|
||||
fn test_buffer_out_of_bounds_write() {
|
||||
let lua = Lua::new();
|
||||
let buf = lua.create_buffer(b"hello, world!").unwrap();
|
||||
buf.write_bytes(14, b"!!");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_large_buffer() {
|
||||
let lua = Lua::new();
|
||||
let err = lua.create_buffer_with_capacity(1_073_741_824 + 1).unwrap_err(); // 1GB
|
||||
assert!(err.to_string().contains("memory allocation error"));
|
||||
|
||||
// Normal buffer is okay
|
||||
let buf = lua.create_buffer_with_capacity(1024 * 1024).unwrap();
|
||||
assert_eq!(buf.len(), 1024 * 1024);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_buffer_cursor() -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
let mut cursor = lua.create_buffer(b"hello, world")?.cursor();
|
||||
|
||||
let mut data = Vec::new();
|
||||
cursor.read_to_end(&mut data)?;
|
||||
assert_eq!(data, b"hello, world");
|
||||
|
||||
// No more data to read
|
||||
let mut one = [0u8; 1];
|
||||
assert_eq!(cursor.read(&mut one)?, 0);
|
||||
|
||||
// Seek to start
|
||||
cursor.seek(SeekFrom::Start(0))?;
|
||||
cursor.read_exact(&mut one)?;
|
||||
assert_eq!(one, [b'h']);
|
||||
|
||||
// Seek to end -5
|
||||
cursor.seek(SeekFrom::End(-5))?;
|
||||
let mut five = [0u8; 5];
|
||||
cursor.read_exact(&mut five)?;
|
||||
assert_eq!(&five, b"world");
|
||||
|
||||
// Seek to current -1
|
||||
cursor.seek(SeekFrom::Current(-1))?;
|
||||
cursor.read_exact(&mut one)?;
|
||||
assert_eq!(one, [b'd']);
|
||||
|
||||
// Invalid seek
|
||||
assert!(cursor.seek(SeekFrom::Current(-100)).is_err());
|
||||
assert!(cursor.seek(SeekFrom::End(1)).is_err());
|
||||
|
||||
// Write data
|
||||
let buf = lua.create_buffer_with_capacity(100)?;
|
||||
cursor = buf.clone().cursor();
|
||||
|
||||
cursor.write_all(b"hello, ...")?;
|
||||
cursor.seek(SeekFrom::Current(-3))?;
|
||||
cursor.write_all(b"Rust!")?;
|
||||
|
||||
assert_eq!(&buf.read_bytes::<12>(0), b"hello, Rust!");
|
||||
|
||||
// Writing beyond the end of the buffer does nothing
|
||||
cursor.seek(SeekFrom::End(0))?;
|
||||
assert_eq!(cursor.write(b".")?, 0);
|
||||
|
||||
// Flush is no-op
|
||||
cursor.flush()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+9
-12
@@ -119,12 +119,11 @@ fn test_compiler() -> Result<()> {
|
||||
.set_debug_level(2)
|
||||
.set_type_info_level(1)
|
||||
.set_coverage_level(2)
|
||||
.set_vector_lib("vector")
|
||||
.set_vector_ctor("new")
|
||||
.set_vector_ctor("vector.new")
|
||||
.set_vector_type("vector")
|
||||
.set_mutable_globals(vec!["mutable_global"])
|
||||
.set_userdata_types(vec!["MyUserdata"])
|
||||
.set_disabled_builtins(vec!["tostring"]);
|
||||
.set_mutable_globals(["mutable_global"])
|
||||
.set_userdata_types(["MyUserdata"])
|
||||
.set_disabled_builtins(["tostring"]);
|
||||
|
||||
assert!(compiler.compile("return tostring(vector.new(1, 2, 3))").is_ok());
|
||||
|
||||
@@ -142,16 +141,14 @@ fn test_compiler() -> Result<()> {
|
||||
#[cfg(feature = "luau")]
|
||||
#[test]
|
||||
fn test_compiler_library_constants() {
|
||||
use mlua::{CompileConstant, Compiler, Vector};
|
||||
use mlua::{Compiler, Vector};
|
||||
|
||||
let compiler = Compiler::new()
|
||||
.set_optimization_level(2)
|
||||
.set_library_constants(vec![
|
||||
("mylib", "const_bool", CompileConstant::Boolean(true)),
|
||||
("mylib", "const_num", CompileConstant::Number(123.0)),
|
||||
("mylib", "const_vec", CompileConstant::Vector(Vector::zero())),
|
||||
("mylib", "const_str", "value1".into()),
|
||||
]);
|
||||
.add_library_constant("mylib.const_bool", true)
|
||||
.add_library_constant("mylib.const_num", 123.0)
|
||||
.add_library_constant("mylib.const_vec", Vector::zero())
|
||||
.add_library_constant("mylib.const_str", "value1");
|
||||
|
||||
let lua = Lua::new();
|
||||
lua.set_compiler(compiler);
|
||||
|
||||
+6
-6
@@ -267,7 +267,7 @@ fn test_registry_value_into_lua() -> Result<()> {
|
||||
let r = lua.create_registry_value(&s)?;
|
||||
let value1 = lua.pack(&r)?;
|
||||
let value2 = lua.pack(r)?;
|
||||
assert_eq!(value1.as_str().as_deref(), Some("hello, world"));
|
||||
assert_eq!(value1.to_string()?, "hello, world");
|
||||
assert_eq!(value1.to_pointer(), value2.to_pointer());
|
||||
|
||||
// Push into stack
|
||||
@@ -560,11 +560,11 @@ fn test_osstring_into_from_lua() -> Result<()> {
|
||||
|
||||
let v = lua.pack(s.as_os_str())?;
|
||||
assert!(v.is_string());
|
||||
assert_eq!(v.as_str().unwrap(), "hello, world");
|
||||
assert_eq!(v.as_string().unwrap(), "hello, world");
|
||||
|
||||
let v = lua.pack(s)?;
|
||||
assert!(v.is_string());
|
||||
assert_eq!(v.as_str().unwrap(), "hello, world");
|
||||
assert_eq!(v.as_string().unwrap(), "hello, world");
|
||||
|
||||
let s = lua.create_string("hello, world")?;
|
||||
let bstr = lua.unpack::<OsString>(Value::String(s))?;
|
||||
@@ -588,11 +588,11 @@ fn test_pathbuf_into_from_lua() -> Result<()> {
|
||||
|
||||
let v = lua.pack(pb.as_path())?;
|
||||
assert!(v.is_string());
|
||||
assert_eq!(v.as_str().unwrap(), pb_str);
|
||||
assert_eq!(v.to_string().unwrap(), pb_str);
|
||||
|
||||
let v = lua.pack(pb.clone())?;
|
||||
assert!(v.is_string());
|
||||
assert_eq!(v.as_str().unwrap(), pb_str);
|
||||
assert_eq!(v.to_string().unwrap(), pb_str);
|
||||
|
||||
let s = lua.create_string(pb_str)?;
|
||||
let bstr = lua.unpack::<PathBuf>(Value::String(s))?;
|
||||
@@ -724,7 +724,7 @@ fn test_char_into_lua() -> Result<()> {
|
||||
|
||||
let v = '🦀';
|
||||
let v2 = v.into_lua(&lua)?;
|
||||
assert_eq!(Some(v.to_string()), v2.as_string_lossy());
|
||||
assert_eq!(*v2.as_string().unwrap(), v.to_string());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+2
-2
@@ -24,7 +24,7 @@ fn test_line_counts() -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
lua.set_hook(HookTriggers::EVERY_LINE, move |_lua, debug| {
|
||||
assert_eq!(debug.event(), DebugEvent::Line);
|
||||
hook_output.lock().unwrap().push(debug.curr_line());
|
||||
hook_output.lock().unwrap().push(debug.current_line().unwrap());
|
||||
Ok(VmState::Continue)
|
||||
})?;
|
||||
lua.load(
|
||||
@@ -240,7 +240,7 @@ fn test_hook_threads() -> Result<()> {
|
||||
let hook_output = output.clone();
|
||||
co.set_hook(HookTriggers::EVERY_LINE, move |_lua, debug| {
|
||||
assert_eq!(debug.event(), DebugEvent::Line);
|
||||
hook_output.lock().unwrap().push(debug.curr_line());
|
||||
hook_output.lock().unwrap().push(debug.current_line().unwrap());
|
||||
Ok(VmState::Continue)
|
||||
})?;
|
||||
|
||||
|
||||
+16
-6
@@ -3,7 +3,6 @@
|
||||
use std::cell::Cell;
|
||||
use std::fmt::Debug;
|
||||
use std::os::raw::c_void;
|
||||
use std::panic::{catch_unwind, AssertUnwindSafe};
|
||||
use std::sync::atomic::{AtomicBool, AtomicPtr, AtomicU64, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -120,11 +119,13 @@ fn test_vector_metatable() -> Result<()> {
|
||||
"#,
|
||||
)
|
||||
.eval::<Table>()?;
|
||||
vector_mt.set_metatable(Some(vector_mt.clone()));
|
||||
vector_mt.set_metatable(Some(vector_mt.clone()))?;
|
||||
lua.set_type_metatable::<Vector>(Some(vector_mt.clone()));
|
||||
lua.globals().set("Vector3", vector_mt)?;
|
||||
|
||||
let compiler = Compiler::new().set_vector_lib("Vector3").set_vector_ctor("new");
|
||||
let compiler = Compiler::new()
|
||||
.set_vector_ctor("Vector3.new")
|
||||
.set_vector_type("Vector3");
|
||||
|
||||
// Test vector methods (fastcall)
|
||||
lua.load(
|
||||
@@ -167,9 +168,9 @@ fn test_readonly_table() -> Result<()> {
|
||||
check_readonly_error(t.raw_pop::<Value>());
|
||||
|
||||
// Special case
|
||||
match catch_unwind(AssertUnwindSafe(|| t.set_metatable(None))) {
|
||||
Ok(_) => panic!("expected panic, got nothing"),
|
||||
Err(_) => {}
|
||||
match t.set_metatable(None) {
|
||||
Err(Error::RuntimeError(e)) if e.contains("attempt to modify a readonly table") => {}
|
||||
r => panic!("expected RuntimeError(...) with a specific message, got {r:?}"),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -329,6 +330,15 @@ fn test_interrupts() -> Result<()> {
|
||||
assert_eq!(yield_count.load(Ordering::Relaxed), 7);
|
||||
assert_eq!(co.status(), ThreadStatus::Finished);
|
||||
|
||||
// Test no yielding at non-yieldable points
|
||||
yield_count.store(0, Ordering::Relaxed);
|
||||
let co = lua.create_thread(lua.create_function(|lua, arg: Value| {
|
||||
(lua.load("return (function(x) return x end)(...)")).call::<Value>(arg)
|
||||
})?)?;
|
||||
let res = co.resume::<String>("abc")?;
|
||||
assert_eq!(res, "abc".to_string());
|
||||
assert_eq!(yield_count.load(Ordering::Relaxed), 3);
|
||||
|
||||
//
|
||||
// Test errors in interrupts
|
||||
//
|
||||
|
||||
@@ -179,6 +179,11 @@ fn test_require_with_config() {
|
||||
let res = run_require(&lua, "./tests/luau/require/with_config/src/alias_requirer").unwrap();
|
||||
assert_eq!("result from dependency", get_str(&res, 1));
|
||||
|
||||
// RequirePathWithAlias (case-insensitive)
|
||||
let res2 = run_require(&lua, "./tests/luau/require/with_config/src/alias_requirer_uc").unwrap();
|
||||
assert_eq!("result from dependency", get_str(&res2, 1));
|
||||
assert_eq!(res.to_pointer(), res2.to_pointer());
|
||||
|
||||
// RequirePathWithParentAlias
|
||||
let res = run_require(&lua, "./tests/luau/require/with_config/src/parent_alias_requirer").unwrap();
|
||||
assert_eq!("result from other_dependency", get_str(&res, 1));
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
return require("@DeP")
|
||||
+2
-2
@@ -43,12 +43,12 @@ fn test_result_conversions() -> Result<()> {
|
||||
let multi_err1 = err1.into_lua_multi(&lua)?;
|
||||
assert_eq!(multi_err1.len(), 2);
|
||||
assert_eq!(multi_err1[0], Value::Nil);
|
||||
assert_eq!(multi_err1[1].as_str().unwrap(), "failure1");
|
||||
assert_eq!(multi_err1[1].as_string().unwrap(), "failure1");
|
||||
|
||||
let ok2 = Ok::<_, Error>("!");
|
||||
let multi_ok2 = ok2.into_lua_multi(&lua)?;
|
||||
assert_eq!(multi_ok2.len(), 1);
|
||||
assert_eq!(multi_ok2[0].as_str().unwrap(), "!");
|
||||
assert_eq!(multi_ok2[0].as_string().unwrap(), "!");
|
||||
let err2 = Err::<String, _>("failure2".into_lua_err());
|
||||
let multi_err2 = err2.into_lua_multi(&lua)?;
|
||||
assert_eq!(multi_err2.len(), 2);
|
||||
|
||||
+4
-2
@@ -382,7 +382,8 @@ fn test_scope_userdata_ref() -> Result<()> {
|
||||
modify_userdata(&lua, &ud)?;
|
||||
|
||||
// We can only borrow userdata scoped
|
||||
assert!((matches!(ud.borrow::<MyUserData>(), Err(Error::UserDataTypeMismatch))));
|
||||
#[rustfmt::skip]
|
||||
assert!(matches!(ud.borrow::<MyUserData>(), Err(Error::UserDataTypeMismatch)));
|
||||
ud.borrow_scoped::<MyUserData, ()>(|ud_inst| {
|
||||
assert_eq!(ud_inst.0.get(), 2);
|
||||
})?;
|
||||
@@ -419,7 +420,8 @@ fn test_scope_userdata_ref_mut() -> Result<()> {
|
||||
let ud = scope.create_userdata_ref_mut(&mut data)?;
|
||||
modify_userdata(&lua, &ud)?;
|
||||
|
||||
assert!((matches!(ud.borrow_mut::<MyUserData>(), Err(Error::UserDataTypeMismatch))));
|
||||
#[rustfmt::skip]
|
||||
assert!(matches!(ud.borrow_mut::<MyUserData>(), Err(Error::UserDataTypeMismatch)));
|
||||
ud.borrow_mut_scoped::<MyUserData, ()>(|ud_inst| {
|
||||
ud_inst.0 += 10;
|
||||
})?;
|
||||
|
||||
+41
-2
@@ -25,7 +25,7 @@ fn test_serialize() -> Result<(), Box<dyn StdError>> {
|
||||
globals.set("null", lua.null())?;
|
||||
|
||||
let empty_array = lua.create_table()?;
|
||||
empty_array.set_metatable(Some(lua.array_metatable()));
|
||||
empty_array.set_metatable(Some(lua.array_metatable()))?;
|
||||
globals.set("empty_array", empty_array)?;
|
||||
|
||||
let val = lua
|
||||
@@ -173,7 +173,7 @@ fn test_serialize_sorted() -> LuaResult<()> {
|
||||
globals.set("null", lua.null())?;
|
||||
|
||||
let empty_array = lua.create_table()?;
|
||||
empty_array.set_metatable(Some(lua.array_metatable()));
|
||||
empty_array.set_metatable(Some(lua.array_metatable()))?;
|
||||
globals.set("empty_array", empty_array)?;
|
||||
|
||||
let value = lua
|
||||
@@ -269,6 +269,45 @@ fn test_serialize_empty_table() -> LuaResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_serialize_mixed_table() -> LuaResult<()> {
|
||||
let lua = Lua::new();
|
||||
|
||||
// Check that sparse array is serialized similarly when using direct serialization
|
||||
// and via `Lua::from_value`
|
||||
let table = lua.load("{1,2,3,nil,5}").eval::<Value>()?;
|
||||
let json1 = serde_json::to_string(&table).unwrap();
|
||||
let json2 = lua.from_value::<serde_json::Value>(table)?;
|
||||
assert_eq!(json1, json2.to_string());
|
||||
|
||||
// A table with several borders should be correctly encoded when `detect_mixed_tables` is enabled
|
||||
let table = lua
|
||||
.load(
|
||||
r#"
|
||||
local t = {1,2,3,nil,5,6}
|
||||
t[10] = 10
|
||||
return t
|
||||
"#,
|
||||
)
|
||||
.eval::<Value>()?;
|
||||
let json = serde_json::to_string(&table.to_serializable().detect_mixed_tables(true)).unwrap();
|
||||
assert_eq!(json, r#"[1,2,3,null,5,6,null,null,null,10]"#);
|
||||
|
||||
// A mixed table with both array-like and map-like entries
|
||||
let table = lua.load(r#"{1,2,3, key="value"}"#).eval::<Value>()?;
|
||||
let json = serde_json::to_string(&table).unwrap();
|
||||
assert_eq!(json, r#"[1,2,3]"#);
|
||||
let json = serde_json::to_string(&table.to_serializable().detect_mixed_tables(true)).unwrap();
|
||||
assert_eq!(json, r#"{"1":1,"2":2,"3":3,"key":"value"}"#);
|
||||
|
||||
// A mixed table with duplicate keys of different types
|
||||
let table = lua.load(r#"{1,2,3, ["1"]="value"}"#).eval::<Value>()?;
|
||||
let json = serde_json::to_string(&table.to_serializable().detect_mixed_tables(true)).unwrap();
|
||||
assert_eq!(json, r#"{"1":1,"2":2,"3":3,"1":"value"}"#);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_value_struct() -> LuaResult<()> {
|
||||
let lua = Lua::new();
|
||||
|
||||
+108
-2
@@ -61,6 +61,15 @@ fn test_table() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")] // Linux allow overcommiting the memory (relevant for CI)
|
||||
fn test_table_with_large_capacity() {
|
||||
let lua = Lua::new();
|
||||
|
||||
let t = lua.create_table_with_capacity(1 << 26, 1 << 26);
|
||||
assert!(t.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_table_push_pop() -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
@@ -263,6 +272,22 @@ fn test_table_for_each() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_table_for_each_value() -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
|
||||
let table = lua.load("{1, 2, 3, 4, 5, nil, 7}").eval::<Table>()?;
|
||||
let mut sum = 0;
|
||||
table.for_each_value::<i32>(|v| {
|
||||
sum += v;
|
||||
Ok(())
|
||||
})?;
|
||||
// Iterations stops at the first nil
|
||||
assert_eq!(sum, 1 + 2 + 3 + 4 + 5);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_table_scope() -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
@@ -298,10 +323,10 @@ fn test_metatable() -> Result<()> {
|
||||
let table = lua.create_table()?;
|
||||
let metatable = lua.create_table()?;
|
||||
metatable.set("__index", lua.create_function(|_, ()| Ok("index_value"))?)?;
|
||||
table.set_metatable(Some(metatable));
|
||||
table.set_metatable(Some(metatable))?;
|
||||
assert_eq!(table.get::<String>("any_key")?, "index_value");
|
||||
assert_eq!(table.raw_get::<Value>("any_key")?, Value::Nil);
|
||||
table.set_metatable(None);
|
||||
table.set_metatable(None)?;
|
||||
assert_eq!(table.get::<Value>("any_key")?, Value::Nil);
|
||||
|
||||
Ok(())
|
||||
@@ -473,3 +498,84 @@ fn test_table_object_like() -> Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_table_get_path() -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
|
||||
// Create a nested table structure
|
||||
let table = lua
|
||||
.load(
|
||||
r#"
|
||||
{
|
||||
a = {
|
||||
b = {
|
||||
c = "hello",
|
||||
d = 42
|
||||
},
|
||||
[1] = "first",
|
||||
["special key"] = "special value"
|
||||
},
|
||||
abc = "top level",
|
||||
x = {},
|
||||
["🚀"] = "rocket",
|
||||
[1] = {
|
||||
["nested-key"] = {
|
||||
[42] = {
|
||||
final = "hello!",
|
||||
},
|
||||
},
|
||||
["key\"with\"quotes"] = "value1",
|
||||
["key'with'quotes"] = "value2",
|
||||
["key\\with\\backslashes"] = "value3",
|
||||
[-2] = "negative index",
|
||||
},
|
||||
}
|
||||
"#,
|
||||
)
|
||||
.eval::<Table>()?;
|
||||
|
||||
// Test basic dot notation
|
||||
assert_eq!(table.get_path::<String>(".a.b.c")?, "hello");
|
||||
assert_eq!(table.get_path::<String>("a.b.c")?, "hello");
|
||||
assert_eq!(table.get_path::<i32>("a.b.d")?, 42);
|
||||
assert_eq!(table.get_path::<String>("abc")?, "top level");
|
||||
|
||||
// Test bracket notation with integer keys
|
||||
assert_eq!(table.get_path::<String>("a[1]")?, "first");
|
||||
assert_eq!(table.get_path::<String>("[1][-2]")?, "negative index");
|
||||
|
||||
// Test bracket notation with string keys
|
||||
assert_eq!(table.get_path::<String>("a[\"special key\"]")?, "special value");
|
||||
assert_eq!(table.get_path::<String>("a['special key']")?, "special value");
|
||||
assert_eq!(table.get_path::<String>(r#"[1]["key\"with\"quotes"]"#)?, "value1");
|
||||
assert_eq!(table.get_path::<String>(r#"[1]['key"with"quotes']"#)?, "value1");
|
||||
assert_eq!(table.get_path::<String>(r#"[1]['key\'with\'quotes']"#)?, "value2");
|
||||
assert_eq!(
|
||||
table.get_path::<String>(r#"[1]["key\\with\\backslashes"]"#)?,
|
||||
"value3"
|
||||
);
|
||||
|
||||
// Test mixed notation
|
||||
assert_eq!(table.get_path::<String>("[1].nested-key[42].final")?, "hello!");
|
||||
|
||||
// Test unicode keys
|
||||
assert_eq!(table.get_path::<String>("🚀")?, "rocket");
|
||||
|
||||
// Test empty path returns the table itself
|
||||
assert_eq!(table.get_path::<Table>("")?, table);
|
||||
|
||||
// Test safe navigation
|
||||
assert_eq!(table.get_path::<String>("a?.b.c")?, "hello");
|
||||
assert_eq!(table.get_path::<Value>("x.y?.z")?, Value::Nil);
|
||||
assert_eq!(table.get_path::<Value>("[1].nested-key[43]?.final")?, Value::Nil);
|
||||
|
||||
// Test path with whitespace
|
||||
assert_eq!(table.get_path::<String>(" .a [\"b\"] .c ")?, "hello");
|
||||
|
||||
// Test indexing non-indexable value
|
||||
let err = table.get_path::<String>("abc.c").unwrap_err().to_string();
|
||||
assert_eq!(err, "runtime error: attempt to index a string value with key 'c'");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+81
-8
@@ -147,6 +147,31 @@ fn test_eval() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_replace_globals() -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
|
||||
let globals = lua.create_table()?;
|
||||
globals.set("foo", "bar")?;
|
||||
|
||||
lua.set_globals(globals.clone())?;
|
||||
let val = lua.load("return foo").eval::<StdString>()?;
|
||||
assert_eq!(val, "bar");
|
||||
|
||||
// Updating globals in sandboxed Lua state is not allowed
|
||||
#[cfg(feature = "luau")]
|
||||
{
|
||||
lua.sandbox(true)?;
|
||||
match lua.set_globals(globals) {
|
||||
Err(Error::RuntimeError(msg))
|
||||
if msg.contains("cannot change globals in a sandboxed Lua state") => {}
|
||||
r => panic!("expected RuntimeError(...) with a specific error message, got {r:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_load_mode() -> Result<()> {
|
||||
let lua = unsafe { Lua::unsafe_new() };
|
||||
@@ -577,6 +602,21 @@ fn test_num_conversion() -> Result<()> {
|
||||
|
||||
assert_eq!(lua.unpack::<i128>(lua.pack(1i128 << 64)?)?, 1i128 << 64);
|
||||
|
||||
// Negative zero
|
||||
let negative_zero = lua.load("-0.0").eval::<f64>()?;
|
||||
assert_eq!(negative_zero, 0.0);
|
||||
// LuaJIT treats -0.0 as a positive zero
|
||||
#[cfg(not(feature = "luajit"))]
|
||||
assert!(negative_zero.is_sign_negative());
|
||||
|
||||
// In Lua <5.3 all numbers are floats
|
||||
#[cfg(not(any(feature = "lua54", feature = "lua53", feature = "luajit")))]
|
||||
{
|
||||
let negative_zero = lua.load("-0").eval::<f64>()?;
|
||||
assert_eq!(negative_zero, 0.0);
|
||||
assert!(negative_zero.is_sign_negative());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1202,6 +1242,17 @@ fn test_register_module() -> Result<()> {
|
||||
res.unwrap_err().to_string(),
|
||||
"runtime error: module name must begin with '@'"
|
||||
);
|
||||
|
||||
// Luau registered modules (aliases) are case-insensitive
|
||||
let res = lua.register_module("@My_Module", &t);
|
||||
assert!(res.is_ok());
|
||||
lua.load(
|
||||
r#"
|
||||
local my_module = require("@MY_MODule")
|
||||
assert(my_module.name == "my_module")
|
||||
"#,
|
||||
)
|
||||
.exec()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -1251,14 +1302,18 @@ fn test_inspect_stack() -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
|
||||
// Not inside any function
|
||||
assert!(lua.inspect_stack(0).is_none());
|
||||
assert!(lua.inspect_stack(0, |_| ()).is_none());
|
||||
|
||||
let logline = lua.create_function(|lua, msg: StdString| {
|
||||
let debug = lua.inspect_stack(1).unwrap(); // caller
|
||||
let source = debug.source().short_src;
|
||||
let source = source.as_deref().unwrap_or("?");
|
||||
let line = debug.curr_line();
|
||||
Ok(format!("{}:{} {}", source, line, msg))
|
||||
let r = lua
|
||||
.inspect_stack(1, |debug| {
|
||||
let source = debug.source().short_src;
|
||||
let source = source.as_deref().unwrap_or("?");
|
||||
let line = debug.current_line().unwrap();
|
||||
format!("{}:{} {}", source, line, msg)
|
||||
})
|
||||
.unwrap();
|
||||
Ok(r)
|
||||
})?;
|
||||
lua.globals().set("logline", logline)?;
|
||||
|
||||
@@ -1281,8 +1336,7 @@ fn test_inspect_stack() -> Result<()> {
|
||||
.exec()?;
|
||||
|
||||
let stack_info = lua.create_function(|lua, ()| {
|
||||
let debug = lua.inspect_stack(1).unwrap(); // caller
|
||||
let stack_info = debug.stack();
|
||||
let stack_info = lua.inspect_stack(1, |debug| debug.stack()).unwrap();
|
||||
Ok(format!("{stack_info:?}"))
|
||||
})?;
|
||||
lua.globals().set("stack_info", stack_info)?;
|
||||
@@ -1312,6 +1366,25 @@ fn test_inspect_stack() -> Result<()> {
|
||||
)
|
||||
.exec()?;
|
||||
|
||||
// Test retrieving currently running function
|
||||
let running_function =
|
||||
lua.create_function(|lua, ()| Ok(lua.inspect_stack(1, |debug| debug.function())))?;
|
||||
lua.globals().set("running_function", running_function)?;
|
||||
lua.load(
|
||||
r#"
|
||||
local function baz()
|
||||
return running_function()
|
||||
end
|
||||
if jit == nil then
|
||||
assert(baz() == baz)
|
||||
else
|
||||
-- luajit inline the "baz" function and returns the chunk itself
|
||||
assert(baz() == running_function())
|
||||
end
|
||||
"#,
|
||||
)
|
||||
.exec()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
+22
-1
@@ -1,6 +1,6 @@
|
||||
use std::panic::catch_unwind;
|
||||
|
||||
use mlua::{Error, Function, Lua, Result, Thread, ThreadStatus};
|
||||
use mlua::{Error, Function, IntoLua, Lua, Result, Thread, ThreadStatus, Value};
|
||||
|
||||
#[test]
|
||||
fn test_thread() -> Result<()> {
|
||||
@@ -252,3 +252,24 @@ fn test_thread_resume_error() -> Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_thread_resume_bad_arg() -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
|
||||
struct BadArg;
|
||||
|
||||
impl IntoLua for BadArg {
|
||||
fn into_lua(self, _lua: &Lua) -> Result<Value> {
|
||||
Err(Error::runtime("bad arg"))
|
||||
}
|
||||
}
|
||||
|
||||
let f = lua.create_thread(lua.create_function(|_, ()| Ok("okay"))?)?;
|
||||
let res = f.resume::<()>((123, BadArg));
|
||||
assert!(matches!(res, Err(Error::RuntimeError(msg)) if msg == "bad arg"));
|
||||
let res = f.resume::<String>(()).unwrap();
|
||||
assert_eq!(res, "okay");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+66
-1
@@ -8,7 +8,7 @@ use std::sync::atomic::{AtomicI64, Ordering};
|
||||
|
||||
use mlua::{
|
||||
AnyUserData, Error, ExternalError, Function, Lua, MetaMethod, Nil, ObjectLike, Result, String, UserData,
|
||||
UserDataFields, UserDataMethods, UserDataRef, Value, Variadic,
|
||||
UserDataFields, UserDataMethods, UserDataRef, UserDataRegistry, Value, Variadic,
|
||||
};
|
||||
|
||||
#[test]
|
||||
@@ -525,6 +525,11 @@ fn test_fields() -> Result<()> {
|
||||
Ok(())
|
||||
});
|
||||
|
||||
// Field that emulates method
|
||||
fields.add_field_function_get("val_fget", |lua, ud| {
|
||||
lua.create_function(move |_, ()| Ok(ud.borrow::<MyUserData>()?.0))
|
||||
});
|
||||
|
||||
// Use userdata "uservalue" storage
|
||||
fields.add_field_function_get("uval", |_, ud| ud.user_value::<Option<String>>());
|
||||
fields.add_field_function_set("uval", |_, ud, s: Option<String>| ud.set_user_value(s));
|
||||
@@ -537,6 +542,10 @@ fn test_fields() -> Result<()> {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
|
||||
methods.add_method("dummy", |_, _, ()| Ok(()));
|
||||
}
|
||||
}
|
||||
|
||||
globals.set("ud", MyUserData(7))?;
|
||||
@@ -546,6 +555,7 @@ fn test_fields() -> Result<()> {
|
||||
assert(ud.val == 7)
|
||||
ud.val = 10
|
||||
assert(ud.val == 10)
|
||||
assert(ud:val_fget() == 10)
|
||||
|
||||
assert(ud.uval == nil)
|
||||
ud.uval = "hello"
|
||||
@@ -1297,3 +1307,58 @@ fn test_userdata_wrappers() -> Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "luau")]
|
||||
#[test]
|
||||
fn test_userdata_namecall() -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
|
||||
struct MyUserData;
|
||||
|
||||
impl UserData for MyUserData {
|
||||
fn register(registry: &mut mlua::UserDataRegistry<Self>) {
|
||||
registry.add_method("method", |_, _, ()| Ok("method called"));
|
||||
registry.add_field_method_get("field", |_, _| Ok("field value"));
|
||||
|
||||
registry.add_meta_method(MetaMethod::Index, |_, _, key: StdString| Ok(key));
|
||||
|
||||
registry.enable_namecall();
|
||||
}
|
||||
}
|
||||
|
||||
let ud = lua.create_userdata(MyUserData)?;
|
||||
lua.globals().set("ud", &ud)?;
|
||||
lua.load(
|
||||
r#"
|
||||
assert(ud:method() == "method called")
|
||||
assert(ud.field == "field value")
|
||||
assert(ud.dynamic_field == "dynamic_field")
|
||||
local ok, err = pcall(function() return ud:dynamic_field() end)
|
||||
assert(tostring(err):find("attempt to call an unknown method 'dynamic_field'") ~= nil)
|
||||
"#,
|
||||
)
|
||||
.exec()?;
|
||||
|
||||
ud.destroy()?;
|
||||
let err = lua.load("ud:method()").exec().unwrap_err();
|
||||
assert!(err.to_string().contains("userdata has been destructed"));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_userdata_get_path() -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
|
||||
struct MyUd;
|
||||
impl UserData for MyUd {
|
||||
fn register(registry: &mut UserDataRegistry<Self>) {
|
||||
registry.add_field("value", "userdata_value");
|
||||
}
|
||||
}
|
||||
|
||||
let ud = lua.create_userdata(MyUd)?;
|
||||
assert_eq!(ud.get_path::<String>(".value")?, "userdata_value");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+1
-10
@@ -255,16 +255,7 @@ fn test_value_conversions() -> Result<()> {
|
||||
Value::String(lua.create_string("hello")?).as_string().unwrap(),
|
||||
"hello"
|
||||
);
|
||||
assert_eq!(
|
||||
Value::String(lua.create_string("hello")?).as_str().unwrap(),
|
||||
"hello"
|
||||
);
|
||||
assert_eq!(
|
||||
Value::String(lua.create_string("hello")?)
|
||||
.as_string_lossy()
|
||||
.unwrap(),
|
||||
"hello"
|
||||
);
|
||||
assert_eq!(Value::String(lua.create_string("hello")?).to_string()?, "hello");
|
||||
assert!(Value::Table(lua.create_table()?).is_table());
|
||||
assert!(Value::Table(lua.create_table()?).as_table().is_some());
|
||||
assert!(Value::Function(lua.create_function(|_, ()| Ok(())).unwrap()).is_function());
|
||||
|
||||
Reference in New Issue
Block a user