Compare commits

..

49 Commits

Author SHA1 Message Date
Alex Orlenko 4ff677f5fc v0.10.4 2025-05-05 15:43:00 +01:00
Alex Orlenko 5b920321dd mlua-sys: v0.7.0 2025-05-05 13:25:55 +01:00
Alex Orlenko 6bb99783e9 Update CHANGELOG 2025-05-05 13:16:26 +01:00
Alex Orlenko 547f82acc1 Bump TARGET_MLUA_LUAU_ABI_VERSION 2025-05-05 12:13:50 +01:00
Alex Orlenko 9ea98e3f1e Sync mlua-sys with the main branch 2025-05-05 12:02:13 +01:00
Alex Orlenko 0ad03bee98 Update README 2025-05-05 11:59:42 +01:00
Alex Orlenko ba7c331984 Update __mlua_async_poll chunk name 2025-05-05 01:02:15 +01:00
Alex Orlenko 53dea3166a Update Lua* dependencies 2025-05-05 01:01:57 +01:00
Alex Orlenko 129dec43ed Silence clippy false positives 2025-05-05 00:59:06 +01:00
Alex Orlenko a7f58e57ec Update scoped userdata mismatch tests 2025-05-05 00:59:00 +01:00
Alex Orlenko e6871525c4 Check that type passed to scoped userdata self argument is userdata.
If passed type is non-userdata we try to get a pointer (which will be null) that triggers an assertion.
Having a check also allow us to generate right error message.
Fixes #569
2025-05-05 00:58:46 +01:00
Alex Orlenko c4956dbf49 Make StateGuard automatically enabled inside callback_error_ext.
Remove manual usage of `StateGuard` in other places.
Closes #567
2025-05-05 00:57:23 +01:00
Alex Orlenko d0ea428e23 Add encode_empty_tables_as_array serialize option.
This will change the behaviour of encoding empty Lua tables into array instead of map.
2025-05-05 00:52:22 +01:00
Alex Orlenko 3e87d3ea9b Optimize Table::is_empty 2025-05-05 00:52:14 +01:00
krakow10 0f8bde4b4e Fix Reversed Comments & Typo (#560)
* Fix reversed comments

* Fix typos
2025-05-05 00:52:07 +01:00
Alex Orlenko b9c9dfbd03 mlua-sys: Update Luau to 0.671 2025-05-05 00:51:38 +01:00
Alex Orlenko 71824297f6 mlua-sys: Add definitions for Luau require library (since 0.669) 2025-05-05 00:50:39 +01:00
Alex Orlenko c0a8064228 Update references to mlua repo 2025-05-05 00:50:31 +01:00
Alex Orlenko 84efb8bf95 Fix warnings when testing documentation 2025-05-05 00:50:19 +01:00
Alex Orlenko cf687a6ac6 Make Lua::weak() method and WeakLua struct public.
This can be useful to prevent circular dependencies between Rust and Lua
or check that Lua instance is still alive.
2025-05-05 00:50:12 +01:00
Alex Orlenko ea85edb242 Update dependencies 2025-05-05 00:49:41 +01:00
Alex Orlenko 788175e0d6 Restrict access to Luau VM from UserData destructors.
It's unsafe to make almost any Lua calls when userdata destructor is running.
This can cause recursive GC run and crash.
See https://github.com/luau-lang/luau/pull/510 for some details.
2025-05-05 00:49:26 +01:00
Alex Orlenko 90ef25a6ee Prepare for custom (Luau) userdata destructors.
We need to add logic later to prevent calling any Lua functions when UserData destructor is running.
2025-05-05 00:49:17 +01:00
Alex Orlenko ac72f6acae Do not propagate collect_garbage flag when clonning Lua 2025-05-05 00:47:39 +01:00
Alex Orlenko 4444ac4ea3 Add AnyUserData::type_id method 2025-05-05 00:47:09 +01:00
Alex Orlenko 5f950e30ec Update Lua::poll_pending doc (still hidden) 2025-05-05 00:47:02 +01:00
Alex Orlenko 311aa5f6e1 Bump Luau to 0.667 2025-05-05 00:46:45 +01:00
Alex Orlenko 739d5f5262 Reduce stack operations when creating userdata 2025-05-05 00:46:28 +01:00
Alex Orlenko 375028e13f Add Chunk::name(), Chunk::environment() and Chunk::mode() functions.
They can be used to retrieve existing chunk params.
2025-05-05 00:46:20 +01:00
Alex Orlenko 46e949c184 Use c string literal where appropriate 2025-05-05 00:44:05 +01:00
Alex Orlenko 62b53e218c Move some userdata helpers from crate::util to crate::userdata::util 2025-05-05 00:43:12 +01:00
Alex Orlenko 0393406b9f Refactor userdata-wrappers feature.
Support borrowing underlying data in `UserDataRef` and `UserDataRefMut`.
2025-05-04 18:45:42 +01:00
Alex Orlenko 272cfdb89b Change Lua library name constants from &str to *const c_char.
It makes easier to pass them to Lua API functions.
2025-05-04 12:49:08 +01:00
Alex Orlenko 8e244a25ea Update test case 2025-05-04 12:48:47 +01:00
Alex Orlenko ef8b8e11ec Support 52-bit integers for Luau
Simply to float conversion (it actually never fails or goes out of range)
2025-05-04 12:48:34 +01:00
Alex Orlenko 2543414726 Fix clippy warnings 2025-05-04 12:47:30 +01:00
Alex Orlenko ee112568d6 Remove Roblox from references to Luau
Closes #537
2025-05-04 12:46:47 +01:00
Alex Orlenko 69d3ddec29 Remove (internal) borrow counter and use instead "locked" flag and strong reference counter 2025-05-04 12:46:35 +01:00
Alex Orlenko 5b0d811c5a Add Variadic to prelude 2025-05-04 12:31:13 +01:00
Alex Orlenko 19536db976 Add bstr/serde dependency if serialize feature flag is enabled 2025-05-04 12:30:52 +01:00
Alex Orlenko fc69551f87 Fix tests 2025-05-04 12:29:57 +01:00
Alex Orlenko a68b0b6905 Do not allow recursive warnings (Lua 5.4) 2025-05-04 12:29:51 +01:00
Alex Orlenko 1ec1cc9922 Fix tests 2025-05-04 12:28:40 +01:00
Joel Natividad fd68b033f0 Fix typos (#522)
* fix various typos in the codebase

* fix typos in CHANGELOG.md
2025-05-04 12:28:24 +01:00
Andrew Farkas c3817409a8 Fix version number in changelog (#521) 2025-05-04 12:28:04 +01:00
Alex Orlenko 863d8092d6 Imporove BorrowedStr/BorrowedBytes ergonomic.
Implement `FromLua` and `IntoLua` for these types to allow working with them directly.
2025-05-04 12:27:24 +01:00
Alex Orlenko 24b6ff3c20 Improve From/Into Lua char conversion 2025-05-04 12:18:28 +01:00
tk bc36261f5c impl FromLua/IntoLua for char (#516) 2025-05-04 12:18:22 +01:00
Alex Orlenko bcb9a4d220 Enable Thread::reset() for all Lua versions 2025-05-04 12:07:55 +01:00
82 changed files with 927 additions and 2127 deletions
+1 -1
View File
@@ -194,7 +194,7 @@ jobs:
matrix:
os: [ubuntu-latest, macos-latest]
rust: [stable]
lua: [lua54, lua53, lua52, lua51, luajit]
lua: [lua54, lua53, lua52, lua51, luajit, luau]
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
-17
View File
@@ -1,17 +0,0 @@
name: Typos Check
on:
pull_request:
workflow_dispatch:
jobs:
run:
name: Spell Check with Typos
runs-on: ubuntu-latest
steps:
- name: Checkout Actions Repository
uses: actions/checkout@v4
- name: Check spelling
uses: crate-ci/typos@master
with:
config: ./typos.toml
-15
View File
@@ -1,18 +1,3 @@
## v0.11.0-beta.1 (May 7th, 2025)
- New "require-by-string" for Luau (with `Require` trait and async support)
- Added `Thread::resume_error` support for Luau
- 52 bit integers support for Luau (this is a breaking change)
- New features for Luau compiler (constants, disabled builtins, known members)
- `AsyncThread<A, R>` changed to `AsyncThread<R>` (`A` pushed to stack immediately)
- Lifetime `'a` moved from `AsChunk<'a>` to `AsChunk::source where Self: 'a`
- `Lua::scope` pass `&Scope` instead of `&mut Scope` to closure
- Added global hooks support (Lua 5.1+)
- Added per-thread hooks support (Lua 5.1+)
- `Lua::init_from_ptr` renamed to `Lua::get_or_init_from_ptr` and returns `&Lua`
- `Lua:load_from_function` is deprecated (this is `register_module` now)
- Added `Lua::register_module` and `Lua::preload_module`
## v0.10.4 (May 5th, 2025)
- Luau updated to 0.672
+6 -3
View File
@@ -1,6 +1,6 @@
[package]
name = "mlua"
version = "0.11.0-beta.1" # remember to update mlua_derive
version = "0.10.4" # remember to update mlua_derive
authors = ["Aleksandr Orlenko <zxteam@pm.me>", "kyren <catherine@kyju.org>"]
rust-version = "1.79.0"
edition = "2021"
@@ -32,7 +32,7 @@ lua52 = ["ffi/lua52"]
lua51 = ["ffi/lua51"]
luajit = ["ffi/luajit"]
luajit52 = ["luajit", "ffi/luajit52"]
luau = ["ffi/luau"]
luau = ["ffi/luau", "dep:libloading"]
luau-jit = ["luau", "ffi/luau-codegen"]
luau-vector4 = ["luau", "ffi/luau-vector4"]
vendored = ["ffi/vendored"]
@@ -46,7 +46,7 @@ anyhow = ["dep:anyhow", "error-send"]
userdata-wrappers = []
[dependencies]
mlua_derive = { version = "=0.11.0-beta.1", optional = true, path = "mlua_derive" }
mlua_derive = { version = "=0.10.1", optional = true, path = "mlua_derive" }
bstr = { version = "1.0", features = ["std"], default-features = false }
either = "1.0"
num-traits = { version = "0.2.14" }
@@ -61,6 +61,9 @@ rustversion = "1.0"
ffi = { package = "mlua-sys", version = "0.7.0", path = "mlua-sys" }
[target.'cfg(unix)'.dependencies]
libloading = { version = "0.8", optional = true }
[dev-dependencies]
trybuild = "1.0"
hyper = { version = "1.2", features = ["full"] }
-2
View File
@@ -17,8 +17,6 @@
[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).
+1 -2
View File
@@ -1,8 +1,7 @@
//! Low level bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT) and Luau.
#![allow(non_camel_case_types, non_snake_case)]
#![allow(non_camel_case_types, non_snake_case, dead_code)]
#![allow(clippy::missing_safety_doc)]
#![allow(unsafe_op_in_unsafe_fn)]
#![doc(test(attr(deny(warnings))))]
#![cfg_attr(docsrs, feature(doc_cfg))]
+2 -2
View File
@@ -18,7 +18,7 @@ pub struct luaL_Reg {
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua51", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
pub fn luaL_register(L: *mut lua_State, libname: *const c_char, l: *const luaL_Reg);
#[link_name = "luaL_getmetafield"]
pub fn luaL_getmetafield_(L: *mut lua_State, obj: c_int, e: *const c_char) -> c_int;
@@ -61,7 +61,7 @@ pub const LUA_NOREF: c_int = -2;
pub const LUA_REFNIL: c_int = -1;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua51", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
pub fn luaL_ref(L: *mut lua_State, t: c_int) -> c_int;
pub fn luaL_unref(L: *mut lua_State, t: c_int, r#ref: c_int);
+4 -4
View File
@@ -90,7 +90,7 @@ pub type lua_Alloc =
unsafe extern "C" fn(ud: *mut c_void, ptr: *mut c_void, osize: usize, nsize: usize) -> *mut c_void;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua51", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
//
// State manipulation
//
@@ -220,7 +220,7 @@ pub const LUA_GCSETPAUSE: c_int = 6;
pub const LUA_GCSETSTEPMUL: c_int = 7;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua51", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
pub fn lua_gc(L: *mut lua_State, what: c_int, data: c_int) -> c_int;
}
@@ -228,7 +228,7 @@ unsafe extern "C-unwind" {
// Miscellaneous functions
//
#[cfg_attr(all(windows, raw_dylib), link(name = "lua51", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
#[link_name = "lua_error"]
fn lua_error_(L: *mut lua_State) -> c_int;
pub fn lua_next(L: *mut lua_State, idx: c_int) -> c_int;
@@ -370,7 +370,7 @@ pub const LUA_MASKCOUNT: c_int = 1 << (LUA_HOOKCOUNT as usize);
pub type lua_Hook = unsafe extern "C-unwind" fn(L: *mut lua_State, ar: *mut lua_Debug);
#[cfg_attr(all(windows, raw_dylib), link(name = "lua51", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
pub fn lua_getstack(L: *mut lua_State, level: c_int, ar: *mut lua_Debug) -> c_int;
pub fn lua_getinfo(L: *mut lua_State, what: *const c_char, ar: *mut lua_Debug) -> c_int;
pub fn lua_getlocal(L: *mut lua_State, ar: *const lua_Debug, n: c_int) -> *const c_char;
+1 -1
View File
@@ -21,7 +21,7 @@ pub const LUA_JITLIBNAME: *const c_char = cstr!("jit");
pub const LUA_FFILIBNAME: *const c_char = cstr!("ffi");
#[cfg_attr(all(windows, raw_dylib), link(name = "lua51", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
pub fn luaopen_base(L: *mut lua_State) -> c_int;
pub fn luaopen_table(L: *mut lua_State) -> c_int;
pub fn luaopen_io(L: *mut lua_State) -> c_int;
+3 -3
View File
@@ -21,7 +21,7 @@ pub struct luaL_Reg {
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
pub fn luaL_checkversion_(L: *mut lua_State, ver: lua_Number);
#[link_name = "luaL_getmetafield"]
@@ -69,7 +69,7 @@ pub const LUA_NOREF: c_int = -2;
pub const LUA_REFNIL: c_int = -1;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
pub fn luaL_ref(L: *mut lua_State, t: c_int) -> c_int;
pub fn luaL_unref(L: *mut lua_State, t: c_int, r#ref: c_int);
@@ -82,7 +82,7 @@ pub unsafe fn luaL_loadfile(L: *mut lua_State, f: *const c_char) -> c_int {
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
pub fn luaL_loadbufferx(
L: *mut lua_State,
buff: *const c_char,
+7 -7
View File
@@ -95,7 +95,7 @@ pub type lua_Alloc =
unsafe extern "C" fn(ud: *mut c_void, ptr: *mut c_void, osize: usize, nsize: usize) -> *mut c_void;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
//
// State manipulation
//
@@ -161,14 +161,14 @@ pub const LUA_OPLT: c_int = 1;
pub const LUA_OPLE: c_int = 2;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
pub fn lua_arith(L: *mut lua_State, op: c_int);
pub fn lua_rawequal(L: *mut lua_State, idx1: c_int, idx2: c_int) -> c_int;
pub fn lua_compare(L: *mut lua_State, idx1: c_int, idx2: c_int, op: c_int) -> c_int;
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
//
// Push functions (C -> stack)
//
@@ -257,7 +257,7 @@ pub unsafe fn lua_pcall(L: *mut lua_State, n: c_int, r: c_int, f: c_int) -> c_in
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
//
// Coroutine functions
//
@@ -289,12 +289,12 @@ pub const LUA_GCGEN: c_int = 10;
pub const LUA_GCINC: c_int = 11;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
pub fn lua_gc(L: *mut lua_State, what: c_int, data: c_int) -> c_int;
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
//
// Miscellaneous functions
//
@@ -448,7 +448,7 @@ pub const LUA_MASKCOUNT: c_int = 1 << (LUA_HOOKCOUNT as usize);
pub type lua_Hook = unsafe extern "C-unwind" fn(L: *mut lua_State, ar: *mut lua_Debug);
#[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
pub fn lua_getstack(L: *mut lua_State, level: c_int, ar: *mut lua_Debug) -> c_int;
pub fn lua_getinfo(L: *mut lua_State, what: *const c_char, ar: *mut lua_Debug) -> c_int;
pub fn lua_getlocal(L: *mut lua_State, ar: *const lua_Debug, n: c_int) -> *const c_char;
+1 -1
View File
@@ -15,7 +15,7 @@ pub const LUA_DBLIBNAME: *const c_char = cstr!("debug");
pub const LUA_LOADLIBNAME: *const c_char = cstr!("package");
#[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
pub fn luaopen_base(L: *mut lua_State) -> c_int;
pub fn luaopen_coroutine(L: *mut lua_State) -> c_int;
pub fn luaopen_table(L: *mut lua_State) -> c_int;
+3 -3
View File
@@ -21,7 +21,7 @@ pub struct luaL_Reg {
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
pub fn luaL_checkversion_(L: *mut lua_State, ver: lua_Number, sz: usize);
pub fn luaL_getmetafield(L: *mut lua_State, obj: c_int, e: *const c_char) -> c_int;
@@ -65,7 +65,7 @@ pub const LUA_NOREF: c_int = -2;
pub const LUA_REFNIL: c_int = -1;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
pub fn luaL_ref(L: *mut lua_State, t: c_int) -> c_int;
pub fn luaL_unref(L: *mut lua_State, t: c_int, r#ref: c_int);
@@ -78,7 +78,7 @@ pub unsafe fn luaL_loadfile(L: *mut lua_State, f: *const c_char) -> c_int {
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
pub fn luaL_loadbufferx(
L: *mut lua_State,
buff: *const c_char,
+7 -7
View File
@@ -102,7 +102,7 @@ pub type lua_Alloc =
unsafe extern "C" fn(ud: *mut c_void, ptr: *mut c_void, osize: usize, nsize: usize) -> *mut c_void;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
//
// State manipulation
//
@@ -172,14 +172,14 @@ pub const LUA_OPLT: c_int = 1;
pub const LUA_OPLE: c_int = 2;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
pub fn lua_arith(L: *mut lua_State, op: c_int);
pub fn lua_rawequal(L: *mut lua_State, idx1: c_int, idx2: c_int) -> c_int;
pub fn lua_compare(L: *mut lua_State, idx1: c_int, idx2: c_int, op: c_int) -> c_int;
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
//
// Push functions (C -> stack)
//
@@ -265,7 +265,7 @@ pub unsafe fn lua_pcall(L: *mut lua_State, n: c_int, r: c_int, f: c_int) -> c_in
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
//
// Coroutine functions
//
@@ -300,12 +300,12 @@ pub const LUA_GCSETSTEPMUL: c_int = 7;
pub const LUA_GCISRUNNING: c_int = 9;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
pub fn lua_gc(L: *mut lua_State, what: c_int, data: c_int) -> c_int;
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
//
// Miscellaneous functions
//
@@ -477,7 +477,7 @@ pub const LUA_MASKCOUNT: c_int = 1 << (LUA_HOOKCOUNT as usize);
pub type lua_Hook = unsafe extern "C-unwind" fn(L: *mut lua_State, ar: *mut lua_Debug);
#[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
pub fn lua_getstack(L: *mut lua_State, level: c_int, ar: *mut lua_Debug) -> c_int;
pub fn lua_getinfo(L: *mut lua_State, what: *const c_char, ar: *mut lua_Debug) -> c_int;
pub fn lua_getlocal(L: *mut lua_State, ar: *const lua_Debug, n: c_int) -> *const c_char;
+1 -1
View File
@@ -16,7 +16,7 @@ pub const LUA_DBLIBNAME: *const c_char = cstr!("debug");
pub const LUA_LOADLIBNAME: *const c_char = cstr!("package");
#[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
pub fn luaopen_base(L: *mut lua_State) -> c_int;
pub fn luaopen_coroutine(L: *mut lua_State) -> c_int;
pub fn luaopen_table(L: *mut lua_State) -> c_int;
+3 -3
View File
@@ -21,7 +21,7 @@ pub struct luaL_Reg {
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
pub fn luaL_checkversion_(L: *mut lua_State, ver: lua_Number, sz: usize);
pub fn luaL_getmetafield(L: *mut lua_State, obj: c_int, e: *const c_char) -> c_int;
@@ -64,7 +64,7 @@ pub const LUA_NOREF: c_int = -2;
pub const LUA_REFNIL: c_int = -1;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
pub fn luaL_ref(L: *mut lua_State, t: c_int) -> c_int;
pub fn luaL_unref(L: *mut lua_State, t: c_int, r#ref: c_int);
@@ -77,7 +77,7 @@ pub unsafe fn luaL_loadfile(L: *mut lua_State, f: *const c_char) -> c_int {
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
pub fn luaL_loadbufferx(
L: *mut lua_State,
buff: *const c_char,
+8 -8
View File
@@ -104,7 +104,7 @@ pub type lua_Alloc =
pub type lua_WarnFunction = unsafe extern "C-unwind" fn(ud: *mut c_void, msg: *const c_char, tocont: c_int);
#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
//
// State manipulation
//
@@ -186,14 +186,14 @@ pub const LUA_OPLT: c_int = 1;
pub const LUA_OPLE: c_int = 2;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
pub fn lua_arith(L: *mut lua_State, op: c_int);
pub fn lua_rawequal(L: *mut lua_State, idx1: c_int, idx2: c_int) -> c_int;
pub fn lua_compare(L: *mut lua_State, idx1: c_int, idx2: c_int, op: c_int) -> c_int;
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
//
// Push functions (C -> stack)
//
@@ -279,7 +279,7 @@ pub unsafe fn lua_pcall(L: *mut lua_State, n: c_int, r: c_int, f: c_int) -> c_in
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
//
// Coroutine functions
//
@@ -303,7 +303,7 @@ pub unsafe fn lua_yield(L: *mut lua_State, n: c_int) -> c_int {
// Warning-related functions
//
#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
pub fn lua_setwarnf(L: *mut lua_State, f: Option<lua_WarnFunction>, ud: *mut c_void);
pub fn lua_warning(L: *mut lua_State, msg: *const c_char, tocont: c_int);
}
@@ -324,12 +324,12 @@ pub const LUA_GCGEN: c_int = 10;
pub const LUA_GCINC: c_int = 11;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
pub fn lua_gc(L: *mut lua_State, what: c_int, ...) -> c_int;
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
//
// Miscellaneous functions
//
@@ -519,7 +519,7 @@ pub const LUA_MASKCOUNT: c_int = 1 << (LUA_HOOKCOUNT as usize);
pub type lua_Hook = unsafe extern "C-unwind" fn(L: *mut lua_State, ar: *mut lua_Debug);
#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
pub fn lua_getstack(L: *mut lua_State, level: c_int, ar: *mut lua_Debug) -> c_int;
pub fn lua_getinfo(L: *mut lua_State, what: *const c_char, ar: *mut lua_Debug) -> c_int;
pub fn lua_getlocal(L: *mut lua_State, ar: *const lua_Debug, n: c_int) -> *const c_char;
+1 -1
View File
@@ -15,7 +15,7 @@ pub const LUA_DBLIBNAME: *const c_char = cstr!("debug");
pub const LUA_LOADLIBNAME: *const c_char = cstr!("package");
#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
extern "C-unwind" {
pub fn luaopen_base(L: *mut lua_State) -> c_int;
pub fn luaopen_coroutine(L: *mut lua_State) -> c_int;
pub fn luaopen_table(L: *mut lua_State) -> c_int;
+1 -1
View File
@@ -368,7 +368,7 @@ pub unsafe fn luaL_loadbufferenv(
mode: *const c_char,
mut env: c_int,
) -> c_int {
unsafe extern "C" {
extern "C" {
fn free(p: *mut c_void);
}
+2 -2
View File
@@ -11,7 +11,7 @@ pub struct luaL_Reg {
pub func: lua_CFunction,
}
unsafe extern "C-unwind" {
extern "C-unwind" {
pub fn luaL_register(L: *mut lua_State, libname: *const c_char, l: *const luaL_Reg);
#[link_name = "luaL_getmetafield"]
pub fn luaL_getmetafield_(L: *mut lua_State, obj: c_int, e: *const c_char) -> c_int;
@@ -182,7 +182,7 @@ pub struct luaL_Strbuf {
// For compatibility
pub type luaL_Buffer = luaL_Strbuf;
unsafe extern "C-unwind" {
extern "C-unwind" {
pub fn luaL_buffinit(L: *mut lua_State, B: *mut luaL_Strbuf);
pub fn luaL_buffinitsize(L: *mut lua_State, B: *mut luaL_Strbuf, size: usize) -> *mut c_char;
pub fn luaL_prepbuffsize(B: *mut luaL_Strbuf, size: usize) -> *mut c_char;
+10 -10
View File
@@ -12,10 +12,10 @@ pub const LUA_MULTRET: c_int = -1;
const LUAI_MAXCSTACK: c_int = 1000000;
// Number of valid Lua userdata tags
pub const LUA_UTAG_LIMIT: c_int = 128;
const LUA_UTAG_LIMIT: c_int = 128;
// Number of valid Lua lightuserdata tags
pub const LUA_LUTAG_LIMIT: c_int = 128;
const LUA_LUTAG_LIMIT: c_int = 128;
//
// Pseudo-indices
@@ -95,7 +95,7 @@ pub const fn luau_version() -> Option<&'static str> {
option_env!("LUAU_VERSION")
}
unsafe extern "C-unwind" {
extern "C-unwind" {
//
// State manipulation
//
@@ -264,14 +264,14 @@ pub const LUA_GCSETGOAL: c_int = 7;
pub const LUA_GCSETSTEPMUL: c_int = 8;
pub const LUA_GCSETSTEPSIZE: c_int = 9;
unsafe extern "C-unwind" {
extern "C-unwind" {
pub fn lua_gc(L: *mut lua_State, what: c_int, data: c_int) -> c_int;
}
//
// Memory statistics
//
unsafe extern "C-unwind" {
extern "C-unwind" {
pub fn lua_setmemcat(L: *mut lua_State, category: c_int);
pub fn lua_totalbytes(L: *mut lua_State, category: c_int) -> usize;
}
@@ -279,7 +279,7 @@ unsafe extern "C-unwind" {
//
// Miscellaneous functions
//
unsafe extern "C-unwind" {
extern "C-unwind" {
pub fn lua_error(L: *mut lua_State) -> !;
pub fn lua_next(L: *mut lua_State, idx: c_int) -> c_int;
pub fn lua_rawiter(L: *mut lua_State, idx: c_int, iter: c_int) -> c_int;
@@ -304,7 +304,7 @@ unsafe extern "C-unwind" {
pub const LUA_NOREF: c_int = -1;
pub const LUA_REFNIL: c_int = 0;
unsafe extern "C-unwind" {
extern "C-unwind" {
pub fn lua_ref(L: *mut lua_State, idx: c_int) -> c_int;
pub fn lua_unref(L: *mut lua_State, r#ref: c_int);
}
@@ -470,7 +470,7 @@ pub type lua_Coverage = unsafe extern "C-unwind" fn(
size: usize,
);
unsafe extern "C-unwind" {
extern "C-unwind" {
pub fn lua_stackdepth(L: *mut lua_State) -> c_int;
pub fn lua_getinfo(L: *mut lua_State, level: c_int, what: *const c_char, ar: *mut lua_Debug) -> c_int;
pub fn lua_getargument(L: *mut lua_State, level: c_int, n: c_int) -> c_int;
@@ -536,12 +536,12 @@ pub struct lua_Callbacks {
pub onallocate: Option<unsafe extern "C-unwind" fn(L: *mut lua_State, osize: usize, nsize: usize)>,
}
unsafe extern "C" {
extern "C" {
pub fn lua_callbacks(L: *mut lua_State) -> *mut lua_Callbacks;
}
// Functions from customization lib
unsafe extern "C" {
extern "C" {
pub fn luau_setfflag(name: *const c_char, value: c_int) -> c_int;
pub fn lua_getmetatablepointer(L: *mut lua_State, idx: c_int) -> *const c_void;
}
+3 -3
View File
@@ -76,7 +76,7 @@ pub type lua_LibraryMemberConstantCallback = unsafe extern "C-unwind" fn(
constant: *mut lua_CompileConstant,
);
unsafe extern "C" {
extern "C" {
pub fn luau_set_compile_constant_nil(cons: *mut lua_CompileConstant);
pub fn luau_set_compile_constant_boolean(cons: *mut lua_CompileConstant, b: c_int);
pub fn luau_set_compile_constant_number(cons: *mut lua_CompileConstant, n: f64);
@@ -84,7 +84,7 @@ unsafe extern "C" {
pub fn luau_set_compile_constant_string(cons: *mut lua_CompileConstant, s: *const c_char, l: usize);
}
unsafe extern "C-unwind" {
extern "C-unwind" {
#[link_name = "luau_compile"]
pub fn luau_compile_(
source: *const c_char,
@@ -94,7 +94,7 @@ unsafe extern "C-unwind" {
) -> *mut c_char;
}
unsafe extern "C" {
extern "C" {
fn free(p: *mut c_void);
}
+1 -1
View File
@@ -4,7 +4,7 @@ use std::os::raw::c_int;
use super::lua::lua_State;
unsafe extern "C-unwind" {
extern "C-unwind" {
pub fn luau_codegen_supported() -> c_int;
pub fn luau_codegen_create(state: *mut lua_State);
pub fn luau_codegen_compile(state: *mut lua_State, idx: c_int);
+1 -1
View File
@@ -15,7 +15,7 @@ pub const LUA_MATHLIBNAME: *const c_char = cstr!("math");
pub const LUA_DBLIBNAME: *const c_char = cstr!("debug");
pub const LUA_VECLIBNAME: *const c_char = cstr!("vector");
unsafe extern "C-unwind" {
extern "C-unwind" {
pub fn luaopen_base(L: *mut lua_State) -> c_int;
pub fn luaopen_coroutine(L: *mut lua_State) -> c_int;
pub fn luaopen_table(L: *mut lua_State) -> c_int;
+1 -1
View File
@@ -117,7 +117,7 @@ pub struct luarequire_Configuration {
// Populates function pointers in the given luarequire_Configuration.
pub type luarequire_Configuration_init = unsafe extern "C" fn(config: *mut luarequire_Configuration);
unsafe extern "C-unwind" {
extern "C-unwind" {
// Initializes and pushes the require closure onto the stack without registration.
pub fn luarequire_pushrequire(
L: *mut lua_State,
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "mlua_derive"
version = "0.11.0-beta.1"
version = "0.10.1"
authors = ["Aleksandr Orlenko <zxteam@pm.me>"]
edition = "2021"
description = "Procedural macros for the mlua crate."
+2 -2
View File
@@ -103,7 +103,7 @@ pub fn chunk(input: TokenStream) -> TokenStream {
struct InnerChunk<F: FnOnce(&Lua) -> Result<Table>>(Cell<Option<F>>);
impl<F> AsChunk for InnerChunk<F>
impl<F> AsChunk<'static> for InnerChunk<F>
where
F: FnOnce(&Lua) -> Result<Table>,
{
@@ -120,7 +120,7 @@ pub fn chunk(input: TokenStream) -> TokenStream {
Some(ChunkMode::Text)
}
fn source<'a>(self) -> IoResult<Cow<'a, [u8]>> {
fn source(self) -> IoResult<Cow<'static, [u8]>> {
Ok(Cow::Borrowed((#source).as_bytes()))
}
}
+54 -164
View File
@@ -2,6 +2,7 @@ use std::borrow::Cow;
use std::collections::HashMap;
use std::ffi::CString;
use std::io::Result as IoResult;
use std::marker::PhantomData;
use std::panic::Location;
use std::path::{Path, PathBuf};
use std::string::String as StdString;
@@ -16,7 +17,7 @@ use crate::value::Value;
/// Trait for types [loadable by Lua] and convertible to a [`Chunk`]
///
/// [loadable by Lua]: https://www.lua.org/manual/5.4/manual.html#3.3.2
pub trait AsChunk {
pub trait AsChunk<'a> {
/// Returns optional chunk name
///
/// See [`Chunk::set_name`] for possible name prefixes.
@@ -38,75 +39,61 @@ pub trait AsChunk {
}
/// Returns chunk data (can be text or binary)
fn source<'a>(self) -> IoResult<Cow<'a, [u8]>>
where
Self: 'a;
fn source(self) -> IoResult<Cow<'a, [u8]>>;
}
impl AsChunk for &str {
fn source<'a>(self) -> IoResult<Cow<'a, [u8]>>
where
Self: 'a,
{
impl<'a> AsChunk<'a> for &'a str {
fn source(self) -> IoResult<Cow<'a, [u8]>> {
Ok(Cow::Borrowed(self.as_ref()))
}
}
impl AsChunk for StdString {
fn source<'a>(self) -> IoResult<Cow<'a, [u8]>> {
impl AsChunk<'static> for StdString {
fn source(self) -> IoResult<Cow<'static, [u8]>> {
Ok(Cow::Owned(self.into_bytes()))
}
}
impl AsChunk for &StdString {
fn source<'a>(self) -> IoResult<Cow<'a, [u8]>>
where
Self: 'a,
{
impl<'a> AsChunk<'a> for &'a StdString {
fn source(self) -> IoResult<Cow<'a, [u8]>> {
Ok(Cow::Borrowed(self.as_bytes()))
}
}
impl AsChunk for &[u8] {
fn source<'a>(self) -> IoResult<Cow<'a, [u8]>>
where
Self: 'a,
{
impl<'a> AsChunk<'a> for &'a [u8] {
fn source(self) -> IoResult<Cow<'a, [u8]>> {
Ok(Cow::Borrowed(self))
}
}
impl AsChunk for Vec<u8> {
fn source<'a>(self) -> IoResult<Cow<'a, [u8]>> {
impl AsChunk<'static> for Vec<u8> {
fn source(self) -> IoResult<Cow<'static, [u8]>> {
Ok(Cow::Owned(self))
}
}
impl AsChunk for &Vec<u8> {
fn source<'a>(self) -> IoResult<Cow<'a, [u8]>>
where
Self: 'a,
{
Ok(Cow::Borrowed(self))
impl<'a> AsChunk<'a> for &'a Vec<u8> {
fn source(self) -> IoResult<Cow<'a, [u8]>> {
Ok(Cow::Borrowed(self.as_ref()))
}
}
impl AsChunk for &Path {
impl AsChunk<'static> for &Path {
fn name(&self) -> Option<StdString> {
Some(format!("@{}", self.display()))
}
fn source<'a>(self) -> IoResult<Cow<'a, [u8]>> {
fn source(self) -> IoResult<Cow<'static, [u8]>> {
std::fs::read(self).map(Cow::Owned)
}
}
impl AsChunk for PathBuf {
impl AsChunk<'static> for PathBuf {
fn name(&self) -> Option<StdString> {
Some(format!("@{}", self.display()))
}
fn source<'a>(self) -> IoResult<Cow<'a, [u8]>> {
fn source(self) -> IoResult<Cow<'static, [u8]>> {
std::fs::read(self).map(Cow::Owned)
}
}
@@ -130,28 +117,6 @@ pub enum ChunkMode {
Binary,
}
/// Represents a constant value that can be used by Luau compiler.
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
#[derive(Clone, Debug)]
pub enum CompileConstant {
Nil,
Boolean(bool),
Number(crate::Number),
Vector(crate::Vector),
String(String),
}
#[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))]
type LibraryMemberConstantMap = std::sync::Arc<HashMap<(String, String), CompileConstant>>;
/// Luau compiler
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
@@ -166,9 +131,6 @@ pub struct Compiler {
vector_type: Option<String>,
mutable_globals: Vec<String>,
userdata_types: Vec<String>,
libraries_with_known_members: Vec<String>,
library_constants: Option<LibraryMemberConstantMap>,
disabled_builtins: Vec<String>,
}
#[cfg(any(feature = "luau", doc))]
@@ -193,9 +155,6 @@ impl Compiler {
vector_type: None,
mutable_globals: Vec::new(),
userdata_types: Vec::new(),
libraries_with_known_members: Vec::new(),
library_constants: None,
disabled_builtins: Vec::new(),
}
}
@@ -228,7 +187,6 @@ impl Compiler {
/// Possible values:
/// * 0 - generate for native modules (default)
/// * 1 - generate for all modules
#[must_use]
pub const fn set_type_info_level(mut self, level: u8) -> Self {
self.type_info_level = level;
self
@@ -271,46 +229,15 @@ impl Compiler {
///
/// 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 {
self.mutable_globals = globals.into_iter().map(|s| s.into()).collect();
pub fn set_mutable_globals(mut self, globals: Vec<String>) -> Self {
self.mutable_globals = globals;
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 {
self.userdata_types = types.into_iter().map(|s| s.into()).collect();
self
}
/// Sets constants for known library members.
///
/// 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.
#[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();
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 {
self.disabled_builtins = builtins.into_iter().map(|s| s.into()).collect();
pub fn set_userdata_types(mut self, types: Vec<String>) -> Self {
self.userdata_types = types;
self
}
@@ -318,9 +245,7 @@ impl Compiler {
///
/// Returns [`Error::SyntaxError`] if the source code is invalid.
pub fn compile(&self, source: impl AsRef<[u8]>) -> Result<Vec<u8>> {
use std::cell::RefCell;
use std::ffi::CStr;
use std::os::raw::{c_char, c_int};
use std::os::raw::c_int;
use std::ptr;
let vector_lib = self.vector_lib.clone();
@@ -352,44 +277,6 @@ impl Compiler {
vec2cstring_ptr!(mutable_globals, mutable_globals_ptr);
vec2cstring_ptr!(userdata_types, userdata_types_ptr);
vec2cstring_ptr!(libraries_with_known_members, libraries_with_known_members_ptr);
vec2cstring_ptr!(disabled_builtins, disabled_builtins_ptr);
thread_local! {
static LIBRARY_MEMBER_CONSTANT_MAP: RefCell<LibraryMemberConstantMap> = Default::default();
}
#[cfg(feature = "luau")]
unsafe extern "C-unwind" fn library_member_constant_callback(
library: *const c_char,
member: *const c_char,
constant: *mut ffi::lua_CompileConstant,
) {
let library = CStr::from_ptr(library).to_string_lossy();
let member = CStr::from_ptr(member).to_string_lossy();
LIBRARY_MEMBER_CONSTANT_MAP.with_borrow(|map| {
if let Some(cons) = map.get(&(library.to_string(), member.to_string())) {
match cons {
CompileConstant::Nil => ffi::luau_set_compile_constant_nil(constant),
CompileConstant::Boolean(b) => {
ffi::luau_set_compile_constant_boolean(constant, *b as c_int)
}
CompileConstant::Number(n) => ffi::luau_set_compile_constant_number(constant, *n),
CompileConstant::Vector(v) => {
#[cfg(not(feature = "luau-vector4"))]
ffi::luau_set_compile_constant_vector(constant, v.x(), v.y(), v.z(), 0.0);
#[cfg(feature = "luau-vector4")]
ffi::luau_set_compile_constant_vector(constant, v.x(), v.y(), v.z(), v.w());
}
CompileConstant::String(s) => ffi::luau_set_compile_constant_string(
constant,
s.as_ptr() as *const c_char,
s.len(),
),
}
}
})
}
let bytecode = unsafe {
let mut options = ffi::lua_CompileOptions::default();
@@ -402,14 +289,6 @@ impl Compiler {
options.vectorType = vector_type.map_or(ptr::null(), |s| s.as_ptr());
options.mutableGlobals = mutable_globals_ptr;
options.userdataTypes = userdata_types_ptr;
options.librariesWithKnownMembers = libraries_with_known_members_ptr;
if let Some(map) = self.library_constants.as_ref() {
if !self.libraries_with_known_members.is_empty() {
LIBRARY_MEMBER_CONSTANT_MAP.with_borrow_mut(|gmap| *gmap = map.clone());
options.libraryMemberConstantCallback = Some(library_member_constant_callback);
}
}
options.disabledBuiltins = disabled_builtins_ptr;
ffi::luau_compile(source.as_ref(), options)
};
@@ -482,6 +361,8 @@ impl Chunk<'_> {
/// Sets or overwrites a Luau compiler used for this chunk.
///
/// See [`Compiler`] for details and possible options.
///
/// Requires `feature = "luau"`
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn set_compiler(mut self, compiler: Compiler) -> Self {
@@ -500,6 +381,8 @@ impl Chunk<'_> {
///
/// See [`exec`] for more details.
///
/// Requires `feature = "async"`
///
/// [`exec`]: Chunk::exec
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
@@ -530,6 +413,8 @@ impl Chunk<'_> {
///
/// See [`eval`] for more details.
///
/// Requires `feature = "async"`
///
/// [`eval`]: Chunk::eval
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
@@ -557,6 +442,8 @@ impl Chunk<'_> {
///
/// See [`call`] for more details.
///
/// Requires `feature = "async"`
///
/// [`call`]: Chunk::call
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
@@ -634,10 +521,10 @@ impl Chunk<'_> {
if self.detect_mode() == ChunkMode::Binary {
let lua = self.lua.lock();
if let Some(mut cache) = lua.app_data_mut_unguarded::<ChunksCache>() {
cache.0.insert(text_source, binary_source.to_vec());
cache.0.insert(text_source, binary_source.as_ref().to_vec());
} else {
let mut cache = ChunksCache(HashMap::new());
cache.0.insert(text_source, binary_source.to_vec());
cache.0.insert(text_source, binary_source.as_ref().to_vec());
let _ = lua.try_set_app_data(cache);
};
}
@@ -671,20 +558,21 @@ impl Chunk<'_> {
}
fn detect_mode(&self) -> ChunkMode {
if let Some(mode) = self.mode {
return mode;
}
if let Ok(source) = &self.source {
#[cfg(not(feature = "luau"))]
if source.starts_with(ffi::LUA_SIGNATURE) {
return ChunkMode::Binary;
}
#[cfg(feature = "luau")]
if *source.first().unwrap_or(&u8::MAX) < b'\n' {
return ChunkMode::Binary;
match (self.mode, &self.source) {
(Some(mode), _) => mode,
(None, Ok(source)) => {
#[cfg(not(feature = "luau"))]
if source.starts_with(ffi::LUA_SIGNATURE) {
return ChunkMode::Binary;
}
#[cfg(feature = "luau")]
if *source.first().unwrap_or(&u8::MAX) < b'\n' {
return ChunkMode::Binary;
}
ChunkMode::Text
}
(None, Err(_)) => ChunkMode::Text, // any value is fine
}
ChunkMode::Text
}
fn convert_name(name: String) -> Result<CString> {
@@ -699,27 +587,29 @@ impl Chunk<'_> {
}
}
struct WrappedChunk<T: AsChunk> {
struct WrappedChunk<'a, T: AsChunk<'a>> {
chunk: T,
caller: &'static Location<'static>,
_marker: PhantomData<&'a T>,
}
impl Chunk<'_> {
impl<'a> Chunk<'a> {
/// Wraps a chunk of Lua code, returning an opaque type that implements [`IntoLua`] trait.
///
/// The resulted `IntoLua` implementation will convert the chunk into a Lua function without
/// executing it.
#[doc(hidden)]
#[track_caller]
pub fn wrap(chunk: impl AsChunk) -> impl IntoLua {
pub fn wrap(chunk: impl AsChunk<'a> + 'a) -> impl IntoLua + 'a {
WrappedChunk {
chunk,
caller: Location::caller(),
_marker: PhantomData,
}
}
}
impl<T: AsChunk> IntoLua for WrappedChunk<T> {
impl<'a, T: AsChunk<'a>> IntoLua for WrappedChunk<'a, T> {
fn into_lua(self, lua: &Lua) -> Result<Value> {
lua.load_with_location(self.chunk, self.caller)
.into_function()
+14 -35
View File
@@ -14,12 +14,9 @@ use crate::value::Value;
#[cfg(feature = "async")]
use {
crate::thread::AsyncThread,
crate::traits::LuaNativeAsyncFn,
crate::types::AsyncCallback,
std::future::{self, Future},
std::pin::Pin,
std::task::{Context, Poll},
};
/// Handle to an internal Lua function.
@@ -131,8 +128,9 @@ impl Function {
/// Returns a future that, when polled, calls `self`, passing `args` as function arguments,
/// and drives the execution.
///
/// Internally it wraps the function to an [`AsyncThread`]. The returned type implements
/// `Future<Output = Result<R>>` and can be awaited.
/// Internally it wraps the function to an [`AsyncThread`].
///
/// Requires `feature = "async"`
///
/// # Examples
///
@@ -157,18 +155,19 @@ impl Function {
/// [`AsyncThread`]: crate::AsyncThread
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub fn call_async<R>(&self, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
pub fn call_async<R>(&self, args: impl IntoLuaMulti) -> impl Future<Output = Result<R>>
where
R: FromLuaMulti,
{
let lua = self.0.lua.lock();
AsyncCallFuture(unsafe {
lua.create_recycled_thread(self).and_then(|th| {
let mut th = th.into_async(args)?;
let thread_res = unsafe {
lua.create_recycled_thread(self).map(|th| {
let mut th = th.into_async(args);
th.set_recyclable(true);
Ok(th)
th
})
})
};
async move { thread_res?.await }
}
/// Returns a function that, when called, calls `self`, passing `args` as the first set of
@@ -431,6 +430,8 @@ impl Function {
///
/// Recording of coverage information is controlled by [`Compiler::set_coverage_level`] option.
///
/// Requires `feature = "luau"`
///
/// [`Compiler::set_coverage_level`]: crate::chunk::Compiler::set_coverage_level
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
@@ -490,6 +491,8 @@ impl Function {
/// Copies the function prototype and all its upvalues to the
/// newly created function.
/// This function returns shallow clone (same handle) for Rust/C functions.
///
/// Requires `feature = "luau"`
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn deep_clone(&self) -> Self {
@@ -641,27 +644,6 @@ impl LuaType for Function {
const TYPE_ID: c_int = ffi::LUA_TFUNCTION;
}
#[cfg(feature = "async")]
#[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> 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() };
match &mut this.0 {
Ok(thread) => {
let pinned_thread = unsafe { Pin::new_unchecked(thread) };
pinned_thread.poll(cx)
}
Err(err) => Poll::Ready(Err(err.clone())),
}
}
}
#[cfg(test)]
mod assertions {
use super::*;
@@ -670,7 +652,4 @@ mod assertions {
static_assertions::assert_not_impl_any!(Function: Send);
#[cfg(feature = "send")]
static_assertions::assert_impl_all!(Function: Send, Sync);
#[cfg(all(feature = "async", feature = "send"))]
static_assertions::assert_impl_all!(AsyncCallFuture<()>: Send);
}
+4 -8
View File
@@ -265,18 +265,14 @@ pub struct DebugStack {
/// Number of upvalues.
pub num_ups: u8,
/// Number of parameters.
///
/// Requires `feature = "lua54/lua53/lua52/luau"`
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau")))
)]
pub num_params: u8,
/// Whether the function is a vararg function.
///
/// Requires `feature = "lua54/lua53/lua52/luau"`
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau")))
)]
pub is_vararg: bool,
}
+2 -9
View File
@@ -67,7 +67,6 @@
#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(not(send), allow(clippy::arc_with_non_send_sync))]
#![allow(clippy::ptr_eq)]
#![allow(unsafe_op_in_unsafe_fn)]
#[macro_use]
mod macros;
@@ -78,7 +77,7 @@ mod conversion;
mod error;
mod function;
mod hook;
#[cfg(any(feature = "luau", doc))]
#[cfg(feature = "luau")]
mod luau;
mod memory;
mod multi;
@@ -128,13 +127,7 @@ pub use crate::hook::HookTriggers;
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub use crate::{
buffer::Buffer,
chunk::{CompileConstant, Compiler},
function::CoverageInfo,
luau::{NavigateError, Require},
vector::Vector,
};
pub use crate::{buffer::Buffer, chunk::Compiler, function::CoverageInfo, vector::Vector};
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
+7 -15
View File
@@ -2,22 +2,11 @@ use std::ffi::CStr;
use std::os::raw::c_int;
use crate::error::Result;
use crate::function::Function;
use crate::state::Lua;
pub use require::{NavigateError, Require};
// Since Luau has some missing standard functions, we re-implement them here
impl Lua {
/// Create a custom Luau `require` function using provided [`Require`] implementation to find
/// 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> {
require::create_require_function(self, require)
}
pub(crate) unsafe fn configure_luau(&self) -> Result<()> {
let globals = self.globals();
@@ -29,10 +18,11 @@ impl Lua {
globals.raw_set("_VERSION", format!("Luau {version}"))?;
}
// Enable default `require` implementation
let require = self.create_require_function(require::TextRequirer::new())?;
self.globals().raw_set("require", require)?;
Ok(())
}
pub(crate) fn disable_c_modules(&self) -> Result<()> {
package::disable_dylibs(self);
Ok(())
}
}
@@ -74,4 +64,6 @@ unsafe extern "C-unwind" fn lua_collectgarbage(state: *mut ffi::lua_State) -> c_
}
}
mod require;
pub(crate) use package::register_package_module;
mod package;
+270
View File
@@ -0,0 +1,270 @@
use std::ffi::CStr;
use std::fmt::Write;
use std::os::raw::c_int;
use std::path::{PathBuf, MAIN_SEPARATOR_STR};
use std::string::String as StdString;
use std::{env, fs};
use crate::chunk::ChunkMode;
use crate::error::Result;
use crate::state::Lua;
use crate::table::Table;
use crate::traits::IntoLua;
use crate::value::Value;
#[cfg(unix)]
use {libloading::Library, rustc_hash::FxHashMap};
//
// Luau package module
//
#[cfg(unix)]
const TARGET_MLUA_LUAU_ABI_VERSION: u32 = 3;
#[cfg(all(unix, feature = "module"))]
#[no_mangle]
#[used]
pub static MLUA_LUAU_ABI_VERSION: u32 = TARGET_MLUA_LUAU_ABI_VERSION;
// We keep reference to the loaded dylibs in application data
#[cfg(unix)]
struct LoadedDylibs(FxHashMap<PathBuf, Library>);
#[cfg(unix)]
impl std::ops::Deref for LoadedDylibs {
type Target = FxHashMap<PathBuf, Library>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[cfg(unix)]
impl std::ops::DerefMut for LoadedDylibs {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
pub(crate) fn register_package_module(lua: &Lua) -> Result<()> {
// Create the package table
let package = lua.create_table()?;
// Set `package.path`
let mut search_path = env::var("LUAU_PATH")
.or_else(|_| env::var("LUA_PATH"))
.unwrap_or_default();
if search_path.is_empty() {
search_path = "?.luau;?.lua".to_string();
}
package.raw_set("path", search_path)?;
// Set `package.cpath`
#[cfg(unix)]
{
let mut search_cpath = env::var("LUAU_CPATH")
.or_else(|_| env::var("LUA_CPATH"))
.unwrap_or_default();
if search_cpath.is_empty() {
if cfg!(any(target_os = "macos", target_os = "ios")) {
search_cpath = "?.dylib".to_string();
} else {
search_cpath = "?.so".to_string();
}
}
package.raw_set("cpath", search_cpath)?;
}
// Set `package.loaded` (table with a list of loaded modules)
let loaded = if let Ok(Some(loaded)) = lua.named_registry_value::<Option<Table>>("_LOADED") {
package.raw_set("loaded", &loaded)?;
loaded
} else {
let loaded = lua.create_table()?;
package.raw_set("loaded", &loaded)?;
lua.set_named_registry_value("_LOADED", &loaded)?;
loaded
};
// Set `package.loaders`
let loaders = lua.create_sequence_from([lua.create_function(lua_loader)?])?;
package.raw_set("loaders", &loaders)?;
#[cfg(unix)]
{
loaders.push(lua.create_function(dylib_loader)?)?;
lua.set_app_data(LoadedDylibs(FxHashMap::default()));
}
lua.set_named_registry_value("_LOADERS", loaders)?;
// Register the module and `require` function in globals
let globals = lua.globals();
globals.raw_set("package", &package)?;
loaded.raw_set("package", package)?;
globals.raw_set("require", unsafe { lua.create_c_function(lua_require)? })?;
Ok(())
}
#[allow(unused_variables)]
pub(crate) fn disable_dylibs(lua: &Lua) {
// Presence of `LoadedDylibs` in app data is used as a flag
// to check whether binary modules are enabled
#[cfg(unix)]
lua.remove_app_data::<LoadedDylibs>();
}
unsafe extern "C-unwind" fn lua_require(state: *mut ffi::lua_State) -> c_int {
ffi::lua_settop(state, 1);
let name = ffi::luaL_checkstring(state, 1);
ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, cstr!("_LOADED")); // _LOADED is at index 2
if ffi::lua_rawgetfield(state, 2, name) != ffi::LUA_TNIL {
return 1; // module is already loaded
}
ffi::lua_pop(state, 1); // remove nil
// load the module
let err_buf = ffi::lua_newuserdata_t(state, StdString::new());
ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, cstr!("_LOADERS")); // _LOADERS is at index 3
for i in 1.. {
if ffi::lua_rawgeti(state, -1, i) == ffi::LUA_TNIL {
// no more loaders?
if (*err_buf).is_empty() {
ffi::luaL_error(state, cstr!("module '%s' not found"), name);
} else {
let bytes = (*err_buf).as_bytes();
let extra = ffi::lua_pushlstring(state, bytes.as_ptr() as *const _, bytes.len());
ffi::luaL_error(state, cstr!("module '%s' not found:%s"), name, extra);
}
}
ffi::lua_pushvalue(state, 1); // name arg
ffi::lua_call(state, 1, 2); // call loader
match ffi::lua_type(state, -2) {
ffi::LUA_TFUNCTION => break, // loader found
ffi::LUA_TSTRING => {
// error message
let msg = ffi::lua_tostring(state, -2);
let msg = CStr::from_ptr(msg).to_string_lossy();
_ = write!(&mut *err_buf, "\n\t{msg}");
}
_ => {}
}
ffi::lua_pop(state, 2); // remove both results
}
ffi::lua_pushvalue(state, 1); // name is 1st argument to module loader
ffi::lua_rotate(state, -2, 1); // loader data <-> name
// stack: ...; loader function; module name; loader data
ffi::lua_call(state, 2, 1);
// stack: ...; result from loader function
if ffi::lua_isnil(state, -1) != 0 {
ffi::lua_pop(state, 1);
ffi::lua_pushboolean(state, 1); // use true as result
}
ffi::lua_pushvalue(state, -1); // make copy of entrypoint result
ffi::lua_setfield(state, 2, name); /* _LOADED[name] = returned value */
1
}
/// Searches for the given `name` in the given `path`.
///
/// `path` is a string containing a sequence of templates separated by semicolons.
fn package_searchpath(name: &str, search_path: &str, try_prefix: bool) -> Option<PathBuf> {
let mut names = vec![name.replace('.', MAIN_SEPARATOR_STR)];
if try_prefix && name.contains('.') {
let prefix = name.split_once('.').map(|(prefix, _)| prefix).unwrap();
names.push(prefix.to_string());
}
for path in search_path.split(';') {
for name in &names {
let file_path = PathBuf::from(path.replace('?', name));
if let Ok(true) = fs::metadata(&file_path).map(|m| m.is_file()) {
return Some(file_path);
}
}
}
None
}
//
// Module loaders
//
/// Tries to load a lua (text) file
fn lua_loader(lua: &Lua, modname: StdString) -> Result<Value> {
let package = {
let loaded = lua.named_registry_value::<Table>("_LOADED")?;
loaded.raw_get::<Table>("package")
}?;
let search_path = package.get::<StdString>("path").unwrap_or_default();
if let Some(file_path) = package_searchpath(&modname, &search_path, false) {
match fs::read(&file_path) {
Ok(buf) => {
return lua
.load(buf)
.set_name(format!("={}", file_path.display()))
.set_mode(ChunkMode::Text)
.into_function()
.map(Value::Function);
}
Err(err) => {
return format!("cannot open '{}': {err}", file_path.display()).into_lua(lua);
}
}
}
Ok(Value::Nil)
}
/// Tries to load a dynamic library
#[cfg(unix)]
fn dylib_loader(lua: &Lua, modname: StdString) -> Result<Value> {
let package = {
let loaded = lua.named_registry_value::<Table>("_LOADED")?;
loaded.raw_get::<Table>("package")
}?;
let search_cpath = package.get::<StdString>("cpath").unwrap_or_default();
let find_symbol = |lib: &Library| unsafe {
if let Ok(entry) = lib.get::<ffi::lua_CFunction>(format!("luaopen_{modname}\0").as_bytes()) {
return lua.create_c_function(*entry).map(Value::Function);
}
// Try all in one mode
if let Ok(entry) =
lib.get::<ffi::lua_CFunction>(format!("luaopen_{}\0", modname.replace('.', "_")).as_bytes())
{
return lua.create_c_function(*entry).map(Value::Function);
}
"cannot find module entrypoint".into_lua(lua)
};
if let Some(file_path) = package_searchpath(&modname, &search_cpath, true) {
let file_path = file_path.canonicalize()?;
// Load the library and check for symbol
unsafe {
let mut loaded_dylibs = match lua.app_data_mut::<LoadedDylibs>() {
Some(loaded_dylibs) => loaded_dylibs,
None => return "dynamic libraries are disabled in safe mode".into_lua(lua),
};
// Check if it's already loaded
if let Some(lib) = loaded_dylibs.get(&file_path) {
return find_symbol(lib);
}
if let Ok(lib) = Library::new(&file_path) {
// Check version
let mod_version = lib.get::<*const u32>(b"MLUA_LUAU_ABI_VERSION");
let mod_version = mod_version.map(|v| **v).unwrap_or_default();
if mod_version != TARGET_MLUA_LUAU_ABI_VERSION {
let err = format!("wrong module ABI version (expected {TARGET_MLUA_LUAU_ABI_VERSION}, got {mod_version})");
return err.into_lua(lua);
}
let symbol = find_symbol(&lib);
loaded_dylibs.insert(file_path, lib);
return symbol;
}
}
}
Ok(Value::Nil)
}
-644
View File
@@ -1,644 +0,0 @@
use std::cell::RefCell;
use std::collections::VecDeque;
use std::ffi::CStr;
use std::io::Result as IoResult;
use std::os::raw::{c_char, c_int, c_void};
use std::path::{Component, Path, PathBuf};
use std::result::Result as StdResult;
use std::{env, fmt, fs, mem, ptr};
use crate::error::Result;
use crate::function::Function;
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.
pub enum NavigateError {
Ambiguous,
NotFound,
}
#[cfg(feature = "luau")]
trait IntoNavigateResult {
fn into_nav_result(self) -> ffi::luarequire_NavigateResult;
}
#[cfg(feature = "luau")]
impl IntoNavigateResult for StdResult<(), NavigateError> {
fn into_nav_result(self) -> ffi::luarequire_NavigateResult {
match self {
Ok(()) => ffi::luarequire_NavigateResult::Success,
Err(NavigateError::Ambiguous) => ffi::luarequire_NavigateResult::Ambiguous,
Err(NavigateError::NotFound) => ffi::luarequire_NavigateResult::NotFound,
}
}
}
#[cfg(feature = "luau")]
type WriteResult = ffi::luarequire_WriteResult;
/// A trait for handling modules loading and navigation in the Luau `require` system.
pub trait Require: MaybeSend {
/// Returns `true` if "require" is permitted for the given chunk name.
fn is_require_allowed(&self, chunk_name: &str) -> bool;
/// Resets the internal state to point at the requirer module.
fn reset(&self, chunk_name: &str) -> StdResult<(), NavigateError>;
/// Resets the internal state to point at an aliased module.
///
/// This function received an exact path from a configuration file.
/// It's only called when an alias's path cannot be resolved relative to its
/// configuration file.
fn jump_to_alias(&self, path: &str) -> StdResult<(), NavigateError>;
// Navigate to parent directory
fn to_parent(&self) -> StdResult<(), NavigateError>;
/// Navigate to the given child directory.
fn to_child(&self, name: &str) -> StdResult<(), NavigateError>;
/// Returns whether the context is currently pointing at a module
fn is_module_present(&self) -> bool;
/// Returns the contents of the current module
///
/// This function is only called if `is_module_present` returns true.
fn contents(&self) -> IoResult<Vec<u8>>;
/// Returns a chunk name for the current module.
///
/// This function is only called if `is_module_present` returns true.
/// The chunk name is used to identify the module using the debug library.
fn chunk_name(&self) -> String;
/// Provides a cache key representing the current module.
///
/// This function is only called if `is_module_present` returns true.
fn cache_key(&self) -> Vec<u8>;
/// Returns whether a configuration file is present in the current context.
fn is_config_present(&self) -> bool;
/// Returns the contents of the configuration file in the current context.
///
/// This function is only called if `is_config_present` returns true.
fn config(&self) -> IoResult<Vec<u8>>;
/// Returns a loader that when called, loads the module and returns the result.
///
/// Loader can be sync or async.
fn loader(&self, lua: &Lua, path: &str, chunk_name: &str, content: &[u8]) -> Result<Function> {
let _ = path;
lua.load(content).set_name(chunk_name).into_function()
}
}
impl fmt::Debug for dyn Require {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "<dyn Require>")
}
}
/// The standard implementation of Luau `require` navigation.
#[derive(Default)]
pub(super) struct TextRequirer {
abs_path: RefCell<PathBuf>,
rel_path: RefCell<PathBuf>,
module_path: RefCell<PathBuf>,
}
impl TextRequirer {
pub(super) fn new() -> Self {
Self::default()
}
fn normalize_chunk_name(chunk_name: &str) -> &str {
if let Some((path, line)) = chunk_name.split_once(':') {
if line.parse::<u32>().is_ok() {
return path;
}
}
chunk_name
}
// Normalizes the path by removing unnecessary components
fn normalize_path(path: &Path) -> PathBuf {
let mut components = VecDeque::new();
for comp in path.components() {
match comp {
Component::Prefix(..) | Component::RootDir => {
components.push_back(comp);
}
Component::CurDir => {}
Component::ParentDir => {
if matches!(components.back(), None | Some(Component::ParentDir)) {
components.push_back(Component::ParentDir);
} else if matches!(components.back(), Some(Component::Normal(..))) {
components.pop_back();
}
}
Component::Normal(..) => components.push_back(comp),
}
}
if matches!(components.front(), None | Some(Component::Normal(..))) {
components.push_front(Component::CurDir);
}
// Join the components back together
components.into_iter().collect()
}
fn find_module_path(path: &Path) -> StdResult<PathBuf, NavigateError> {
let mut found_path = None;
let current_ext = (path.extension().and_then(|s| s.to_str()))
.map(|s| format!("{s}."))
.unwrap_or_default();
for ext in ["luau", "lua"] {
let candidate = path.with_extension(format!("{current_ext}{ext}"));
if candidate.is_file() {
if found_path.is_some() {
return Err(NavigateError::Ambiguous);
}
found_path = Some(candidate);
}
}
if path.is_dir() {
if found_path.is_some() {
return Err(NavigateError::Ambiguous);
}
for component in ["init.luau", "init.lua"] {
let candidate = path.join(component);
if candidate.is_file() {
if found_path.is_some() {
return Err(NavigateError::Ambiguous);
}
found_path = Some(candidate);
}
}
if found_path.is_none() {
found_path = Some(PathBuf::new());
}
}
found_path.ok_or(NavigateError::NotFound)
}
}
impl Require for TextRequirer {
fn is_require_allowed(&self, chunk_name: &str) -> bool {
chunk_name.starts_with('@')
}
fn reset(&self, chunk_name: &str) -> StdResult<(), NavigateError> {
if !chunk_name.starts_with('@') {
return Err(NavigateError::NotFound);
}
let chunk_name = &Self::normalize_chunk_name(chunk_name)[1..];
let path = Self::normalize_path(chunk_name.as_ref());
if path.extension() == Some("rs".as_ref()) {
let cwd = match env::current_dir() {
Ok(cwd) => cwd,
Err(_) => return Err(NavigateError::NotFound),
};
self.abs_path.replace(Self::normalize_path(&cwd.join(&path)));
self.rel_path.replace(path);
self.module_path.replace(PathBuf::new());
return Ok(());
}
if path.is_absolute() {
let module_path = Self::find_module_path(&path)?;
self.abs_path.replace(path.clone());
self.rel_path.replace(path);
self.module_path.replace(module_path);
} else {
// Relative path
let cwd = match env::current_dir() {
Ok(cwd) => cwd,
Err(_) => return Err(NavigateError::NotFound),
};
let abs_path = cwd.join(&path);
let module_path = Self::find_module_path(&abs_path)?;
self.abs_path.replace(Self::normalize_path(&abs_path));
self.rel_path.replace(path);
self.module_path.replace(module_path);
}
Ok(())
}
fn jump_to_alias(&self, path: &str) -> StdResult<(), NavigateError> {
let path = Self::normalize_path(path.as_ref());
let module_path = Self::find_module_path(&path)?;
self.abs_path.replace(path.clone());
self.rel_path.replace(path);
self.module_path.replace(module_path);
Ok(())
}
fn to_parent(&self) -> StdResult<(), NavigateError> {
let mut abs_path = self.abs_path.borrow().clone();
if !abs_path.pop() {
return Err(NavigateError::NotFound);
}
let mut rel_parent = self.rel_path.borrow().clone();
rel_parent.pop();
let module_path = Self::find_module_path(&abs_path)?;
self.abs_path.replace(abs_path);
self.rel_path.replace(Self::normalize_path(&rel_parent));
self.module_path.replace(module_path);
Ok(())
}
fn to_child(&self, name: &str) -> StdResult<(), NavigateError> {
let abs_path = self.abs_path.borrow().join(name);
let rel_path = self.rel_path.borrow().join(name);
let module_path = Self::find_module_path(&abs_path)?;
self.abs_path.replace(abs_path);
self.rel_path.replace(rel_path);
self.module_path.replace(module_path);
Ok(())
}
fn is_module_present(&self) -> bool {
self.module_path.borrow().is_file()
}
fn contents(&self) -> IoResult<Vec<u8>> {
fs::read(&*self.module_path.borrow())
}
fn chunk_name(&self) -> String {
format!("@{}", self.rel_path.borrow().display())
}
fn cache_key(&self) -> Vec<u8> {
self.module_path.borrow().display().to_string().into_bytes()
}
fn is_config_present(&self) -> bool {
self.abs_path.borrow().join(".luaurc").is_file()
}
fn config(&self) -> IoResult<Vec<u8>> {
fs::read(self.abs_path.borrow().join(".luaurc"))
}
}
#[cfg(feature = "luau")]
pub(super) unsafe extern "C" fn init_config(config: *mut ffi::luarequire_Configuration) {
if config.is_null() {
return;
}
unsafe extern "C" fn is_require_allowed(
_state: *mut ffi::lua_State,
ctx: *mut c_void,
requirer_chunkname: *const c_char,
) -> bool {
if requirer_chunkname.is_null() {
return false;
}
let this = &*(ctx as *const Box<dyn Require>);
let chunk_name = CStr::from_ptr(requirer_chunkname).to_string_lossy();
this.is_require_allowed(&chunk_name)
}
unsafe extern "C" fn reset(
_state: *mut ffi::lua_State,
ctx: *mut c_void,
requirer_chunkname: *const c_char,
) -> ffi::luarequire_NavigateResult {
let this = &*(ctx as *const Box<dyn Require>);
let chunk_name = CStr::from_ptr(requirer_chunkname).to_string_lossy();
this.reset(&chunk_name).into_nav_result()
}
unsafe extern "C" fn jump_to_alias(
_state: *mut ffi::lua_State,
ctx: *mut c_void,
path: *const c_char,
) -> ffi::luarequire_NavigateResult {
let this = &*(ctx as *const Box<dyn Require>);
let path = CStr::from_ptr(path).to_string_lossy();
this.jump_to_alias(&path).into_nav_result()
}
unsafe extern "C" fn to_parent(
_state: *mut ffi::lua_State,
ctx: *mut c_void,
) -> ffi::luarequire_NavigateResult {
let this = &*(ctx as *const Box<dyn Require>);
this.to_parent().into_nav_result()
}
unsafe extern "C" fn to_child(
_state: *mut ffi::lua_State,
ctx: *mut c_void,
name: *const c_char,
) -> ffi::luarequire_NavigateResult {
let this = &*(ctx as *const Box<dyn Require>);
let name = CStr::from_ptr(name).to_string_lossy();
this.to_child(&name).into_nav_result()
}
unsafe extern "C" fn is_module_present(_state: *mut ffi::lua_State, ctx: *mut c_void) -> bool {
let this = &*(ctx as *const Box<dyn Require>);
this.is_module_present()
}
unsafe extern "C" fn get_contents(
state: *mut ffi::lua_State,
ctx: *mut c_void,
buffer: *mut c_char,
buffer_size: usize,
size_out: *mut usize,
) -> WriteResult {
let this = &*(ctx as *const Box<dyn Require>);
write_to_buffer(state, buffer, buffer_size, size_out, || this.contents())
}
unsafe extern "C" fn get_chunkname(
state: *mut ffi::lua_State,
ctx: *mut c_void,
buffer: *mut c_char,
buffer_size: usize,
size_out: *mut usize,
) -> WriteResult {
let this = &*(ctx as *const Box<dyn Require>);
write_to_buffer(state, buffer, buffer_size, size_out, || {
Ok(this.chunk_name().into_bytes())
})
}
unsafe extern "C" fn get_cache_key(
state: *mut ffi::lua_State,
ctx: *mut c_void,
buffer: *mut c_char,
buffer_size: usize,
size_out: *mut usize,
) -> WriteResult {
let this = &*(ctx as *const Box<dyn Require>);
write_to_buffer(state, buffer, buffer_size, size_out, || Ok(this.cache_key()))
}
unsafe extern "C" fn is_config_present(_state: *mut ffi::lua_State, ctx: *mut c_void) -> bool {
let this = &*(ctx as *const Box<dyn Require>);
this.is_config_present()
}
unsafe extern "C" fn get_config(
state: *mut ffi::lua_State,
ctx: *mut c_void,
buffer: *mut c_char,
buffer_size: usize,
size_out: *mut usize,
) -> WriteResult {
let this = &*(ctx as *const Box<dyn Require>);
write_to_buffer(state, buffer, buffer_size, size_out, || this.config())
}
unsafe extern "C-unwind" fn load(
state: *mut ffi::lua_State,
ctx: *mut c_void,
path: *const c_char,
chunk_name: *const c_char,
contents: *const c_char,
) -> c_int {
let this = &*(ctx as *const Box<dyn Require>);
let path = CStr::from_ptr(path).to_string_lossy();
let chunk_name = CStr::from_ptr(chunk_name).to_string_lossy();
let contents = CStr::from_ptr(contents).to_bytes();
callback_error_ext(state, ptr::null_mut(), false, move |extra, _| {
let rawlua = (*extra).raw_lua();
rawlua.push(this.loader(rawlua.lua(), &path, &chunk_name, contents)?)?;
Ok(1)
})
}
(*config).is_require_allowed = is_require_allowed;
(*config).reset = reset;
(*config).jump_to_alias = jump_to_alias;
(*config).to_parent = to_parent;
(*config).to_child = to_child;
(*config).is_module_present = is_module_present;
(*config).get_contents = get_contents;
(*config).get_chunkname = get_chunkname;
(*config).get_cache_key = get_cache_key;
(*config).is_config_present = is_config_present;
(*config).get_config = get_config;
(*config).load = load;
}
/// Helper function to write data to a buffer
#[cfg(feature = "luau")]
unsafe fn write_to_buffer(
state: *mut ffi::lua_State,
buffer: *mut c_char,
buffer_size: usize,
size_out: *mut usize,
data_fetcher: impl Fn() -> IoResult<Vec<u8>>,
) -> WriteResult {
struct DataCache(Option<Vec<u8>>);
// The initial buffer size can be too small, to avoid making a second data fetch call,
// we cache the content in the first call, and then re-use it.
let lua = Lua::get_or_init_from_ptr(state);
match lua.try_app_data_mut::<DataCache>() {
Ok(Some(mut data_cache)) => {
if let Some(data) = data_cache.0.take() {
mlua_assert!(data.len() <= buffer_size, "buffer is too small");
*size_out = data.len();
ptr::copy_nonoverlapping(data.as_ptr(), buffer as *mut _, data.len());
return WriteResult::Success;
}
}
Ok(None) => {
// Init the cache
_ = lua.try_set_app_data(DataCache(None));
}
Err(_) => {}
}
match data_fetcher() {
Ok(data) => {
*size_out = data.len();
if *size_out > buffer_size {
// Cache the data for the next call to avoid getting the contents again
if let Ok(Some(mut data_cache)) = lua.try_app_data_mut::<DataCache>() {
data_cache.0 = Some(data);
}
return WriteResult::BufferTooSmall;
}
ptr::copy_nonoverlapping(data.as_ptr(), buffer as *mut _, data.len());
WriteResult::Success
}
Err(_) => WriteResult::Failure,
}
}
#[cfg(feature = "luau")]
pub fn create_require_function<R: Require + '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.. {
if ffi::lua_getinfo(state, level, cstr!("s"), &mut ar) == 0 {
ffi::luaL_error(state, cstr!("require is not supported in this context"));
}
if CStr::from_ptr(ar.what) != c"C" {
break;
}
}
ffi::lua_pushstring(state, ar.source);
1
}
unsafe extern "C-unwind" fn get_cache_key(state: *mut ffi::lua_State) -> c_int {
let requirer = ffi::lua_touserdata(state, ffi::lua_upvalueindex(1)) as *const Box<dyn Require>;
let cache_key = (*requirer).cache_key();
ffi::lua_pushlstring(state, cache_key.as_ptr() as *const _, cache_key.len());
1
}
let (get_cache_key, find_current_file, proxyrequire, registered_modules, loader_cache) = unsafe {
lua.exec_raw::<(Function, Function, Function, Table, Table)>((), move |state| {
let requirer_ptr = ffi::lua_newuserdata_t::<Box<dyn Require>>(state, Box::new(require));
ffi::lua_pushcclosured(state, get_cache_key, cstr!("get_cache_key"), 1);
ffi::lua_pushcfunctiond(state, find_current_file, cstr!("find_current_file"));
ffi::luarequire_pushproxyrequire(state, init_config, requirer_ptr as *mut _);
ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_REGISTERED_MODULES_TABLE);
ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, cstr!("__MLUA_LOADER_CACHE"));
})
}?;
unsafe extern "C-unwind" fn error(state: *mut ffi::lua_State) -> c_int {
ffi::luaL_where(state, 1);
ffi::lua_pushvalue(state, 1);
ffi::lua_concat(state, 2);
ffi::lua_error(state);
}
unsafe extern "C-unwind" fn r#type(state: *mut ffi::lua_State) -> c_int {
ffi::lua_pushstring(state, ffi::lua_typename(state, ffi::lua_type(state, 1)));
1
}
let (error, r#type) = unsafe {
lua.exec_raw::<(Function, Function)>((), move |state| {
ffi::lua_pushcfunctiond(state, error, cstr!("error"));
ffi::lua_pushcfunctiond(state, r#type, cstr!("type"));
})
}?;
// Prepare environment for the "require" function
let env = lua.create_table_with_capacity(0, 7)?;
env.raw_set("get_cache_key", get_cache_key)?;
env.raw_set("find_current_file", find_current_file)?;
env.raw_set("proxyrequire", proxyrequire)?;
env.raw_set("REGISTERED_MODULES", registered_modules)?;
env.raw_set("LOADER_CACHE", loader_cache)?;
env.raw_set("error", error)?;
env.raw_set("type", r#type)?;
lua.load(
r#"
local path = ...
if type(path) ~= "string" then
error("bad argument #1 to 'require' (string expected, got " .. type(path) .. ")")
end
-- Check if the module (path) is explicitly registered
local maybe_result = REGISTERED_MODULES[path]
if maybe_result ~= nil then
return maybe_result
end
local loader = proxyrequire(path, find_current_file())
local cache_key = get_cache_key()
-- Check if the loader result is already cached
local result = LOADER_CACHE[cache_key]
if result ~= nil then
return result
end
-- Call the loader function and cache the result
result = loader()
if result == nil then
result = true
end
LOADER_CACHE[cache_key] = result
return result
"#,
)
.try_cache()
.set_name("=__mlua_require")
.set_environment(env)
.into_function()
}
#[cfg(test)]
mod tests {
use std::path::Path;
use super::TextRequirer;
#[test]
fn test_path_normalize() {
for (input, expected) in [
// Basic formatting checks
("", "./"),
(".", "./"),
("a/relative/path", "./a/relative/path"),
// Paths containing extraneous '.' and '/' symbols
("./remove/extraneous/symbols/", "./remove/extraneous/symbols"),
("./remove/extraneous//symbols", "./remove/extraneous/symbols"),
("./remove/extraneous/symbols/.", "./remove/extraneous/symbols"),
("./remove/extraneous/./symbols", "./remove/extraneous/symbols"),
("../remove/extraneous/symbols/", "../remove/extraneous/symbols"),
("../remove/extraneous//symbols", "../remove/extraneous/symbols"),
("../remove/extraneous/symbols/.", "../remove/extraneous/symbols"),
("../remove/extraneous/./symbols", "../remove/extraneous/symbols"),
("/remove/extraneous/symbols/", "/remove/extraneous/symbols"),
("/remove/extraneous//symbols", "/remove/extraneous/symbols"),
("/remove/extraneous/symbols/.", "/remove/extraneous/symbols"),
("/remove/extraneous/./symbols", "/remove/extraneous/symbols"),
// Paths containing '..'
("./remove/me/..", "./remove"),
("./remove/me/../", "./remove"),
("../remove/me/..", "../remove"),
("../remove/me/../", "../remove"),
("/remove/me/..", "/remove"),
("/remove/me/../", "/remove"),
("./..", "../"),
("./../", "../"),
("../..", "../../"),
("../../", "../../"),
// '..' disappears if path is absolute and component is non-erasable
("/../", "/"),
] {
let path = TextRequirer::normalize_path(input.as_ref());
assert_eq!(
&path,
expected.as_ref() as &Path,
"wrong normalization for {input}"
);
}
}
}
+1 -4
View File
@@ -22,10 +22,7 @@ pub use crate::HookTriggers as LuaHookTriggers;
#[cfg(feature = "luau")]
#[doc(no_inline)]
pub use crate::{
CompileConstant as LuaCompileConstant, CoverageInfo as LuaCoverageInfo,
NavigateError as LuaNavigateError, Require as LuaRequire, Vector as LuaVector,
};
pub use crate::{CoverageInfo as LuaCoverageInfo, Vector as LuaVector};
#[cfg(feature = "async")]
#[doc(no_inline)]
+12
View File
@@ -17,6 +17,8 @@ use crate::value::Value;
pub trait LuaSerdeExt: Sealed {
/// A special value (lightuserdata) to encode/decode optional (none) values.
///
/// Requires `feature = "serialize"`
///
/// # Example
///
/// ```
@@ -40,6 +42,8 @@ pub trait LuaSerdeExt: Sealed {
/// As result, encoded Array will contain only sequence part of the table, with the same length
/// as the `#` operator on that table.
///
/// Requires `feature = "serialize"`
///
/// # Example
///
/// ```
@@ -67,6 +71,8 @@ pub trait LuaSerdeExt: Sealed {
/// Converts `T` into a [`Value`] instance.
///
/// Requires `feature = "serialize"`
///
/// [`Value`]: crate::Value
///
/// # Example
@@ -98,6 +104,8 @@ pub trait LuaSerdeExt: Sealed {
/// Converts `T` into a [`Value`] instance with options.
///
/// Requires `feature = "serialize"`
///
/// # Example
///
/// ```
@@ -121,6 +129,8 @@ pub trait LuaSerdeExt: Sealed {
/// Deserializes a [`Value`] into any serde deserializable object.
///
/// Requires `feature = "serialize"`
///
/// # Example
///
/// ```
@@ -148,6 +158,8 @@ pub trait LuaSerdeExt: Sealed {
/// Deserializes a [`Value`] into any serde deserializable object with options.
///
/// Requires `feature = "serialize"`
///
/// # Example
///
/// ```
+120 -229
View File
@@ -2,7 +2,7 @@ use std::any::TypeId;
use std::cell::{BorrowError, BorrowMutError, RefCell};
use std::marker::PhantomData;
use std::ops::Deref;
use std::os::raw::{c_char, c_int};
use std::os::raw::c_int;
use std::panic::Location;
use std::result::Result as StdResult;
use std::{fmt, mem, ptr};
@@ -30,7 +30,7 @@ use crate::util::{
use crate::value::{Nil, Value};
#[cfg(not(feature = "luau"))]
use crate::{hook::HookTriggers, types::HookKind};
use crate::hook::HookTriggers;
#[cfg(any(feature = "luau", doc))]
use crate::{buffer::Buffer, chunk::Compiler};
@@ -46,7 +46,7 @@ use serde::Serialize;
pub(crate) use extra::ExtraData;
pub use raw::RawLua;
pub(crate) use util::callback_error_ext;
use util::callback_error_ext;
/// Top level Lua struct which represents an instance of Lua VM.
pub struct Lua {
@@ -74,6 +74,7 @@ pub(crate) struct LuaGuard(ArcReentrantMutexGuard<RawLua>);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum GCMode {
Incremental,
/// Requires `feature = "lua54"`
#[cfg(feature = "lua54")]
#[cfg_attr(docsrs, doc(cfg(feature = "lua54")))]
Generational,
@@ -223,7 +224,6 @@ impl Lua {
let lua = unsafe { Self::inner_new(libs, options) };
#[cfg(not(feature = "luau"))]
if libs.contains(StdLib::PACKAGE) {
mlua_expect!(lua.disable_c_modules(), "Error disabling C modules");
}
@@ -263,7 +263,7 @@ impl Lua {
/// Creates a new Lua state with required `libs` and `options`
unsafe fn inner_new(libs: StdLib, options: LuaOptions) -> Lua {
let lua = Lua {
raw: RawLua::new(libs, &options),
raw: RawLua::new(libs, options),
collect_garbage: true,
};
@@ -273,23 +273,16 @@ impl Lua {
lua
}
/// Returns or constructs Lua instance from a raw state.
/// Constructs a new Lua instance from an existing raw state.
///
/// Once initialized, the returned Lua instance is cached in the registry and can be retrieved
/// Once called, a returned Lua state is cached in the registry and can be retrieved
/// by calling this function again.
///
/// # Safety
/// The `Lua` must outlive the chosen lifetime `'a`.
#[allow(clippy::missing_safety_doc)]
#[inline]
pub unsafe fn get_or_init_from_ptr<'a>(state: *mut ffi::lua_State) -> &'a Lua {
debug_assert!(!state.is_null(), "Lua state is null");
match ExtraData::get(state) {
extra if !extra.is_null() => (*extra).lua(),
_ => {
// The `owned` flag is set to `false` as we don't own the Lua state.
RawLua::init_from_ptr(state, false);
(*ExtraData::get(state)).lua()
}
pub unsafe fn init_from_ptr(state: *mut ffi::lua_State) -> Lua {
Lua {
raw: RawLua::init_from_ptr(state, false),
collect_garbage: true,
}
}
@@ -338,6 +331,12 @@ impl Lua {
R::from_stack_multi(nresults, &lua)
}
#[doc(hidden)]
#[deprecated(since = "0.10.0", note = "please use `load_std_libs` instead")]
pub fn load_from_std_lib(&self, libs: StdLib) -> Result<()> {
self.load_std_libs(libs)
}
/// Loads the specified subset of the standard libraries into an existing Lua state.
///
/// Use the [`StdLib`] flags to specify the libraries you want to load.
@@ -345,78 +344,40 @@ impl Lua {
unsafe { self.lock().load_std_libs(libs) }
}
/// Registers module into an existing Lua state using the specified value.
/// Loads module `modname` into an existing Lua state using the specified entrypoint
/// function.
///
/// After registration, the given value will always be immediately returned when the
/// given module is [required].
/// Internally calls the Lua function `func` with the string `modname` as an argument,
/// sets the call result to `package.loaded[modname]` and returns copy of the result.
///
/// [required]: https://www.lua.org/manual/5.4/manual.html#pdf-require
pub fn register_module(&self, modname: &str, value: impl IntoLua) -> Result<()> {
#[cfg(not(feature = "luau"))]
const LOADED_MODULES_KEY: *const c_char = ffi::LUA_LOADED_TABLE;
#[cfg(feature = "luau")]
const LOADED_MODULES_KEY: *const c_char = ffi::LUA_REGISTERED_MODULES_TABLE;
if cfg!(feature = "luau") && !modname.starts_with('@') {
return Err(Error::runtime("module name must begin with '@'"));
}
unsafe {
self.exec_raw::<()>(value, |state| {
ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, LOADED_MODULES_KEY);
ffi::lua_pushlstring(state, modname.as_ptr() as *const c_char, modname.len() as _);
ffi::lua_pushvalue(state, -3);
ffi::lua_rawset(state, -3);
})
}
}
/// Preloads module into an existing Lua state using the specified loader function.
/// If `package.loaded[modname]` value is not nil, returns copy of the value without
/// calling the function.
///
/// When the module is required, the loader function will be called with module name as the
/// first argument.
/// If the function does not return a non-nil value then this method assigns true to
/// `package.loaded[modname]`.
///
/// This is similar to setting the [`package.preload[modname]`] field.
/// Behavior is similar to Lua's [`require`] function.
///
/// [`package.preload[modname]`]: https://www.lua.org/manual/5.4/manual.html#pdf-package.preload
#[cfg(not(feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
pub fn preload_module(&self, modname: &str, func: Function) -> Result<()> {
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
let preload = unsafe {
self.exec_raw::<Option<Table>>((), |state| {
ffi::lua_getfield(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_PRELOAD_TABLE);
})?
};
#[cfg(any(feature = "lua51", feature = "luajit"))]
let preload = unsafe {
self.exec_raw::<Option<Table>>((), |state| {
if ffi::lua_getfield(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_LOADED_TABLE) != ffi::LUA_TNIL {
ffi::luaL_getsubtable(state, -1, ffi::LUA_LOADLIBNAME);
ffi::luaL_getsubtable(state, -1, cstr!("preload"));
ffi::lua_rotate(state, 1, 1);
}
})?
};
if let Some(preload) = preload {
preload.raw_set(modname, func)?;
}
Ok(())
}
#[doc(hidden)]
#[deprecated(since = "0.11.0", note = "Use `register_module` instead")]
#[cfg(not(feature = "luau"))]
#[cfg(not(tarpaulin_include))]
pub fn load_from_function<T: FromLua>(&self, modname: &str, func: Function) -> Result<T> {
/// [`require`]: https://www.lua.org/manual/5.4/manual.html#pdf-require
pub fn load_from_function<T>(&self, modname: &str, func: Function) -> Result<T>
where
T: FromLua,
{
let lua = self.lock();
let state = lua.state();
let loaded = unsafe {
self.exec_raw::<Table>((), |state| {
ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_LOADED_TABLE);
})?
let _sg = StackGuard::new(state);
check_stack(state, 2)?;
protect_lua!(state, 0, 1, fn(state) {
ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, cstr!("_LOADED"));
})?;
Table(lua.pop_ref())
};
let value = match loaded.raw_get(modname)? {
let modname = unsafe { lua.create_string(modname)? };
let value = match loaded.raw_get(&modname)? {
Value::Nil => {
let result = match func.call(modname)? {
let result = match func.call(&modname)? {
Value::Nil => Value::Boolean(true),
res => res,
};
@@ -430,14 +391,24 @@ impl Lua {
/// Unloads module `modname`.
///
/// This method does not support unloading binary Lua modules since they are internally cached
/// and can be unloaded only by closing Lua state.
///
/// This is similar to calling [`Lua::register_module`] with `Nil` value.
/// Removes module from the [`package.loaded`] table which allows to load it again.
/// It does not support unloading binary Lua modules since they are internally cached and can be
/// unloaded only by closing Lua state.
///
/// [`package.loaded`]: https://www.lua.org/manual/5.4/manual.html#pdf-package.loaded
pub fn unload_module(&self, modname: &str) -> Result<()> {
self.register_module(modname, Nil)
pub fn unload(&self, modname: &str) -> Result<()> {
let lua = self.lock();
let state = lua.state();
let loaded = unsafe {
let _sg = StackGuard::new(state);
check_stack(state, 2)?;
protect_lua!(state, 0, 1, fn(state) {
ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, cstr!("_LOADED"));
})?;
Table(lua.pop_ref())
};
loaded.raw_set(modname, Nil)
}
// Executes module entrypoint function, which returns only one Value.
@@ -451,9 +422,13 @@ impl Lua {
R: IntoLua,
{
// Make sure that Lua is initialized
let _ = Self::get_or_init_from_ptr(state);
let mut lua = Self::init_from_ptr(state);
lua.collect_garbage = false;
// `Lua` is no longer needed and must be dropped at this point to avoid memory leak
// in case of possible longjmp (lua_error) below
drop(lua);
callback_error_ext(state, ptr::null_mut(), true, move |extra, nargs| {
callback_error_ext(state, ptr::null_mut(), move |extra, nargs| {
let rawlua = (*extra).raw_lua();
let args = A::from_stack_args(nargs, 1, None, rawlua)?;
func(rawlua.lua(), args)?.push_into_stack(rawlua)?;
@@ -510,6 +485,8 @@ impl Lua {
/// # #[cfg(not(feature = "luau"))]
/// # fn main() {}
/// ```
///
/// Requires `feature = "luau"`
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn sandbox(&self, enabled: bool) -> Result<()> {
@@ -535,26 +512,6 @@ impl Lua {
}
}
/// Sets or replaces a global hook function that will periodically be called as Lua code
/// executes.
///
/// All new threads created (by mlua) after this call will use the global hook function.
///
/// For more information see [`Lua::set_hook`].
#[cfg(not(feature = "luau"))]
#[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,
{
let lua = self.lock();
unsafe {
(*lua.extra.get()).hook_triggers = triggers;
(*lua.extra.get()).hook_callback = Some(XRc::new(callback));
lua.set_thread_hook(lua.state(), HookKind::Global)
}
}
/// Sets a hook function that will periodically be called as Lua code executes.
///
/// When exactly the hook function is called depends on the contents of the `triggers`
@@ -565,10 +522,12 @@ impl Lua {
/// limited form of execution limits by setting [`HookTriggers.every_nth_instruction`] and
/// erroring once an instruction limit has been reached.
///
/// This method sets a hook function for the *current* thread of this Lua instance.
/// This method sets a hook function for the current thread of this Lua instance.
/// If you want to set a hook function for another thread (coroutine), use
/// [`Thread::set_hook`] instead.
///
/// Please note you cannot have more than one hook function set at a time for this Lua instance.
///
/// # Example
///
/// Shows each line number of code being executed by the Lua interpreter.
@@ -593,28 +552,15 @@ impl Lua {
/// [`HookTriggers.every_nth_instruction`]: crate::HookTriggers::every_nth_instruction
#[cfg(not(feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
pub fn set_hook<F>(&self, triggers: HookTriggers, callback: F) -> Result<()>
pub fn set_hook<F>(&self, triggers: HookTriggers, callback: F)
where
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))) }
unsafe { lua.set_thread_hook(lua.state(), triggers, callback) };
}
/// Removes a global hook previously set by [`Lua::set_global_hook`].
///
/// This function has no effect if a hook was not previously set.
#[cfg(not(feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
pub fn remove_global_hook(&self) {
let lua = self.lock();
unsafe {
(*lua.extra.get()).hook_callback = None;
(*lua.extra.get()).hook_triggers = HookTriggers::default();
}
}
/// Removes any hook from the current thread.
/// Removes any hook previously set by [`Lua::set_hook`] or [`Thread::set_hook`].
///
/// This function has no effect if a hook was not previously set.
#[cfg(not(feature = "luau"))]
@@ -622,7 +568,17 @@ impl Lua {
pub fn remove_hook(&self) {
let lua = self.lock();
unsafe {
ffi::lua_sethook(lua.state(), None, 0, 0);
let state = lua.state();
ffi::lua_sethook(state, None, 0, 0);
match lua.main_state {
Some(main_state) if state != main_state.as_ptr() => {
// If main_state is different from state, remove hook from it too
ffi::lua_sethook(main_state.as_ptr(), None, 0, 0);
}
_ => {}
};
(*lua.extra.get()).hook_callback = None;
(*lua.extra.get()).hook_thread = ptr::null_mut();
}
}
@@ -678,15 +634,17 @@ impl Lua {
where
F: Fn(&Lua) -> Result<VmState> + MaybeSend + 'static,
{
use std::rc::Rc;
unsafe extern "C-unwind" fn interrupt_proc(state: *mut ffi::lua_State, gc: c_int) {
if gc >= 0 {
// We don't support GC interrupts since they cannot survive Lua exceptions
return;
}
let result = callback_error_ext(state, ptr::null_mut(), false, move |extra, _| {
let result = callback_error_ext(state, ptr::null_mut(), move |extra, _| {
let interrupt_cb = (*extra).interrupt_callback.clone();
let interrupt_cb = mlua_expect!(interrupt_cb, "no interrupt callback set in interrupt_proc");
if XRc::strong_count(&interrupt_cb) > 2 {
if Rc::strong_count(&interrupt_cb) > 2 {
return Ok(VmState::Continue); // Don't allow recursion
}
interrupt_cb((*extra).lua())
@@ -702,7 +660,7 @@ impl Lua {
// Set interrupt callback
let lua = self.lock();
unsafe {
(*lua.extra.get()).interrupt_callback = Some(XRc::new(callback));
(*lua.extra.get()).interrupt_callback = Some(Rc::new(callback));
(*ffi::lua_callbacks(lua.main_state())).interrupt = Some(interrupt_proc);
}
}
@@ -720,95 +678,9 @@ impl Lua {
}
}
/// Sets a thread creation callback that will be called when a thread is created.
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn set_thread_creation_callback<F>(&self, callback: F)
where
F: Fn(&Lua, Thread) -> Result<()> + MaybeSend + 'static,
{
let lua = self.lock();
unsafe {
(*lua.extra.get()).thread_creation_callback = Some(XRc::new(callback));
(*ffi::lua_callbacks(lua.main_state())).userthread = Some(Self::userthread_proc);
}
}
/// Sets a thread collection callback that will be called when a thread is destroyed.
///
/// Luau GC does not support exceptions during collection, so the callback must be
/// non-panicking. If the callback panics, the program will be aborted.
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn set_thread_collection_callback<F>(&self, callback: F)
where
F: Fn(crate::LightUserData) + MaybeSend + 'static,
{
let lua = self.lock();
unsafe {
(*lua.extra.get()).thread_collection_callback = Some(XRc::new(callback));
(*ffi::lua_callbacks(lua.main_state())).userthread = Some(Self::userthread_proc);
}
}
#[cfg(feature = "luau")]
unsafe extern "C-unwind" fn userthread_proc(parent: *mut ffi::lua_State, child: *mut ffi::lua_State) {
let extra = ExtraData::get(child);
if !parent.is_null() {
// Thread is created
let callback = match (*extra).thread_creation_callback {
Some(ref cb) => cb.clone(),
None => return,
};
if XRc::strong_count(&callback) > 2 {
return; // Don't allow recursion
}
ffi::lua_pushthread(child);
ffi::lua_xmove(child, (*extra).ref_thread, 1);
let value = Thread((*extra).raw_lua().pop_ref_thread(), child);
callback_error_ext(parent, extra, false, move |extra, _| {
callback((*extra).lua(), value)
})
} else {
// Thread is about to be collected
let callback = match (*extra).thread_collection_callback {
Some(ref cb) => cb.clone(),
None => return,
};
// We need to wrap the callback call in non-unwind function as it's not safe to unwind when
// Luau GC is running.
// This will trigger `abort()` if the callback panics.
unsafe extern "C" fn run_callback(
callback: *const crate::types::ThreadCollectionCallback,
value: *mut ffi::lua_State,
) {
(*callback)(crate::LightUserData(value as _));
}
(*extra).running_gc = true;
run_callback(&callback, child);
(*extra).running_gc = false;
}
}
/// Removes any thread creation or collection callbacks previously set by
/// [`Lua::set_thread_creation_callback`] or [`Lua::set_thread_collection_callback`].
///
/// This function has no effect if a thread callbacks were not previously set.
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn remove_thread_callbacks(&self) {
let lua = self.lock();
unsafe {
let extra = lua.extra.get();
(*extra).thread_creation_callback = None;
(*extra).thread_collection_callback = None;
(*ffi::lua_callbacks(lua.main_state())).userthread = None;
}
}
/// Sets the warning function to be used by Lua to emit warnings.
///
/// Requires `feature = "lua54"`
#[cfg(feature = "lua54")]
#[cfg_attr(docsrs, doc(cfg(feature = "lua54")))]
pub fn set_warning_function<F>(&self, callback: F)
@@ -821,7 +693,7 @@ impl Lua {
unsafe extern "C-unwind" fn warn_proc(ud: *mut c_void, msg: *const c_char, tocont: c_int) {
let extra = ud as *mut ExtraData;
callback_error_ext((*extra).raw_lua().state(), extra, false, |extra, _| {
callback_error_ext((*extra).raw_lua().state(), extra, |extra, _| {
let warn_callback = (*extra).warn_callback.clone();
let warn_callback = mlua_expect!(warn_callback, "no warning callback set in warn_proc");
if XRc::strong_count(&warn_callback) > 2 {
@@ -842,6 +714,8 @@ impl Lua {
/// Removes warning function previously set by `set_warning_function`.
///
/// This function has no effect if a warning function was not previously set.
///
/// Requires `feature = "lua54"`
#[cfg(feature = "lua54")]
#[cfg_attr(docsrs, doc(cfg(feature = "lua54")))]
pub fn remove_warning_function(&self) {
@@ -856,6 +730,8 @@ impl Lua {
///
/// A message in a call with `incomplete` set to `true` should be continued in
/// another call to this function.
///
/// Requires `feature = "lua54"`
#[cfg(feature = "lua54")]
#[cfg_attr(docsrs, doc(cfg(feature = "lua54")))]
pub fn warning(&self, msg: impl AsRef<str>, incomplete: bool) {
@@ -930,6 +806,8 @@ impl Lua {
}
/// Returns `true` if the garbage collector is currently running automatically.
///
/// Requires `feature = "lua54/lua53/lua52/luau"`
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))]
pub fn gc_is_running(&self) -> bool {
let lua = self.lock();
@@ -1067,6 +945,8 @@ impl Lua {
/// Returns the previous mode. More information about the generational GC
/// can be found in the Lua 5.4 [documentation][lua_doc].
///
/// Requires `feature = "lua54"`
///
/// [lua_doc]: https://www.lua.org/manual/5.4/manual.html#2.5.2
#[cfg(feature = "lua54")]
#[cfg_attr(docsrs, doc(cfg(feature = "lua54")))]
@@ -1087,6 +967,8 @@ impl Lua {
/// including via `require` function.
///
/// See [`Compiler`] for details and possible options.
///
/// Requires `feature = "luau"`
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn set_compiler(&self, compiler: Compiler) {
@@ -1128,13 +1010,13 @@ impl Lua {
///
/// [`Chunk::exec`]: crate::Chunk::exec
#[track_caller]
pub fn load<'a>(&self, chunk: impl AsChunk + 'a) -> Chunk<'a> {
pub fn load<'a>(&self, chunk: impl AsChunk<'a>) -> Chunk<'a> {
self.load_with_location(chunk, Location::caller())
}
pub(crate) fn load_with_location<'a>(
&self,
chunk: impl AsChunk + 'a,
chunk: impl AsChunk<'a>,
location: &'static Location<'static>,
) -> Chunk<'a> {
Chunk {
@@ -1161,6 +1043,8 @@ impl Lua {
/// Create and return a Luau [buffer] object from a byte slice of data.
///
/// Requires `feature = "luau"`
///
/// [buffer]: https://luau.org/library#buffer-library
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
@@ -1325,6 +1209,8 @@ impl Lua {
///
/// The family of `call_async()` functions takes care about creating [`Thread`].
///
/// Requires `feature = "async"`
///
/// # Examples
///
/// Non blocking sleep:
@@ -1390,6 +1276,8 @@ impl Lua {
}
/// Creates a Lua userdata object from a custom serializable userdata type.
///
/// Requires `feature = "serialize"`
#[cfg(feature = "serialize")]
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
#[inline]
@@ -1418,6 +1306,8 @@ impl Lua {
/// Creates a Lua userdata object from a custom serializable Rust type.
///
/// See [`Lua::create_any_userdata`] for more details.
///
/// Requires `feature = "serialize"`
#[cfg(feature = "serialize")]
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
#[inline]
@@ -1542,7 +1432,7 @@ impl Lua {
Err(_) => return,
},
ffi::LUA_TTHREAD => {
ffi::lua_pushthread(state);
ffi::lua_newthread(state);
}
#[cfg(feature = "luau")]
ffi::LUA_TBUFFER => {
@@ -1600,9 +1490,10 @@ impl Lua {
/// lifetimes only outlive the scope lifetime.
pub fn scope<'env, R>(
&self,
f: impl for<'scope> FnOnce(&'scope Scope<'scope, 'env>) -> Result<R>,
f: impl for<'scope> FnOnce(&'scope mut Scope<'scope, 'env>) -> Result<R>,
) -> Result<R> {
f(&Scope::new(self.lock_arc()))
// TODO: Update to `&Scope` in next major release
f(&mut Scope::new(self.lock_arc()))
}
/// Attempts to coerce a Lua value into a String in a manner consistent with Lua's internal
@@ -2077,8 +1968,8 @@ impl Lua {
pub(crate) fn lock(&self) -> ReentrantMutexGuard<RawLua> {
let rawlua = self.raw.lock();
#[cfg(feature = "luau")]
if unsafe { (*rawlua.extra.get()).running_gc } {
panic!("Luau VM is suspended while GC is running");
if unsafe { (*rawlua.extra.get()).running_userdata_gc } {
panic!("Luau VM is suspended while userdata destructor is running");
}
rawlua
}
@@ -2104,8 +1995,8 @@ impl WeakLua {
pub(crate) fn lock(&self) -> LuaGuard {
let guard = LuaGuard::new(self.0.upgrade().expect("Lua instance is destroyed"));
#[cfg(feature = "luau")]
if unsafe { (*guard.extra.get()).running_gc } {
panic!("Luau VM is suspended while GC is running");
if unsafe { (*guard.extra.get()).running_userdata_gc } {
panic!("Luau VM is suspended while userdata destructor is running");
}
guard
}
+8 -13
View File
@@ -75,18 +75,14 @@ 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_thread: *mut ffi::lua_State,
#[cfg(feature = "lua54")]
pub(super) warn_callback: Option<crate::types::WarnCallback>,
#[cfg(feature = "luau")]
pub(super) interrupt_callback: Option<crate::types::InterruptCallback>,
#[cfg(feature = "luau")]
pub(super) thread_creation_callback: Option<crate::types::ThreadCreationCallback>,
#[cfg(feature = "luau")]
pub(super) thread_collection_callback: Option<crate::types::ThreadCollectionCallback>,
#[cfg(feature = "luau")]
pub(crate) running_gc: bool,
pub(crate) running_userdata_gc: bool,
#[cfg(feature = "luau")]
pub(super) sandboxed: bool,
#[cfg(feature = "luau")]
@@ -177,23 +173,19 @@ impl ExtraData {
#[cfg(not(feature = "luau"))]
hook_callback: None,
#[cfg(not(feature = "luau"))]
hook_triggers: Default::default(),
hook_thread: ptr::null_mut(),
#[cfg(feature = "lua54")]
warn_callback: None,
#[cfg(feature = "luau")]
interrupt_callback: None,
#[cfg(feature = "luau")]
thread_creation_callback: None,
#[cfg(feature = "luau")]
thread_collection_callback: None,
#[cfg(feature = "luau")]
sandboxed: false,
#[cfg(feature = "luau")]
compiler: None,
#[cfg(feature = "luau-jit")]
enable_jit: true,
#[cfg(feature = "luau")]
running_gc: false,
running_userdata_gc: false,
}));
// Store it in the registry
@@ -207,6 +199,9 @@ impl ExtraData {
raw: XRc::clone(raw),
collect_garbage: false,
});
if self.owned {
XRc::decrement_strong_count(XRc::as_ptr(raw));
}
self.weak.write(WeakLua(XRc::downgrade(raw)));
}
@@ -249,7 +244,7 @@ impl ExtraData {
}
#[inline(always)]
pub(crate) unsafe fn raw_lua(&self) -> &RawLua {
pub(super) unsafe fn raw_lua(&self) -> &RawLua {
&*self.lua.assume_init_ref().raw.data_ptr()
}
+47 -133
View File
@@ -38,10 +38,7 @@ use super::extra::ExtraData;
use super::{Lua, LuaOptions, WeakLua};
#[cfg(not(feature = "luau"))]
use crate::{
hook::Debug,
types::{HookCallback, HookKind, VmState},
};
use crate::hook::{Debug, HookTriggers};
#[cfg(feature = "async")]
use {
@@ -58,25 +55,17 @@ pub struct RawLua {
pub(super) state: Cell<*mut ffi::lua_State>,
pub(super) main_state: Option<NonNull<ffi::lua_State>>,
pub(super) extra: XRc<UnsafeCell<ExtraData>>,
owned: bool,
}
impl Drop for RawLua {
fn drop(&mut self) {
unsafe {
if !self.owned {
if !(*self.extra.get()).owned {
return;
}
let mem_state = MemoryState::get(self.main_state());
#[cfg(feature = "luau")]
{
// Reset any callbacks
(*ffi::lua_callbacks(self.main_state())).interrupt = None;
(*ffi::lua_callbacks(self.main_state())).userthread = None;
}
ffi::lua_close(self.main_state());
// Deallocate `MemoryState`
@@ -121,7 +110,7 @@ impl RawLua {
unsafe { (*self.extra.get()).ref_thread }
}
pub(super) unsafe fn new(libs: StdLib, options: &LuaOptions) -> XRc<ReentrantMutex<Self>> {
pub(super) unsafe fn new(libs: StdLib, options: LuaOptions) -> XRc<ReentrantMutex<Self>> {
let mem_state: *mut MemoryState = Box::into_raw(Box::default());
let mut state = ffi::lua_newstate(ALLOCATOR, mem_state as *mut c_void);
// If state is null then switch to Lua internal allocator
@@ -198,8 +187,6 @@ impl RawLua {
init_internal_metatable::<XRc<UnsafeCell<ExtraData>>>(state, None)?;
init_internal_metatable::<Callback>(state, None)?;
init_internal_metatable::<CallbackUpvalue>(state, None)?;
#[cfg(not(feature = "luau"))]
init_internal_metatable::<HookCallback>(state, None)?;
#[cfg(feature = "async")]
{
init_internal_metatable::<AsyncCallback>(state, None)?;
@@ -241,17 +228,8 @@ impl RawLua {
// Make sure that we don't store current state as main state (if it's not available)
main_state: get_main_state(state).and_then(NonNull::new),
extra: XRc::clone(&extra),
owned,
}));
(*extra.get()).set_lua(&rawlua);
if owned {
// If Lua state is managed by us, then make internal `RawLua` reference "weak"
XRc::decrement_strong_count(XRc::as_ptr(&rawlua));
} else {
// If Lua state is not managed by us, then keep internal `RawLua` reference "strong"
// but `Extra` reference weak (it will be collected from registry at lua_close time)
XRc::decrement_strong_count(XRc::as_ptr(&extra));
}
rawlua
}
@@ -293,15 +271,10 @@ impl RawLua {
let res = load_std_libs(self.main_state(), libs);
// If `package` library loaded into a safe lua state then disable C modules
#[cfg(not(feature = "luau"))]
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");
}
let curr_libs = (*self.extra.get()).libs;
if is_safe && (curr_libs ^ (curr_libs | libs)).contains(StdLib::PACKAGE) {
mlua_expect!(self.lua().disable_c_modules(), "Error during disabling C modules");
}
#[cfg(feature = "luau")]
let _ = is_safe;
unsafe { (*self.extra.get()).libs |= libs };
res
@@ -401,22 +374,41 @@ impl RawLua {
status
}
/// Sets a hook for a thread (coroutine).
/// Sets a 'hook' function for a thread (coroutine).
#[cfg(not(feature = "luau"))]
pub(crate) unsafe fn set_thread_hook(
pub(crate) unsafe fn set_thread_hook<F>(
&self,
thread_state: *mut ffi::lua_State,
hook: HookKind,
) -> Result<()> {
// Key to store hooks in the registry
const HOOKS_KEY: *const c_char = cstr!("__mlua_hooks");
state: *mut ffi::lua_State,
triggers: HookTriggers,
callback: F,
) where
F: Fn(&Lua, Debug) -> Result<crate::VmState> + MaybeSend + 'static,
{
use crate::types::VmState;
use std::rc::Rc;
unsafe fn process_status(state: *mut ffi::lua_State, event: c_int, status: VmState) {
match status {
unsafe extern "C-unwind" fn hook_proc(state: *mut ffi::lua_State, ar: *mut ffi::lua_Debug) {
let extra = ExtraData::get(state);
if (*extra).hook_thread != state {
// Hook was destined for a different thread, ignore
ffi::lua_sethook(state, None, 0, 0);
return;
}
let result = callback_error_ext(state, extra, move |extra, _| {
let hook_cb = (*extra).hook_callback.clone();
let hook_cb = mlua_expect!(hook_cb, "no hook callback set in hook_proc");
if Rc::strong_count(&hook_cb) > 2 {
return Ok(VmState::Continue); // Don't allow recursion
}
let rawlua = (*extra).raw_lua();
let debug = Debug::new(rawlua, ar);
hook_cb((*extra).lua(), debug)
});
match result {
VmState::Continue => {}
VmState::Yield => {
// Only count and line events can yield
if event == ffi::LUA_HOOKCOUNT || event == ffi::LUA_HOOKLINE {
if (*ar).event == ffi::LUA_HOOKCOUNT || (*ar).event == ffi::LUA_HOOKLINE {
#[cfg(any(feature = "lua54", feature = "lua53"))]
if ffi::lua_isyieldable(state) != 0 {
ffi::lua_yield(state, 0);
@@ -431,83 +423,9 @@ impl RawLua {
}
}
unsafe extern "C-unwind" fn global_hook_proc(state: *mut ffi::lua_State, ar: *mut ffi::lua_Debug) {
let status = callback_error_ext(state, ptr::null_mut(), false, move |extra, _| {
match (*extra).hook_callback.clone() {
Some(hook_callback) => {
let rawlua = (*extra).raw_lua();
let debug = Debug::new(rawlua, ar);
hook_callback((*extra).lua(), debug)
}
None => {
ffi::lua_sethook(state, None, 0, 0);
Ok(VmState::Continue)
}
}
});
process_status(state, (*ar).event, status);
}
unsafe extern "C-unwind" fn hook_proc(state: *mut ffi::lua_State, ar: *mut ffi::lua_Debug) {
let top = ffi::lua_gettop(state);
let mut hook_callback_ptr = ptr::null();
ffi::luaL_checkstack(state, 3, ptr::null());
if ffi::lua_getfield(state, ffi::LUA_REGISTRYINDEX, HOOKS_KEY) == ffi::LUA_TTABLE {
ffi::lua_pushthread(state);
if ffi::lua_rawget(state, -2) == ffi::LUA_TUSERDATA {
hook_callback_ptr = get_internal_userdata::<HookCallback>(state, -1, ptr::null());
}
}
ffi::lua_settop(state, top);
if hook_callback_ptr.is_null() {
ffi::lua_sethook(state, None, 0, 0);
return;
}
let status = callback_error_ext(state, ptr::null_mut(), false, |extra, _| {
let rawlua = (*extra).raw_lua();
let debug = Debug::new(rawlua, ar);
let hook_callback = (*hook_callback_ptr).clone();
hook_callback((*extra).lua(), debug)
});
process_status(state, (*ar).event, status)
}
let (triggers, callback) = match hook {
HookKind::Global if (*self.extra.get()).hook_callback.is_none() => {
return Ok(());
}
HookKind::Global => {
let triggers = (*self.extra.get()).hook_triggers;
let (mask, count) = (triggers.mask(), triggers.count());
ffi::lua_sethook(thread_state, Some(global_hook_proc), mask, count);
return Ok(());
}
HookKind::Thread(triggers, callback) => (triggers, callback),
};
// Hooks for threads stored in the registry (in a weak table)
let state = self.state();
let _sg = StackGuard::new(state);
check_stack(state, 3)?;
protect_lua!(state, 0, 0, |state| {
if ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, HOOKS_KEY) == 0 {
// Table just created, initialize it
ffi::lua_pushliteral(state, c"k");
ffi::lua_setfield(state, -2, cstr!("__mode")); // hooktable.__mode = "k"
ffi::lua_pushvalue(state, -1);
ffi::lua_setmetatable(state, -2); // metatable(hooktable) = hooktable
}
ffi::lua_pushthread(thread_state);
ffi::lua_xmove(thread_state, state, 1); // key (thread)
let _ = push_internal_userdata(state, callback, false); // value (hook callback)
ffi::lua_rawset(state, -3); // hooktable[thread] = hook callback
})?;
ffi::lua_sethook(thread_state, Some(hook_proc), triggers.mask(), triggers.count());
Ok(())
(*self.extra.get()).hook_callback = Some(Rc::new(callback));
(*self.extra.get()).hook_thread = state; // Mark for what thread the hook is set
ffi::lua_sethook(state, Some(hook_proc), triggers.mask(), triggers.count());
}
/// See [`Lua::create_string`]
@@ -574,20 +492,11 @@ impl RawLua {
let _sg = StackGuard::new(state);
check_stack(state, 3)?;
let protect = !self.unlikely_memory_error();
#[cfg(feature = "luau")]
let protect = protect || (*self.extra.get()).thread_creation_callback.is_some();
let thread_state = if !protect {
let thread_state = if self.unlikely_memory_error() {
ffi::lua_newthread(state)
} else {
protect_lua!(state, 0, 1, |state| ffi::lua_newthread(state))?
};
// Inherit global hook if set
#[cfg(not(feature = "luau"))]
self.set_thread_hook(thread_state, HookKind::Global)?;
let thread = Thread(self.pop_ref(), thread_state);
ffi::lua_xpush(self.ref_thread(), thread_state, func.0.index);
Ok(thread)
@@ -1191,7 +1100,7 @@ impl RawLua {
pub(crate) fn create_callback(&self, func: Callback) -> Result<Function> {
unsafe extern "C-unwind" fn call_callback(state: *mut ffi::lua_State) -> c_int {
let upvalue = get_userdata::<CallbackUpvalue>(state, ffi::lua_upvalueindex(1));
callback_error_ext(state, (*upvalue).extra.get(), true, |extra, nargs| {
callback_error_ext(state, (*upvalue).extra.get(), |extra, nargs| {
// Lua ensures that `LUA_MINSTACK` stack spaces are available (after pushing arguments)
// The lock must be already held as the callback is executed
let rawlua = (*extra).raw_lua();
@@ -1238,7 +1147,7 @@ impl RawLua {
// Async functions cannot be scoped and therefore destroyed,
// so the first upvalue is always valid
let upvalue = get_userdata::<AsyncCallbackUpvalue>(state, ffi::lua_upvalueindex(1));
callback_error_ext(state, (*upvalue).extra.get(), true, |extra, nargs| {
callback_error_ext(state, (*upvalue).extra.get(), |extra, nargs| {
// Lua ensures that `LUA_MINSTACK` stack spaces are available (after pushing arguments)
// The lock must be already held as the callback is executed
let rawlua = (*extra).raw_lua();
@@ -1262,7 +1171,7 @@ impl RawLua {
unsafe extern "C-unwind" fn poll_future(state: *mut ffi::lua_State) -> c_int {
let upvalue = get_userdata::<AsyncPollUpvalue>(state, ffi::lua_upvalueindex(1));
callback_error_ext(state, (*upvalue).extra.get(), true, |extra, _| {
callback_error_ext(state, (*upvalue).extra.get(), |extra, _| {
// Lua ensures that `LUA_MINSTACK` stack spaces are available (after pushing arguments)
// The lock must be already held as the future is polled
let rawlua = (*extra).raw_lua();
@@ -1477,6 +1386,11 @@ unsafe fn load_std_libs(state: *mut ffi::lua_State, libs: StdLib) -> Result<()>
if libs.contains(StdLib::PACKAGE) {
requiref(state, ffi::LUA_LOADLIBNAME, ffi::luaopen_package, 1)?;
}
#[cfg(feature = "luau")]
if libs.contains(StdLib::PACKAGE) {
let lua = (*ExtraData::get(state)).lua();
crate::luau::register_package_module(lua)?;
}
#[cfg(feature = "luajit")]
if libs.contains(StdLib::JIT) {
+1 -9
View File
@@ -24,10 +24,9 @@ impl Drop for StateGuard<'_> {
// An optimized version of `callback_error` that does not allocate `WrappedFailure` userdata
// and instead reuses unused values from previous calls (or allocates new).
pub(crate) unsafe fn callback_error_ext<F, R>(
pub(super) unsafe fn callback_error_ext<F, R>(
state: *mut ffi::lua_State,
mut extra: *mut ExtraData,
wrap_error: bool,
f: F,
) -> R
where
@@ -115,13 +114,6 @@ where
Ok(Err(err)) => {
let wrapped_error = prealloc_failure.r#use(state, extra);
if !wrap_error {
ptr::write(wrapped_error, WrappedFailure::Error(err));
get_internal_metatable::<WrappedFailure>(state);
ffi::lua_setmetatable(state, -2);
ffi::lua_error(state)
}
// Build `CallbackError` with traceback
let traceback = if ffi::lua_checkstack(state, ffi::LUA_TRACEBACK_STACK) != 0 {
ffi::luaL_traceback(state, state, ptr::null(), 0);
+10 -11
View File
@@ -6,11 +6,9 @@ pub struct StdLib(u32);
impl StdLib {
/// [`coroutine`](https://www.lua.org/manual/5.4/manual.html#6.2) library
///
/// Requires `feature = "lua54/lua53/lua52/luau"`
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau")))
)]
pub const COROUTINE: StdLib = StdLib(1);
/// [`table`](https://www.lua.org/manual/5.4/manual.html#6.6) library
@@ -28,24 +26,21 @@ impl StdLib {
pub const STRING: StdLib = StdLib(1 << 4);
/// [`utf8`](https://www.lua.org/manual/5.4/manual.html#6.5) library
///
/// Requires `feature = "lua54/lua53/luau"`
#[cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))))]
pub const UTF8: StdLib = StdLib(1 << 5);
/// [`bit`](https://www.lua.org/manual/5.2/manual.html#6.7) library
///
/// Requires `feature = "lua52/luajit/luau"`
#[cfg(any(feature = "lua52", feature = "luajit", feature = "luau", doc))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "lua52", feature = "luajit", feature = "luau")))
)]
pub const BIT: StdLib = StdLib(1 << 6);
/// [`math`](https://www.lua.org/manual/5.4/manual.html#6.7) library
pub const MATH: StdLib = StdLib(1 << 7);
/// [`package`](https://www.lua.org/manual/5.4/manual.html#6.3) library
#[cfg(not(feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
pub const PACKAGE: StdLib = StdLib(1 << 8);
/// [`buffer`](https://luau.org/library#buffer-library) library
@@ -59,11 +54,15 @@ impl StdLib {
pub const VECTOR: StdLib = StdLib(1 << 10);
/// [`jit`](http://luajit.org/ext_jit.html) library
///
/// Requires `feature = "luajit"`
#[cfg(any(feature = "luajit", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luajit")))]
pub const JIT: StdLib = StdLib(1 << 11);
/// (**unsafe**) [`ffi`](http://luajit.org/ext_ffi.html) library
///
/// Requires `feature = "luajit"`
#[cfg(any(feature = "luajit", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luajit")))]
pub const FFI: StdLib = StdLib(1 << 30);
+13
View File
@@ -501,6 +501,13 @@ impl Table {
}
}
#[doc(hidden)]
#[deprecated(since = "0.10.0", note = "please use `metatable` instead")]
#[cfg(not(tarpaulin_include))]
pub fn get_metatable(&self) -> Option<Table> {
self.metatable()
}
/// Sets or removes the metatable of this table.
///
/// If `metatable` is `None`, the metatable is removed (if no metatable is set, this does
@@ -537,6 +544,8 @@ impl Table {
}
/// Sets `readonly` attribute on the table.
///
/// Requires `feature = "luau"`
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn set_readonly(&self, enabled: bool) {
@@ -552,6 +561,8 @@ impl Table {
}
/// Returns `readonly` attribute of the table.
///
/// Requires `feature = "luau"`
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn is_readonly(&self) -> bool {
@@ -569,6 +580,8 @@ impl Table {
/// - Fast-path for some built-in functions (fastcall).
///
/// For `safeenv` environments, monkey patching or modifying values may not work as expected.
///
/// Requires `feature = "luau"`
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn set_safeenv(&self, enabled: bool) {
+87 -151
View File
@@ -11,7 +11,7 @@ use crate::util::{check_stack, error_traceback_thread, pop_error, StackGuard};
#[cfg(not(feature = "luau"))]
use crate::{
hook::{Debug, HookTriggers},
types::HookKind,
types::MaybeSend,
};
#[cfg(feature = "async")]
@@ -47,24 +47,17 @@ pub enum ThreadStatus {
/// to the thread stack.
#[derive(Clone, Copy)]
enum ThreadStatusInner {
New(c_int),
New,
Running,
Yielded(c_int),
Yielded,
Finished,
Error,
}
impl ThreadStatusInner {
#[cfg(feature = "async")]
#[inline(always)]
fn is_resumable(self) -> bool {
matches!(self, ThreadStatusInner::New(_) | ThreadStatusInner::Yielded(_))
}
#[cfg(feature = "async")]
#[inline(always)]
fn is_yielded(self) -> bool {
matches!(self, ThreadStatusInner::Yielded(_))
matches!(self, ThreadStatusInner::New | ThreadStatusInner::Yielded)
}
}
@@ -79,13 +72,16 @@ unsafe impl Sync for Thread {}
/// Thread (coroutine) representation as an async [`Future`] or [`Stream`].
///
/// Requires `feature = "async"`
///
/// [`Future`]: std::future::Future
/// [`Stream`]: futures_util::stream::Stream
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct AsyncThread<R> {
pub struct AsyncThread<A, R> {
thread: Thread,
init_args: Option<A>,
ret: PhantomData<R>,
recycle: bool,
}
@@ -145,46 +141,9 @@ impl Thread {
R: FromLuaMulti,
{
let lua = self.0.lua.lock();
let mut pushed_nargs = match self.status_inner(&lua) {
ThreadStatusInner::New(nargs) | ThreadStatusInner::Yielded(nargs) => nargs,
_ => return Err(Error::CoroutineUnresumable),
};
let state = lua.state();
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 {
check_stack(thread_state, nargs)?;
ffi::lua_xmove(state, thread_state, nargs);
pushed_nargs += nargs;
}
let (_, nresults) = self.resume_inner(&lua, pushed_nargs)?;
check_stack(state, nresults + 1)?;
ffi::lua_xmove(thread_state, state, nresults);
R::from_stack_multi(nresults, &lua)
if !self.status_inner(&lua).is_resumable() {
return Err(Error::CoroutineUnresumable);
}
}
/// Resumes execution of this thread, immediately raising an error.
///
/// This is a Luau specific extension.
#[cfg(feature = "luau")]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn resume_error<R>(&self, error: impl crate::IntoLua) -> Result<R>
where
R: FromLuaMulti,
{
let lua = self.0.lua.lock();
match self.status_inner(&lua) {
ThreadStatusInner::New(_) | ThreadStatusInner::Yielded(_) => {}
_ => return Err(Error::CoroutineUnresumable),
};
let state = lua.state();
let thread_state = self.state();
@@ -192,11 +151,7 @@ impl Thread {
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 (_, nresults) = self.resume_inner(&lua, ffi::LUA_RESUMEERROR)?;
let nresults = self.resume_inner(&lua, args)?;
check_stack(state, nresults + 1)?;
ffi::lua_xmove(thread_state, state, nresults);
@@ -207,33 +162,35 @@ impl Thread {
/// Resumes execution of this thread.
///
/// It's similar to `resume()` but leaves `nresults` values on the thread stack.
unsafe fn resume_inner(&self, lua: &RawLua, nargs: c_int) -> Result<(ThreadStatusInner, c_int)> {
unsafe fn resume_inner(&self, lua: &RawLua, args: impl IntoLuaMulti) -> Result<c_int> {
let state = lua.state();
let thread_state = self.state();
let mut nresults = 0;
#[cfg(not(feature = "luau"))]
let ret = ffi::lua_resume(thread_state, state, nargs, &mut nresults as *mut c_int);
#[cfg(feature = "luau")]
let ret = ffi::lua_resumex(thread_state, state, nargs, &mut nresults as *mut c_int);
match ret {
ffi::LUA_OK => Ok((ThreadStatusInner::Finished, nresults)),
ffi::LUA_YIELD => Ok((ThreadStatusInner::Yielded(0), nresults)),
ffi::LUA_ERRMEM => {
// Don't call error handler for memory errors
Err(pop_error(thread_state, ret))
}
_ => {
check_stack(state, 3)?;
protect_lua!(state, 0, 1, |state| error_traceback_thread(state, thread_state))?;
Err(pop_error(state, ret))
}
let nargs = args.push_into_stack_multi(lua)?;
if nargs > 0 {
check_stack(thread_state, nargs)?;
ffi::lua_xmove(state, thread_state, nargs);
}
let mut nresults = 0;
let ret = ffi::lua_resume(thread_state, state, nargs, &mut nresults as *mut c_int);
if ret != ffi::LUA_OK && ret != ffi::LUA_YIELD {
if ret == ffi::LUA_ERRMEM {
// Don't call error handler for memory errors
return Err(pop_error(thread_state, ret));
}
check_stack(state, 3)?;
protect_lua!(state, 0, 1, |state| error_traceback_thread(state, thread_state))?;
return Err(pop_error(state, ret));
}
Ok(nresults)
}
/// Gets the status of the thread.
pub fn status(&self) -> ThreadStatus {
match self.status_inner(&self.0.lua.lock()) {
ThreadStatusInner::New(_) | ThreadStatusInner::Yielded(_) => ThreadStatus::Resumable,
ThreadStatusInner::New | ThreadStatusInner::Yielded => ThreadStatus::Resumable,
ThreadStatusInner::Running => ThreadStatus::Running,
ThreadStatusInner::Finished => ThreadStatus::Finished,
ThreadStatusInner::Error => ThreadStatus::Error,
@@ -248,10 +205,9 @@ impl Thread {
return ThreadStatusInner::Running;
}
let status = unsafe { ffi::lua_status(thread_state) };
let top = unsafe { ffi::lua_gettop(thread_state) };
match status {
ffi::LUA_YIELD => ThreadStatusInner::Yielded(top),
ffi::LUA_OK if top > 0 => ThreadStatusInner::New(top - 1),
ffi::LUA_YIELD => ThreadStatusInner::Yielded,
ffi::LUA_OK if unsafe { ffi::lua_gettop(thread_state) } > 0 => ThreadStatusInner::New,
ffi::LUA_OK => ThreadStatusInner::Finished,
_ => ThreadStatusInner::Error,
}
@@ -260,31 +216,16 @@ impl Thread {
/// Sets a hook function that will periodically be called as Lua code executes.
///
/// This function is similar or [`Lua::set_hook`] except that it sets for the thread.
/// You can have multiple hooks for different threads.
///
/// To remove a hook call [`Thread::remove_hook`].
/// To remove a hook call [`Lua::remove_hook`].
#[cfg(not(feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
pub fn set_hook<F>(&self, triggers: HookTriggers, callback: F) -> Result<()>
pub fn set_hook<F>(&self, triggers: HookTriggers, callback: F)
where
F: Fn(&crate::Lua, Debug) -> Result<crate::VmState> + crate::MaybeSend + 'static,
F: Fn(&crate::Lua, Debug) -> Result<crate::VmState> + MaybeSend + 'static,
{
let lua = self.0.lua.lock();
unsafe {
lua.set_thread_hook(
self.state(),
HookKind::Thread(triggers, crate::types::XRc::new(callback)),
)
}
}
/// Removes any hook function from this thread.
#[cfg(not(feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
pub fn remove_hook(&self) {
let _lua = self.0.lua.lock();
unsafe {
ffi::lua_sethook(self.state(), None, 0, 0);
lua.set_thread_hook(self.state(), triggers, callback);
}
}
@@ -308,7 +249,7 @@ impl Thread {
match self.status_inner(&lua) {
ThreadStatusInner::Running => return Err(Error::runtime("cannot reset a running thread")),
// Any Lua can reuse new or finished thread
ThreadStatusInner::New(_) => unsafe { ffi::lua_settop(thread_state, 0) },
ThreadStatusInner::New => unsafe { ffi::lua_settop(thread_state, 0) },
ThreadStatusInner::Finished => {}
#[cfg(not(any(feature = "lua54", feature = "luau")))]
_ => return Err(Error::runtime("cannot reset non-finished thread")),
@@ -344,9 +285,7 @@ impl Thread {
/// Converts [`Thread`] to an [`AsyncThread`] which implements [`Future`] and [`Stream`] traits.
///
/// Only resumable threads can be converted to [`AsyncThread`].
///
/// `args` are pushed to the thread stack and will be used when the thread is resumed.
/// `args` are passed as arguments to the thread function for first call.
/// The object calls [`resume`] while polling and also allow to run Rust futures
/// to completion using an executor.
///
@@ -354,6 +293,8 @@ impl Thread {
/// values whereas [`Future`] version discards that values and poll until the final
/// one (returned from the thread function).
///
/// Requires `feature = "async"`
///
/// [`Future`]: std::future::Future
/// [`Stream`]: futures_util::stream::Stream
/// [`resume`]: https://www.lua.org/manual/5.4/manual.html#lua_resume
@@ -377,7 +318,7 @@ impl Thread {
/// end)
/// "#).eval()?;
///
/// let mut stream = thread.into_async::<i64>(1)?;
/// let mut stream = thread.into_async::<i64>(1);
/// let mut sum = 0;
/// while let Some(n) = stream.try_next().await? {
/// sum += n;
@@ -390,31 +331,15 @@ impl Thread {
/// ```
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub fn into_async<R>(self, args: impl IntoLuaMulti) -> Result<AsyncThread<R>>
pub fn into_async<R>(self, args: impl IntoLuaMulti) -> AsyncThread<impl IntoLuaMulti, R>
where
R: FromLuaMulti,
{
let lua = self.0.lua.lock();
if !self.status_inner(&lua).is_resumable() {
return Err(Error::CoroutineUnresumable);
}
let state = lua.state();
let thread_state = self.state();
unsafe {
let _sg = StackGuard::new(state);
let nargs = args.push_into_stack_multi(&lua)?;
if nargs > 0 {
check_stack(thread_state, nargs)?;
ffi::lua_xmove(state, thread_state, nargs);
}
Ok(AsyncThread {
thread: self,
ret: PhantomData,
recycle: false,
})
AsyncThread {
thread: self,
init_args: Some(args),
ret: PhantomData,
recycle: false,
}
}
@@ -451,6 +376,8 @@ impl Thread {
/// # #[cfg(not(feature = "luau"))]
/// # fn main() { }
/// ```
///
/// Requires `feature = "luau"`
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
#[doc(hidden)]
@@ -493,7 +420,7 @@ impl LuaType for Thread {
}
#[cfg(feature = "async")]
impl<R> AsyncThread<R> {
impl<A, R> AsyncThread<A, R> {
#[inline(always)]
pub(crate) fn set_recyclable(&mut self, recyclable: bool) {
self.recycle = recyclable;
@@ -501,7 +428,7 @@ impl<R> AsyncThread<R> {
}
#[cfg(feature = "async")]
impl<R> Drop for AsyncThread<R> {
impl<A, R> Drop for AsyncThread<A, R> {
fn drop(&mut self) {
if self.recycle {
if let Some(lua) = self.thread.0.lua.try_lock() {
@@ -513,15 +440,14 @@ impl<R> Drop for AsyncThread<R> {
}
#[cfg(feature = "async")]
impl<R: FromLuaMulti> Stream for AsyncThread<R> {
impl<A: IntoLuaMulti, R: FromLuaMulti> Stream for AsyncThread<A, R> {
type Item = Result<R>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let lua = self.thread.0.lua.lock();
let nargs = match self.thread.status_inner(&lua) {
ThreadStatusInner::New(nargs) | ThreadStatusInner::Yielded(nargs) => nargs,
_ => return Poll::Ready(None),
};
if !self.thread.status_inner(&lua).is_resumable() {
return Poll::Ready(None);
}
let state = lua.state();
let thread_state = self.thread.state();
@@ -530,34 +456,36 @@ impl<R: FromLuaMulti> Stream for AsyncThread<R> {
let _thread_sg = StackGuard::with_top(thread_state, 0);
let _wg = WakerGuard::new(&lua, cx.waker());
let (status, nresults) = (self.thread).resume_inner(&lua, nargs)?;
// This is safe as we are not moving the whole struct
let this = self.get_unchecked_mut();
let nresults = if let Some(args) = this.init_args.take() {
this.thread.resume_inner(&lua, args)?
} else {
this.thread.resume_inner(&lua, ())?
};
if status.is_yielded() {
if nresults == 1 && is_poll_pending(thread_state) {
return Poll::Pending;
}
// Continue polling
cx.waker().wake_by_ref();
if nresults == 1 && is_poll_pending(thread_state) {
return Poll::Pending;
}
check_stack(state, nresults + 1)?;
ffi::lua_xmove(thread_state, state, nresults);
cx.waker().wake_by_ref();
Poll::Ready(Some(R::from_stack_multi(nresults, &lua)))
}
}
}
#[cfg(feature = "async")]
impl<R: FromLuaMulti> Future for AsyncThread<R> {
impl<A: IntoLuaMulti, R: FromLuaMulti> Future for AsyncThread<A, R> {
type Output = Result<R>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let lua = self.thread.0.lua.lock();
let nargs = match self.thread.status_inner(&lua) {
ThreadStatusInner::New(nargs) | ThreadStatusInner::Yielded(nargs) => nargs,
_ => return Poll::Ready(Err(Error::CoroutineUnresumable)),
};
if !self.thread.status_inner(&lua).is_resumable() {
return Poll::Ready(Err(Error::CoroutineUnresumable));
}
let state = lua.state();
let thread_state = self.thread.state();
@@ -566,13 +494,21 @@ impl<R: FromLuaMulti> Future for AsyncThread<R> {
let _thread_sg = StackGuard::with_top(thread_state, 0);
let _wg = WakerGuard::new(&lua, cx.waker());
let (status, nresults) = self.thread.resume_inner(&lua, nargs)?;
// This is safe as we are not moving the whole struct
let this = self.get_unchecked_mut();
let nresults = if let Some(args) = this.init_args.take() {
this.thread.resume_inner(&lua, args)?
} else {
this.thread.resume_inner(&lua, ())?
};
if status.is_yielded() {
if !(nresults == 1 && is_poll_pending(thread_state)) {
// Ignore value returned via yield()
cx.waker().wake_by_ref();
}
if nresults == 1 && is_poll_pending(thread_state) {
return Poll::Pending;
}
if ffi::lua_status(thread_state) == ffi::LUA_YIELD {
// Ignore value returned via yield()
cx.waker().wake_by_ref();
return Poll::Pending;
}
@@ -626,7 +562,7 @@ mod assertions {
#[cfg(feature = "send")]
static_assertions::assert_impl_all!(Thread: Send, Sync);
#[cfg(all(feature = "async", not(feature = "send")))]
static_assertions::assert_not_impl_any!(AsyncThread<()>: Send);
static_assertions::assert_not_impl_any!(AsyncThread<(), ()>: Send);
#[cfg(all(feature = "async", feature = "send"))]
static_assertions::assert_impl_all!(AsyncThread<()>: Send, Sync);
static_assertions::assert_impl_all!(AsyncThread<(), ()>: Send, Sync);
}
+4
View File
@@ -175,6 +175,8 @@ pub trait ObjectLike: Sealed {
/// Gets the function associated to key `name` from the object and asynchronously calls it,
/// passing the object itself along with `args` as function arguments.
///
/// Requires `feature = "async"`
///
/// This might invoke the `__index` metamethod.
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
@@ -193,6 +195,8 @@ pub trait ObjectLike: Sealed {
/// Gets the function associated to key `name` from the object and asynchronously calls it,
/// passing `args` as function arguments.
///
/// Requires `feature = "async"`
///
/// This might invoke the `__index` metamethod.
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
+6 -23
View File
@@ -1,9 +1,10 @@
use std::cell::UnsafeCell;
use std::os::raw::{c_int, c_void};
use std::rc::Rc;
use crate::error::Result;
#[cfg(not(feature = "luau"))]
use crate::hook::{Debug, HookTriggers};
use crate::hook::Debug;
use crate::state::{ExtraData, Lua, RawLua};
// Re-export mutex wrappers
@@ -72,35 +73,17 @@ pub enum VmState {
Yield,
}
#[cfg(not(feature = "luau"))]
pub(crate) enum HookKind {
Global,
Thread(HookTriggers, HookCallback),
}
#[cfg(all(feature = "send", not(feature = "luau")))]
pub(crate) type HookCallback = XRc<dyn Fn(&Lua, Debug) -> Result<VmState> + Send>;
pub(crate) type HookCallback = Rc<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 = Rc<dyn Fn(&Lua, Debug) -> Result<VmState>>;
#[cfg(all(feature = "send", feature = "luau"))]
pub(crate) type InterruptCallback = XRc<dyn Fn(&Lua) -> Result<VmState> + Send>;
pub(crate) type InterruptCallback = Rc<dyn Fn(&Lua) -> Result<VmState> + Send>;
#[cfg(all(not(feature = "send"), feature = "luau"))]
pub(crate) type InterruptCallback = XRc<dyn Fn(&Lua) -> Result<VmState>>;
#[cfg(all(feature = "send", feature = "luau"))]
pub(crate) type ThreadCreationCallback = XRc<dyn Fn(&Lua, crate::Thread) -> Result<()> + Send>;
#[cfg(all(not(feature = "send"), feature = "luau"))]
pub(crate) type ThreadCreationCallback = XRc<dyn Fn(&Lua, crate::Thread) -> Result<()>>;
#[cfg(all(feature = "send", feature = "luau"))]
pub(crate) type ThreadCollectionCallback = XRc<dyn Fn(crate::LightUserData) + Send>;
#[cfg(all(not(feature = "send"), feature = "luau"))]
pub(crate) type ThreadCollectionCallback = XRc<dyn Fn(crate::LightUserData)>;
pub(crate) type InterruptCallback = Rc<dyn Fn(&Lua) -> Result<VmState>>;
#[cfg(all(feature = "send", feature = "lua54"))]
pub(crate) type WarnCallback = XRc<dyn Fn(&Lua, &str, bool) -> Result<()> + Send>;
+34 -20
View File
@@ -56,32 +56,30 @@ pub enum MetaMethod {
/// The unary minus (`-`) operator.
Unm,
/// The floor division (//) operator.
/// Requires `feature = "lua54/lua53/luau"`
#[cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))))]
IDiv,
/// The bitwise AND (&) operator.
/// Requires `feature = "lua54/lua53"`
#[cfg(any(feature = "lua54", feature = "lua53"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "lua53"))))]
BAnd,
/// The bitwise OR (|) operator.
/// Requires `feature = "lua54/lua53"`
#[cfg(any(feature = "lua54", feature = "lua53"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "lua53"))))]
BOr,
/// The bitwise XOR (binary ~) operator.
/// Requires `feature = "lua54/lua53"`
#[cfg(any(feature = "lua54", feature = "lua53"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "lua53"))))]
BXor,
/// The bitwise NOT (unary ~) operator.
/// Requires `feature = "lua54/lua53"`
#[cfg(any(feature = "lua54", feature = "lua53"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "lua53"))))]
BNot,
/// The bitwise left shift (<<) operator.
#[cfg(any(feature = "lua54", feature = "lua53"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "lua53"))))]
Shl,
/// The bitwise right shift (>>) operator.
#[cfg(any(feature = "lua54", feature = "lua53"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "lua53"))))]
Shr,
/// The string concatenation operator `..`.
Concat,
@@ -106,16 +104,16 @@ pub enum MetaMethod {
/// The `__pairs` metamethod.
///
/// This is not an operator, but it will be called by the built-in `pairs` function.
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luajit52"))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luajit52")))
)]
///
/// Requires `feature = "lua54/lua53/lua52"`
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luajit52",))]
Pairs,
/// The `__ipairs` metamethod.
///
/// This is not an operator, but it will be called by the built-in [`ipairs`] function.
///
/// Requires `feature = "lua52"`
///
/// [`ipairs`]: https://www.lua.org/manual/5.2/manual.html#pdf-ipairs
#[cfg(any(feature = "lua52", feature = "luajit52", doc))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua52", feature = "luajit52"))))]
@@ -124,6 +122,8 @@ pub enum MetaMethod {
///
/// Executed before the iteration begins, and should return an iterator function like `next`
/// (or a custom one).
///
/// Requires `feature = "lua"`
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
Iter,
@@ -134,6 +134,8 @@ pub enum MetaMethod {
/// More information about to-be-closed variables can be found in the Lua 5.4
/// [documentation][lua_doc].
///
/// Requires `feature = "lua54"`
///
/// [lua_doc]: https://www.lua.org/manual/5.4/manual.html#3.3.8
#[cfg(feature = "lua54")]
#[cfg_attr(docsrs, doc(cfg(feature = "lua54")))]
@@ -270,6 +272,8 @@ pub trait UserDataMethods<T> {
///
/// Refer to [`add_method`] for more information about the implementation.
///
/// Requires `feature = "async"`
///
/// [`add_method`]: UserDataMethods::add_method
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
@@ -285,6 +289,8 @@ pub trait UserDataMethods<T> {
///
/// Refer to [`add_method`] for more information about the implementation.
///
/// Requires `feature = "async"`
///
/// [`add_method`]: UserDataMethods::add_method
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
@@ -323,6 +329,8 @@ pub trait UserDataMethods<T> {
///
/// This is an async version of [`add_function`].
///
/// Requires `feature = "async"`
///
/// [`add_function`]: UserDataMethods::add_function
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
@@ -365,12 +373,11 @@ pub trait UserDataMethods<T> {
///
/// This is an async version of [`add_meta_method`].
///
/// Requires `feature = "async"`
///
/// [`add_meta_method`]: UserDataMethods::add_meta_method
#[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))]
#[cfg_attr(
docsrs,
doc(cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau")))))
)]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn add_async_meta_method<M, A, MR, R>(&mut self, name: impl ToString, method: M)
where
T: 'static,
@@ -384,6 +391,8 @@ pub trait UserDataMethods<T> {
///
/// This is an async version of [`add_meta_method_mut`].
///
/// Requires `feature = "async"`
///
/// [`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")))]
@@ -421,12 +430,11 @@ pub trait UserDataMethods<T> {
///
/// This is an async version of [`add_meta_function`].
///
/// Requires `feature = "async"`
///
/// [`add_meta_function`]: UserDataMethods::add_meta_function
#[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))]
#[cfg_attr(
docsrs,
doc(cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau")))))
)]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn add_async_meta_function<F, A, FR, R>(&mut self, name: impl ToString, function: F)
where
F: Fn(Lua, A) -> FR + MaybeSend + 'static,
@@ -887,6 +895,12 @@ impl AnyUserData {
self.raw_metatable().map(UserDataMetatable)
}
#[doc(hidden)]
#[deprecated(since = "0.10.0", note = "please use `metatable` instead")]
pub fn get_metatable(&self) -> Result<UserDataMetatable> {
self.metatable()
}
fn raw_metatable(&self) -> Result<Table> {
let lua = self.0.lua.lock();
let state = lua.state();
+2 -2
View File
@@ -443,11 +443,11 @@ pub(crate) unsafe extern "C" fn collect_userdata<T>(
// Almost none Lua operations are allowed when destructor is running,
// so we need to set a flag to prevent calling any Lua functions
let extra = (*ffi::lua_callbacks(state)).userdata as *mut crate::state::ExtraData;
(*extra).running_gc = true;
(*extra).running_userdata_gc = true;
// Luau does not support _any_ panics in destructors (they are declared as "C", NOT as "C-unwind"),
// so any panics will trigger `abort()`.
ptr::drop_in_place(ud as *mut T);
(*extra).running_gc = false;
(*extra).running_userdata_gc = false;
}
// This method can be called by user or Lua GC to destroy the userdata.
+1 -1
View File
@@ -315,7 +315,7 @@ pub(crate) unsafe fn init_error_registry(state: *mut ffi::lua_State) -> Result<(
let _ = write!(&mut (*err_buf), "{error}");
Ok(err_buf)
}
Some(WrappedFailure::Panic(Some(panic))) => {
Some(WrappedFailure::Panic(Some(ref panic))) => {
let err_buf_key = &ERROR_PRINT_BUFFER_KEY as *const u8 as *const c_void;
ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, err_buf_key);
let err_buf = ffi::lua_touserdata(state, -1) as *mut String;
+17 -15
View File
@@ -10,71 +10,73 @@ pub(crate) trait TypeKey: Any {
fn type_key() -> *const c_void;
}
static STRING_TYPE_KEY: u8 = 0;
impl TypeKey for String {
#[inline(always)]
fn type_key() -> *const c_void {
static STRING_TYPE_KEY: u8 = 0;
&STRING_TYPE_KEY as *const u8 as *const c_void
}
}
static CALLBACK_TYPE_KEY: u8 = 0;
impl TypeKey for Callback {
#[inline(always)]
fn type_key() -> *const c_void {
static CALLBACK_TYPE_KEY: u8 = 0;
&CALLBACK_TYPE_KEY as *const u8 as *const c_void
}
}
static CALLBACK_UPVALUE_TYPE_KEY: u8 = 0;
impl TypeKey for CallbackUpvalue {
#[inline(always)]
fn type_key() -> *const c_void {
static CALLBACK_UPVALUE_TYPE_KEY: u8 = 0;
&CALLBACK_UPVALUE_TYPE_KEY as *const u8 as *const c_void
}
}
#[cfg(not(feature = "luau"))]
impl TypeKey for crate::types::HookCallback {
#[inline(always)]
fn type_key() -> *const c_void {
static HOOK_CALLBACK_TYPE_KEY: u8 = 0;
&HOOK_CALLBACK_TYPE_KEY as *const u8 as *const c_void
}
}
#[cfg(feature = "async")]
static ASYNC_CALLBACK_TYPE_KEY: u8 = 0;
#[cfg(feature = "async")]
impl TypeKey for AsyncCallback {
#[inline(always)]
fn type_key() -> *const c_void {
static ASYNC_CALLBACK_TYPE_KEY: u8 = 0;
&ASYNC_CALLBACK_TYPE_KEY as *const u8 as *const c_void
}
}
#[cfg(feature = "async")]
static ASYNC_CALLBACK_UPVALUE_TYPE_KEY: u8 = 0;
#[cfg(feature = "async")]
impl TypeKey for AsyncCallbackUpvalue {
#[inline(always)]
fn type_key() -> *const c_void {
static ASYNC_CALLBACK_UPVALUE_TYPE_KEY: u8 = 0;
&ASYNC_CALLBACK_UPVALUE_TYPE_KEY as *const u8 as *const c_void
}
}
#[cfg(feature = "async")]
static ASYNC_POLL_UPVALUE_TYPE_KEY: u8 = 0;
#[cfg(feature = "async")]
impl TypeKey for AsyncPollUpvalue {
#[inline(always)]
fn type_key() -> *const c_void {
static ASYNC_POLL_UPVALUE_TYPE_KEY: u8 = 0;
&ASYNC_POLL_UPVALUE_TYPE_KEY as *const u8 as *const c_void
}
}
#[cfg(feature = "async")]
static WAKER_TYPE_KEY: u8 = 0;
#[cfg(feature = "async")]
impl TypeKey for Option<std::task::Waker> {
#[inline(always)]
fn type_key() -> *const c_void {
static WAKER_TYPE_KEY: u8 = 0;
&WAKER_TYPE_KEY as *const u8 as *const c_void
}
}
+3 -61
View File
@@ -273,7 +273,7 @@ async fn test_async_lua54_to_be_closed() -> Result<()> {
// Don't close by default when awaiting async threads
let co = lua.create_thread(f.clone())?;
let _ = co.clone().into_async::<()>(())?.await;
let _ = co.clone().into_async::<()>(()).await;
assert_eq!(globals.get::<usize>("close_count")?, 1);
let _ = co.reset(f);
assert_eq!(globals.get::<usize>("close_count")?, 2);
@@ -300,7 +300,7 @@ async fn test_async_thread_stream() -> Result<()> {
.eval()?,
)?;
let mut stream = thread.into_async::<i64>(1)?;
let mut stream = thread.into_async::<i64>(1);
let mut sum = 0;
while let Some(n) = stream.try_next().await? {
sum += n;
@@ -325,7 +325,7 @@ async fn test_async_thread() -> Result<()> {
}
})?;
let res: String = lua.create_thread(f)?.into_async(())?.await?;
let res: String = lua.create_thread(f)?.into_async(()).await?;
assert_eq!(res, "done");
@@ -567,61 +567,3 @@ async fn test_async_terminate() -> Result<()> {
Ok(())
}
#[tokio::test]
async fn test_async_task() -> Result<()> {
let lua = Lua::new();
let delay = lua.create_function(|lua, (secs, f, args): (f32, Function, MultiValue)| {
let thread = lua.create_thread(f)?;
let thread2 = thread.clone().into_async::<()>(args)?;
tokio::task::spawn_local(async move {
tokio::time::sleep(Duration::from_secs_f32(secs)).await;
_ = thread2.await;
});
Ok(thread)
})?;
lua.globals().set("delay", delay)?;
let local = tokio::task::LocalSet::new();
local
.run_until(async {
_ = lua
.load("delay(0.1, function(msg) global_msg = msg end, 'done')")
.exec_async()
.await;
})
.await;
local.await;
assert_eq!(lua.globals().get::<String>("global_msg")?, "done");
Ok(())
}
#[tokio::test]
#[cfg(not(feature = "luau"))]
async fn test_async_hook() -> Result<()> {
use std::sync::atomic::{AtomicBool, Ordering};
let lua = Lua::new();
static HOOK_CALLED: AtomicBool = AtomicBool::new(false);
lua.set_global_hook(mlua::HookTriggers::new().every_line(), move |_, _| {
if !HOOK_CALLED.swap(true, Ordering::Relaxed) {
#[cfg(any(feature = "lu53", feature = "lua54"))]
return Ok(mlua::VmState::Yield);
}
Ok(mlua::VmState::Continue)
})?;
let sleep = lua.create_async_function(move |_lua, n: u64| async move {
sleep_ms(n).await;
Ok(())
})?;
lua.globals().set("sleep", sleep)?;
lua.load(r"sleep(100)").exec_async().await?;
assert!(HOOK_CALLED.load(Ordering::Relaxed));
Ok(())
}
+5 -30
View File
@@ -114,6 +114,8 @@ fn test_chunk_macro() -> Result<()> {
#[cfg(feature = "luau")]
#[test]
fn test_compiler() -> Result<()> {
use std::vec;
let compiler = mlua::Compiler::new()
.set_optimization_level(2)
.set_debug_level(2)
@@ -122,11 +124,10 @@ fn test_compiler() -> Result<()> {
.set_vector_lib("vector")
.set_vector_ctor("new")
.set_vector_type("vector")
.set_mutable_globals(vec!["mutable_global"])
.set_userdata_types(vec!["MyUserdata"])
.set_disabled_builtins(vec!["tostring"]);
.set_mutable_globals(vec!["mutable_global".into()])
.set_userdata_types(vec!["MyUserdata".into()]);
assert!(compiler.compile("return tostring(vector.new(1, 2, 3))").is_ok());
assert!(compiler.compile("return vector.new(1, 2, 3)").is_ok());
// Error
match compiler.compile("%") {
@@ -139,32 +140,6 @@ fn test_compiler() -> Result<()> {
Ok(())
}
#[cfg(feature = "luau")]
#[test]
fn test_compiler_library_constants() {
use mlua::{CompileConstant, 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()),
]);
let lua = Lua::new();
lua.set_compiler(compiler);
let const_bool = lua.load("return mylib.const_bool").eval::<bool>().unwrap();
assert_eq!(const_bool, true);
let const_num = lua.load("return mylib.const_num").eval::<f64>().unwrap();
assert_eq!(const_num, 123.0);
let const_vec = lua.load("return mylib.const_vec").eval::<Vector>().unwrap();
assert_eq!(const_vec, Vector::zero());
let const_str = lua.load("return mylib.const_str").eval::<String>();
assert_eq!(const_str.unwrap(), "value1");
}
#[test]
fn test_chunk_wrap() -> Result<()> {
let lua = Lua::new();
+17 -45
View File
@@ -1,5 +1,6 @@
#![cfg(not(feature = "luau"))]
use std::ops::Deref;
use std::sync::atomic::{AtomicI64, Ordering};
use std::sync::{Arc, Mutex};
@@ -26,7 +27,7 @@ fn test_line_counts() -> Result<()> {
assert_eq!(debug.event(), DebugEvent::Line);
hook_output.lock().unwrap().push(debug.curr_line());
Ok(VmState::Continue)
})?;
});
lua.load(
r#"
local x = 2 + 3
@@ -61,7 +62,7 @@ fn test_function_calls() -> Result<()> {
let name = names.name.map(|s| s.into_owned());
hook_output.lock().unwrap().push((name, source.what));
Ok(VmState::Continue)
})?;
});
lua.load(
r#"
@@ -100,13 +101,17 @@ fn test_error_within_hook() -> Result<()> {
lua.set_hook(HookTriggers::EVERY_LINE, |_lua, _debug| {
Err(Error::runtime("Something happened in there!"))
})?;
});
let err = lua.load("x = 1").exec().expect_err("panic didn't propagate");
match err {
Error::RuntimeError(msg) => assert_eq!(msg, "Something happened in there!"),
err => panic!("expected `RuntimeError` with a specific message, got {err:?}"),
}
Error::CallbackError { cause, .. } => match cause.deref() {
Error::RuntimeError(s) => assert_eq!(s, "Something happened in there!"),
_ => panic!("wrong callback error kind caught"),
},
_ => panic!("wrong error kind caught"),
};
Ok(())
}
@@ -130,7 +135,7 @@ fn test_limit_execution_instructions() -> Result<()> {
Ok(VmState::Continue)
}
},
)?;
);
lua.globals().set("x", Value::Integer(0))?;
let _ = lua
@@ -153,7 +158,7 @@ fn test_hook_removal() -> Result<()> {
lua.set_hook(HookTriggers::new().every_nth_instruction(1), |_lua, _debug| {
Err(Error::runtime("this hook should've been removed by this time"))
})?;
});
assert!(lua.load("local x = 1").exec().is_err());
lua.remove_hook();
@@ -200,10 +205,10 @@ fn test_hook_swap_within_hook() -> Result<()> {
});
Ok(VmState::Continue)
})
})?;
});
Ok(VmState::Continue)
})
})?;
});
TL_LUA.with(|tl| {
let tl = tl.borrow();
@@ -242,7 +247,7 @@ fn test_hook_threads() -> Result<()> {
assert_eq!(debug.event(), DebugEvent::Line);
hook_output.lock().unwrap().push(debug.curr_line());
Ok(VmState::Continue)
})?;
});
co.resume::<()>(())?;
lua.remove_hook();
@@ -272,7 +277,7 @@ fn test_hook_yield() -> Result<()> {
.into_function()?;
let co = lua.create_thread(func)?;
co.set_hook(HookTriggers::EVERY_LINE, move |_lua, _debug| Ok(VmState::Yield))?;
co.set_hook(HookTriggers::EVERY_LINE, move |_lua, _debug| Ok(VmState::Yield));
#[cfg(any(feature = "lua54", feature = "lua53"))]
{
@@ -292,36 +297,3 @@ fn test_hook_yield() -> Result<()> {
Ok(())
}
#[test]
fn test_global_hook() -> Result<()> {
let lua = Lua::new();
let counter = Arc::new(AtomicI64::new(0));
let hook_counter = counter.clone();
lua.set_global_hook(HookTriggers::EVERY_LINE, move |_lua, debug| {
assert_eq!(debug.event(), DebugEvent::Line);
hook_counter.fetch_add(1, Ordering::Relaxed);
Ok(VmState::Continue)
})?;
let thread = lua.create_thread(
lua.load(
r#"
local x = 2 + 3
local y = x * 63
coroutine.yield()
local z = string.len(x..", "..y)
"#,
)
.into_function()?,
)?;
thread.resume::<()>(()).unwrap();
lua.remove_global_hook();
thread.resume::<()>(()).unwrap();
assert_eq!(thread.status(), ThreadStatus::Finished);
assert_eq!(counter.load(Ordering::Relaxed), 3);
Ok(())
}
+84 -89
View File
@@ -1,10 +1,9 @@
#![cfg(feature = "luau")]
use std::cell::Cell;
use std::fmt::Debug;
use std::os::raw::c_void;
use std::fs;
use std::panic::{catch_unwind, AssertUnwindSafe};
use std::sync::atomic::{AtomicBool, AtomicPtr, AtomicU64, Ordering};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use mlua::{Compiler, Error, Lua, LuaOptions, Result, StdLib, Table, ThreadStatus, Value, Vector, VmState};
@@ -16,6 +15,83 @@ fn test_version() -> Result<()> {
Ok(())
}
#[test]
fn test_require() -> Result<()> {
// Ensure that require() is not available if package module is not loaded
let mut lua = Lua::new_with(StdLib::NONE, LuaOptions::default())?;
assert!(lua.globals().get::<Option<Value>>("require")?.is_none());
assert!(lua.globals().get::<Option<Value>>("package")?.is_none());
if cfg!(target_arch = "wasm32") {
// TODO: figure out why emscripten fails on file operations
// Also see https://github.com/rust-lang/rust/issues/119250
return Ok(());
}
lua = Lua::new();
// Check that require() can load stdlib modules (including `package`)
lua.load(
r#"
local math = require("math")
assert(math == _G.math, "math module does not match _G.math")
local package = require("package")
assert(package == _G.package, "package module does not match _G.package")
"#,
)
.exec()?;
let temp_dir = tempfile::tempdir().unwrap();
fs::write(
temp_dir.path().join("module.luau"),
r#"
counter = (counter or 0) + 1
return {
counter = counter,
error = function() error("test") end,
}
"#,
)?;
lua.globals()
.get::<Table>("package")?
.set("path", temp_dir.path().join("?.luau").to_string_lossy())?;
lua.load(
r#"
local module = require("module")
assert(module.counter == 1)
module = require("module")
assert(module.counter == 1)
local ok, err = pcall(module.error)
assert(not ok and string.find(err, "module.luau") ~= nil)
"#,
)
.exec()?;
// Require non-existent module
match lua.load("require('non-existent')").exec() {
Err(Error::RuntimeError(e)) if e.contains("module 'non-existent' not found") => {}
r => panic!("expected RuntimeError(...) with a specific message, got {r:?}"),
}
// Require binary module in safe mode
lua.globals()
.get::<Table>("package")?
.set("cpath", temp_dir.path().join("?.so").to_string_lossy())?;
fs::write(temp_dir.path().join("dylib.so"), "")?;
match lua.load("require('dylib')").exec() {
Err(Error::RuntimeError(e)) if cfg!(unix) && e.contains("module 'dylib' not found") => {
assert!(e.contains("dynamic libraries are disabled in safe mode"))
}
Err(Error::RuntimeError(e)) if e.contains("module 'dylib' not found") => {}
r => panic!("expected RuntimeError(...) with a specific message, got {r:?}"),
}
Ok(())
}
#[cfg(not(feature = "luau-vector4"))]
#[test]
fn test_vectors() -> Result<()> {
@@ -319,8 +395,11 @@ fn test_interrupts() -> Result<()> {
//
lua.set_interrupt(|_| Err(Error::runtime("error from interrupt")));
match f.call::<()>(()) {
Err(Error::RuntimeError(ref msg)) => assert_eq!(msg, "error from interrupt"),
res => panic!("expected `RuntimeError` with a specific message, got {res:?}"),
Err(Error::CallbackError { cause, .. }) => match *cause {
Error::RuntimeError(ref m) if m == "error from interrupt" => {}
ref e => panic!("expected RuntimeError with a specific message, got {:?}", e),
},
r => panic!("expected CallbackError, got {:?}", r),
}
lua.remove_interrupt();
@@ -333,87 +412,3 @@ fn test_fflags() {
// We cannot really on any particular feature flag to be present
assert!(Lua::set_fflag("UnknownFlag", true).is_err());
}
#[test]
fn test_thread_events() -> Result<()> {
let lua = Lua::new();
let count = Arc::new(AtomicU64::new(0));
let thread_data: Arc<(AtomicPtr<c_void>, AtomicBool)> = Arc::new(Default::default());
let (count2, thread_data2) = (count.clone(), thread_data.clone());
lua.set_thread_creation_callback(move |_, thread| {
count2.fetch_add(1, Ordering::Relaxed);
(thread_data2.0).store(thread.to_pointer() as *mut _, Ordering::Relaxed);
thread_data2.1.store(false, Ordering::Relaxed);
Ok(())
});
let (count3, thread_data3) = (count.clone(), thread_data.clone());
lua.set_thread_collection_callback(move |thread_ptr| {
count3.fetch_add(1, Ordering::Relaxed);
if thread_data3.0.load(Ordering::Relaxed) == thread_ptr.0 {
thread_data3.1.store(true, Ordering::Relaxed);
}
});
let t = lua.create_thread(lua.load("return 123").into_function()?)?;
assert_eq!(count.load(Ordering::Relaxed), 1);
let t_ptr = t.to_pointer();
assert_eq!(t_ptr, thread_data.0.load(Ordering::Relaxed));
assert!(!thread_data.1.load(Ordering::Relaxed));
// Thead will be destroyed after GC cycle
drop(t);
lua.gc_collect()?;
assert_eq!(count.load(Ordering::Relaxed), 2);
assert_eq!(t_ptr, thread_data.0.load(Ordering::Relaxed));
assert!(thread_data.1.load(Ordering::Relaxed));
// Check that recursion is not allowed
let count4 = count.clone();
lua.set_thread_creation_callback(move |lua, _value| {
count4.fetch_add(1, Ordering::Relaxed);
let _ = lua.create_thread(lua.load("return 123").into_function().unwrap())?;
Ok(())
});
let t = lua.create_thread(lua.load("return 123").into_function()?)?;
assert_eq!(count.load(Ordering::Relaxed), 3);
lua.remove_thread_callbacks();
drop(t);
lua.gc_collect()?;
assert_eq!(count.load(Ordering::Relaxed), 3);
// Test error inside callback
lua.set_thread_creation_callback(move |_, _| Err(Error::runtime("error when processing thread event")));
let result = lua.create_thread(lua.load("return 123").into_function()?);
assert!(result.is_err());
assert!(
matches!(result, Err(Error::RuntimeError(err)) if err.contains("error when processing thread event"))
);
// Test context switch when running Lua script
let count = Cell::new(0);
lua.set_thread_creation_callback(move |_, _| {
count.set(count.get() + 1);
if count.get() == 2 {
return Err(Error::runtime("thread limit exceeded"));
}
Ok(())
});
let result = lua
.load(
r#"
local co = coroutine.wrap(function() return coroutine.create(print) end)
co()
"#,
)
.exec();
assert!(result.is_err());
assert!(matches!(result, Err(Error::RuntimeError(err)) if err.contains("thread limit exceeded")));
Ok(())
}
#[path = "luau/require.rs"]
mod require;
-143
View File
@@ -1,143 +0,0 @@
use mlua::{IntoLua, Lua, Result, Value};
fn run_require(lua: &Lua, path: impl IntoLua) -> Result<Value> {
lua.load(r#"return require(...)"#).call(path)
}
#[track_caller]
fn get_str(value: &Value, key: impl IntoLua) -> String {
value.as_table().unwrap().get::<String>(key).unwrap()
}
#[test]
fn test_require_errors() {
let lua = Lua::new();
// RequireAbsolutePath
let res = run_require(&lua, "/an/absolute/path");
assert!(res.is_err());
assert!(
(res.unwrap_err().to_string()).contains("require path must start with a valid prefix: ./, ../, or @")
);
// RequireUnprefixedPath
let res = run_require(&lua, "an/unprefixed/path");
assert!(res.is_err());
assert!(
(res.unwrap_err().to_string()).contains("require path must start with a valid prefix: ./, ../, or @")
);
// Pass non-string to require
let res = run_require(&lua, true);
assert!(res.is_err());
assert!((res.unwrap_err().to_string())
.contains("bad argument #1 to 'require' (string expected, got boolean)"));
}
#[test]
fn test_require_without_config() {
let lua = Lua::new();
// RequireSimpleRelativePath
let res = run_require(&lua, "./require/without_config/dependency").unwrap();
assert_eq!("result from dependency", get_str(&res, 1));
// RequireRelativeToRequiringFile
let res = run_require(&lua, "./require/without_config/module").unwrap();
assert_eq!("result from dependency", get_str(&res, 1));
assert_eq!("required into module", get_str(&res, 2));
// RequireLua
let res = run_require(&lua, "./require/without_config/lua_dependency").unwrap();
assert_eq!("result from lua_dependency", get_str(&res, 1));
// RequireInitLuau
let res = run_require(&lua, "./require/without_config/luau").unwrap();
assert_eq!("result from init.luau", get_str(&res, 1));
// RequireInitLua
let res = run_require(&lua, "./require/without_config/lua").unwrap();
assert_eq!("result from init.lua", get_str(&res, 1));
// RequireSubmoduleUsingSelf
let res = run_require(&lua, "./require/without_config/nested_module_requirer").unwrap();
assert_eq!("result from submodule", get_str(&res, 1));
// RequireWithFileAmbiguity
let res = run_require(&lua, "./require/without_config/ambiguous_file_requirer");
assert!(res.is_err());
assert!((res.unwrap_err().to_string())
.contains("could not resolve child component \"dependency\" (ambiguous)"));
// RequireWithDirectoryAmbiguity
let res = run_require(&lua, "./require/without_config/ambiguous_directory_requirer");
assert!(res.is_err());
assert!((res.unwrap_err().to_string())
.contains("could not resolve child component \"dependency\" (ambiguous)"));
// CheckCachedResult
let res = run_require(&lua, "./require/without_config/validate_cache").unwrap();
assert!(res.is_table());
}
#[test]
fn test_require_with_config() {
let lua = Lua::new();
// RequirePathWithAlias
let res = run_require(&lua, "./require/with_config/src/alias_requirer").unwrap();
assert_eq!("result from dependency", get_str(&res, 1));
// RequirePathWithParentAlias
let res = run_require(&lua, "./require/with_config/src/parent_alias_requirer").unwrap();
assert_eq!("result from other_dependency", get_str(&res, 1));
// RequirePathWithAliasPointingToDirectory
let res = run_require(&lua, "./require/with_config/src/directory_alias_requirer").unwrap();
assert_eq!("result from subdirectory_dependency", get_str(&res, 1));
// RequireAliasThatDoesNotExist
let res = run_require(&lua, "@this.alias.does.not.exist");
assert!(res.is_err());
assert!((res.unwrap_err().to_string()).contains("@this.alias.does.not.exist is not a valid alias"));
// IllegalAlias
let res = run_require(&lua, "@");
assert!(res.is_err());
assert!((res.unwrap_err().to_string()).contains("@ is not a valid alias"));
}
#[cfg(feature = "async")]
#[tokio::test]
async fn test_async_require() -> Result<()> {
let lua = Lua::new();
let temp_dir = tempfile::tempdir().unwrap();
let temp_path = temp_dir.path().join("async_chunk.luau");
std::fs::write(
&temp_path,
r#"
sleep_ms(10)
return "result_after_async_sleep"
"#,
)
.unwrap();
lua.globals().set(
"sleep_ms",
lua.create_async_function(|_, ms: u64| async move {
tokio::time::sleep(std::time::Duration::from_millis(ms)).await;
Ok(())
})?,
)?;
lua.load(
r#"
local result = require("./async_chunk")
assert(result == "result_after_async_sleep")
"#,
)
.set_name(format!("@{}", temp_dir.path().join("require.rs").display()))
.exec_async()
.await
}
-6
View File
@@ -1,6 +0,0 @@
{
"aliases": {
"dep": "./this_should_be_overwritten_by_child_luaurc",
"otherdep": "./src/other_dependency"
}
}
@@ -1,6 +0,0 @@
{
"aliases": {
"dep": "./dependency",
"subdir": "./subdirectory"
}
}
@@ -1 +0,0 @@
return require("@dep")
@@ -1 +0,0 @@
return {"result from dependency"}
@@ -1 +0,0 @@
return(require("@subdir/subdirectory_dependency"))
@@ -1 +0,0 @@
return {"result from other_dependency"}
@@ -1 +0,0 @@
return require("@otherdep")
@@ -1 +0,0 @@
return {"result from subdirectory_dependency"}
@@ -1 +0,0 @@
return {"result from dependency"}
@@ -1 +0,0 @@
return {"result from dependency"}
@@ -1 +0,0 @@
return {"result from dependency"}
@@ -1 +0,0 @@
return {"result from dependency"}
@@ -1,3 +0,0 @@
local result = require("./ambiguous/directory/dependency")
result[#result+1] = "required into module"
return result
@@ -1,3 +0,0 @@
local result = require("./ambiguous/file/dependency")
result[#result+1] = "required into module"
return result
@@ -1 +0,0 @@
return {"result from dependency"}
@@ -1 +0,0 @@
return {"result from init.lua"}
@@ -1 +0,0 @@
return {"result from lua_dependency"}
@@ -1 +0,0 @@
return {"result from init.luau"}
@@ -1,3 +0,0 @@
local result = require("./dependency")
result[#result+1] = "required into module"
return result
@@ -1,2 +0,0 @@
local result = require("@self/submodule")
return result
@@ -1 +0,0 @@
return {"result from submodule"}
@@ -1,3 +0,0 @@
local result = require("./nested")
result[#result+1] = "required into module"
return result
@@ -1,4 +0,0 @@
local result1 = require("./dependency")
local result2 = require("./dependency")
assert(result1 == result2, "expect the same result when requiring the same module twice")
return {}
+1
View File
@@ -18,6 +18,7 @@ lua53 = ["mlua/lua53"]
lua52 = ["mlua/lua52"]
lua51 = ["mlua/lua51"]
luajit = ["mlua/luajit"]
luau = ["mlua/luau"]
[dependencies]
mlua = { path = "../..", features = ["module"] }
+1
View File
@@ -10,6 +10,7 @@ lua53 = ["mlua/lua53"]
lua52 = ["mlua/lua52"]
lua51 = ["mlua/lua51"]
luajit = ["mlua/luajit"]
luau = ["mlua/luau"]
vendored = ["mlua/vendored"]
[dependencies]
+30 -93
View File
@@ -3,12 +3,13 @@ use std::collections::HashMap;
use std::iter::FromIterator;
use std::panic::{catch_unwind, AssertUnwindSafe};
use std::string::String as StdString;
use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::Arc;
use std::{error, f32, f64, fmt};
use mlua::{
ffi, ChunkMode, Error, ExternalError, Function, Lua, LuaOptions, Nil, Result, StdLib, String, Table,
UserData, Value, Variadic,
ChunkMode, Error, ExternalError, Function, Lua, LuaOptions, Nil, Result, StdLib, String, Table, UserData,
Value, Variadic,
};
#[test]
@@ -944,11 +945,9 @@ fn test_rust_function() -> Result<()> {
fn test_c_function() -> Result<()> {
let lua = Lua::new();
extern "C-unwind" fn c_function(state: *mut mlua::lua_State) -> std::os::raw::c_int {
unsafe {
ffi::lua_pushboolean(state, 1);
ffi::lua_setglobal(state, b"c_function\0" as *const _ as *const _);
}
unsafe extern "C-unwind" fn c_function(state: *mut mlua::lua_State) -> std::os::raw::c_int {
ffi::lua_pushboolean(state, 1);
ffi::lua_setglobal(state, b"c_function\0" as *const _ as *const _);
0
}
@@ -1169,79 +1168,36 @@ fn test_jit_version() -> Result<()> {
}
#[test]
fn test_register_module() -> Result<()> {
fn test_load_from_function() -> Result<()> {
let lua = Lua::new();
let t = lua.create_table()?;
t.set("name", "my_module")?;
lua.register_module("@my_module", &t)?;
lua.load(
r#"
local my_module = require("@my_module")
assert(my_module.name == "my_module")
"#,
)
.exec()?;
lua.unload_module("@my_module")?;
lua.load(
r#"
local ok, err = pcall(function() return require("@my_module") end)
assert(not ok)
"#,
)
.exec()?;
#[cfg(feature = "luau")]
{
// Luau registered modules must have '@' prefix
let res = lua.register_module("my_module", 123);
assert!(res.is_err());
assert_eq!(
res.unwrap_err().to_string(),
"runtime error: module name must begin with '@'"
);
}
Ok(())
}
#[test]
#[cfg(not(feature = "luau"))]
fn test_preload_module() -> Result<()> {
let lua = Lua::new();
let loader = lua.create_function(move |lua, modname: String| {
let i = Arc::new(AtomicU32::new(0));
let i2 = i.clone();
let func = lua.create_function(move |lua, modname: String| {
i2.fetch_add(1, Ordering::Relaxed);
let t = lua.create_table()?;
t.set("name", modname)?;
t.set("__name", modname)?;
Ok(t)
})?;
lua.preload_module("@my_module", loader.clone())?;
lua.load(
r#"
-- `my_module` is global for purposes of next test
my_module = require("@my_module")
assert(my_module.name == "@my_module")
local my_module2 = require("@my_module")
assert(my_module == my_module2)
"#,
)
.exec()
.unwrap();
let t: Table = lua.load_from_function("my_module", func.clone())?;
assert_eq!(t.get::<String>("__name")?, "my_module");
assert_eq!(i.load(Ordering::Relaxed), 1);
let _: Value = lua.load_from_function("my_module", func.clone())?;
assert_eq!(i.load(Ordering::Relaxed), 1);
let func_nil = lua.create_function(move |_, _: String| Ok(Value::Nil))?;
let v: Value = lua.load_from_function("my_module2", func_nil)?;
assert_eq!(v, Value::Boolean(true));
// Test unloading and loading again
lua.unload_module("@my_module")?;
lua.load(
r#"
local my_module3 = require("@my_module")
-- `my_module` is not equal to `my_module3` because it was reloaded
assert(my_module ~= my_module3)
"#,
)
.exec()
.unwrap();
lua.unload("my_module")?;
let _: Value = lua.load_from_function("my_module", func)?;
assert_eq!(i.load(Ordering::Relaxed), 2);
// Unloading nonexistent module must not fail
lua.unload("my_module2")?;
Ok(())
}
@@ -1370,7 +1326,8 @@ fn test_warnings() -> Result<()> {
lua.set_warning_function(|_, _, _| Err(Error::runtime("warning error")));
assert!(matches!(
lua.load(r#"warn("test")"#).exec(),
Err(Error::RuntimeError(ref err)) if err == "warning error"
Err(Error::CallbackError { cause, .. })
if matches!(*cause, Error::RuntimeError(ref err) if err == "warning error")
));
// Recursive warning
@@ -1486,23 +1443,3 @@ fn test_gc_drop_ref_thread() -> Result<()> {
Ok(())
}
#[cfg(not(feature = "luau"))]
#[test]
fn test_get_or_init_from_ptr() -> Result<()> {
// This would not work with Luau, the state must be init by mlua internally
let state = unsafe { ffi::luaL_newstate() };
let mut lua = unsafe { Lua::get_or_init_from_ptr(state) };
lua.globals().set("hello", "world678")?;
// The same Lua instance must be returned
lua = unsafe { Lua::get_or_init_from_ptr(state) };
assert_eq!(lua.globals().get::<String>("hello")?, "world678");
unsafe { ffi::lua_close(state) };
// Lua must not be accessed after closing
Ok(())
}
+1 -26
View File
@@ -164,7 +164,7 @@ fn test_thread_reset() -> Result<()> {
let result = thread.resume::<()>(());
assert!(
matches!(result, Err(Error::CallbackError{ ref cause, ..})
if matches!(cause.as_ref(), Error::RuntimeError(err)
if matches!(cause.as_ref(), Error::RuntimeError(ref err)
if err == "cannot reset a running thread")
),
"unexpected result: {result:?}",
@@ -227,28 +227,3 @@ fn test_thread_pointer() -> Result<()> {
Ok(())
}
#[test]
#[cfg(feature = "luau")]
fn test_thread_resume_error() -> Result<()> {
let lua = Lua::new();
let thread = lua
.load(
r#"
coroutine.create(function()
local ok, err = pcall(coroutine.yield, 123)
assert(not ok, "yield should fail")
assert(err == "myerror", "unexpected error: " .. tostring(err))
return "success"
end)
"#,
)
.eval::<Thread>()?;
assert_eq!(thread.resume::<i64>(())?, 123);
let status = thread.resume_error::<String>("myerror").unwrap();
assert_eq!(status, "success");
Ok(())
}
-6
View File
@@ -1,6 +0,0 @@
[default]
extend-ignore-identifiers-re = ["catched", "2nd", "ser"]
[default.extend-words]
thr = "thr"
aas = "aas"